Modbus RTU 传感器到 MQTT
概览
本示例展示了如何通过串口轮询 Modbus RTU 温度和湿度传感器 (EID041),并将解析后的数值以 JSON 格式发布到 MQTT 代理。它展示了 CycBox 的以下功能:
- 轮询 Modbus RTU 设备 —— 使用内置的 Modbus RTU 编解码器(modbus_rtu_codec)
- 解析传感器寄存器 —— 包括整型和浮点型数据类型
- 将结构化数据发布到 MQTT —— 使用 JSON 格式
- 定期数据采集 —— 具有可配置的轮询间隔
这是工业物联网中的一种常见模式,即需要通过 MQTT 将 Modbus 设备中的传感器数据转发到云平台或监控系统。
场景
一个 EID041 温度和湿度传感器通过串口(例如 /dev/ttyUSB0)连接,并使用 Modbus RTU 协议进行通信。要求如下:
- 每 5 秒使用 Modbus RTU 读取输入寄存器(功能码 0x04)轮询传感器一次
- 从整型(16位)和浮点型(32位)寄存器中解析温度和湿度值
- 将解析后的值作为 JSON 负载发布到 MQTT 主题
cycbox/eid041
EID041 寄存器映射
| 寄存器地址 | 数量 | 数据类型 | 描述 |
|---|---|---|---|
| 0x0000 | 1 | int16 | 温度 (0.1°C 分辨率,有符号) |
| 0x0001 | 1 | uint16 | 湿度 (0.1%RH 分辨率) |
| 0x0002-0x0003 | 2 | float32 (大端) | 温度 (°C) |
| 0x0004-0x0005 | 2 | float32 (大端) | 湿度 (%RH) |
配置
CycBox 配置了两个连接:
- 连接 0: 带 Modbus RTU 编解码器的串口,用于轮询传感器
- 连接 1: MQTT 客户端,用于发布解析后的数据
{
"version": "1.8.1",
"name": "EID041 Modbus RTU 到 MQTT",
"description": "通过 Modbus RTU 轮询 EID041 温湿度传感器,并以 JSON 格式发布到 MQTT",
"configs": [
{
"app": {
"app_transport": "serial",
"app_codec": "modbus_rtu_codec",
"app_transformer": "disable",
"app_encoding": "UTF-8"
},
"serial": {
"serial_port": "/dev/ttyUSB0",
"serial_baud_rate": 9600,
"serial_data_bits": 8,
"serial_parity": "none",
"serial_stop_bits": "1",
"serial_flow_control": "none"
},
"modbus_rtu_codec": {
"with_receive_timeout": 20
}
},
{
"app": {
"app_transport": "mqtt",
"app_codec": "timeout_codec",
"app_transformer": "disable",
"app_encoding": "UTF-8"
},
"mqtt": {
"mqtt_broker_url": "mqtt://broker.emqx.io:1883",
"mqtt_client_id": "cycbox_eid041",
"mqtt_username": "",
"mqtt_password": "",
"mqtt_use_tls": false,
"mqtt_ca_path": "",
"mqtt_client_cert_path": "",
"mqtt_client_key_path": "",
"mqtt_subscribe_topics": "cycbox/#",
"mqtt_subscribe_qos": 1
},
"timeout_codec": {
"with_receive_timeout": 100
}
}
]
}
Lua 脚本逻辑
Lua 脚本处理三个任务:定期轮询、响应解析和 MQTT 发布。
脚本拆解
-- 配置
local SLAVE_ADDR = 3 -- Modbus 从站地址
local START_ADDR = 0x0000 -- 从寄存器 0 开始读取
local NUM_REGISTERS = 6 -- 读取全部 6 个寄存器 (2 个整型 + 4 个浮点型)
local POLL_INTERVAL = 5000 -- 每 5 秒轮询一次 (5000ms)
local MQTT_TOPIC = "cycbox/eid041"
local MQTT_CONNECTION_ID = 1 -- MQTT 是第二个连接 (从 0 开始计数)
运作原理
1. 定期轮询 (on_timer)
local last_poll_time = 0
function on_timer(elapsed_ms)
if elapsed_ms - last_poll_time >= POLL_INTERVAL then
modbus_rtu_read_input_registers(SLAVE_ADDR, START_ADDR, NUM_REGISTERS, 0, 0)
last_poll_time = elapsed_ms
end
end
- 使用每 100ms 调用一次的
on_timer() - 跟踪已用时间以触发每 5 秒一次的轮询
- 向连接 0 上的传感器发送 Modbus RTU 读取输入寄存器请求(功能码 0x04)
2. 响应解析 (on_receive)
function on_receive()
local payload = message.payload
if #payload < 3 then return false end
local slave_addr = read_u8(payload, 1)
local function_code = read_u8(payload, 2)
-- 仅处理来自我们设备的读取输入寄存器响应
if function_code ~= 0x04 or slave_addr ~= SLAVE_ADDR then
return false
end
-- 对于整型寄存器,使用编解码器解析后的值
local temperature_int_raw = message:get_value(
string.format("modbus_rtu_%d:input_%d", slave_addr, 30001 + START_ADDR))
local humidity_int_raw = message:get_value(
string.format("modbus_rtu_%d:input_%d", slave_addr, 30001 + START_ADDR + 1))
-- 直接从负载字节中解析浮点值
local temperature_float_value = read_float_be(payload, 8)
local humidity_float_value = read_float_be(payload, 12)
-- ... 构建 JSON 并发布
end
- 通过检查功能码和从站地址来验证 Modbus 响应
- 使用具有编解码器分配的数值 ID 的
message:get_value()获取整型寄存器值 - 使用
read_float_be()直接从原始负载中解析 float32 值
3. 数值提取与 MQTT 发布
-- 缩放整型值 (0.1 分辨率)
local temperature_int_value = temperature_int_raw * 0.1
message:add_float_value("temperature_int", temperature_int_value)
-- 添加浮点值
message:add_float_value("temperature_float", temperature_float_value)
message:add_float_value("humidity_float", humidity_float_value)
-- 构建并发布 JSON
local json_payload = '{"temperature":25.3,"humidity":60.1,"temperature_float":25.32,"humidity_float":60.12}'
mqtt_publish(MQTT_TOPIC, json_payload, 0, false, 0, MQTT_CONNECTION_ID)
- 整型值按 0.1 进行缩放,以转换为实际单位
- 使用
message:add_float_value()将所有值添加到消息元数据中,用于 UI 图表展示 - 构建 JSON 负载并发布到连接 1 上的 MQTT 主题
MQTT 输出示例
{
"temperature": 25.3,
"humidity": 60.1,
"temperature_float": 25.32,
"humidity_float": 60.12
}
Modbus RTU 编解码器数值 ID
Modbus RTU 编解码器会自动解析寄存器值并按照以下模式分配 ID:
modbus_rtu_{slave_addr}:input_{30001 + register_address}
对于本示例(从站地址为 3,起始地址为 0x0000):
| 寄存器 | 数值 ID | 描述 |
|---|---|---|
| 0x0000 | modbus_rtu_3:input_30001 | 温度 (int16 原始值) |
| 0x0001 | modbus_rtu_3:input_30002 | 湿度 (uint16 原始值) |
| 0x0002 | modbus_rtu_3:input_30003 | 温度浮点值 高位字 |
| 0x0003 | modbus_rtu_3:input_30004 | 温度浮点值 低位字 |
| 0x0004 | modbus_rtu_3:input_30005 | 湿度浮点值 高位字 |
| 0x0005 | modbus_rtu_3:input_30006 | 湿度浮点值 低位字 |
查看 GitHub 上的完整示例脚本 。