跳到主要内容

Modbus 辅助函数

Modbus 辅助函数提供了便利的 Lua API 用于发送 Modbus RTU 和 Modbus TCP 请求。这些函数自动处理协议帧、参数验证和特定编解码器的有效负载格式。

概述

Modbus 辅助函数支持8 个标准 Modbus 功能码,涵盖最常见的工业自动化操作:

功能码操作辅助函数
0x01读线圈modbus_read_coils()
0x02读离散输入modbus_read_discrete_inputs()
0x03读保持寄存器modbus_read_holding_registers()
0x04读输入寄存器modbus_read_input_registers()
0x05写单个线圈modbus_write_single_coil()
0x06写单个寄存器modbus_write_single_register()
0x0F写多个线圈modbus_write_multiple_coils()
0x10写多个寄存器modbus_write_multiple_registers()

自动 RTU/TCP 检测

所有辅助函数自动检测编解码器类型(modbus_rtu_codecmodbus_tcp_codec)并相应格式化有效负载:

  • Modbus RTU:在有效负载中包含从机地址
  • Modbus TCP:省略从机地址(由 MBAP 头处理)

多连接支持

所有函数接受可选的 connection_id 参数以目标特定连接。如果省略,消息将发送到连接 0(默认连接)。

API 参考

modbus_read_coils(slave, start, qty, delay, connection_id) → boolean

读取线圈状态(功能码 0x01)。

参数

  • slave (number) - 从机/单元 ID (1-247)
  • start (number) - 起始线圈地址 (0-65535)
  • qty (number) - 要读取的线圈数 (1-2000)
  • delay (number) - 发送前的延迟(毫秒)
  • connection_id (number, 可选) - 目标连接 ID(默认:0)

返回值

  • true - 请求成功调度
  • false - 调度失败(通道关闭或错误)

示例

-- 从从机 1 读取从地址 100 开始的 10 个线圈
local last_poll_time = 0

function on_timer(elapsed_ms)
if elapsed_ms - last_poll_time >= 1000 then -- 每秒轮询一次
modbus_read_coils(1, 100, 10, 0) -- 发送到连接 0
last_poll_time = elapsed_ms
end
end

-- 多连接示例:在不同连接上轮询不同的从机
local last_multi_poll = 0

function on_timer(elapsed_ms)
if elapsed_ms - last_multi_poll >= 1000 then
modbus_read_coils(1, 100, 10, 0, 0) -- 连接 0 上的从机 1
modbus_read_coils(2, 100, 10, 0, 1) -- 连接 1 上的从机 2
last_multi_poll = elapsed_ms
end
end

modbus_read_discrete_inputs(slave, start, qty, delay, connection_id) → boolean

读取离散输入状态(功能码 0x02)。

参数

  • slave (number) - 从机/单元 ID (1-247)
  • start (number) - 起始输入地址 (0-65535)
  • qty (number) - 要读取的输入数 (1-2000)
  • delay (number) - 发送前的延迟(毫秒)
  • connection_id (number, 可选) - 目标连接 ID(默认:0)

返回值

  • true - 请求成功调度
  • false - 调度失败

示例

-- 从地址 0 读取 8 个离散输入
function on_start()
modbus_read_discrete_inputs(1, 0, 8, 100) -- 100ms 后读取
end

modbus_read_holding_registers(slave, start, qty, delay, connection_id) → boolean

读取保持寄存器值(功能码 0x03)。

参数

  • slave (number) - 从机/单元 ID (1-247)
  • start (number) - 起始寄存器地址 (0-65535)
  • qty (number) - 要读取的寄存器数 (1-125)
  • delay (number) - 发送前的延迟(毫秒)
  • connection_id (number, 可选) - 目标连接 ID(默认:0)

返回值

  • true - 请求成功调度
  • false - 调度失败

示例

-- 从地址 0(Modbus 地址 40001)开始读取 10 个保持寄存器
local last_poll_time = 0

function on_timer(elapsed_ms)
if elapsed_ms - last_poll_time >= 500 then
modbus_read_holding_registers(1, 0, 10, 0)
last_poll_time = elapsed_ms
end
end

-- 多寄存器读取和连接路由
local last_multi_read = 0

function on_timer(elapsed_ms)
if elapsed_ms - last_multi_read >= 1000 then
local count = get_connection_count()
for conn_id = 0, count - 1 do
if get_codec(conn_id) == "modbus_rtu" then
modbus_read_holding_registers(1, 0, 10, 0, conn_id)
end
end
last_multi_read = elapsed_ms
end
end

modbus_read_input_registers(slave, start, qty, delay, connection_id) → boolean

读取输入寄存器值(功能码 0x04)。

参数

  • slave (number) - 从机/单元 ID (1-247)
  • start (number) - 起始寄存器地址 (0-65535)
  • qty (number) - 要读取的寄存器数 (1-125)
  • delay (number) - 发送前的延迟(毫秒)
  • connection_id (number, 可选) - 目标连接 ID(默认:0)

返回值

  • true - 请求成功调度
  • false - 调度失败

示例

-- 读取 5 个输入寄存器(传感器数据)
function on_start()
modbus_read_input_registers(1, 0, 5, 0)
end

modbus_write_single_coil(slave, addr, value, delay, connection_id) → boolean

写入单个线圈值(功能码 0x05)。

参数

  • slave (number) - 从机/单元 ID (1-247)
  • addr (number) - 线圈地址 (0-65535)
  • value (boolean) - 线圈值 (true = ON/0xFF00, false = OFF/0x0000)
  • delay (number) - 发送前的延迟(毫秒)
  • connection_id (number, 可选) - 目标连接 ID(默认:0)

返回值

  • true - 请求成功调度
  • false - 调度失败

示例

-- 打开地址 100 处的线圈
function on_receive()
local payload = message.payload
if #payload > 0 and string.byte(payload, 1) == 0x01 then
-- 当我们收到命令 0x01 时打开线圈 100
modbus_write_single_coil(1, 100, true, 0)
end
end

-- 特定连接的线圈控制
function on_receive()
if message.connection_id == 0 then
-- 基于来自连接 0 的数据控制连接 1 上的线圈
modbus_write_single_coil(1, 100, true, 0, 1)
end
end

modbus_write_single_register(slave, addr, value, delay, connection_id) → boolean

写入单个寄存器值(功能码 0x06)。

参数

  • slave (number) - 从机/单元 ID (1-247)
  • addr (number) - 寄存器地址 (0-65535)
  • value (number) - 寄存器值 (0-65535, 无符号 16 位)
  • delay (number) - 发送前的延迟(毫秒)
  • connection_id (number, 可选) - 目标连接 ID(默认:0)

返回值

  • true - 请求成功调度
  • false - 调度失败

示例

-- 每 5 秒向保持寄存器 0 写入值 1234
local last_write_time = 0

function on_timer(elapsed_ms)
if elapsed_ms - last_write_time >= 5000 then
modbus_write_single_register(1, 0, 1234, 0)
last_write_time = elapsed_ms
end
end

modbus_write_multiple_coils(slave, start, values_table, delay, connection_id) → boolean

写入多个线圈值(功能码 0x0F)。

参数

  • slave (number) - 从机/单元 ID (1-247)
  • start (number) - 起始线圈地址 (0-65535)
  • values_table (table) - Lua 布尔值数组 (1-2000 个元素)
  • delay (number) - 发送前的延迟(毫秒)
  • connection_id (number, 可选) - 目标连接 ID(默认:0)

返回值

  • true - 请求成功调度
  • false - 调度失败

验证

  • 表必须包含 1-2000 个布尔值
  • 每个值必须是 truefalse
  • 值按照 Modbus 规范打包到字节中(LSB 优先)

示例

-- 从地址 100 开始写入 8 个线圈值
function on_start()
local coils = {true, false, true, true, false, false, true, false}
modbus_write_multiple_coils(1, 100, coils, 100)
end

-- 用于测试的图案生成
local last_pattern_time = 0

function on_timer(elapsed_ms)
if elapsed_ms - last_pattern_time >= 2000 then
-- 创建交替图案
local pattern = {}
for i = 1, 16 do
pattern[i] = (i % 2 == 1)
end
modbus_write_multiple_coils(1, 0, pattern, 0)
last_pattern_time = elapsed_ms
end
end

modbus_write_multiple_registers(slave, start, values_table, delay, connection_id) → boolean

写入多个寄存器值(功能码 0x10)。

参数

  • slave (number) - 从机/单元 ID (1-247)
  • start (number) - 起始寄存器地址 (0-65535)
  • values_table (table) - Lua 整数值数组 (1-125 个元素)
  • delay (number) - 发送前的延迟(毫秒)
  • connection_id (number, 可选) - 目标连接 ID(默认:0)

返回值

  • true - 请求成功调度
  • false - 调度失败

验证

  • 表必须包含 1-125 个整数值
  • 每个值必须在 0-65535 范围内(无符号 16 位)
  • 值编码为大端序 u16

示例

-- 向保持寄存器写入配置
function on_start()
local config = {100, 200, 300, 400, 500}
modbus_write_multiple_registers(1, 0, config, 0)
end

-- 从一个连接读取传感器数据并写入另一个连接
function on_receive()
if message.connection_id == 0 then
-- 解析传感器数据
local temp = read_u16_be(message.payload, 1)
local humidity = read_u16_be(message.payload, 3)

-- 写入到连接 1 上的 Modbus 设备
modbus_write_multiple_registers(1, 0, {temp, humidity}, 0, 1)
end
return false
end

完整示例:Modbus 轮询和控制

此示例演示了具有轮询、控制和多连接支持的完整 Modbus 主机实现:

-- Modbus 主机与轮询和控制
-- 轮询保持寄存器并根据寄存器值控制线圈

-- 配置
local POLL_INTERVAL_MS = 1000
local SLAVE_ID = 1
local HOLDING_REG_START = 0
local HOLDING_REG_COUNT = 10
local COIL_START = 0

-- 跟踪最后轮询时间
local last_poll_time = 0

function on_start()
local count = get_connection_count()
log("info", "Modbus 主机启动,共 " .. count .. " 个连接")

-- 记录连接配置
for i = 0, count - 1 do
local codec = get_codec(i)
local transport = get_transport(i)
log("info", "连接 " .. i .. ":" .. transport .. " 使用 " .. codec)
end
end

function on_timer(elapsed_ms)
-- 每 POLL_INTERVAL_MS 轮询每个 Modbus 连接
if elapsed_ms - last_poll_time >= POLL_INTERVAL_MS then
local count = get_connection_count()

for conn_id = 0, count - 1 do
local codec = get_codec(conn_id)

-- 仅轮询 Modbus 连接
if codec == "modbus_rtu" or codec == "modbus_tcp" then
-- 读取保持寄存器
modbus_read_holding_registers(
SLAVE_ID,
HOLDING_REG_START,
HOLDING_REG_COUNT,
0,
conn_id
)
end
end

last_poll_time = elapsed_ms
end
end

function on_receive()
local conn_id = message.connection_id
local payload = message.payload

-- 解析 Modbus 响应(功能码 0x03 - 读保持寄存器)
if #payload >= 3 and string.byte(payload, 1) == 0x03 then
local byte_count = string.byte(payload, 2)
local register_count = byte_count / 2

log("info", "连接 " .. conn_id .. ":接收 " .. register_count .. " 个寄存器")

-- 解析寄存器值
for i = 0, register_count - 1 do
local offset = 3 + (i * 2) -- 偏移 3 = 跳过功能码 + 字节计数
if offset + 1 <= #payload then
local value = read_u16_be(payload, offset)
local reg_addr = HOLDING_REG_START + i

-- 添加到图表
message:add_int_value("寄存器 " .. reg_addr, value)

-- 控制逻辑:如果寄存器值超过阈值则打开线圈
if value > 500 then
log("warn", "寄存器 " .. reg_addr .. " 超过阈值:" .. value)
modbus_write_single_coil(SLAVE_ID, COIL_START + i, true, 0, conn_id)
else
modbus_write_single_coil(SLAVE_ID, COIL_START + i, false, 0, conn_id)
end
end
end

return true -- 我们添加了值
end

return false
end

function on_send()
local conn_id = message.connection_id
log("debug", "向连接 " .. conn_id .. " 发送 Modbus 请求")
return false
end

function on_send_confirm()
-- 可选:跟踪成功的发送
return false
end

常见模式

定期轮询

使用 on_timer() 和全局变量来跟踪轮询间隔:

-- 跟踪最后轮询时间
local last_poll_time = 0

function on_timer(elapsed_ms)
-- 每秒轮询一次(1000ms)
if elapsed_ms - last_poll_time >= 1000 then
modbus_read_holding_registers(1, 0, 10, 0)
last_poll_time = elapsed_ms
end
end

注意:由于 on_timer() 每 100ms 调用一次,使用 elapsed_ms % 1000 == 0 不会可靠工作。始终使用 elapsed_ms - last_time >= interval 的模式和全局变量。

顺序请求

使用延迟来序列化多个请求:

function on_start()
-- 使用错开延迟读取不同的寄存器块
modbus_read_holding_registers(1, 0, 10, 0) -- 立即
modbus_read_holding_registers(1, 10, 10, 100) -- 100ms 后
modbus_read_holding_registers(1, 20, 10, 200) -- 200ms 后
end

响应驱动的写入

解析响应并根据值进行写入:

function on_receive()
local payload = message.payload
if #payload >= 5 and string.byte(payload, 1) == 0x03 then
local reg_value = read_u16_be(payload, 3)

-- 根据寄存器值写入线圈
if reg_value > 1000 then
modbus_write_single_coil(1, 100, true, 0)
end
end
return false
end

多连接路由

将 Modbus 命令路由到特定连接:

local last_poll_time = 0

function on_timer(elapsed_ms)
if elapsed_ms - last_poll_time >= 1000 then
-- 轮询连接 0 (串行) 上的从机 1
modbus_read_holding_registers(1, 0, 10, 0, 0)

-- 轮询连接 1 (TCP) 上的从机 2
modbus_read_holding_registers(2, 0, 10, 0, 1)

last_poll_time = elapsed_ms
end
end

错误处理

所有 Modbus 辅助函数执行参数验证并在出错时返回 false

-- 无效数量(寄存器超过 125)
local success = modbus_read_holding_registers(1, 0, 200, 0)
if not success then
log("error", "未能调度 Modbus 请求")
end

-- 无效寄存器值(超过 65535)
local values = {70000} -- 超出范围
modbus_write_multiple_registers(1, 0, values, 0) -- 引发 Lua 错误

常见验证错误

  • 线圈:数量必须为 1-2000
  • 离散输入:数量必须为 1-2000
  • 保持寄存器:数量必须为 1-125
  • 输入寄存器:数量必须为 1-125
  • 寄存器值:必须为 0-65535(无符号 16 位)
  • 线圈值:必须为布尔值 (true/false)

调试

启用调试日志以跟踪 Modbus 操作:

function on_send()
log("debug", "向从机 " .. message.connection_id .. " 发送 Modbus 请求")
return false
end

function on_send_confirm()
log("debug", "Modbus 请求已确认")
return false
end

function on_receive()
local payload = message.payload
log("debug", "接收 " .. #payload .. " 字节")

-- 记录功能码
if #payload > 0 then
local func_code = string.byte(payload, 1)
log("debug", "功能码:0x" .. string.format("%02X", func_code))
end

return false
end

性能考虑

  1. 请求时序:适当间隔请求以避免压倒从机
  2. 批量操作:使用写多个函数而不是多个单独写入
  3. 连接过滤:仅轮询具有 Modbus 编解码器的连接
  4. 响应解析:在读取前验证响应长度以避免错误
-- 好:批量写入
modbus_write_multiple_registers(1, 0, {100, 200, 300}, 0)

-- 效率较低:多个单独写入
modbus_write_single_register(1, 0, 100, 0)
modbus_write_single_register(1, 1, 200, 100)
modbus_write_single_register(1, 2, 300, 200)

另请参阅