Connecting Industrial Protocols
Available inEdgeCloudEnterprise
This section takes common data types, inclulding OPC UA, Modbus and API as examples to demonstrate the process of connecting data sources to UNS through Node-RED.
OPC UA
Section titled “OPC UA”Configuring Data Flow
Section titled “Configuring Data Flow”- Log in to Tier0, go to Flow.
- Click New Flow at the upper-right corner, enter a name, select Flow Type as Source Flow and click Save.
- Access the flow, drag inject, OPC UA Client and mqtt out nodes from the left list to the canvas.
- Connect them by order and double-click each node to configure the details.
- inject
-
Click add at the lower-left corner to add an entry of property.
-
Set the properties to be the following items:
Terminal window {"msg.payload": "[multiple data tags]","msg.topic": "multiple"} -
Click Done
-
- OPC UA Client
- Enter the Endpoint with the address and port of the OPC UA server.
- Set the Action to be SUBSCRIBE.
- Set the Interval of collecting the data, and click Done.
- mqtt out
- Expand the Server drop-down list and select emqx:1883.
- Copy the topic path from UNS.
- Click Done.
- Click Deploy at the upper-right corner, and trigger the flow by clicking the inject node switch.
Flow Reference
Section titled “Flow Reference”Show OPC UA flow JSON
[ { "id": "fn_mapper_01", "type": "function", "z": "b002a07d1163c3de", "name": "ISA95 Mapping (Air Compressor)", "func": "// OPC UA NodeId → ISA-95 MQTT Topic Mapping\n\nconst mapping = {\n \"ns=2;i=2\": {\n topic: \"global_plant/Smart_Manufacturing_Group/Suzhou_Plant/Utility_Area/Compressed_Air_System/Air_Compressor_Station/Air_Compressor_01/Metric/outlet_temperature\",\n field: \"temperature\"\n },\n \"ns=2;i=3\": {\n topic: \"global_plant/Smart_Manufacturing_Group/Suzhou_Plant/Utility_Area/Compressed_Air_System/Air_Compressor_Station/Air_Compressor_01/Metric/discharge_pressure\",\n field: \"pressure\"\n },\n \"ns=2;i=4\": {\n topic: \"global_plant/Smart_Manufacturing_Group/Suzhou_Plant/Utility_Area/Compressed_Air_System/Air_Compressor_Station/Air_Compressor_01/Metric/vibration_rms\",\n field: \"vibration\"\n }\n};\n\nlet value = msg.payload;\n\n// OPC UA structure normalize\nif (value && typeof value === \"object\") {\n if (value.value?.value !== undefined) {\n value = value.value.value;\n } else if (value.value !== undefined) {\n value = value.value;\n }\n}\n\nconst m = mapping[msg.topic];\nif (!m) return null;\n\nmsg.topic = m.topic;\n\nmsg.payload = {\n [m.field]: Number(value)\n};\n\nreturn msg;", "outputs": 1, "timeout": "", "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 340, "y": 320, "wires": [ [ "mqtt_out_01" ] ] }, { "id": "mqtt_out_01", "type": "mqtt out", "z": "b002a07d1163c3de", "name": "MQTT Air Compressor Metrics", "topic": "", "qos": "0", "retain": "false", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "broker-djnwpg00y370", "x": 570, "y": 200, "wires": [] }, { "id": "1f242deefa9fdba9", "type": "OpcUa-Client", "z": "b002a07d1163c3de", "endpoint": "298ef5d50e9dc411", "action": "subscribe", "deadbandtype": "a", "deadbandvalue": 1, "time": 10, "timeUnit": "s", "certificate": "n", "localfile": "", "localkeyfile": "", "securitymode": "None", "securitypolicy": "None", "useTransport": false, "maxChunkCount": 1, "maxMessageSize": 8192, "receiveBufferSize": 8192, "sendBufferSize": 8192, "setstatusandtime": false, "keepsessionalive": false, "name": "", "x": 220, "y": 220, "wires": [ [ "fn_mapper_01" ], [], [] ] }, { "id": "7be5f021ed1f10ef", "type": "inject", "z": "b002a07d1163c3de", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "multiple", "payload": "[{\"nodeId\":\"ns=2;i=2\",\"datatype\":\"Float\"},{\"nodeId\":\"ns=2;i=3\",\"datatype\":\"Float\"},{\"nodeId\":\"ns=2;i=4\",\"datatype\":\"Float\"}]", "payloadType": "json", "x": 110, "y": 160, "wires": [ [ "1f242deefa9fdba9" ] ] }, { "id": "broker-djnwpg00y370", "type": "mqtt-broker", "z": "b002a07d1163c3de", "name": "emqx:1883", "broker": "emqx", "port": "1883", "clientid": "355424069147520", "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closeQos": "0", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" }, { "id": "298ef5d50e9dc411", "type": "OpcUa-Endpoint", "endpoint": "opc.tcp://172.31.151.237:4841", "secpol": "None", "secmode": "None", "none": true, "login": false, "usercert": false, "usercertificate": "", "userprivatekey": "" }, { "id": "a003de8af8e62c75", "type": "global-config", "env": [], "modules": { "node-red-contrib-opcua": "0.2.339" } }]Modbus
Section titled “Modbus”Configuring Data Flow
Section titled “Configuring Data Flow”- In the flow, drag a modbus-read node from the left list to the canvas.
- Double-click the node, and configure the required information.
- Settings
- FC: Function code that specifies the Modbus action, e.g., FC 3 = Read Holding Registers.
- Address: The starting register address to read from (usually zero-based).
- Quantity: The number of consecutive registers to read.
- Poll Rate: How often the node polls the Modbus device (e.g., every 10 seconds).
- Server: Reference to a configured Modbus server (IP, port, protocol, etc.).
- Server
- Host: Modbus server IP.
- Port: Modbus server port.
- Click Done to finish node configuration, and drag in an mqtt out node to the canvas.
- Double-click mqtt out, expand the Server drop-down list and select emqx:1883.
- Copy the topic path from UNS, click Done and click Deploy at the upper-right corner.
- Click the switch of the modbus read node to trigger the flow, and go back to UNS to check the result.
Flow Reference
Section titled “Flow Reference”Show Modbus flow JSON
[ { "id": "d3becb19e0d762f4", "type": "modbus-read", "z": "b002a07d1163c3de", "name": "status", "topic": "", "showStatusActivities": false, "logIOActivities": false, "showErrors": false, "showWarnings": true, "unitid": "1", "dataType": "HoldingRegister", "adr": "0", "quantity": "1", "rate": "10", "rateUnit": "s", "delayOnStart": false, "startDelayTime": "", "server": "c665b74952f6cf5a", "useIOFile": false, "ioFile": "", "useIOForPayload": false, "emptyMsgOnFail": false, "x": 130, "y": 500, "wires": [ [ "2987638967f632b4", "09b8538e09d0766a" ], [] ] }, { "id": "b068c119928c029a", "type": "mqtt out", "z": "b002a07d1163c3de", "name": "status", "topic": "global_plant/Smart_Manufacturing_Group/Suzhou_Plant/Utility_Area/Compressed_Air_System/Air_Compressor_Station/Air_Compressor_01/State/compressor_status", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "broker-djnwpg00y370", "x": 510, "y": 540, "wires": [] }, { "id": "2987638967f632b4", "type": "debug", "z": "b002a07d1163c3de", "name": "debug 3", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 260, "y": 600, "wires": [] }, { "id": "09b8538e09d0766a", "type": "function", "z": "b002a07d1163c3de", "name": "function 1", "func": "const value = msg.payload;\n\n// 1️⃣ verify\nif (value === undefined || value === null) {\n node.warn(\"Empty Modbus payload\");\n return null;\n}\n\n// 2️⃣ convert to number\nconst status_code = Number(value);\n\n// 3️⃣ mapping to status\nconst statusMap = {\n 0: \"maintenance\",\n 1: \"running\",\n 2: \"standby\",\n 3: \"debug\",\n 4: \"fault\"\n};\n\n// 4️⃣ filtering\nif (!(status_code in statusMap)) {\n node.warn(\"Invalid status_code: \" + status_code);\n return null;\n}\n\n// 5️⃣ contextualized output\nmsg.payload = {\n status_code: status_code,\n status_label: statusMap[status_code],\n _valid: true,\n _ts: new Date().toISOString()\n};\n\n// 6️⃣ optional set topic\nmsg.topic = \"v1/aircompressor01/state\";\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 320, "y": 500, "wires": [ [ "b068c119928c029a" ] ] }, { "id": "c665b74952f6cf5a", "type": "modbus-client", "name": "modbus", "clienttype": "tcp", "bufferCommands": true, "stateLogEnabled": false, "queueLogEnabled": false, "failureLogEnabled": true, "tcpHost": "172.31.151.237", "tcpPort": "5020", "tcpType": "DEFAULT", "serialPort": "/dev/ttyUSB", "serialType": "RTU-BUFFERD", "serialBaudrate": 9600, "serialDatabits": 8, "serialStopbits": 1, "serialParity": "none", "serialConnectionDelay": 100, "serialAsciiResponseStartDelimiter": "0x3A", "unit_id": 1, "commandDelay": 1, "clientTimeout": 1000, "reconnectOnTimeout": true, "reconnectTimeout": 2000, "parallelUnitIdsAllowed": true, "showErrors": false, "showWarnings": true, "showLogs": true }, { "id": "broker-djnwpg00y370", "type": "mqtt-broker", "z": "b002a07d1163c3de", "name": "emqx:1883", "broker": "emqx", "port": "1883", "clientid": "355424069147520", "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closeQos": "0", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" }, { "id": "5cc203d0788c324d", "type": "global-config", "env": [], "modules": { "node-red-contrib-modbus": "5.43.0" } }]Configuring Data Flow
Section titled “Configuring Data Flow”- In the flow, drag a modbus-read node from the left list to the canvas.
- Double-click the node, and configure the required information.
- Settings
- FC: Function code that specifies the Modbus action, e.g., FC 3 = Read Holding Registers.
- Address: The starting register address to read from (usually zero-based).
- Quantity: The number of consecutive registers to read.
- Poll Rate: How often the node polls the Modbus device (e.g., every 10 seconds).
- Server: Reference to a configured Modbus server (IP, port, protocol, etc.).
- Server
- Host: Modbus server IP.
- Port: Modbus server port.
- Click Deploy at the upper-right corner, and trigger the flow by clicking the inject node switch.
Flow Reference
Section titled “Flow Reference”Show API flow JSON
[ { "id": "46273fcbb98afa11", "type": "inject", "z": "932355c6886aaf27", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 170, "y": 380, "wires": [ [ "3aca156076ad6dd1" ] ] }, { "id": "3aca156076ad6dd1", "type": "http request", "z": "932355c6886aaf27", "name": "", "method": "GET", "ret": "obj", "paytoqs": "ignore", "url": "http://127.0.0.1:1880/api/wms/picking-zone-a/status", "tls": "", "persist": false, "proxy": "", "insecureHTTPParser": false, "authType": "", "senderr": false, "headers": [], "x": 380, "y": 380, "wires": [ [ "83fd65cf177c2ddf" ] ] }, { "id": "83fd65cf177c2ddf", "type": "mqtt out", "z": "932355c6886aaf27", "name": "", "topic": "v1/Suzhou_Plant/Assembly-Area-1/Line-01/Station-Screwing/Metric/torque", "qos": "", "retain": "", "respTopic": "", "contentType": "", "userProps": "", "correl": "", "expiry": "", "broker": "broker-djm245hxt9d9", "x": 780, "y": 440, "wires": [] }, { "id": "broker-djm245hxt9d9", "type": "mqtt-broker", "name": "emqx:1883", "broker": "emqx", "port": "1883", "clientid": "355039328257392", "usetls": false, "protocolVersion": "4", "keepalive": "60", "cleansession": true, "birthTopic": "", "birthQos": "0", "birthPayload": "", "closeTopic": "", "closeQos": "0", "closePayload": "", "willTopic": "", "willQos": "0", "willPayload": "" }]