利用ioBroker的Node-Red中的modbus插件和ioBroker进行无缝衔接

记得个把月前我发过一个贴,在这 ,没看过的记得先看一下

我也不管有多少人看过这个,经过这几个月的更新,iob官方已经更新了我之前提到的各种。
你需要将iob的nr插件更新到最新版本,步骤略。

先来看个图。


这个是根据我自己的空调控制面板做的一个流,就当实践材料讲解吧。

[{"id":"d6feaa7a.d6db98","type":"comment","z":"fbb8084d.79dbf8","name":"MODBUS命令参数对照表","info":"功能码-寄存器地址-风机盘管-数据解释\n\n---01的实际寄存器地址要件减1---\n01-1-盘管阀-0 关、 1 开\n01-5-送风机-高-0 关、 1 高\n01-6-送风机-中 0 关、 1 中\n01-7-送风机-低 0 关、 1 低\n01-8-地暖阀-0 关、 1 开\n\n---03/06的实际寄存器地址要用括号内的---\n03/06-3(2)-状态-00: 关、 01: 开、 02: 防冻启动(只读);\n03/06-4(3)-模式-1 制冷, 2 制热, 3 通风, 4 地暖, 5 地暖+制热\n03/06-5(4)-设置温度-温度值(5~35℃);\n03/06-6(5)-风机模式-00:高速、 01:中速、 02:低速、 03:自动;\n03-7(6)-机型-05: T8600-TB10-9JS0-M0\n03/06-8(7)-设定防冻温度 温度值(5~+17℃);\n03-9(8)-通讯检测-每次通讯在 0/1 间翻转;\n03/06-12(11)-防冻功能-00: 关、 01: 开;\n03/06-13(12)-键盘锁定-00:无锁; 01: 锁定所有键; 02:锁定除风速键和上下键外其他键; 03:锁定开关键和时钟键。\n03/06-14(13)-门卡信息-00:拔卡 01:插卡\n03/06-15(14)-门卡拔出后制冷温度-设定范围 22~32℃\n03/06-16(15)-门卡拔出后制热温度-设定范围 10~21℃\n\n---04的实际寄存器地址要件减1---\n04-1-室内温度-温度值(0~+99℃) 温度值是实际的10倍\n","x":210,"y":900,"wires":[]},{"id":"2c13f2c0.dfdf5e","type":"function","z":"fbb8084d.79dbf8","name":"读取设备请求","func":"//构造输出内容模板\nvar att = {\n    'fc': '', //功能码\n    'unitid': '', //设备地址\n    'address': '', //寄存器地址\n    'quantity': 1 //数量始终为1\n};\n\nvar UNITID = [2,3,4,5]; //设备地址,需要自己在设备上面设置\nvar FC = [1,3,4]; //功能码,参考MODBUS命令表\nvar ADDRESS_1 = [0,4,5,6,7]; //寄存器地址,对应功能码1\nvar ADDRESS_3 = [2,3,4,5,7,11,12]; //寄存器地址,对应功能码3\nvar ADDRESS_4 = [0]; //寄存器地址,对应功能码4\n\n\nfor(var i=0;i<UNITID.length;i++){ //遍历设备\n    for(var j=0;j<FC.length;j++){ //遍历功能码\n        switch (FC[j]) { //匹配功能码\n            case 1: //如果是功能码1\n                for(var k=0;k<ADDRESS_1.length;k++){ //遍历寄存器地址\n                    //生成数据\n                    att = {'unitid': UNITID[i], 'fc': FC[j], 'address': ADDRESS_1[k], 'quantity': 1 };\n                    node.send({payload:att});\n                }\n                break;\n            case 3: //如果是功能码3\n                for(var m=0;m<ADDRESS_3.length;m++){ //遍历寄存器地址\n                    //生成数据\n                    att = {'unitid': UNITID[i], 'fc': FC[j], 'address': ADDRESS_3[m], 'quantity': 1 };\n                    node.send({payload:att});\n                }\n                break;\n            case 4: //如果是功能码4\n                for(var n=0;n<ADDRESS_4.length;n++){ //遍历寄存器地址\n                    //生成数据\n                    att = {'unitid': UNITID[i], 'fc': FC[j], 'address': ADDRESS_4[n], 'quantity': 1 };\n                    node.send({payload:att});\n                }\n        }\n    }\n}\n\n","outputs":1,"noerr":0,"x":340,"y":940,"wires":[["535a0d8e.0a1824"]]},{"id":"b378a0a8.c1e1d","type":"inject","z":"fbb8084d.79dbf8","name":"15秒更新","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":160,"y":940,"wires":[["2c13f2c0.dfdf5e"]]},{"id":"535a0d8e.0a1824","type":"delay","z":"fbb8084d.79dbf8","name":"消息排队间隔 70ms","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"0.07","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":550,"y":960,"wires":[["1eff812d.1ccf7f"]]},{"id":"1a211f19.53e161","type":"modbus-flex-getter","z":"fbb8084d.79dbf8","name":"状态读取","showStatusActivities":true,"showErrors":true,"logIOActivities":false,"server":"b107d417.3dd8a8","useIOFile":false,"ioFile":"","useIOForPayload":false,"x":980,"y":940,"wires":[["4e2230ab.016a2","e98748e9.d495e8"],[]]},{"id":"e78c4f16.228c7","type":"modbus-flex-write","z":"fbb8084d.79dbf8","name":"状态写入","showStatusActivities":true,"showErrors":true,"server":"b107d417.3dd8a8","x":980,"y":980,"wires":[["4e2230ab.016a2","f5c2f386.66aac"],[]]},{"id":"158861fb.df8c0e","type":"ioBroker in","z":"fbb8084d.79dbf8","name":"York","topic":"node-red.0.York.*","payloadType":"value","onlyack":"","func":"all","gap":"","x":170,"y":980,"wires":[["7b64ac7.ffc3854","3a2b3ae6.7edde6"]]},{"id":"2f436fc5.ff67c","type":"ioBroker out","z":"fbb8084d.79dbf8","name":"推送到 iob","topic":"","ack":"true","autoCreate":"true","stateName":"","stateUnit":"","stateMin":"","stateMax":"","x":1350,"y":960,"wires":[]},{"id":"1eff812d.1ccf7f","type":"function","z":"fbb8084d.79dbf8","name":"判断是读取还是写入操作","func":"if(msg.topic){ //如果存在msg.topic则认为是写入(控制设备)操作(由ioBroker in输入)\n    if(msg.topic.indexOf('node-red/0/York') > -1){ //判断msg.topic是否包含node-red/0/York,用于剔除非需求消息\n        var arr = msg.topic.split('/'); //分割topic\n        var att = { //构造消息内容\n            'unitid': '', //设备地址\n            'fc': '', //功能码\n            'address': '', //寄存器地址\n            'value': '', //参数值\n            'quantity': 1 //数量始终为1\n        };\n\t\t//判断是哪个房间的\n\t\tvar room_unitid;\n\t\tswitch (arr[3]) { //根据topic获取的内容进行设备判断\n\t\t\tcase 'SecondBedroom': //次卧\n\t\t\t\troom_unitid = 2; //\n\t\t\t\tbreak;\n\t\t\tcase 'MasterBedroom': //主卧\n\t\t\t\troom_unitid = 3;\n\t\t\t\tbreak;\n\t\t\tcase 'ChildrenRoom': //儿童房\n\t\t\t\troom_unitid = 4;\n\t\t\t\tbreak;\n\t\t\tcase 'LivingRoom': //客餐厅\n\t\t\t\troom_unitid = 5;\n\t\t}\n\t\tswitch (arr[4]) { //根据topic获取的内容进行操作判断\n\t\t\tcase 'Power':\n\t\t\t\tatt = {'unitid': room_unitid, 'fc': 6, 'address': 2, 'value':msg.payload===true?1:0, 'quantity': 1 };\n\t\t\t\tbreak;\n\t\t\tcase 'Mode':\n\t\t\t\tatt = {'unitid': room_unitid, 'fc': 6, 'address': 3, 'value':Number(msg.payload), 'quantity': 1 };\n\t\t\t\tbreak;\n\t\t\tcase 'Temperature_Set':\n\t\t\t\tatt = {'unitid': room_unitid, 'fc': 6, 'address': 4, 'value':Number(msg.payload*10), 'quantity': 1 };\n\t\t\t\tbreak;\n\t\t\tcase 'Wind_Speed':\n\t\t\t\tatt = {'unitid': room_unitid, 'fc': 6, 'address': 5, 'value':Number(msg.payload), 'quantity': 1 };\n\t\t\t\tbreak;\n\t\t\tcase 'Anti-frost_Temperature':\n\t\t\t\tatt = {'unitid': room_unitid, 'fc': 6, 'address': 7, 'value':Number(msg.payload*10), 'quantity': 1 };\n\t\t\t\tbreak;\n\t\t\tcase 'Anti-frost_Protector':\n\t\t\t\tatt = {'unitid': room_unitid, 'fc': 6, 'address': 11, 'value':msg.payload===true?1:0, 'quantity': 1 };\n\t\t\t\tbreak;\n\t\t\tcase 'Key':\n\t\t\t\tatt = {'unitid': room_unitid, 'fc': 6, 'address': 12, 'value':Number(msg.payload), 'quantity': 1 };\n        }\n        if(att.fc !== ''){ //验证生成的消息是否包含fc参数\n            node.status({text:`控制${arr[3]}_${arr[4]}:${att.value}`}); //显示状态\n            node.send([null,{payload:att}]); //输出结果\n        }\n    }\n}else{ //如果不存在msg.topic则认为是读取操作\n    node.status({text:`读取设备状态`});\n    node.send([msg,null]);\n}\n","outputs":2,"noerr":0,"x":770,"y":960,"wires":[["1a211f19.53e161"],["e78c4f16.228c7","e15f87bc.69df08"]]},{"id":"7b64ac7.ffc3854","type":"function","z":"fbb8084d.79dbf8","name":"和存储值对比","func":"if(msg.payload != flow.get(msg.topic)){ //判断iob更新的数据和存储的数据是否一致\n    var arr = msg.topic.split('/'); //切割topic\n    node.status({text:`更新${arr[3]}: ${arr[4]}`}); //显示操作\n    return msg; //对比不一致的话输出内容进行下一步操作\n}else{ //如果对比一致,则不进行任何处理\n    node.status({text:'比对一致,不处理'});\n}","outputs":1,"noerr":0,"x":340,"y":980,"wires":[["d073a5ff.ce6748","535a0d8e.0a1824"]]},{"id":"10b7a1d5.1171fe","type":"debug","z":"fbb8084d.79dbf8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1330,"y":920,"wires":[]},{"id":"d073a5ff.ce6748","type":"debug","z":"fbb8084d.79dbf8","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":510,"y":1000,"wires":[]},{"id":"4e2230ab.016a2","type":"function","z":"fbb8084d.79dbf8","name":"数据处理后存储","func":"var arr; //参数输入\n\nif(msg.payload.fc){ //如果输入的消息中包含msg.payload.fc信息\n\tarr = msg.payload; //则消息为写入操作传递,直接引用传递过来的参数\n}else{ //否则重构数据结构,数据来源为查询操作的msg.input.payload\n\tarr = {\n\t\t'fc': msg.input.payload.fc, //功能码\n\t\t'unitid': msg.input.payload.unitid, //设备地址\n\t\t'address': msg.input.payload.address, //寄存器地址\n\t\t'value': msg.payload[0], //状态码\n\t\t'quantity': 1 //数量始终为1\n\t};\n}\n\n//该值用于消息传递或者存储数据\nvar york_iob = {};\n// 案例 york_iob = {topic:'York/SecondBedroom/Coil_Valve', payload:arr.value===1?true:false, stateName:'name', stateRole:'state', stateType:'boolean', stateReadonly:true, stateUnit:'unit', stateMin:'1', stateMax:'10'}\n\n//判断是哪个房间的\nvar room = \"\";\nswitch (arr.unitid) { //设备编号\n    case 2: //次卧\n\t\troom = \"SecondBedroom\";\n\t\troom_name = \"次卧\";\n\t\tbreak;\n    case 3: //主卧\n\t\troom = \"MasterBedroom\";\n\t\troom_name = \"主卧\";\n\t\tbreak;\n    case 4: //儿童房\n\t\troom = \"ChildrenRoom\";\n\t\troom_name = \"儿童房\";\n\t\tbreak;\n    case 5: //客餐厅\n\t\troom = \"LivingRoom\";\n\t\troom_name = \"客餐厅\";\n}\n\n//风机状态\nif(arr.fc == 1 && arr.address == 4 && arr.value === true){\n    york_iob = {topic:`York/${room}/Fan`, stateName:`${room_name}风机状态`, payload:\"high\", stateRole:'state', stateType:'string', stateReadonly:true};\n}else if(arr.fc == 1 && arr.address == 5 && arr.value === true){\n    york_iob = {topic:`York/${room}/Fan`, stateName:`${room_name}风机状态`, payload:\"middle\", stateRole:'state', stateType:'string', stateReadonly:true};\n}else if(arr.fc == 1 && arr.address == 6 && arr.value === true){\n    york_iob = {topic:`York/${room}/Fan`, stateName:`${room_name}风机状态`, payload:\"low\", stateRole:'state', stateType:'string', stateReadonly:true};\n}else{\n\tyork_iob = {topic:`York/${room}/Fan`, stateName:`${room_name}风机状态`, payload:\"stop\", stateRole:'state', stateType:'string', stateReadonly:true};\n}\n\n//匹配房间、操作内容,并生成消息内容\nswitch (arr.fc) { //功能码:1/3/4/6\n\tcase 1: //盘管阀、风机、地暖阀运行状态\n\t\tswitch (arr.address) { //寄存器地址\n\t\t\tcase 0: //盘管阀\n\t\t\t\tyork_iob = {topic:`York/${room}/Coil_Valve`, stateName:`${room_name}盘管阀状态`, payload:arr.value===1?\"on\":\"off\", stateRole:'state', stateType:'string', stateReadonly:true};\n\t\t\t\tbreak;\n\t\t\tcase 7: //地暖阀\n\t\t\t\tyork_iob = {topic:`York/${room}/FloorHeating_Valve`, stateName:`${room_name}地暖阀状态`, payload:arr.value===1?\"on\":\"off\", stateRole:'state', stateType:'string', stateReadonly:true};\n\t\t}\n\t\tbreak;\n\tcase 3: //获取控制面板设置值\n\t\tswitch (arr.address) { //寄存器地址\n\t\t\tcase 2: //控制器开关\n\t\t\t\tyork_iob = {topic:`York/${room}/Power`, stateName:`${room_name}温控系统电源开关`, payload:arr.value===1?true:false, stateRole:'switch', stateType:'boolean', stateReadonly:false};\n\t\t\t\tbreak;\n\t\t\tcase 3: //运行模式\n\t\t\t\tyork_iob = {topic:`York/${room}/Mode`, stateName:`${room_name}温控系统运行模式`, payload:Number(arr.value), stateRole:'state', stateType:'number', stateReadonly:false};\n\t\t\t\tbreak;\n\t\t\tcase 4: //设置的温度\n\t\t\t\tyork_iob = {topic:`York/${room}/Temperature_Set`, stateName:`${room_name}预期温度`, payload:Number(arr.value/10), stateRole:'value.temperature', stateType:'number', stateReadonly:false, stateUnit:'℃', stateMin:15, stateMax:29};\n\t\t\t\tbreak;\n\t\t\tcase 5: //风速\n\t\t\t\tyork_iob = {topic:`York/${room}/Wind_Speed`, stateName:`${room_name}风机运行模式`, payload:Number(arr.value), stateRole:'state', stateType:'number', stateReadonly:false};\n\t\t\t\tbreak;\n\t\t\tcase 7: //防冻温度\n\t\t\t\tyork_iob = {topic:`York/${room}/Anti-frost_Temperature`, stateName:`${room_name}温控系统防冻温度`, payload:Number(arr.value/10), stateRole:'value.temperature', stateType:'number', stateReadonly:false, stateUnit:'℃', stateMin:5, stateMax:10};\n\t\t\t\tbreak;\n\t\t\tcase 11: //防冻功能\n\t\t\t\tyork_iob = {topic:`York/${room}/Anti-frost_Protector`, stateName:`${room_name}温控系统防冻功能`, payload:arr.value===1?true:false, stateRole:'switch', stateType:'boolean', stateReadonly:false};\n\t\t\t\tbreak;\n\t\t\tcase 12: //键盘状态\n\t\t\t\tyork_iob = {topic:`York/${room}/Key`, stateName:`${room_name}温控系统按键锁`, payload:Number(arr.value), stateRole:'state', stateType:'number', stateReadonly:false};\n\t\t}\n\t\tbreak;\n\tcase 4: //环境温度\n\t\tswitch (arr.address) { //寄存器地址\n\t\t\tcase 0: //环境温度\n\t\t\t\tyork_iob = {topic:`York/${room}/Temperature`, stateName:`${room_name}环境温度`, payload:Number(arr.value/10), stateRole:'value.temperature', stateType:'number', stateReadonly:true, stateUnit:'℃'};\n\t\t}\n\t\tbreak;\n\tcase 6: //操控控制面板\n\t\tswitch (arr.address) { //寄存器地址\n\t\t\tcase 2: //控制器开关\n\t\t\t\tyork_iob = {topic:`York/${room}/Power`, stateName:`${room_name}温控系统电源开关`, payload:arr.value===1?true:false, stateRole:'switch', stateType:'boolean', stateReadonly:false};\n\t\t\t\tbreak;\n\t\t\tcase 3: //运行模式\n\t\t\t\tyork_iob = {topic:`York/${room}/Mode`, stateName:`${room_name}温控系统运行模式`, payload:Number(arr.value), stateRole:'state', stateType:'number', stateReadonly:false};\n\t\t\t\tbreak;\n\t\t\tcase 4: //设置的温度\n\t\t\t\tyork_iob = {topic:`York/${room}/Temperature_Set`, stateName:`${room_name}预期温度`, payload:Number(arr.value/10), stateRole:'value.temperature', stateType:'number', stateReadonly:false, stateUnit:'℃', stateMin:15, stateMax:29};\n\t\t\t\tbreak;\n\t\t\tcase 5: //风速\n\t\t\t\tyork_iob = {topic:`York/${room}/Wind_Speed`, stateName:`${room_name}风机运行模式`, payload:Number(arr.value), stateRole:'state', stateType:'number', stateReadonly:false};\n\t\t\t\tbreak;\n\t\t\tcase 7: //防冻温度\n\t\t\t\tyork_iob = {topic:`York/${room}/Anti-frost_Temperature`, stateName:`${room_name}温控系统防冻温度`, payload:Number(arr.value/10), stateRole:'value.temperature', stateType:'number', stateReadonly:false, stateUnit:'℃', stateMin:5, stateMax:10};\n\t\t\t\tbreak;\n\t\t\tcase 11: //防冻功能\n\t\t\t\tyork_iob = {topic:`York/${room}/Anti-frost_Protector`, stateName:`${room_name}温控系统防冻功能`, payload:arr.value===1?true:false, stateRole:'switch', stateType:'boolean', stateReadonly:false};\n\t\t\t\tbreak;\n\t\t\tcase 12: //键盘状态\n\t\t\t\tyork_iob = {topic:`York/${room}/Key`, stateName:`${room_name}温控系统按键锁`, payload:Number(arr.value), stateRole:'state', stateType:'number', stateReadonly:false};\n\t\t}\n}\n\n//生成ioBroker消息topic\nvar topic = 'node-red/0/'+york_iob.topic;\n\n//存储topic和值\nflow.set(topic,york_iob.payload);\n\nmsg = york_iob;\nmsg.topic = topic;\n\n//发送消息\nnode.send(msg);\n\n","outputs":1,"noerr":0,"x":1160,"y":960,"wires":[["2f436fc5.ff67c","10b7a1d5.1171fe"]]},{"id":"e98748e9.d495e8","type":"debug","z":"fbb8084d.79dbf8","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1130,"y":920,"wires":[]},{"id":"f5c2f386.66aac","type":"debug","z":"fbb8084d.79dbf8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1130,"y":1000,"wires":[]},{"id":"3a2b3ae6.7edde6","type":"debug","z":"fbb8084d.79dbf8","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":310,"y":1040,"wires":[]},{"id":"e15f87bc.69df08","type":"debug","z":"fbb8084d.79dbf8","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":970,"y":1060,"wires":[]},{"id":"b107d417.3dd8a8","type":"modbus-client","z":"","name":"","clienttype":"tcp","bufferCommands":false,"stateLogEnabled":false,"tcpHost":"192.168.88.99","tcpPort":"502","tcpType":"TELNET","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":1,"commandDelay":1000,"clientTimeout":1000,"reconnectTimeout":2000}]

modbus操作只用了2个节点,一个是读取,一个是写入,目的自然是为了避免发生撞车事件。
主要工作都是通过function完成,里面有比较详细的注解,具体可以看注解,都不是很难的if、case等,
唯一复杂的可能就是逻辑,逻辑理顺了这个流也就看懂了。。

短暂测试过一段时间,应该是没什么问题的,有什么改进、bug跟帖回复。


目前这个也只写了部分,设备初始化,自动化都还没完成 :joy: