一个完整的 Modbus 协议 Lua 实现,支持 RTU、TCP 和 ASCII 三种传输方式。
中文 | English
- 支持 Modbus TCP、RTU 和 ASCII 协议
- 支持主站(Master)和从站(Slave)模式
- 支持标准 Modbus 功能码(0x01-0x10)
0x01- 读取线圈(Read Coils)0x02- 读取离散输入(Read Discrete Inputs)0x03- 读取保持寄存器(Read Holding Registers)0x04- 读取输入寄存器(Read Input Registers)0x05- 写单个线圈(Write Single Coil)0x06- 写单个寄存器(Write Single Register)0x0F- 写多个线圈(Write Multiple Coils)0x10- 写多个寄存器(Write Multiple Registers)
- 提供 Skynet 框架集成
- 支持大端序和小端序字节顺序
- 完整的数据打包和解包功能
lua-modbus/
├── modbus/
│ ├── pdu/ # 协议数据单元(PDU)处理
│ │ ├── init.lua # PDU 主要接口
│ │ ├── request.lua
│ │ └── response.lua
│ ├── data/ # 数据打包/解包
│ │ ├── pack.lua # 数据打包
│ │ └── unpack.lua # 数据解包
│ ├── apdu/ # 应用层协议数据单元
│ │ ├── tcp.lua # Modbus TCP 协议
│ │ ├── rtu.lua # Modbus RTU 协议
│ │ └── ascii.lua # Modbus ASCII 协议
│ ├── master/ # 主站实现
│ │ ├── stream.lua # 通用流接口
│ │ └── skynet.lua # Skynet 框架集成
│ ├── slave/ # 从站实现
│ │ ├── stream.lua # 通用流接口
│ │ └── skynet.lua # Skynet 框架集成
│ ├── buffer.lua # 流缓冲区
│ ├── code.lua # 功能码映射
│ ├── exceptions.lua # 异常代码
│ └── ecm.lua # 错误校验机制
├── docs/ # API 文档
├── config.ld # lDoc 配置文件
└── README.md
luarocks install lua-modbus- 克隆仓库:
git clone https://github.com/yourusername/lua-modbus.git
cd lua-modbus- 将
modbus目录复制到你的 Lua 路径:
cp -r modbus /usr/local/share/lua/5.3/或者将项目路径添加到 LUA_PATH:
export LUA_PATH="/path/to/lua-modbus/?.lua;;"local master = require 'modbus.master.stream'
local pdu = require 'modbus.pdu'
-- 创建流对象
local stream = {
-- 发送数据
send = function(data)
-- 实现 TCP 或串口发送
end,
-- 接收数据
recv = function(len, timeout)
-- 实现 TCP 或串口接收,返回接收到的数据
end
}
-- 创建主站(TCP 模式)
local m = master:new('tcp', stream)
-- 创建 PDU 对象
local p = pdu:new()
-- 创建读取保持寄存器请求(功能码 0x03)
-- 从地址 0 开始读取 10 个寄存器
local req = p:make_request(0x03, 0, 10)
-- 发送请求到单元 ID 为 1 的从站
m:request(1, req, function(unit, pdu, key)
if not pdu then
print("请求失败:", key)
else
print("收到响应,从站:", unit, "PDU长度:", #pdu)
-- 解析响应数据...
end
end, 5) -- 5 秒超时
-- 主处理循环
while running do
m:run_once(1000) -- 1 秒超时
endlocal skynet = require 'skynet'
local master = require 'modbus.master.skynet'
local pdu = require 'modbus.pdu'
skynet.start(function()
-- 创建主站
local m = master:new('tcp', {
link = 'tcp',
tcp = {
host = '127.0.0.1',
port = 502,
nodelay = true
}
})
-- 启动主站
m:start()
-- 创建 PDU 对象
local p = pdu:new()
-- 发送请求
local req = p:make_request(0x03, 0, 10)
local resp, err = m:request(1, req, 5000) -- 5 秒超时
if err then
print("请求失败:", err)
else
print("收到响应:", #resp)
end
end)local slave = require 'modbus.slave.stream'
local pdu = require 'modbus.pdu'
-- 创建流对象
local stream = {
send = function(data)
-- 发送数据
end,
recv = function(len, timeout)
-- 接收数据
end
}
-- 创建从站(RTU 模式)
local s = slave:new('rtu', stream)
-- 创建 PDU 对象
local p = pdu:new()
-- 添加单元 ID 为 1 的设备
s:add_unit(1, function(pdu, response_handler)
-- 解析请求 PDU
local fc = string.byte(pdu, 1) -- 功能码
local addr = string.unpack('>I2', pdu, 2) -- 起始地址
local len = string.unpack('>I2', pdu, 4) -- 数量
print(string.format("收到请求: FC=0x%02X, ADDR=%d, LEN=%d", fc, addr, len))
if fc == 0x03 then
-- 读取保持寄存器
-- 这里返回模拟数据
local values = {1000, 2000, 3000, 4000, 5000}
local resp = p:make_response(0x03, addr, table.unpack(values))
response_handler(resp)
else
-- 其他功能码...
end
end)
-- 主处理循环
while running do
s:run_once(1000)
endlocal skynet = require 'skynet'
local slave = require 'modbus.slave.skynet'
local pdu = require 'modbus.pdu'
skynet.start(function()
-- 创建从站
local s = slave:new('rtu', {
link = 'serial',
serial = {
port = '/dev/ttyUSB0',
baudrate = 9600,
data_bits = 8,
parity = 'NONE',
stop_bits = 1,
flow_control = 'OFF'
}
})
-- 创建 PDU 对象
local p = pdu:new()
-- 添加单元
s:add_unit(1, function(pdu, response_handler)
local fc = string.byte(pdu, 1)
local addr = string.unpack('>I2', pdu, 2)
local len = string.unpack('>I2', pdu, 4)
if fc == 0x03 then
-- 读取保持寄存器
local values = {1000, 2000, 3000}
local resp = p:make_response(0x03, addr, table.unpack(values))
response_handler(resp)
elseif fc == 0x06 then
-- 写单个寄存器
local value = string.unpack('>I2', pdu, 4)
print(string.format("写寄存器 %d = %d", addr, value))
local resp = p:make_response(0x06, addr, value)
response_handler(resp)
end
end)
-- 启动从站
s:start()
end)完整的 API 文档请参考:docs/index.html
- modbus.pdu - 协议数据单元(PDU)处理
- modbus.data.pack - 数据打包
- modbus.data.unpack - 数据解包
- modbus.apdu.tcp - Modbus TCP 协议
- modbus.apdu.rtu - Modbus RTU 协议
- modbus.master.stream - 通用主站接口
- modbus.slave.stream - 通用从站接口
- modbus.master.skynet - Skynet 主站实现
- modbus.slave.skynet - Skynet 从站实现
local packer = require 'modbus.data.pack'
local p = packer:new()
-- 打包 16 位无符号整数
local data = p:uint16(1000)
-- 打包多个寄存器值
local values = {1000, 2000, 3000}
local data_list = {}
for _, v in ipairs(values) do
table.insert(data_list, p:uint16(v))
end
local data = table.concat(data_list)
-- 打包位值
local bits = {true, false, true, true, false}
local data = p:bit(table.unpack(bits))local unpacker = require 'modbus.data.unpack'
local u = unpacker:new()
-- 解包 16 位无符号整数
local val, next_pos = u:uint16(data, 1)
-- 解包多个寄存器值
local index = 1
local values = {}
while index <= #data do
local val
val, index = u:uint16(data, index)
table.insert(values, val)
end使用 ldoc 生成 API 文档:
ldoc -f markdown -d docs -t "lua-modbus API 文档" modbus/生成的文档位于 docs/ 目录,用浏览器打开 docs/index.html 查看。
- Lua 5.1+ 或 LuaJIT
- middleclass - Lua 类库
-
字节序:Modbus 协议默认使用大端序(Big-Endian),但某些设备可能使用小端序。创建对象时可以通过
little_endian参数指定。 -
超时处理:在实际应用中,应根据网络情况设置合适的超时时间。
-
错误处理:建议在回调函数中检查返回值,处理超时和错误情况。
-
串口配置:使用 RTU/ASCII 模式时,确保串口参数(波特率、数据位、校验位等)与设备匹配。
MIT License - 详见 LICENSE 文件
KooIoT
欢迎提交 Issue 和 Pull Request!