教程-西门子S7协议转换为HTTP协议

描述

需求概述

本章要实现一个流程: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"]]}]

 


审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分