這篇文章主要是帶你從零到一,完整掌握透過 Node-Red 讀取 ModbusTCP 資料的流程,快速建立起自己的 Modbus 通訊監控或資料記錄系統。讀完之後,你會清楚知道如何使用 node-red-contrib-modbus 節點採集暫存器類型的「數值資料」與「布林訊號」。文中示範兩組流程:一組讀取 D 暫存器,另一組讀取 Y 線圈,搭配範例設定、function 節點處理技巧,讓你能直接複製到自己的專案中。同時也會示範如何使用 ModRSsim2 建立模擬從站,進行本機測試,避免一開始就連 PLC 造成除錯負擔。
流程軟體 Node-Red 準備工作
這裡使用 Node-Red 的 node-red-contrib-modbus 節點,透過 ModbusTCP 來採集從站(PLC 或 感測器等)的資料。
- 安裝 node.js 環境
- 安裝 Node-RED 本體(不要安裝在 Docker 上)
- node-red-contrib-modbus 節點
Node-Red 流程建立與設定
這裡用兩個流程,分別採集數值(D)與布林(Y) 來作範例:

數值(D)流程設定
- inject 節點
- 設定手動觸發或者自動定時觸發
- Modbus-Getter 節點
- Address 設定位址 0、Quantity 設定資料長度 1
- FC 設定 FC3 讀取暫存器

- debug 節點
- 輸出 msg.payload 內容
布林(Y)流程設定
- inject 節點
- 設定手動觸發或者自動定時觸發
- Modbus-Getter 節點
- Address 設定位址 0、Quantity 設定資料長度 1
- FC 設定 FC2 讀取 Coil 輸出線圈(Y)
- function 節點
- 輸入程式碼:
// 從 payload 陣列中取出第一個元素
msg.payload = msg.payload[0];
// 返回被修改後的訊息物件
return msg;

*說明
即使我們只需要一個信號,但 Modbus 預設讀取的數值是陣列,例如:
array[8]
0: true
1: false
2: false
3: false
4: false
5: false
6: false
7: false
因此該 function 節點的作用,是只從陣列第一位 [0] 過濾我們要的參數。
- debug 節點
- 輸出 msg.payload 內容
Modbus Server 設定範例

- 類型選擇 TCP
- H 使用 ModRSsim2 時使用本機的 IP 地址 127.0.0.1 ,如果使用外部裝置如 PLC 則填入目標 IP 地址
- Port 在 Node-Red 使用 502
Modbus 模擬軟體 ModRSsim2 準備工作
還不知道 ModRSsim2 是什麼嗎?
點此看介紹 Node-RED 連接失敗怎麼辦? 建立 Modbus TCP 模擬從站測試
這裡使用 ModRSsim2 建立一個 Modbus TCP 從站,來測試 Node-Red 流程的讀取。
- 設定通訊協議 TCP/IP MODBUS 如下:

- 設定 Prot 502

- 確定連線狀態

Node-Red 與 ModRSsim2 通訊實測
初始連線測試
- 觸發數值(D)流程時,msg.payload 輸出 D 值 0
- 觸發布林(Y)流程,msg.payload 輸出 Y 值 false

暫存器變更測試
- 變更暫存器位址 01 為 1

- 變更暫存器位址 400001 為 100

- 觸發數值(D)流程時,msg.payload 輸出 D 值 100
- 觸發布林(Y)流程,msg.payload 輸出 Y 值 true

以上範例程式碼(匯入 Node-Red)
[{"id":"f7482f5fef479ad8","type":"tab","label":"Modus TCP","disabled":false,"info":"","env":[]},{"id":"9b1c63ebf97da009","type":"debug","z":"f7482f5fef479ad8","name":"數值輸出","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":560,"y":320,"wires":[]},{"id":"6e779b91d6bec537","type":"modbus-getter","z":"f7482f5fef479ad8","name":"D0","showStatusActivities":false,"showErrors":false,"showWarnings":true,"logIOActivities":false,"unitid":"","dataType":"HoldingRegister","adr":"0","quantity":"1","server":"4483385.85f9ac8","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"enableDeformedMessages":false,"startDelayTime":"","x":390,"y":320,"wires":[["9b1c63ebf97da009"],[]]},{"id":"194376635f1c9c66","type":"inject","z":"f7482f5fef479ad8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":230,"y":320,"wires":[["6e779b91d6bec537"]]},{"id":"8f9b273c3f4289ce","type":"debug","z":"f7482f5fef479ad8","name":"I/O輸出","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":420,"wires":[]},{"id":"29ef059dfcbabef2","type":"modbus-getter","z":"f7482f5fef479ad8","name":"Y0","showStatusActivities":false,"showErrors":false,"showWarnings":true,"logIOActivities":false,"unitid":"","dataType":"Coil","adr":"0","quantity":"1","server":"4483385.85f9ac8","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"enableDeformedMessages":false,"startDelayTime":"","x":390,"y":420,"wires":[["6778d94bb85b8d13"],[]]},{"id":"479807666a535d15","type":"inject","z":"f7482f5fef479ad8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":230,"y":420,"wires":[["29ef059dfcbabef2"]]},{"id":"6778d94bb85b8d13","type":"function","z":"f7482f5fef479ad8","name":"取陣列第0位","func":"// 從 payload 陣列中取出第一個元素\nmsg.payload = msg.payload[0];\n\n// 返回被修改後的訊息物件\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":420,"wires":[["8f9b273c3f4289ce"]]},{"id":"4483385.85f9ac8","type":"modbus-client","name":"local","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"failureLogEnabled":false,"tcpHost":"127.0.0.1","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","serialAsciiResponseStartDelimiter":"","unit_id":1,"commandDelay":1,"clientTimeout":1000,"reconnectOnTimeout":false,"reconnectTimeout":2000,"parallelUnitIdsAllowed":false,"showErrors":false,"showWarnings":true,"showLogs":true},{"id":"2f2ce5e88c584af3","type":"global-config","env":[],"modules":{"node-red-contrib-modbus":"5.44.1"}}]


