node-red实现ModBus-RTU 通信协议(RS485信号输出)的数据交互
node-red实现485型 - 温湿度变表数据转换
本文参考《86 壳液晶温湿度变送器使用说明书(485 型)》,介绍基于485型 - 温湿度变表,通过node-red对数据进行读写的操作。
一、介绍
该变送器带有液晶显示,实时显示温湿度,背部免螺丝端子接线,可安装在标准86mm接线盒上。设备采用标准ModBus-RTU 通信协议,RS485信号输出,通信距离最大可达2000米(实测)。探头内置型、外延型可选,广泛适用于通讯机房、仓库楼宇以及自控等需要温湿度监测的场所。
回到目录
二、 通讯协议
2.1 通讯基本参数

2.2 数据帧格式定义
采用 ModBus-RTU 通讯规约,格式如下:
- 初始结构 ≥4 字节的时间
- 地址码 = 1 字节
- 功能码 = 1 字节
- 数据区 = N 字节
- 错误校验 = 16 位 CRC 码
- 结束结构 ≥4 字节的时间
- 地址码:为变送器的地址,在通讯网络中是唯一的(出厂默认 0x01)。
- 功能码:主机所发指令功能指示。
- 数据区:数据区是具体通讯数据,注意 16bits 数据高字节在前!
- CRC 码:二字节的校验码。

2.3 寄存器地址
2.4 通讯协议示例以及解释

- 温度计算:
当温度低于 0 ℃ 时温度数据以补码的形式上传。
温度:FF9B H(十六进制)= -101 => 温度 = -10.1℃ - 湿度计算:
湿度:292 H (十六进制)= 658 => 湿度 = 65.8%RH
回到目录
三、 node-red实现数据交互
3.1 node-red-node-serialport节点读取数据

根据第二章节对设备问询帧地址的定义,模拟读取如下配置信息的设备数据:
注意:这里地址码变了,我读的是地址码(devID)为40的设备地址数据,示例中读取的是地址码(devID)为1的数据,其他的设置是一样的
| 项 | 说明 | 十进制 | 十六进制 |
|---|---|---|---|
| 设备地址 | DevID | 40 | 0x28 |
| 功能码 | 读 | 3 | 0x03 |
| 数据起始地址 | address | 0 | 0x00 0x00 |
| 数据长度 | len | 2 | 0x00 0x02 |
| 检验码 | crc | 2 | 0xC3 0xF2 |
CRC(Cyclic Redundancy Check,循环冗余校验)是一种数据校验方式,主要用于判断数据传输过程中数据是否发生改变、传输是否出错,并在传输时保证数据完整性。上位机须按照MODBUS协议的命令格式发送数据(包括计算的CRC值),从机才能正确辨识数据。若无CRC值,从机将返回含有错误号的应答包,不会得到正确结果。标准的做法,发送前计算CRC值并一起发送,接收后也计算CRC值并与接收的校验码对比是否相等,以辨别数据是否准确。CRC校验码的计算,参考链接:modbus rtu协议的CRC(循环冗余校验)在线计算
计算CRC为:F2 C3 ,高位在左,低位在右,交换得到数据为:0xC3, 0xF2
- 通过node-red进行读取操作如下:
- 编辑命令:
msg.payload = Buffer.from([0x28, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC3, 0xF2]); - 向串口发送问询帧命令

- 设备返回数据,格式如下:

- 收到应答帧如下:
msg.payload = [0x28,0x03,0x04,0x01,0x3d,0x00,0xb8,0xd2,0xb3]
根据如下应答帧格式:
这里返回的数据为:
湿度值:0x01 0x3d -> 013dH -> 转换为10进制:11616+316+131 = 317
温度值:0x00,0xb8 -> 00b8H -> 转换为10进制:0+0+11×16+8×1 = 184
- 根据2.4节说明书中定义的温湿度计算方法,进行转换计算如下:
- 当温度低于 0 ℃ 时温度数据以补码的形式上传,
即温度是带符号的,需要进行处理
if(msg.payload != undefined && msg.payload != null
&& msg.payload != "" && msg.payload.length > 6){
msg.humi = Signed16ToInt16Be(msg.payload[3]*16*16 + msg.payload[4]) * 0.1;
msg.temp = Signed16ToInt16Be(msg.payload[5]*16*16 + msg.payload[6]) * 0.1;
}
return msg;
/** 有符号整数补码转换转成16位有符号整数:
* 如-80的补码为65456,读取出来是65456,需要通过该函数转换成-80
*/
function Signed16ToInt16Be(num){
//32767是16位带符号证书能表示的最大正数,当数值从32767再增加1时,就会上溢变成负数。
//即mod通信中所有的负数都是用补码形式存储(如‘-80’实际读\写出来是‘65456’)
//需要检测是否发生了从正数到负数的转换,即是否发生了上溢或下溢,如果有,进行转换
return num >= 32768 ? num - 65536 : num;
}

- 温湿度都需要进行分辨率处理,即最终数据为:值*0.1
最终得到:- 湿度值:0x01 0x3d -> 013dH -> 转换为10进制:11616+316+131 = 317 * 0.1 = 31.7%RH
- 温度值:0x00,0xb8 -> 00b8H -> 转换为10进制:0+0+11×16+8×1 = 184 = 18.4 ℃
3.2 node-red-contrib-modbus节点读取数据
通过node-red-contrib-modbus的RS485的串口进行数据操作,寄存器地址如下:
nodered读取代码示例如下:
其中转换函数如下:
if(msg.payload != undefined && msg.payload != null
&& msg.payload != "" && msg.payload.length > 1){
msg.humi = msg.payload[0] * 0.1;//湿度
msg.temp = msg.payload[1] * 0.1;//温度
msg.data = msg.topic + " : 温度->" + msg.temp + "℃ 湿度->" + msg.humi +" %RH";
}
return msg;
四、源码
[
{
"id": "22d7aa8312dc9a77",
"type": "debug",
"z": "146fc0e173b359f7",
"name": "debug 5",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 920,
"y": 220,
"wires": []
},
{
"id": "3bc4f2ae3df03b84",
"type": "modbus-flex-sequencer",
"z": "146fc0e173b359f7",
"name": "RS485-S3-th1",
"sequences": [
{
"name": "th-1",
"unitid": "1",
"fc": "FC3",
"address": "0",
"quantity": "2"
}
],
"server": "e5a968ffcde8210c",
"showStatusActivities": false,
"showErrors": false,
"showWarnings": false,
"logIOActivities": false,
"useIOFile": false,
"ioFile": "",
"useIOForPayload": false,
"emptyMsgOnFail": true,
"keepMsgProperties": false,
"delayOnStart": false,
"startDelayTime": "",
"x": 640,
"y": 220,
"wires": [
[
"99774077dc9657ef"
],
[]
]
},
{
"id": "84426b1fee609fb1",
"type": "inject",
"z": "146fc0e173b359f7",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 490,
"y": 220,
"wires": [
[
"3bc4f2ae3df03b84"
]
]
},
{
"id": "99774077dc9657ef",
"type": "function",
"z": "146fc0e173b359f7",
"name": "转换",
"func": "if(msg.payload != undefined && msg.payload != null\n && msg.payload != \"\" && msg.payload.length > 1){\n msg.humi = msg.payload[0] * 0.1;\n msg.temp = msg.payload[1] * 0.1;\n msg.data = msg.topic + \" : 温度->\" + msg.temp + \"℃ 湿度->\" + msg.humi +\" %RH\";\n}\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 790,
"y": 220,
"wires": [
[
"22d7aa8312dc9a77"
]
]
},
{
"id": "7dd37b33034d6543",
"type": "inject",
"z": "146fc0e173b359f7",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 470,
"y": 320,
"wires": [
[
"6425031d131e352d"
]
]
},
{
"id": "6425031d131e352d",
"type": "function",
"z": "146fc0e173b359f7",
"name": "指令-40",
"func": "msg.topic = \"40\";\nmsg.payload = Buffer.from([0x28, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC3, 0xF2]);\nreturn msg;",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 600,
"y": 320,
"wires": [
[
"ed90905d2e9f93dc"
]
]
},
{
"id": "ed90905d2e9f93dc",
"type": "serial request",
"z": "146fc0e173b359f7",
"name": "",
"serial": "40511a18915f475f",
"x": 740,
"y": 320,
"wires": [
[
"fcf760e9049ad139"
]
]
},
{
"id": "fcf760e9049ad139",
"type": "function",
"z": "146fc0e173b359f7",
"name": "转换",
"func": "if(msg.payload != undefined && msg.payload != null\n && msg.payload != \"\" && msg.payload.length > 6){\n msg.humi = Signed16ToInt16Be(msg.payload[3]*16*16 + msg.payload[4]) * 0.1;\n msg.temp = Signed16ToInt16Be(msg.payload[5]*16*16 + msg.payload[6]) * 0.1;\n msg.data = msg.topic + \" : 温度->\" + msg.temp + \"℃ 湿度->\" + msg.humi +\" %RH\";\n}\nreturn msg;\nfunction Signed16ToInt16Be(num){\n //32767是16位带符号证书能表示的最大正数,当数值从32767再增加1时,就会上溢变成负数。\n //即mod通信中所有的负数都是用补码形式存储(如‘-80’实际读\\写出来是‘65456’)\n //需要检测是否发生了从正数到负数的转换,即是否发生了上溢或下溢,如果有,进行转换\n return num >= 32768 ? num - 65536 : num;\n}",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 890,
"y": 320,
"wires": [
[
"8c0ad5c1417504c8"
]
]
},
{
"id": "8c0ad5c1417504c8",
"type": "debug",
"z": "146fc0e173b359f7",
"name": "debug 9",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 1020,
"y": 320,
"wires": []
},
{
"id": "e5a968ffcde8210c",
"type": "modbus-client",
"name": "",
"clienttype": "serial",
"bufferCommands": true,
"stateLogEnabled": false,
"queueLogEnabled": false,
"failureLogEnabled": true,
"tcpHost": "127.0.0.1",
"tcpPort": "502",
"tcpType": "DEFAULT",
"serialPort": "/dev/ttyS3",
"serialType": "RTU-BUFFERD",
"serialBaudrate": "9600",
"serialDatabits": "8",
"serialStopbits": "1",
"serialParity": "none",
"serialConnectionDelay": "300",
"serialAsciiResponseStartDelimiter": "",
"unit_id": "",
"commandDelay": "1",
"clientTimeout": "2000",
"reconnectOnTimeout": false,
"reconnectTimeout": "2000",
"parallelUnitIdsAllowed": true
},
{
"id": "40511a18915f475f",
"type": "serial-port",
"serialport": "/dev/ttyS4",
"serialbaud": "9600",
"databits": "8",
"parity": "none",
"stopbits": "1",
"waitfor": "",
"dtr": "none",
"rts": "none",
"cts": "none",
"dsr": "none",
"newline": "300",
"bin": "bin",
"out": "time",
"addchar": "",
"responsetimeout": "1000"
}
]
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐




所有评论(0)