PICTURE於11.1版本新增了PANEL iH Pro外部函式呼叫功能,也於11.3版開始對應PANEL iH。
可透過自製動態連結檔案(DLL)調用外部函式庫,再由PICTURE呼叫該DLL即可實現外部函式的間接呼叫機制。
如下圖所示
此功能可取代以往需透過 iPC 使用 FOCAS2 函式庫進行 CNC 資料讀取與寫入的方式,現在可直接由 PANEL iH 呼叫 FOCAS2 函式達成相同目的。
此外,亦可利用 PANEL iH 的 WinCE 系統所提供的 API,實現 TCP/IP 資料傳輸功能。
範例概圖如下:
應用範例1. 藉由 FOCAS2 函式庫寫入刀具補正
應用範例2. 藉由 WinCE Winsock 函式庫進行 TCP/IP 傳輸
下方提供說明文件與專案範本,詳細內容請參考說明文件,有問題也歡迎提出討論~
說明文件
專案範本
PICTURE 外部函式呼叫功能介紹
Re: PICTURE 外部函式呼叫功能介紹
TCP/IP應用於RFID設備範例 ( Pepperl+Fuchs IC-KP2-2HB17-2V1D )
Pepperl+Fuchs IC-KP2-2HB17-2V1D為內建 TCP Server 的設備,特性如下:
• 運作模式:設備以 TCP Server 角色待命接收指令,在此範例中CNC為Client端。
• 通訊埠:固定為 TCP Port 10000。
• 傳輸格式:以 byte 為單位的二進位資料流(包含封包長度與命令碼等欄位)。
• 連線方式:用戶端(Client)需建立 socket 並連線至設備 IP 與 port 10000。
指令格式(Command Frame)
回應格式(Response Frame)
上述表格定義了與設備通訊所使用的標準資料格式。要與裝置正確溝通,必須依照協定格式傳送對應的命令代碼(Command Code)與參數。以下分別說明 寫入指令 及 讀取指令 的範例格式。
寫入指令格式(Client → Server)
表示向 Channel 1, Address 0 寫入 2 word(資料為 0x12345678)
裝置回應(Response)(Server → Client)
回傳 Channel 1, Address 0 寫入 1 word(資料為 0x1234)成功
讀取指令格式(Client → Server)
表示從 Channel 1 的 Tag 位置 0,讀取 1 個字(Word)(即 4 bytes)
裝置回應(Response)(Server → Client)
回傳從 Channel 1 的 Tag 位置 0,讀取 1 個字(Word)(即 4 bytes)成功
由於 PICTURE 中的 mRuby 腳本在處理資料型態上較為困難,因此在與 IC-KP2-2HB17-2V1D 裝置進行 TCP 通訊時,建議透過以下方式進行整體架構設計:
資料傳送
mRuby 腳本僅負責以 字串形式 組裝並傳遞待發送的資料內容。
該字串(通常為 HEX 格式)會作為參數傳入 PICTURE 所呼叫的動態連結檔案(C++ DLL)。
資料處理(DLL)
在 DLL 端,C++ 程式負責:
- 將 HEX 字串轉換為對應的 Byte 陣列(Binary 格式)。
- 透過 TCP Socket 傳送封包給設備端。
- 接收來自設備的回應資料(Binary 格式)。
- 將回應資料轉換為 HEX 字串格式。
資料回傳
經過處理後的 HEX 字串,作為 DLL 函式的回傳值,提供給 mRuby 腳本使用。
mRuby 腳本再以字串操作方式解析與應用回應資料(例如擷取命令回應碼、狀態碼、資料內容等)。
透過此架構,mRuby 僅需操作字串,將複雜的資料型態轉換與 TCP 封包組解工作交由 C++ DLL 處理,可大幅簡化腳本邏輯並提高穩定性與可維護性。
Ruby程式碼範例如下
動態連結檔案(C++)程式碼範例如下
Pepperl+Fuchs IC-KP2-2HB17-2V1D為內建 TCP Server 的設備,特性如下:
• 運作模式:設備以 TCP Server 角色待命接收指令,在此範例中CNC為Client端。
• 通訊埠:固定為 TCP Port 10000。
• 傳輸格式:以 byte 為單位的二進位資料流(包含封包長度與命令碼等欄位)。
• 連線方式:用戶端(Client)需建立 socket 並連線至設備 IP 與 port 10000。
指令格式(Command Frame)
| 位元組位置 | 說明 |
|---|---|
| Byte 0 | 長度高位((N+1)/256) |
| Byte 1 | 長度低位((N+1)%256) |
| Byte 2 | 命令代碼(Command Code) |
| Byte 3 | 通道/旗標(Channel + toggle bit) |
| Byte 4~5 | 命令參數(依命令定義而定) |
| Byte 6~N | 寫入資料(如有) |
| 位元組位置 | 說明 |
|---|---|
| Byte 0~1 | 回傳封包長度 |
| Byte 2 | 命令碼(回應) |
| Byte 3 | 通道/旗標(回應) |
| Byte 4 | 狀態(0 = 成功;其他 = 錯誤碼) |
| Byte 5 | 回應計數器(Reply Counter) |
| Byte 6~N | 回傳資料 |
上述表格定義了與設備通訊所使用的標準資料格式。要與裝置正確溝通,必須依照協定格式傳送對應的命令代碼(Command Code)與參數。以下分別說明 寫入指令 及 讀取指令 的範例格式。
寫入指令格式(Client → Server)
| 位元組 | 說明 |
|---|---|
| Byte 0~1 | 長度 0x00 0A |
| Byte 2 | 命令碼(SW) 0x40 |
| Byte 3 | 通道/字數 0x21 |
| Byte 4~5 | 寫入位址 0x00 00 |
| Byte 6~7 | 寫入資料 (高/低) 0x12 34 56 78 |
代碼: 選擇全部
00 0A 40 21 00 00 12 34 56 78裝置回應(Response)(Server → Client)
| 位元組 | 說明 |
|---|---|
| Byte 0~1 | 長度 0x00 06 |
| Byte 2 | 命令碼(Echo) 0x40 |
| Byte 3 | 通道/字數 0x21 |
| Byte 4 | 狀態(成功為 0) 0x00 |
| Byte 5 | 回應計數器 0x03 |
代碼: 選擇全部
00 06 40 21 00 03讀取指令格式(Client → Server)
| 位元組 | 說明 |
|---|---|
| Byte 0 | 長度高位 0x00 |
| Byte 1 | 長度低位 0x06 |
| Byte 2 | 命令碼(SR) 0x10 |
| Byte 3 | 通道/字數 0x12 |
| Byte 4 | 讀取位址高位 0x00 |
| Byte 5 | 讀取位址低位 0x00 |
代碼: 選擇全部
00 06 10 12 00 00裝置回應(Response)(Server → Client)
| 位元組 | 說明 |
|---|---|
| Byte 0~1 | 長度 0x00 0E (14) |
| Byte 2 | 命令碼(Echo) 0x10 |
| Byte 3 | Channel/字數 0x12 |
| Byte 4 | 狀態(成功=0) 0x00 |
| Byte 5 | 回應計數器 0x02 |
| Byte 6~13 | 資料(4 bytes) 31 32 33 34 (ASCII "1234") |
代碼: 選擇全部
00 0E 10 22 00 02 31 32 33 34由於 PICTURE 中的 mRuby 腳本在處理資料型態上較為困難,因此在與 IC-KP2-2HB17-2V1D 裝置進行 TCP 通訊時,建議透過以下方式進行整體架構設計:
資料傳送
mRuby 腳本僅負責以 字串形式 組裝並傳遞待發送的資料內容。
該字串(通常為 HEX 格式)會作為參數傳入 PICTURE 所呼叫的動態連結檔案(C++ DLL)。
資料處理(DLL)
在 DLL 端,C++ 程式負責:
- 將 HEX 字串轉換為對應的 Byte 陣列(Binary 格式)。
- 透過 TCP Socket 傳送封包給設備端。
- 接收來自設備的回應資料(Binary 格式)。
- 將回應資料轉換為 HEX 字串格式。
資料回傳
經過處理後的 HEX 字串,作為 DLL 函式的回傳值,提供給 mRuby 腳本使用。
mRuby 腳本再以字串操作方式解析與應用回應資料(例如擷取命令回應碼、狀態碼、資料內容等)。
透過此架構,mRuby 僅需操作字串,將複雜的資料型態轉換與 TCP 封包組解工作交由 C++ DLL 處理,可大幅簡化腳本邏輯並提高穩定性與可維護性。
Ruby程式碼範例如下
代碼: 選擇全部
# TCP_Write
keyin_data = rdstr(400, 0, 0, 40, 99, 0) # 從系統變數讀取輸入資料(例:畫面鍵入)
keyin_hex = "000A40120000" + keyin_data.to_s # 前面為寫入用固定指令,keyin_data為要寫入的資料
TcpSendHexString($P_sock, keyin_hex, keyin_hex.length)
recv1 = TcpRecvHexString($P_sock)
sleep(100)
recv2 = TcpRecvHexString($P_sock)
$P_recvdata1 = recv1.to_s
$P_recvdata2 = recv2.to_s
代碼: 選擇全部
# TCP_Read
keyin_hex = "000610120000" # 讀取用固定指令
TcpSendHexString($P_sock, keyin_hex, keyin_hex.length)
recv1 = TcpRecvHexString($P_sock)
sleep(100)
recv2 = TcpRecvHexString($P_sock)
$P_recvdata1 = recv1.to_s
$P_recvdata2 = recv2.to_s
代碼: 選擇全部
__declspec(dllexport) int TcpSendHexString(int sock, const char* hexstr, int hexlen) {
char buffer[1024] = {0};
int len = 0;
// 檢查長度是否為偶數
if (hexlen % 2 != 0) {
return -2;
}
// 檢查是否為合法 HEX 字元
for (int i = 0; i < hexlen; ++i) {
char c = hexstr[i];
if (!((c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') ||
(c >= 'a' && c <= 'f'))) {
return -3;
}
}
// 將字串轉換為 byte buffer
for (int i = 0; i < hexlen; i += 2) {
unsigned char high = hexCharToByte(hexstr[i]);
unsigned char low = hexCharToByte(hexstr[i + 1]);
buffer[len++] = (unsigned char)((high << 4) | low);
}
int sent = send((SOCKET)sock, buffer, len, 0);
if (sent <= 0) {
//MessageBoxA(NULL, "send() 傳送失敗", "Socket 錯誤", MB_OK);
return -1;
}
return sent;
}
代碼: 選擇全部
// 接收資料並回傳 HEX 字串
__declspec(dllexport) const char* TcpRecvHexString(int sock)
{
memset(g_recvRaw, 0, sizeof(g_recvRaw));
memset(g_hexBuffer, 0, sizeof(g_hexBuffer));
int total = 0;
// 第一步:先讀取封包長度的前兩個位元組(高位 + 低位)
while (total < 2) {
int ret = recv((SOCKET)sock, g_recvRaw + total, 2 - total, 0);
if (ret <= 0) return ""; // 連線中斷或錯誤
total += ret;
}
// 第二步:解析封包總長度(Byte[0] = 高位, Byte[1] = 低位)
int packet_len = ((unsigned char)g_recvRaw[0] << 8) | (unsigned char)g_recvRaw[1];
// 安全檢查,避免溢出
if (packet_len > sizeof(g_recvRaw)) return "";
// 第三步:讀取剩餘封包內容
while (total < packet_len) {
int ret = recv((SOCKET)sock, g_recvRaw + total, packet_len - total, 0);
if (ret <= 0) return "";
total += ret;
}
// 第四步:將整包資料轉為十六進位格式的字串
char* p = g_hexBuffer;
for (int i = 0; i < total; ++i) {
#ifdef _WIN32_WCE
sprintf(p, "%02X ", (unsigned char)g_recvRaw[i]); //WinCE
#else
wsprintfA(p, "%02X ", (unsigned char)g_recvRaw[i]); //CNC GUIDE, iH PRO
#endif
p += 3;
}
return g_hexBuffer;
}
