跳转到内容

连接工业协议

适用版本EdgeCloudEnterprise

本节以 OPC UA、Modbus 和 API 等常见数据类型为例,展示如何通过 数据连接 将数据源连接到 UNS

  1. 进入 Tier0 的 数据流程,创建一个 数据连接
  2. 在数据流中加入 injectOPC UA Clientmqtt out 节点。
  3. 按顺序连接节点,并双击每个节点配置详细信息。
  • inject
    1. 单击左下角 add 添加一条属性。

    2. 按照以下内容配置属性。

      Terminal window
      {
      "msg.payload": "[multiple data tags]",
      "msg.topic": "multiple"
      }
  • OPC UA Client
    Terminal window
    "Endpoint": "<OPC UA 服务器地址和端口>", //e.g. opc.tcp://127.0.0.1:4850
    "Action": "SUBSCRIBE",
    "Interval": "<数据采集频率>" //e.g. 10 秒
  • mqtt out
    Terminal window
    "Server": "<内置 UNS broker>", //与数据流同名
    "Topic": "<UNS 模型>",
  1. 部署并触发数据流。
显示 OPC UA 数据流 JSON
Terminal window
[
{
"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"
}
}
]
  1. 在数据流中加入 modbus-readmqtt out 节点。
  2. 双击节点配置信息。
    • modbus-read
      • Settings
        • FC:选择 Modbus 功能码。例如,FC 3 表示读取保持寄存器。
        • Address:设置起始寄存器地址。例如,从 0 开始读取。
        • Quantity:设置要连续读取的寄存器数量。
        • Poll Rate:设置 Modbus 轮询频率。例如,每 10 秒读取一次。
        • Server:选择或创建 Modbus 服务器,配置 IP、端口和协议。
      • Server
        • Host:Modbus 服务器 IP。
        • Port:Modbus 服务器端口。
    • mqtt out
      Terminal window
      "Server": "<内置 UNS broker>", //与数据流同名
      "Topic": "<UNS 模型>",
  3. 部署数据流。
显示 Modbus 数据流 JSON
Terminal window
[
{
"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"
}
}
]
  1. 在数据流中加入 injecthttp requestmqtt out 节点。
  2. 双击节点配置信息。
    • http request
      Terminal window
      "Method": "<API method>",
      "URL": "<API URL>",
      "Return": "a parsed JSON object",
      "Headers": "<API headers>"
    • mqtt out
      Terminal window
      "Server": "<内置 UNS broker>", //与数据流同名
      "Topic": "<UNS 模型>",
  3. 部署数据流。
显示 API 数据流 JSON
Terminal window
[
{
"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": ""
}
]