跳到主要内容

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 协议进行通信。要求如下:

  1. 每 5 秒使用 Modbus RTU 读取输入寄存器(功能码 0x04)轮询传感器一次
  2. 从整型(16位)和浮点型(32位)寄存器中解析温度和湿度值
  3. 将解析后的值作为 JSON 负载发布到 MQTT 主题 cycbox/eid041

EID041 寄存器映射

寄存器地址数量数据类型描述
0x00001int16温度 (0.1°C 分辨率,有符号)
0x00011uint16湿度 (0.1%RH 分辨率)
0x0002-0x00032float32 (大端)温度 (°C)
0x0004-0x00052float32 (大端)湿度 (%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描述
0x0000modbus_rtu_3:input_30001温度 (int16 原始值)
0x0001modbus_rtu_3:input_30002湿度 (uint16 原始值)
0x0002modbus_rtu_3:input_30003温度浮点值 高位字
0x0003modbus_rtu_3:input_30004温度浮点值 低位字
0x0004modbus_rtu_3:input_30005湿度浮点值 高位字
0x0005modbus_rtu_3:input_30006湿度浮点值 低位字

查看 GitHub 上的完整示例脚本

相关文档