需求概述
本章要實(shí)現(xiàn)一個(gè)流程:EG8200采集西門子S7-200Smart的數(shù)據(jù),并組裝成JSON格式通過(guò)HTTP上報(bào)應(yīng)用平臺(tái)。
源-(S7轉(zhuǎn)Modbus-https://www.iotrouter.com/news/2150.html)
要采集的PLC點(diǎn)位表如下:
PLC | S7-200 Smart | ||
IP | 192.168.0.34/102 | ||
點(diǎn)表(DB1) | |||
地址 | 數(shù)據(jù)類型 | 屬性 | 名稱 |
V0.0 | Boolean | 只讀 | 風(fēng)機(jī)1接觸器異常 |
V0.6 | Boolean | 只讀 | 風(fēng)機(jī)2接觸器異常 |
V1.6 | Boolean | 只讀 | 風(fēng)機(jī)3接觸器異常 |
V2.5 | Boolean | 只讀 | 變頻器通訊故障 |
I0.0 | Boolean | 只讀 | 電機(jī)高溫報(bào)警 |
I0.1 | Boolean | 只讀 | 溫度狀態(tài)異常 |
VD4 | Float | 只讀 | 油壓工程值 |
VD8 | Float | 只讀 | 吸入壓力工程值 |
VD12 | Float | 只讀 | 燃油油位工程值 |
VD16 | Float | 只讀 | 機(jī)柜溫度值 |
VD20 | Float | 只讀 | 發(fā)動(dòng)機(jī)電壓值 |
VD24 | Float | 只讀 | 發(fā)動(dòng)機(jī)電流值 |
VD28 | Float | 只讀 | 發(fā)動(dòng)機(jī)工作時(shí)間 |
VD32 | Float | 只讀 | 泵壓工程值 |
VW120 | Unsigned16 | 只讀 | 本機(jī)編號(hào) |
VW750 | Unsigned16 | 只讀 | 本機(jī)組號(hào) |
VW2402 | Unsigned16 | 只讀 | 電機(jī)1溫度 |
VW2404 | Unsigned16 | 只讀 | 電機(jī)2溫度 |
VW2406 | Unsigned16 | 只讀 | 電機(jī)3溫度 |
VW2408 | Unsigned16 | 只讀 | 風(fēng)機(jī)1溫度 |
VW2410 | Unsigned16 | 只讀 | 風(fēng)機(jī)2溫度 |
VW2412 | Unsigned16 | 只讀 | 風(fēng)機(jī)3溫度 |
##數(shù)據(jù)推送 url:192.168.0.32:1880/api/device/reportData method:POST JSON格式: { "deviceInfo": { "machineNumber": 9301, "machineGroupNumber": 1 }, "status": { "fan1ContactError": true, "fan2ContactError": false, "fan3ContactError": false, "inverterCommError": false, "motorHighTempAlarm": false, "temperatureStatusError": false }, "data": { "oilPressure": 17.83, "suctionPressure": 3.7, "fuelLevel": 0.23, "cabinetTemperature": 237.2, "engineVoltage": 415.64, "engineCurrent": 65.1, "engineRuntime": 72.6, "pumpPressure": 85.3, "motor1Temperature": 20, "motor2Temperature": 19, "motor3Temperature": 19, "fan1Temperature": 21, "fan2Temperature": 33, "fan3Temperature": 26 }, "timestamp": "2022-7-08 11:28:55" }
需求分析
在制作流程時(shí),基礎(chǔ)的邏輯是根據(jù)數(shù)據(jù)走向來(lái)制作流程。其中主要工作分為三步:
第一步:通過(guò)S7協(xié)議讀取PLC變量數(shù)據(jù),得到的數(shù)據(jù)存儲(chǔ)在內(nèi)存中(西門子節(jié)點(diǎn))
第二步:將數(shù)據(jù)按照J(rèn)SON格式進(jìn)行格式化(函數(shù)節(jié)點(diǎn))
第三步:配置HTTP 請(qǐng)求參數(shù)
需求實(shí)現(xiàn)
1. 采集PLC數(shù)據(jù)
從節(jié)點(diǎn)庫(kù)拖入一個(gè)西門子節(jié)點(diǎn),以及一個(gè)調(diào)試節(jié)點(diǎn),調(diào)試節(jié)點(diǎn)用于查看讀取到的PLC數(shù)據(jù)集,方便定位問(wèn)
雙擊西門子節(jié)點(diǎn),根據(jù)需求概述的內(nèi)容填寫對(duì)應(yīng)的設(shè)置參數(shù),如下圖所示:
IP:PLC的IP 端口:102(S7協(xié)議通信默認(rèn)端口102) 采集周期:1000ms(默認(rèn)2000ms,可自定義) 超時(shí)周期:2000ms(默認(rèn)2000ms,可自定義) 數(shù)據(jù)點(diǎn)配置(根據(jù)幫助文檔可以找到對(duì)應(yīng)關(guān)系) V0.0 V0.6 V1.6 V2.5 I0.0 I0.1 VD4 VD8 VD12 VD16 VD20 VD24 VD28 VD32 VW120 VW750 VW2402 VW2404 VW2408 VW2410 VW2412 如果是連續(xù)地址,可配置起始地址并填寫讀取長(zhǎng)度即可
如果設(shè)置正確,調(diào)試窗口會(huì)有日志打印,顯示的是讀取到的數(shù)據(jù)內(nèi)容:
有的時(shí)候PLC數(shù)據(jù)點(diǎn)比較多,手動(dòng)依次錄入比較繁瑣。節(jié)點(diǎn)支持?jǐn)?shù)據(jù)點(diǎn)的導(dǎo)入導(dǎo)出或者參數(shù)傳遞的方式來(lái)讀取:
本例程用到的傳參方案,函數(shù)節(jié)點(diǎn)內(nèi)的代碼如下:
msg.payload = [ { func: "ReadBoolArray", body: { name: "", address: "V0", length: 8 } }, { func: "ReadBoolArray", body: { name: "", address: "V1", length: 8 } }, { func: "ReadBoolArray", body: { name: "", address: "V2", length: 8 } }, { func: "ReadFloatArray", body: { name: "", address: "VD4", length: 8 } }, { func: "ReadUInt16", body: { name: "", address: "VW120" } }, { func: "ReadUInt16", body: { name: "", address: "VW750" } }, { func: "ReadUInt16Array", body: { name: "", address: "VW2402", length: 6 } }, { func: "ReadBoolArray", body: { name: "", address: "I0", length: 8 } } ] return msg;
2. 數(shù)據(jù)格式化
根據(jù)步驟引導(dǎo),在調(diào)試窗口可以看到讀到的PLC數(shù)據(jù)如下:
因?yàn)閼?yīng)用平臺(tái)已經(jīng)規(guī)定了數(shù)據(jù)必須按照J(rèn)SON格式上報(bào)。接下來(lái)使用函數(shù)節(jié)點(diǎn)Javascrip代碼將數(shù)據(jù)進(jìn)行格式化,代碼如下:
class DeviceReport { constructor(machineNumber, machineGroupNumber) { this.deviceInfo = { machineNumber: machineNumber, machineGroupNumber: machineGroupNumber }; this.status = { fan1ContactError: 0, fan2ContactError: 0, fan3ContactError: 0, inverterCommError: 0, motorHighTempAlarm: 0, temperatureStatusError: 0 }; this.data = { oilPressure: 0, suctionPressure: 0, fuelLevel: 0, cabinetTemperature: 0, engineVoltage: 0, engineCurrent: 0, engineRuntime: 0, pumpPressure: 0, motor1Temperature: 0, motor2Temperature: 0, motor3Temperature: 0, fan1Temperature: 0, fan2Temperature: 0, fan3Temperature: 0 }; this.timestamp = null; } // 設(shè)置狀態(tài)數(shù)據(jù)(1表示異常,0表示正常) setStatusData(statusData) { this.status = statusData; } // 設(shè)置傳感器數(shù)據(jù) setSensorData(sensorData) { this.data = sensorData; } // 設(shè)置時(shí)間戳 setTimestamp(timestamp) { this.timestamp = timestamp; } generateReport() { return { deviceInfo: this.deviceInfo, status: this.status, data: this.data, timestamp: this.timestamp }; } } function dateFormat(fmt, timestamp) { let ret; const opt = { "Y+": timestamp.getFullYear().toString(), // 年 "m+": (timestamp.getMonth() + 1).toString(), // 月 "d+": timestamp.getDate().toString(), // 日 "H+": timestamp.getHours().toString(), // 時(shí) "M+": timestamp.getMinutes().toString(), // 分 "S+": timestamp.getSeconds().toString() // 秒 }; for (let k in opt) { ret = new RegExp("(" + k + ")").exec(fmt); if (ret) { fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0"))) }; }; return fmt; } function setValue(data) { var myDeviceReport = new DeviceReport(data.VW120, data.VW750) var v0 = data.V0 var v1 = data.V1 var v2 = data.V2 var i0 = data.I0 var vd4 = data.VD4 var vd2402 = data.VW2402 const fmt = dateFormat("YYYY-mm-dd HH:MM:SS", new Date()) try { // 設(shè)置狀態(tài)數(shù)據(jù) const statusData = { fan1ContactError: v0[0], fan2ContactError: v0[6], fan3ContactError: v1[6], inverterCommError: v2[5], motorHighTempAlarm: i0[0], temperatureStatusError: i0[1] } myDeviceReport.setStatusData(statusData) // 設(shè)置傳感器數(shù)據(jù) const sensorData = { oilPressure: vd4[0], suctionPressure: vd4[1], fuelLevel: vd4[2], cabinetTemperature: vd4[3], engineVoltage: vd4[4], engineCurrent: vd4[5], engineRuntime: vd4[6], pumpPressure: vd4[7], motor1Temperature: vd2402[0], motor2Temperature: vd2402[1], motor3Temperature: vd2402[2], fan1Temperature: vd2402[3], fan2Temperature: vd2402[4], fan3Temperature: vd2402[5] } myDeviceReport.setSensorData(sensorData) // 設(shè)置時(shí)間戳 myDeviceReport.setTimestamp(fmt) //生成JSON對(duì)象返回 return myDeviceReport.generateReport() } catch (err) { node.error(err.message); return null } } var plcData = msg.payload if (setValue(plcData)){ msg.payload = JSON.stringify(setValue(plcData),null,2) return msg }
復(fù)制以上代碼,粘貼到函數(shù)節(jié)點(diǎn)內(nèi),部署后即可看到效果:
可以看到,已經(jīng)將讀到的PLC數(shù)據(jù),按照需求要求轉(zhuǎn)換成了最終的JSON格式,且對(duì)數(shù)據(jù)進(jìn)行了一定程度的計(jì)算(兩位小數(shù))。此處只是函數(shù)節(jié)點(diǎn)的冰山一角,因?yàn)橹С諮avascrip語(yǔ)言編程,幾乎你能想到的任何功能都可以在這里實(shí)現(xiàn)。
3. 通過(guò)http周期上報(bào)
拖入一個(gè)HTTP節(jié)點(diǎn),根據(jù)提示進(jìn)行配置地址和請(qǐng)求方式,即可實(shí)現(xiàn)數(shù)據(jù)上報(bào):
服務(wù)端返回成功,至此,數(shù)據(jù)上報(bào)已經(jīng)完成,很簡(jiǎn)單幾步即可實(shí)現(xiàn):采集PLC數(shù)據(jù)并按照自定義JSON格式上報(bào)。
源碼
所有的流程支持以json格式導(dǎo)入導(dǎo)出,方便與其他人分享做好的流程。本章節(jié)的流程json文件如下,復(fù)制后在菜單欄右上角選擇導(dǎo)入粘貼即可,導(dǎo)出同理
[{"id":"edf0c4c538469caa","type":"Siemens","z":"5bb1b94e3403e2f2","name":"","protocol":"iplink_SiemensS7","IPAddress":"192.168.0.34","DestPort":"102","IPLinkTimeout":"2000","IPCommTimeout":"2000","Spname":"COM10","Baudrate":"9600","Databits":"8","Stopbit":"1","Paritybit":"0","RTSEnable":false,"SpLinktimeout":"2000","SpIntertimeout":"-1","workMode":"read","readMode":"trigger","readCyc":"1000","vartable":[],"SiemensS7Model":"5","SiemensS7ConnectionType":"1","SiemensS7Rack":"0","SiemensS7Slot":"0","SiemensS7LocalTSAP":"102","SiemensS7DestTSAP":"4D57","SiemensPPIStation":"2","stringvar":[],"customName":"Siemens:S7-S200Smart","x":510,"y":1080,"wires":[["be890e0383a04306"]]},{"id":"90be80aa485c48ae","type":"debug","z":"5bb1b94e3403e2f2","name":"調(diào)試 22","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1020,"y":1080,"wires":[]},{"id":"2485d94e74e61614","type":"function","z":"5bb1b94e3403e2f2","name":"函數(shù)計(jì)算 18","func":"msg.payload = [n { func: "ReadBoolArray", body: { name: "", address: "V0", length: 8 } },n { func: "ReadBoolArray", body: { name: "", address: "V1", length: 8 } },n { func: "ReadBoolArray", body: { name: "", address: "V2", length: 8 } },n { func: "ReadFloatArray", body: { name: "", address: "VD4", length: 8 } },n { func: "ReadUInt16", body: { name: "", address: "VW120" } },n { func: "ReadUInt16", body: { name: "", address: "VW750" } },n { func: "ReadUInt16Array", body: { name: "", address: "VW2402", length: 6 } },n { func: "ReadBoolArray", body: { name: "", address: "I0", length: 8 } }n]nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":1080,"wires":[["edf0c4c538469caa"]]},{"id":"8db3c718ecac3ed7","type":"inject","z":"5bb1b94e3403e2f2","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":150,"y":1080,"wires":[["2485d94e74e61614"]]},{"id":"be890e0383a04306","type":"function","z":"5bb1b94e3403e2f2","name":"函數(shù)計(jì)算 19","func":"class DeviceReport {n constructor(machineNumber, machineGroupNumber) {n this.deviceInfo = {n machineNumber: machineNumber,n machineGroupNumber: machineGroupNumbern };n this.status = {n fan1ContactError: 0,n fan2ContactError: 0,n fan3ContactError: 0,n inverterCommError: 0,n motorHighTempAlarm: 0,n temperatureStatusError: 0n };n this.data = {n oilPressure: 0,n suctionPressure: 0,n fuelLevel: 0,n cabinetTemperature: 0,n engineVoltage: 0,n engineCurrent: 0,n engineRuntime: 0,n pumpPressure: 0,n motor1Temperature: 0,n motor2Temperature: 0,n motor3Temperature: 0,n fan1Temperature: 0,n fan2Temperature: 0,n fan3Temperature: 0n };n this.timestamp = null;n }nn // 設(shè)置狀態(tài)數(shù)據(jù)(1表示異常,0表示正常)n setStatusData(statusData) {n this.status = statusData;n }nn // 設(shè)置傳感器數(shù)據(jù)n setSensorData(sensorData) {n this.data = sensorData;n }nn // 設(shè)置時(shí)間戳n setTimestamp(timestamp) {n this.timestamp = timestamp;n }n generateReport() {n return {n deviceInfo: this.deviceInfo,n status: this.status,n data: this.data,n timestamp: this.timestampn };n }n}nnfunction dateFormat(fmt, timestamp) {n let ret;n const opt = {n "Y+": timestamp.getFullYear().toString(), // 年n "m+": (timestamp.getMonth() + 1).toString(), // 月n "d+": timestamp.getDate().toString(), // 日n "H+": timestamp.getHours().toString(), // 時(shí)n "M+": timestamp.getMinutes().toString(), // 分n "S+": timestamp.getSeconds().toString() // 秒n };n for (let k in opt) {n ret = new RegExp("(" + k + ")").exec(fmt);n if (ret) {n fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))n };n };n return fmt;n}nfunction setValue(data) {n var myDeviceReport = new DeviceReport(data.VW120, data.VW750)n var v0 = data.V0n var v1 = data.V1n var v2 = data.V2n var i0 = data.I0n var vd4 = data.VD4n var vd2402 = data.VW2402n const fmt = dateFormat("YYYY-mm-dd HH:MM:SS", new Date())n try {n // 設(shè)置狀態(tài)數(shù)據(jù)n const statusData = {n fan1ContactError: v0[0],n fan2ContactError: v0[6],n fan3ContactError: v1[6],n inverterCommError: v2[5],n motorHighTempAlarm: i0[0],n temperatureStatusError: i0[1]n }n myDeviceReport.setStatusData(statusData)nn // 設(shè)置傳感器數(shù)據(jù)n const sensorData = {n oilPressure: vd4[0],n suctionPressure: vd4[1],n fuelLevel: vd4[2],n cabinetTemperature: vd4[3],n engineVoltage: vd4[4],n engineCurrent: vd4[5],n engineRuntime: vd4[6],n pumpPressure: vd4[7],n motor1Temperature: vd2402[0],n motor2Temperature: vd2402[1],n motor3Temperature: vd2402[2],n fan1Temperature: vd2402[3],n fan2Temperature: vd2402[4],n fan3Temperature: vd2402[5]n }n myDeviceReport.setSensorData(sensorData)n // 設(shè)置時(shí)間戳n myDeviceReport.setTimestamp(fmt)n //生成JSON對(duì)象返回n return myDeviceReport.generateReport()n } catch (err) {n node.error(err.message);n return nulln }nn}nvar plcData = msg.payloadnif (setValue(plcData)) {n msg.payload = JSON.stringify(setValue(plcData), null, 2)n return msgn}n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":710,"y":1080,"wires":[["d140dc012c0cd68f"]]},{"id":"d140dc012c0cd68f","type":"http request","z":"5bb1b94e3403e2f2","name":"","method":"POST","ret":"txt","paytoqs":"ignore","url":"192.168.0.32:1880/api/device/reportData","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":860,"y":1080,"wires":[["90be80aa485c48ae"]]}]
審核編輯 黃宇
-
西門子
+關(guān)注
關(guān)注
94文章
3034瀏覽量
115779 -
HTTP
+關(guān)注
關(guān)注
0文章
504瀏覽量
31194 -
邊緣計(jì)算
+關(guān)注
關(guān)注
22文章
3084瀏覽量
48892
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論