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)
| 位元組位置 |
說明 |
| 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 |
寫入資料(如有) |
回應格式(Response Frame)
| 位元組位置 |
說明 |
| 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

表示向 Channel 1, Address 0 寫入 2 word(資料為 0x12345678)
裝置回應(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

回傳 Channel 1, Address 0 寫入 1 word(資料為 0x1234)成功
讀取指令格式(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

表示從 Channel 1 的 Tag 位置 0,讀取 1 個字(Word)(即 4 bytes)
裝置回應(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

回傳從 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程式碼範例如下
代碼: 選擇全部
# 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
動態連結檔案(C++)程式碼範例如下
代碼: 選擇全部
__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;
}