2013年10月4日 星期五

revise after 1.0.06211 release version

在發行 1.0.06211 版本後, 進行的修改日誌

TODO:

Bug:
當plcsnd送出PE01(70bytes), 但表訂PE01為64bytes, 故plcrcv會分兩次進行資料擷取, 第一次擷取64bytes, 並回送reply ack, 此時plcsnd會等候接收reply ack, plcrcv進行第二次擷取6bytes, 並回送reply ack, 此時plcsnd已不會主動接收reply ack, 卻會導致listenDisconnected(), peekN()有資料, 而一直迴圈peekN(), 而一直引發syslog()

 

2013.07.01 (changeset 5)

ExceptHandler::
# 加入 ExceptHandler, 加強應用程序發生exception時, 可以記錄stack trace infomation

2013.07.05 (changeset 5)

SockTcpHandler::
# 修正bug: 當外部程式呼用listenDisconnect(), 監聽斷線信號, 重新建立連線, 若同時呼用sendtoServer(), 函式內部也會重新建立連線, 兩者會相互影響, 導致引發socket exception, 終止程序
# 當外部程式呼用listenDisconnect()時, 強制將sendtoServer()的自動重連的機制關閉
TimerHandler::
# 修正 bug: 沒有正常終止回收thread object的資源
# 修改TimerInfo_T, 加入屬性 HANDLE hnd, 記錄handle of thread
# 修改tm_func(), 加入_endthreadex()
# 修改~TimerHandler(), 使用WaitForSingleObject(), 等待thread正常結束回應
# 修改~TimerHandler(), 使用CloseHandle(), 強制thread結束, 並回收資源
# 修改remove(), 使用WaitForSingleObject(), 等待thread正常結束回應
# 修改remove(), 使用CloseHandle(), 強制thread結束, 並回收資源

2013.07.10  (* 重大修改, 演進為 1.1.xxxxx 版本號) (changeset 6)

ApMsgHandler::
# 增加ap_syslog(), 編組訊息日誌, 並拋送至MQ_LOGGER (意使將log message的I/O作業, 委由其它程式處理)
xxxxxHandler::
# 啟用ap_syslog(), 編譯程式前, 預先定義_SYSLOG, 以將所有ap_log() 置換為 ap_syslog()

2013.07.11 (changeset 6)

ApMsgHandler::
# MQ_LOGGER, MQ_PCCOMM, MQ_RECORDER, MQ_MSGMGR視為物件全域變數, 預設開啟並聯附MSMQ
# 呼用alarm(), sendHmi(), replyHmi(), ap_syslog (), 皆使用上述已開啟並聯附的MSMQ
# 減少快速頻繁開啟-->關閉MSMQ, 導致效能低落

2013.07.11 (changeset 6)

LogHandler::
LogHandlerEx::
# 修改formation(), 記錄日誌時間至毫秒單位(milli-second)
# 增加getDTimeMsec(), 取得目前時間至毫秒單位(milli-second)

2013.07.16 (changeset 7)

LogHandler::
# 首次開啟log file時, 寫入NGO middle-ware be compiled date time
# 換日或分割log file時, 寫入NGO middle-ware be compiled date time
# 利於發行版本的確認及追蹤

2013.08.12 (changeset 8)

SockTcpHandler::
# 回呼外部指定函式指標前, 增加log, 以確認是否有執行該回呼函式
# 當遠端主機無回覆任何ACK message(NOP), 不斷線並持續重送, 達到3次時, 才會斷線重新連線

2013.08.12 (changeset 10)

ApMsgHandler::
# 常用的MSMQ, 改以static形式宣告, 以提升執行效率, 避免一再重覆建立
# MQ_MSGMGR, MQ_LOGGER, MQ_PCCOMM, MQ_RECORDER, MQ_SYS

2013.08.14 (changeset 11)

Fixed Bug #N/A :

  • 因為ApMsgHandler的生成, 較早於main()中LogHandler的生成, 導致會產生無路徑的log file
  • 未定義_SYSLOG, 則避免生成SysLogMQ

** commonlib - ApMsgHandler **
當定義_SYSLOG, 才會生成SysLogMQ
當定義_SYSLOG, ap_syslog才會生效

2013.08.14 (changeset 12)

Fixed Bug #N/A :

  • 監聽遠端主機是否關閉連線機制, 仍會引發非預期的例外異常, 導致無法再主動重新連線, 甚至中止程序
  • 設計原則, 儘量避免主動式關閉連線, 多利用回傳eACK, 交由外部應用層決定是否關閉連線, ex: sendtoServer(), listenDisconnected()

** commonlib - SockTcpHandler **
modify function, listenDisconnected(), 取消主動關閉連線作業
modify function, close(), 當CSock<0時, 仍必須將fConnect初始為false
modify function, sendtoServer(), 取消主動關閉連線作業

2013.10.04 (changeset 13)

Fixed Bug #N/A -- to fixed some bugs

  • 將GetLabel() and GetFormatName()註解, 不記錄相關log

** IPCHandler -- MSQHandder.h / MSQHandler.cpp **
(1) 修改create(), 註解呼叫GetLabel() and GetFormatName()之程式碼

2013.10.04 (changeset 14)

Changed #N/A -- enhance that reject client connection, and buffer size

  • 在回呼OnAccepted(), 可以由外部決定是否拒絕當次連線
  • 擴充緩衝區的容量

** DLHandler -- SockTcpHandler.h / SockTcpHandler.cpp **
(1) 修改acceptClient(), 回呼OnAccepted()的傳回值<=0時, 表示外部拒絕當次連線
(2) 修改runSingleSvr(), 依據acceptClient()的回傳值因應不同程序, 小於0:表示連線異常, 等於0表示外部拒絕當次連線並中斷連線, 大於0表示接受連線
(3) 修改MAXDATA, 由4096變更為8192, Buf[]及RBuf[]皆擴增至8192 bytes

2013.10.04 (changeset 15)

Fixed #N/A -- date time could milli-second precision

  • 日期時間格式, 只能精確至亳秒(1/1000sec)

** DataHandler -- DataHandler.h / DataHandler.cpp **
(1) 修改getDTimeMsec(), 由6位微秒減為3位毫秒

2013.10.04 (changeset 16)

Fixed #N/A -- enhance some log

  • 針對外部回呼函式, 增加log記錄

** DLHandler -- RpcHandler.h / RpcHandler.cpp **
(1) 修改receiveFunc(), 回呼OnReceive(), 增加log
(2) 修改sendServer(), 回呼AfterSend), 增加log

2013.12.03 (changeset 17)

Fixed #N/A -- remove winsock header file from IniHandler

  • 避免winsock.h & winsock2.h衝突

** IniHandler -- IniHandler.h **
(1) 移除winsock2.h & ws2tcpip.h & ws2_32.lib

Fixed #N/A -- remove external  argument when call ApMsgHandler::alarm ()

  • 移除SYSTEM / Client 的附加資訊

** ApMsgHandler -- ApMsgHandler.cpp **
(1) alarm(), 移除argument[9]的資訊

2013年8月28日 星期三

dblink about SQL Server

在A主機的SQL Server想查詢B主機的SQL Server, 可以採用dblink的概念, 建立連結伺服器, 接著就可以SQL語法進行資料查詢操作, 非常方便。

1. 建立連結伺服器
exec sp_addlinkedserver
@server = ‘testDB’  -- 別名
,@srvproduct = ‘’
,@provider = ‘SQLOLEDB’
,@datasrc = ‘172.20.199.xxx’  -- 遠端主機

2.建立連結登入資料
exec sp_addlinkedsrvlogin
@rmtsrvname = ‘testDB’ -- 別名
,@useself = ‘false’
,@locallogin = NULL
,@rmtuser = ‘testmgr’   -- 遠端使用者帳號
,@rmtpassword = ‘testmgr’   -- 遠端使用者密碼

3.查看連結伺服器資料
select * from sys.sysservers

4.查詢連結伺服器的資料庫的所有資料表
exec sp_tables_ex testDB

5.查詢連結伺服器的資料表的資料
select * from testDB.test.dbo.schl

6.移除連結登入資料
exec sp_droplinkedsrvlogin
@rmtsrvname = ‘testDB’
,@locallogin = NULL

7.移除連結伺服器
exec sp_dropserver @server = ‘testDB’

2013年6月10日 星期一

How to install the NGO common library

1.  請下載兩個檔案

  • NGO_CL_Installer.msi
  • setup.exe

2. 安裝步驟如下:

2.1 啟動安裝精靈
    NGO_setup1

2.2 指定應用程式的根目錄 C:\rcm3app (請依實際目錄路徑)
    NGO_setup2

2.3 確認安裝
    NGO_setup3

2.4 安裝完成
    NGO_setup4

2.5 部置檔案目錄結構
    NGO_setup5

3. VC++專案, 請於專案屬性加入下述兩點路徑

  • Inlcude Path : c:\acl3app\src\common\inc
  • Library Path : c:\acl3app\lib

4. 即可進行專案編譯

2013年6月8日 星期六

How to pack NGO common library into a Installer

目前NGO common library完成度已接近80%, 起先顧及bug太多, 與方便修正的即時性,  皆先發佈source code給同仁編譯使用, 但這麼一來就容易造成版本的問題及維護整合的困難性

是該以library package的形式, 後續發佈給大家使用, 便想到以封裝成install檔案, 不知可行性如何??

經過實際試做後, 建議可以先針對下述兩點, 略做規劃:

第一. 要發佈哪些檔案??
1. common.lib              (NGO common library)
2. mqoa.dll                    (MSMQ library)
3. msado15.dll             (MS ADO library)
4. oncrpcms.dll            (ONC rpc library for MS)
5. oncrpc.dll                  (ONC rpc library)
6. head file

第二. 要如何配置目錄路徑
c:\rcm3app
                      ---> \lib                                  (存放*.dll and *.lib)
                      ---> \src\common\inc      (存放*.h)

Install_Directory

第三. 如何使用Visual Studio製作安裝檔?  (可以參考下列推薦網站連結唷!!)

2013年6月7日 星期五

手動設定 VS 2010 的 VC++ IncludePath/LibraryPath/OutputPath

在建立新專案時, 總是要逐一設定專案屬性的輸出目錄 / include目錄 / library目錄, 雖有複製貼上的神技, 但有沒有方法可以變成自行定義的預設初始化路徑...

只要編輯下述兩個檔案, 即可輕輕鬆鬆建立新專案

x86 (win32) : Microsoft.Cpp.Win32.v100.props
C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Platforms\Win32\PlatformToolsets\v100\Microsoft.Cpp.Win32.v100.props

x64 (win32) : Microsoft.Cpp.x64.v100.props
C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Platforms\x64\PlatformToolsets\v100\Microsoft.Cpp.x64.v100.props

開啟編輯上述任一檔案時, 注意下面的文字結構, 黃色標示部份, 為自行定義加入的路徑變數, 存檔再重新開啟visual studio就可以看到, 已自動加入專案屬性的目錄路徑

<PropertyGroup>
<OutDir>$(AppBinPath)\</OutDir>
<IncludePath Condition="'$(IncludePath)' == ''">$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSdkDir)include;$(FrameworkSDKDir)\include;$(AppDirPath)\src\common\inc;$(AppDirPath)\src\inc;</IncludePath>
<LibraryPath Condition="'$(LibraryPath)' == ''">$(VCInstallDir)lib\amd64;$(VCInstallDir)atlmfc\lib\amd64;$(WindowsSdkDir)lib\x64;$(AppDirPath)\lib;$(AppDirPath)\src;$(AppDirPath)\src\common\dll;</LibraryPath>
</PropertyGroup>

參考來源 :: http://idaiwan.pixnet.net/blog/post/30789152

How to use IniHandler??

1. 作為讀取參數設定檔之用途

2. INI file format (內容嚴禁空白)
[SectionName]
key=value
[Host]
ipaddress=192.168.19.1
port=9999
[RCM3]
ipaddress=192.168.19.2
port=8888


PS: 將上述(2)內容存檔於C:\ipaddress.ini

ex:
#include “IniHandler.h”

main (void)
{
/* 建立 INI file handler */
IniHandler ini_hand;

/* 打開INI file */
ini_hand.open ("c:\\ipaddress.ini");

/* 讀取所有的Section Name */
vSectionNames_T names = ini_hand.readSectionNames ();

vSection_T section;
/* 逐一以Section Name, 讀取該Section內所有的key value */
for (int i=0; i<(int)names.size(); i++) {
    ap_log (DBUG, "get the tag %s", names[i].c_str());

    section.clear ();
    /* 帶入section name, 讀取 key-value */
    section = ini_hand.readKeyValues ((char *)names[i].c_str());

    /* 逐一印出key-value */
    for (int j=0; j<(int)section.size(); j++) {
        ap_log (DBUG, "key = %s, values = %s", section[j].key, section[j].value);
    }
}
/* 關閉 INI file */
ini_hand.close ();
}

2013年3月24日 星期日

Windows Server 2008 R2 無法安裝 Visual Studio 2010 : VC 9.0 runtime error code 1603


安裝好Windows Server 2008 R2後, 話不多說, 接著安裝Visual Studio 2010, 沒想到第一個組件 VC 9.0 runtime 就發生問題了, Google 前輩們的解決方法, 執行以下的動作, 終於安裝成功 (卻也花費了半天的早上)



  1. 於控制台中, 新增/移除程式的項目中, 卸除microsoft visual C++ 2008 redistributable
  2. 於執行中, 鍵入%temp%, 清除該資料夾的一切內容
  3. 啟用Windows Modules Installer服務, 於執行鍵入regedit, 移動於HKEY_LOCAL_MACHNE\System\CurrentControlSet\Control的目錄中, 查看右側內容, 有無RegistrySizeLimit, 若沒有, 則新增該值, 內容為16進位的0xFFFFFFFF
  4. 重新啟動Windows Modules Installer服務, 並改為自動


做完上述的動作後, 記得重新開機, 另注意Visual Studio 2010的原始安裝檔的權限, 建議以Administrators進行安裝



2013年1月29日 星期二

How to use TimerHandler ??

  1. 建議以process專屬的MQ, 設置定時器, 儘可能避免以外部process進行定時器的設置, 造成日後難以追查觸發的來源或控管不易
  2. 建議有限度地新增定時觸發事件, 以避免降低系統效能

#設置計時器, 以給定的MQ為觸發對象
TimerHandler::TimerHandler (char *mq_name);
  mq_name: process專屬的MQ名稱

ex:
TimerHandler timer (MQ_EDPLCRCV);

#新增定時觸發事件
TimerHandler::add (char *tm_name, int msec, int msg_id, void *msg, int msg_size);
  tm_name: 定時事件名稱
  msec : 間隔時間 (毫秒 milli-seconds)
  msg_id: 觸發事件代碼
  msg: 觸發資料
  msg_size: 觸發資料長度

ex:
timer.add (“Timer_AliveMsg”, 30000, MSG_TIMER_ALIVEMSG, 0, 0);
timer.add (“Timer_Hello”, 30000, MSG_TIMER_HELLO, "hello world", strlen("hello world"));

#啟動/停止/重置
TimerHandler::set_enable (char *tm_name, bool run_it);
  tm_name: 定時事件名稱
  run_it: 啟動/重置(enable), 停止(disable)

ex:
timer.set_enable (“Timer_AliveMsg”, true);
timer.set_enable (“Timer_AliveMsg”, false);

#移除定時觸發事件
TimerHandler::remove (char *tm_name);
  tm_name: 定時事件名稱

ex:
timer.remove ("Timer_AliveMsg");

#設置間隔時間 (milli-seconds)
TimerHandler::set_interval (char *tm_name, int msec);
  tm_name: 定時事件名稱
  msec: 間隔時間 (毫秒 milli-seconds)

ex:
timer.set_interval ("Timer_AliveMsg", 10000);

2013年1月5日 星期六

how to pass arguments to another process by using ApMsgHandler


鋼捲資料, ex:鋼捲號碼1830912, 厚度12mm, 重量3600kg

範例一.
試將參數化資料, 以MSMQ傳送至pdimgr

#將鋼捲資料, 以參數化方式送出至MSMQ
ApMsgHandler ApMQ = ApMsgHandler (MQ_SYS);
msginfo_body_T msg_body; memset (&msg_body, 0x00, sizeof(msginfo_body_T));
msg_body.argv[0].i16 = (short) 12;
msg_body.argv[1].i32 = (int) 3600
memcpy (msg_body.argv[2].chr, "1830912", 7);

ApMQ.send (MQ_PDIMGR, PdiMgrMsg::COILDATA, &msg_body);

#將鋼捲資料, 自MSMQ以參數化方式取出
ApMsgHandler ApMQ = ApMsgHandler (MQ_PDIMGR);
msginfo_body_T *pMsgData = (msginfo_body_T *)ApMQ.getMsgArg();

short thickness   = pMsgData->argv[0].i16;
int wgt = pMsgData->argv[1].i32;
char coilid[12] = "";
strcpy (coilid, pMsgData->argv[2].chr);

 

範例二.
試將連續性或結構性資料, 以MSMQ傳送至pdimgr

struct PDI {
    char coilid[12];   // 鋼捲號碼
    int     thk;            // 厚度
    int     wgt;           // 重量
};

#將鋼捲資料, 以結構性方式送出至MSMQ

ApMsgHandler ApMQ = ApMsgHandler (MQ_SYS);
struct PDI pdi_data;
memcpy (pdi_data.coilid, "1820912", 7);
pdi_data.thk = 12;
pdi_data.wgt = 3600;

ApMQ.send (MQ_PDIMGR, EP01, &pdi_data, sizeof(pdi_data));

#將鋼捲資料, 自MSMQ以結構性方式取出
ApMsgHandler ApMQ = ApMsgHandler (MQ_PDIMGR);
struct PDI *ptr = (struct PDI*)ApMQ.getMsgArg();

struct PDI pdi_data;
memcpy (&pdi_data, ptr, sizeof(pdi_data));

2012年12月31日 星期一

到底是誰殺了PDI??

到底是誰殺了PDI??
每當程控資料一有異常時, 系統工程員就要準備接受挑戰, 因為現場人員幾乎會先認定是系統問題, 當你查明確定是由HMI以人工作業方式觸發後, 現場人員又會挑釁你, 到底是哪台電腦動了殺機, 這下頭大了, 只能像福爾摩斯一樣, 逐一查明各個HMI的log, 找出所使用的殺人兇器  (重點是還會被疑犯案過程是造假的...)

圖一. 犯案流程
 
舉例: 在現有架構下,  當HMI下達刪除鋼捲PDI後 (如上圖一)
(1) 以~xxxxx^xxxxx^xxxxx^xxxxx&的訊息格式, 通知server (此時記錄著pc name)
(2) pccomm解譯上述(1)訊息格式後, 通知distribution (此時記錄著pc name)
(3) distribution依據訊息代碼, 決定分派至schmgr (此時記錄著pc name)
(4) schmgr確認該鋼捲PDI沒有被排程鎖定後, 通知pdimgr (此時抹滅掉pc name)
(5) pdimgr被通知殺了該鋼捲PDI (此時已無pc name)
(6) pdimgr將該犯案事件寫入資料庫, 但是誰殺的?? 演變成一齣懸案
 

原來是現有架構成了幫兇, 各個後端程式皆是以MSMQ進行作業流程的觸發點, pccomm & distribution是既定的公用程式, 故HMI的pc name皆得以保留記錄 (可以規範記錄兇手)

但schmgr & pdimgr仍由系統人員開發, 除非佛心來著, 每每要將HMI的pc name, 額外記錄於要通知其它程式的MSMQ, 若有一個疏忽, 毀了屍滅了跡, 就讓兇手逍遙法外了

現有架構各個程式的作業模式如下:
recieve from MSMQ >> do some activity >> send to MSMQ or not
當接收一個message後, 進行某些活動, 再傳送另一個message給其它程式
試想, 當傳送一個message時, 從已接收的message中, 將pc name擷取出來, 記錄於其中, 應該就可行囉!
 
最後, 哪到底是誰殺了PDI?? 請愛用ApMsgHandler::getClntName (), 因為兇手就藏在細節裡

changeset#4

SockTcpHandler 增加 setCheckAckSize(), 由開發人員自訂是否需要檢查ack message size

因應某一伺服端程控,  ack message 屬於不定長度, 現有SockTcpHandler無法判定, 導致視為異常作業, 自動終止連線, 再重新連線

目前SockTcpHandler::setAckSize (), 意即指定元件須確認 ack message size相同時, 才會將ack messsage以call back方式引入, 反之, 視為異常作業, 自行斷線重新連線

試以增加一函式, SockTcpHandler::setCheckAckSize (), 由同仁自訂是否需要由元件檢查ack message size, 預設為true (檢查)
反之(不檢查), 元件則直接將接收的 ack message以call back方式引入


提供使用範例程式碼如下::

void NGOSndCtrl::run()
{
    int rcv_len = 0;
    myTcpClnt.openClient(server, _TCPPORT);
    myTcpClnt.setHeaderSize(sizeof(TCP_HEAD));
    myTcpClnt.setAckSize(sizeof(TCP_ACK), sizeof(TCP_HEAD)+1);
    myTcpClnt.setNeedAck(1);
    myTcpClnt.setCheckAckSize (ChkACKSize_NO);  // 設定為不檢查
    myTcpClnt.setTimeout(1000); // -1 is no time out
    /*an option, custom to ckeck reply ack message */
    myTcpClnt.setOnReadAck(CB(onReadAck));
    myTcpClnt.runClient();

    .....(以下省略)
}

當要接收不同的ack message時, 應如何善用SockTcpHandler::setAckSize()

同位提出一個需求討論:
假若不同的L2 client side (L2A, L2B), 需回覆不同格式的ack format時(ACK_A, ACK_B), 如何因應??
struct ackA {
  TCP_HEAD head;
  char blank1;
  char acept; /**< accept or not */
  char trans_id[4];
  char err_code[4];
  char blank2[2];
  char err_msg[31];
} ACK_A;

struct ackB {
  TCP_HEAD head;
  char blank1;
  char acept; /**< accept or not */
  char trans_id[4];
  char err_code[4];
} ACK_B;

建議做法::
可以於SockTcpHandler::setAckSize (ack_size, accept_position)進行調整
    ack_size: 客戶端連線所回覆的ack message資料長度
    accept_position: 客戶端連線對於前一筆送出資料的驗證結果的字元位置


針對L2A : myTcpSvr.setAckSize(sizeof(ACK_A), sizeof(TCP_HEAD)+2);
針對L2B : myTcpSvr.setAckSize(sizeof(ACK_B), sizeof(TCP_HEAD)+2);


提供一般Control Class::run ()的範例程式碼::

void NGORcvCtrl::run()
{
    /* specify the header size of message */
    myTcpSvr.setHeaderSize(sizeof(TCP_HEAD));
    /* specify the ack size and function position */
    myTcpSvr.setAckSize(sizeof(TCP_ACK), sizeof(TCP_HEAD)+1);
    /* set need to reply ack */
    myTcpSvr.setNeedAck(1);
    /* assign a funcion, when tcp server on receive complete message will call back */
    myTcpSvr.setOnReceive(CB(onReceive));
    /* assign a funcion, when tcp server on reply custom ack will call back it */
    myTcpSvr.setOnReply(CB_RP(onReply));
    /* run the tcp server */
    myTcpSvr.runSingleSvr ();
}