Modbus 辅助函数
编解码器自动解析值
某些编解码器会自动解析协议响应并将类型化数值附加到消息。这些值可以在 on_receive() 中通过 message:get_value(id) 和 message.values_json 获取,无需编写任何 Lua 解析代码。
Modbus RTU 编解码器
当选择 modbus_rtu 编解码器时,响应会自动与先前的请求关联并解析为数值。
每个寄存器/线圈会生成两个键 —— 一个逻辑 Modicon 地址和一个协议类型十六进制地址:
| 功能码 | 逻辑键 | 协议类型键 | 数值类型 |
|---|---|---|---|
| 0x01 读取线圈 (Coils) | modbus_rtu_{slave}:{1+addr} | modbus_rtu_{slave}:coil_{addr:04X} | 布尔值 |
| 0x02 读取离散输入 | modbus_rtu_{slave}:{10001+addr} | modbus_rtu_{slave}:discrete_{addr:04X} | 布尔值 |
| 0x03 读取保持寄存器 | modbus_rtu_{slave}:{40001+addr} | modbus_rtu_{slave}:holding_{addr:04X} | uint16 |
| 0x04 读取输入寄存器 | modbus_rtu_{slave}:{30001+addr} | modbus_rtu_{slave}:input_{addr:04X} | uint16 |
| 0x05 写单个线圈 | modbus_rtu_{slave}:{1+addr} | modbus_rtu_{slave}:coil_{addr:04X} | 布尔值 |
| 0x06 写单个寄存器 | modbus_rtu_{slave}:{40001+addr} | modbus_rtu_{slave}:holding_{addr:04X} | uint16 |
{slave}是 Modbus 从站地址(十进制){addr}是从 0 开始的协议地址- 逻辑键使用 Modicon 惯例(十进制):线圈从 1 开始,离散输入从 10001 开始,输入寄存器从 30001 开始,保持寄存器从 40001 开始
- 协议类型键使用 4 位大写十六进制表示地址(例如
coil_0000、holding_0003) - 每个解析的值都会同时生成两个键;使用更方便的那个即可
- 仅当编解码器可以将响应与先前的请求匹配时,才会解析数值
- 整数/浮点值可以在 UI 的图表中可视化
Modbus TCP 编解码器
与 RTU 相同,但使用 modbus_tcp 前缀,并从 MBAP 报头获取单元 ID (Unit ID)。每个值同样会生成两个键:
| 功能码 | 逻辑键 | 协议类型键 | 数值类型 |
|---|---|---|---|
| 0x01 读取线圈 | modbus_tcp_{unit}:{1+addr} | modbus_tcp_{unit}:coil_{addr:04X} | 布尔值 |
| 0x02 读取离散输入 | modbus_tcp_{unit}:{10001+addr} | modbus_tcp_{unit}:discrete_{addr:04X} | 布尔值 |
| 0x03 读取保持寄存器 | modbus_tcp_{unit}:{40001+addr} | modbus_tcp_{unit}:holding_{addr:04X} | uint16 |
| 0x04 读取输入寄存器 | modbus_tcp_{unit}:{30001+addr} | modbus_tcp_{unit}:input_{addr:04X} | uint16 |
| 0x05 写单个线圈 | modbus_tcp_{unit}:{1+addr} | modbus_tcp_{unit}:coil_{addr:04X} | 布尔值 |
| 0x06 写单个寄存器 | modbus_tcp_{unit}:{40001+addr} | modbus_tcp_{unit}:holding_{addr:04X} | uint16 |
示例:Senseair S8 CO₂ 传感器
自动解析的值与通过 message:add_int_value()、message:add_float_value() 等手动添加的值共存。
使用 Lua 可以合并或转换自动解析的 uint16 寄存器为更有意义的值(例如,将两个寄存器合并为一个 32 位值)。
以下示例轮询 Senseair S8 CO₂ 传感器,演示了定期轮询、值解析、多寄存器合并以及状态/告警解码。
local SLAVE_ADDR = 254
local POLL_MS = 2000
local timer_ms = 0
function on_start()
log("info", "Starting Senseair S8 Modbus polling script.")
-- 在启动时读取一次设备信息寄存器(Map Ver、FW Ver、Sensor ID Hi、Sensor ID Lo)。
-- 每次后续调用都给定一个非零延迟,使脚本在发送下一个请求前等待上一个请求的响应。
-- 若不使用错峰延迟,请求会背靠背排队,设备可能无法正确响应连续的快速读取。
modbus_rtu_read_input_registers(SLAVE_ADDR, 0x001B, 4, 100, 0)
-- 在上一个请求之后 200 ms 发送下一个请求,给设备留出响应时间
modbus_rtu_read_holding_registers(SLAVE_ADDR, 0x001F, 1, 200, 0)
end
function on_timer(now_ms)
-- on_timer 每 100 ms 触发一次;手动累计已过时间
timer_ms = timer_ms + 100
-- 每隔 POLL_MS 毫秒定期读取活跃数据(CO2、状态、告警)。
-- 此处延迟为 0,因为这是轮询周期中唯一的请求。
if timer_ms >= POLL_MS then
-- 从 0x0000 读取 4 个寄存器:MeterStatus、AlarmStatus、OutputStatus、SpaceCO2
modbus_rtu_read_input_registers(SLAVE_ADDR, 0x0000, 4, 0, 0)
timer_ms = 0
end
end
function on_receive()
-- 仅处理来自连接 0(主串口)的消息
if message.connection_id ~= 0 then return false end
local modified = false
-- 空间 CO2 浓度(协议地址 0x0003,逻辑地址 30004)
local co2 = message:get_value(string.format("modbus_rtu_%d:input_0003", SLAVE_ADDR))
if co2 then
message:add_int_value("CO2_ppm", co2)
modified = true
end
-- MeterStatus 寄存器(协议地址 0x0000):位域错误标志
local status = message:get_value(string.format("modbus_rtu_%d:input_0000", SLAVE_ADDR))
if status then
message:add_int_value("MeterStatus", status)
if status ~= 0 then
-- Bit 0:内部致命错误;Bit 5:测量值超出有效范围
local fatal = bit.band(status, 0x01)
local out_of_range = bit.band(status, 0x20)
if fatal > 0 then log("error", "Sensor reported FATAL ERROR (bit 0)") end
if out_of_range > 0 then log("warn", "Sensor reading OUT OF RANGE (bit 5)") end
end
modified = true
end
-- OutputStatus / 告警标志(协议地址 0x0002):bit 0 反映当前告警状态
local out_status = message:get_value(string.format("modbus_rtu_%d:input_0002", SLAVE_ADDR))
if out_status then
local alarm = (bit.band(out_status, 0x01) > 0)
message:add_bool_value("Alarm_Active", alarm)
modified = true
end
-- 固件版本(协议地址 0x001C):高字节 = 主版本,低字节 = 次版本
local fw = message:get_value(string.format("modbus_rtu_%d:input_001C", SLAVE_ADDR))
if fw then
local main_ver = bit.rshift(fw, 8)
local sub_ver = bit.band(fw, 0xFF)
message:add_string_value("Firmware_Version", string.format("%d.%d", main_ver, sub_ver))
modified = true
end
-- 传感器 ID:两个相邻的 16 位寄存器合并为 32 位值。
-- id_hi (0x001D) 存储高 16 位;id_lo (0x001E) 存储低 16 位。
-- 两者必须出现在同一条消息中(来自单次多寄存器读取)。
local id_hi = message:get_value(string.format("modbus_rtu_%d:input_001D", SLAVE_ADDR))
local id_lo = message:get_value(string.format("modbus_rtu_%d:input_001E", SLAVE_ADDR))
if id_hi and id_lo then
local sensor_id = id_hi * 65536 + id_lo
message:add_int_value("Sensor_ID", sensor_id)
modified = true
end
-- ABC(自动基线校正)周期,单位为小时(保持寄存器 0x001F)
local abc = message:get_value(string.format("modbus_rtu_%d:holding_001F", SLAVE_ADDR))
if abc then
message:add_int_value("ABC_Period_Hours", abc)
modified = true
end
-- 仅在至少添加了一个值时返回 true,以便引擎
-- 知道消息负载已被丰富并应转发
return modified
end
关键模式:
- 错峰启动读取:
on_start中每个modbus_rtu_read_*调用都使用非零delay, 确保下一个请求在设备有时间响应上一个请求后才排队发送。 若不使用错峰延迟,快速背靠背的请求可能导致设备丢帧或错帧。 - 定时器累计:
on_timer每 100 ms 触发一次;手动累计已过时间 以实现比最小回调间隔更长的轮询周期。 - 协议类型十六进制键:
input_0003、holding_001F等使用基于 0 的协议地址的 4 位大写十六进制形式, 比 Modicon 地址更易于与设备数据手册对照。 - 多寄存器 32 位值:两个相邻的 uint16 寄存器通过
id_hi * 65536 + id_lo合并 以重建 32 位传感器 ID。 return modified:仅在添加了值时返回true,避免对属于其他设备或功能码的消息 进行不必要的引擎处理。
Modbus RTU 辅助函数
所有 RTU 函数在成功时返回 true,失败时返回 false。无效参数会引发 Lua 运行时错误。
所有函数均接受一个可选的尾随参数 connection_id (默认: 0)。
| 函数 | 参数 |
|---|---|
modbus_rtu_read_coils(slave, start, qty, delay, connection_id) | slave: 0-255, start: 0-65535, qty: 1-2000, delay: ms |
modbus_rtu_read_discrete_inputs(slave, start, qty, delay, connection_id) | slave: 0-255, start: 0-65535, qty: 1-2000, delay: ms |
modbus_rtu_read_holding_registers(slave, start, qty, delay, connection_id) | slave: 0-255, start: 0-65535, qty: 1-125, delay: ms |
modbus_rtu_read_input_registers(slave, start, qty, delay, connection_id) | slave: 0-255, start: 0-65535, qty: 1-125, delay: ms |
modbus_rtu_write_single_coil(slave, addr, value, delay, connection_id) | slave: 0-255, addr: 0-65535, value: 布尔值, delay: ms |
modbus_rtu_write_single_register(slave, addr, value, delay, connection_id) | slave: 0-255, addr: 0-65535, value: 0-65535, delay: ms |
modbus_rtu_write_multiple_coils(slave, start, values_table, delay, connection_id) | slave: 0-255, start: 0-65535, values_table: 布尔数组 (1-2000), delay: ms |
modbus_rtu_write_multiple_registers(slave, start, values_table, delay, connection_id) | slave: 0-255, start: 0-65535, values_table: 整数数组 (1-125, 每个 0-65535), delay: ms |
Modbus TCP 辅助函数
与 RTU 相同,但没有 slave 参数(单元 ID 在 MBAP 报头中)。全部返回 true/false。
所有函数均接受一个可选的尾随参数 connection_id (默认: 0)。
| 函数 | 参数 |
|---|---|
modbus_tcp_read_coils(start, qty, delay, connection_id) | start: 0-65535, qty: 1-2000, delay: ms |
modbus_tcp_read_discrete_inputs(start, qty, delay, connection_id) | start: 0-65535, qty: 1-2000, delay: ms |
modbus_tcp_read_holding_registers(start, qty, delay, connection_id) | start: 0-65535, qty: 1-125, delay: ms |
modbus_tcp_read_input_registers(start, qty, delay, connection_id) | start: 0-65535, qty: 1-125, delay: ms |
modbus_tcp_write_single_coil(addr, value, delay, connection_id) | addr: 0-65535, value: 布尔值, delay: ms |
modbus_tcp_write_single_register(addr, value, delay, connection_id) | addr: 0-65535, value: 0-65535, delay: ms |
modbus_tcp_write_multiple_coils(start, values_table, delay, connection_id) | start: 0-65535, values_table: 布尔数组 (1-2000), delay: ms |
modbus_tcp_write_multiple_registers(start, values_table, delay, connection_id) | start: 0-65535, values_table: 整数数组 (1-125, 每个 0-65535), delay: ms |