需求概述
本章要实现一个流程:EG8200采集西门子S7-200Smart的数据,并组装成JSON格式通过HTTP上报应用平台。
源-(S7转Modbus-https://www.iotrouter.com/news/2150.html)
要采集的PLC点位表如下:
PLC | S7-200 Smart |
IP | 192.168.0.34/102 |
点表(DB1) |
地址 | 数据类型 | 属性 | 名称 |
V0.0 | Boolean | 只读 | 风机1接触器异常 |
V0.6 | Boolean | 只读 | 风机2接触器异常 |
V1.6 | Boolean | 只读 | 风机3接触器异常 |
V2.5 | Boolean | 只读 | 变频器通讯故障 |
I0.0 | Boolean | 只读 | 电机高温报警 |
I0.1 | Boolean | 只读 | 温度状态异常 |
VD4 | Float | 只读 | 油压工程值 |
VD8 | Float | 只读 | 吸入压力工程值 |
VD12 | Float | 只读 | 燃油油位工程值 |
VD16 | Float | 只读 | 机柜温度值 |
VD20 | Float | 只读 | 发动机电压值 |
VD24 | Float | 只读 | 发动机电流值 |
VD28 | Float | 只读 | 发动机工作时间 |
VD32 | Float | 只读 | 泵压工程值 |
VW120 | Unsigned16 | 只读 | 本机编号 |
VW750 | Unsigned16 | 只读 | 本机组号 |
VW2402 | Unsigned16 | 只读 | 电机1温度 |
VW2404 | Unsigned16 | 只读 | 电机2温度 |
VW2406 | Unsigned16 | 只读 | 电机3温度 |
VW2408 | Unsigned16 | 只读 | 风机1温度 |
VW2410 | Unsigned16 | 只读 | 风机2温度 |
VW2412 | Unsigned16 | 只读 | 风机3温度 |
HTTP通信相关参数格式如下:
##数据推送
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"
}
需求分析
在制作流程时,基础的逻辑是根据数据走向来制作流程。其中主要工作分为三步:
步:通过S7协议读取PLC变量数据,得到的数据存储在内存中(西门子节点)
第二步:将数据按照JSON格式进行格式化(函数节点)
第三步:配置HTTP 请求参数
需求实现
1. 采集PLC数据
从节点库拖入一个西门子节点,以及一个调试节点,调试节点用于查看读取到的PLC数据集,方便定位问
双击西门子节点,根据需求概述的内容填写对应的设置参数,如下图所示:
IP:PLC的IP端口:102(S7协议通信默认端口102)
采集周期:1000ms(默认2000ms,可自定义)
超时周期:2000ms(默认2000ms,可自定义)
数据点配置(根据帮助文档可以找到对应关系)
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
如果是连续地址,可配置起始地址并填写读取长度即可
如果设置正确,调试窗口会有日志打印,显示的是读取到的数据内容:
有的时候PLC数据点比较多,手动依次录入比较繁琐。节点支持数据点的导入导出或者参数传递的方式来读取:
本例程用到的传参方案,函数节点内的代码如下:
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. 数据格式化
根据步骤引导,在调试窗口可以看到读到的PLC数据如下:
因为应用平台已经规定了数据必须按照JSON格式上报。接下来使用函数节点Javascrip代码将数据进行格式化,代码如下:
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;
}
// 设置状态数据(1表示异常,0表示正常)
setStatusData(statusData) {
this.status = statusData;
}
// 设置传感器数据
setSensorData(sensorData) {
this.data = sensorData;
}
// 设置时间戳
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(), // 时
"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 {
// 设置状态数据
const statusData = {
fan1ContactError: v0[0],
fan2ContactError: v0[6],
fan3ContactError: v1[6],
inverterCommError: v2[5],
motorHighTempAlarm: i0[0],
temperatureStatusError: i0[1]
}
myDeviceReport.setStatusData(statusData)
// 设置传感器数据
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)
// 设置时间戳
myDeviceReport.setTimestamp(fmt)
//生成JSON对象返回
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
}
复制以上代码,粘贴到函数节点内,部署后即可看到效果:
可以看到,已经将读到的PLC数据,按照需求要求转换成了终的JSON格式,且对数据进行了一定程度的计算(两位小数)。此处只是函数节点的冰山一角,因为支持Javascrip语言编程,几乎你能想到的任何功能都可以在这里实现。
3. 通过http周期上报
拖入一个HTTP节点,根据提示进行配置地址和请求方式,即可实现数据上报:
服务端返回成功,至此,数据上报已经完成,很简单几步即可实现:采集PLC数据并按照自定义JSON格式上报。
源码
所有的流程支持以json格式导入导出,方便与其他人分享做好的流程。本章节的流程json文件如下,复制后在菜单栏右上角选择导入粘贴即可,导出同理
[{"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":"调试 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":"函数计算 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":"函数计算 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 // 设置状态数据(1表示异常,0表示正常)n setStatusData(statusData) {n this.status = statusData;n }nn // 设置传感器数据n setSensorData(sensorData) {n this.data = sensorData;n }nn // 设置时间戳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(), // 时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 // 设置状态数据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 // 设置传感器数据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 // 设置时间戳n myDeviceReport.setTimestamp(fmt)n //生成JSON对象返回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"]]}]