设备驱动程序开发
设备驱动程序是系统的核心组件,管理员可以在线开发设备驱动程序而不需要专门的开发环境。设备驱动程序使用lua脚本语言编写,可以及时观察运行效果,程序的错误会记录在日志中。
设备驱动程序支持的数据类型:
I8- 8位有符号整数,取值范围:-128 ~ 127U8- 8位无符号整数,取值范围:0 ~ 255I16- 16位有符号整数,取值范围:-32768 ~ 32767U16- 16位无符号整数,取值范围:0 ~ 65535I32- 32位有符号整数,取值范围:-2147483648 ~ 2147483647U32- 32位无符号整数,取值范围:0 ~ 4294967295F32- 32位(单精度)浮点数(IEEE 754),取值范围:-3.40282347e+38 ~ 3.40282347e+38I64- 64位有符号整数,取值范围:-9223372036854775808 ~ 9223372036854775807U64- 64位无符号整数,取值范围:0 ~ 18446744073709551615F64- 64位(双精度)浮点数(IEEE 754),取值范围:-1.7976931348623157e+308 ~ 1.7976931348623157e+308Bool- 布尔型变量,只有两个值:false和true
用户自定义的LUA模块
Section titled “用户自定义的LUA模块”用户可以自定义LUA模块,这些模块可被用于设备驱动程序。一般的模块的基本结构如下:
-- 假设模块名为:demo
local M = {}
function M.area_of_circle(r) return math.pi * r^2end
return M调用的方法:
local m = require(':demo') --注意这里的 ':'
local area = m.area_of_circle(1) -- 半径为1的圆的面积
...定义设备支持的变量
Section titled “定义设备支持的变量”定义供设备驱动程序外部使用的变量,包括变量的名称(外部标识符)、数据类型和是否可写标志。
lua语言编写的设备驱动程序实现,可以直接在代码编辑器中编辑(经过签名的设备驱动程序不会显示代码编辑器)。设备驱动程序的本质是请求帧(发送的帧)的编码和响应帧(接收的帧)的解码和数据解析。
三个主要函数
Section titled “三个主要函数”sync_decode_frame
Section titled “sync_decode_frame”响应帧解码函数,这是一个同步函数,内部不能调用任何异步函数(如
time.sleep_ms)。主要功能是确定响应帧是否接收完整、是否出错。对于模拟设备驱动程序无须实现该函数。当请求帧发送之后等待接收期间,每当有新的数据字节接收到会调用该函数。该函数携带一个由系统提供的参数:储存当前接收到的数据的字节缓冲区(DecodeBuffer),同时返回DecodeResult。
async_poll
Section titled “async_poll”轮询设备数据,这是一个异步函数,内部可以调用异步函数。主要功能是发送请求并且解析接收到的响应,最后返回采集的数据(NameValues)。该函数会不断地被调用(周期不是固定的,决定于总线上的设备数量和每个设备的响应时间)。
函数参数:
对于
模拟设备驱动程序,该函数无参数。而对于非模拟设备驱动程序,该函数携带两个参数,依次是:client和slave_id。client代表发送请求的代理(Client),slave_id代表目标从设备的ID(地址),目前仅支持非负整数的设备地址。
async_write
Section titled “async_write”写操作,这是一个异步函数,内部可以调用异步函数。主要功能是发送写操作请求并返回操作结果。
函数参数:
对于
模拟设备驱动程序,该函数携带两个参数,依次是:name和value。而对于非模拟设备驱动程序,该函数携带四个参数,依次是:client、slave_id、name和value。name是设备驱动程序定义的供外部使用的变量名,value是准备写的值。
代码的最后需要返回这三个函数:return { decode = sync_decode_frame, poll = async_poll, write = async_write }。如果设备不支持写操作,不需要实现 async_write,并且返回:return { decode = sync_decode_frame, poll = async_poll }。
对于模拟设备驱动程序,最后需要这样返回:
- 支持写操作
return { poll = async_poll, write = async_write } - 不支持写操作
return { poll = async_poll }
ByteBufferMut
Section titled “ByteBufferMut”用于请求帧编码的字节缓冲区,由设备驱动程序自身创建,可修改。
ByteBufferMut.new()- 创建一个空的字节缓冲区对象,如:local buf = ByteBufferMut.new()ByteBufferMut.with_capacity(cap)- 创建一个空的字节缓冲区对象,同时分配初始的缓冲区容量,cap是待分配的缓冲区的容量(字节数)。如:local buf = ByteBufferMut.with_capacity(8)。如果缓冲区的容量可以确定,使用该函数创建将会减少内存分配的次数(会稍微提高一些性能)
buf:len()- 返回缓冲区内存储的字节的数量。
buf[i]- 通过索引号读取相应字节的值,索引值i从 0 开始,如果i超出范围将发生错误。buf:peek_i8(i)- 返回位于位置i处开始的8位有符号整型值,如果超出范围将发生错误。buf:peek_u8(i)- 返回位于位置i处开始的8位无符号整型值,如果超出范围将发生错误。buf:peek_i16(i)- 返回位于位置i处开始的16位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i16_le(i)- 返回位于位置i处开始的16位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u16(i)- 返回位于位置i处开始的16位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u16_le(i)- 返回位于位置i处开始的16位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_i32(i)- 返回位于位置i处开始的32位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i32_le(i)- 返回位于位置i处开始的32位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u32(i)- 返回位于位置i处开始的32位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u32_le(i)- 返回位于位置i处开始的32位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_f32(i)- 返回位于位置i处开始的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:peek_f32_le(i)- 返回位于位置i处开始的32位浮点型值(LittleEndian),如果超出范围将发生错误。buf:peek_i64(i)- 返回位于位置i处开始的64位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i64_le(i)- 返回位于位置i处开始的64位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u64(i)- 返回位于位置i处开始的64位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u64_le(i)- 返回位于位置i处开始的64位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_f64(i)- 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。buf:peek_f64_le(i)- 返回位于位置i处开始的64位浮点型值(LittleEndian),如果超出范围将发生错误。
buf:append_i8(value)- 在缓冲区尾部追加8位有符号整型值。buf:append_u8(value)- 在缓冲区尾部追加8位无符号整型值。buf:append_i16(value)- 在缓冲区尾部追加16位有符号整型值(BigEndian)。buf:append_i16_le(value)- 在缓冲区尾部追加16位有符号整型值(LittleEndian)。buf:append_u16(value)- 在缓冲区尾部追加16位无符号整型值(BigEndian)。buf:append_u16_le(value)- 在缓冲区尾部追加16位无符号整型值(LittleEndian)。buf:append_i32(value)- 在缓冲区尾部追加32位有符号整型值(BigEndian)。buf:append_i32_le(value)- 在缓冲区尾部追加32位有符号整型值(LittleEndian)。buf:append_u32(value)- 在缓冲区尾部追加32位无符号整型值(BigEndian)。buf:append_u32_le(value)- 在缓冲区尾部追加32位无符号整型值(LittleEndian)。buf:append_f32(value)- 在缓冲区尾部追加32位浮点型值(BigEndian)。buf:append_f32_le(value)- 在缓冲区尾部追加32位浮点型值(LittleEndian)。buf:append_i64(value)- 在缓冲区尾部追加64位有符号整型值(BigEndian)。buf:append_i64_le(value)- 在缓冲区尾部追加64位有符号整型值(LittleEndian)。buf:append_u64(value)- 在缓冲区尾部追加64位无符号整型值(BigEndian)。buf:append_u64_le(value)- 在缓冲区尾部追加64位无符号整型值(LittleEndian)。buf:append_f64(value)- 在缓冲区尾部追加64位浮点型值(BigEndian)。buf:append_f64_le(value)- 在缓冲区尾部追加64位浮点型值(LittleEndian)。
buf:set_i8(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的8位有符号整型值,如果超出范围将发生错误。buf:set_u8(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的8位无符号整型值,如果超出范围将发生错误。buf:set_i16(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的16位有符号整型值(BigEndian),如果超出范围将发生错误。buf:set_i16_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的16位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:set_u16(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的16位无符号整型值(BigEndian),如果超出范围将发生错误。buf:set_u16_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的16位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:set_i32(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的32位有符号整型值(BigEndian),如果超出范围将发生错误。buf:set_i32_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的32位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:set_u32(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的32位无符号整型值(BigEndian),如果超出范围将发生错误。buf:set_u32_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的32位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:set_f32(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:set_f32_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的32位浮点型值(LittleEndian),如果超出范围将发生错误。buf:set_i64(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的64位有符号整型值(BigEndian),如果超出范围将发生错误。buf:set_i64_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的64位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:set_u64(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的64位无符号整型值(BigEndian),如果超出范围将发生错误。buf:set_u64_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的64位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:set_f64(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的64位浮点型值(BigEndian),如果超出范围将发生错误。buf:set_f64_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的64位浮点型值(LittleEndian),如果超出范围将发生错误。
常用校验算法
Section titled “常用校验算法”buf:xor_checksum(from, to)- 返回该缓冲区数据的字节“异或”校验和(8位无符号整型值),逐字节“异或”的结果。buf:xor_checksum_seeded(seed, from, to)- 返回该缓冲区数据的字节“异或”校验和(8位无符号整型值),逐字节“异或”的结果再与seed进行“异或”操作。seed: 种子值,8位无符号整型值。如果seed为 0 则与buf:xor_checksum(from, to)结果相同。buf:checksum8(from, to)- 返回该缓冲区数据的字节“累加”校验和(8位无符号整型值),逐字节相加的结果取低8位。buf:checksum16(from, to)- 返回该缓冲区数据的字节“累加”校验和(16位无符号整型值),逐字节相加的结果取低16位。buf:checksum16_words(from, to)- 返回该缓冲区数据的字“累加”校验和(16位无符号整型值),逐字(连续2字节按BigEndian得到的16位无符号整型值)相加的结果取低16位。如果缓冲区长度为奇数,则最后一个字节的值作为最后一个字的高8位,而低8位为0。buf:crc16_modbus(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 modbus rtu 的 CRC 校验。buf:crc16_dnp(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 dnp 的 CRC 校验。buf:crc16_profibus(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 profibus 的 CRC 校验。
DecodeBuffer
Section titled “DecodeBuffer”系统提供的字节缓冲区对象,只读。传递给 sync_decode_frame 函数。
buf:len()- 缓冲区内存储的字节的数量。
buf[i]- 通过索引号读取相应字节的值,索引值i从 0 开始,如果i超出范围将发生错误。buf:peek_i8(i)- 返回位于位置i处开始的8位有符号整型值,如果超出范围将发生错误。buf:peek_u8(i)- 返回位于位置i处开始的8位无符号整型值,如果超出范围将发生错误。buf:peek_i16(i)- 返回位于位置i处开始的16位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i16_le(i)- 返回位于位置i处开始的16位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u16(i)- 返回位于位置i处开始的16位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u16_le(i)- 返回位于位置i处开始的16位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_i32(i)- 返回位于位置i处开始的32位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i32_le(i)- 返回位于位置i处开始的32位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u32(i)- 返回位于位置i处开始的32位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u32_le(i)- 返回位于位置i处开始的32位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_f32(i)- 返回位于位置i处开始的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:peek_f32_le(i)- 返回位于位置i处开始的32位浮点型值(LittleEndian),如果超出范围将发生错误。buf:peek_i64(i)- 返回位于位置i处开始的64位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i64_le(i)- 返回位于位置i处开始的64位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u64(i)- 返回位于位置i处开始的64位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u64_le(i)- 返回位于位置i处开始的64位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_f64(i)- 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。buf:peek_f64_le(i)- 返回位于位置i处开始的64位浮点型值(LittleEndian),如果超出范围将发生错误。
常用校验算法
Section titled “常用校验算法”buf:xor_checksum(from, to)- 返回该缓冲区数据的字节“异或”校验和(8位无符号整型值),逐字节“异或”的结果。buf:xor_checksum_seeded(seed, from, to)- 返回该缓冲区数据的字节“异或”校验和(8位无符号整型值),逐字节“异或”的结果再与seed进行“异或”操作。seed: 种子值,8位无符号整型值。如果seed为 0 则与buf:xor_checksum(from, to)结果相同。buf:checksum8(from, to)- 返回该缓冲区数据的字节“累加”校验和(8位无符号整型值),逐字节相加的结果取低8位。buf:checksum16(from, to)- 返回该缓冲区数据的字节“累加”校验和(16位无符号整型值),逐字节相加的结果取低16位。buf:checksum16_words(from, to)- 返回该缓冲区数据的字“累加”校验和(16位无符号整型值),逐字(连续2字节按BigEndian得到的16位无符号整型值)相加的结果取低16位。如果缓冲区长度为奇数,则最后一个字节的值作为最后一个字的高8位,而低8位为0。buf:crc16_modbus(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 modbus rtu 的 CRC 校验。buf:crc16_dnp(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 dnp 的 CRC 校验。buf:crc16_profibus(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 profibus 的 CRC 校验。
DecodeResult
Section titled “DecodeResult”用于 sync_decode_frame 的返回值,代表当前帧解码的状态。如果接收过程中检测到错误,应该调用 error 函数来结束帧解码; 如果已经接收到完整的响应帧,应该返回 DecodeResult.Finished(bytes),其中bytes是帧的长度;如果没有检测到错误并且没有接收到完整的响应帧,则返回 DecodeResult.ExpectMore(bytes), 其中 bytes 可选,代表还需要接收的字节数,如果能确定还需要接收的字节数则可以提供该参数,否则,直接返回 DecodeResult.ExpectMore()。
ByteBuffer
Section titled “ByteBuffer”系统提供的字节缓冲区对象,只读。当接收到一个完整的响应帧(sync_decode_frame 函数返回 DecodeResult.Finished(bytes))后,client:recv(timeout_ms) 或 client:request(buf, timeout_ms) 将返回该对象 (参考:Client)。
buf:len()- 缓冲区内存储的字节的数量。
buf[i]- 通过索引号读取相应字节的值,索引值i从 0 开始,如果i超出范围将发生错误。buf:peek_i8(i)- 返回位于位置i处开始的8位有符号整型值,如果超出范围将发生错误。buf:peek_u8(i)- 返回位于位置i处开始的8位无符号整型值,如果超出范围将发生错误。buf:peek_i16(i)- 返回位于位置i处开始的16位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i16_le(i)- 返回位于位置i处开始的16位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u16(i)- 返回位于位置i处开始的16位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u16_le(i)- 返回位于位置i处开始的16位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_i32(i)- 返回位于位置i处开始的32位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i32_le(i)- 返回位于位置i处开始的32位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u32(i)- 返回位于位置i处开始的32位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u32_le(i)- 返回位于位置i处开始的32位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_f32(i)- 返回位于位置i处开始的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:peek_f32_le(i)- 返回位于位置i处开始的32位浮点型值(LittleEndian),如果超出范围将发生错误。buf:peek_i64(i)- 返回位于位置i处开始的64位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i64_le(i)- 返回位于位置i处开始的64位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u64(i)- 返回位于位置i处开始的64位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u64_le(i)- 返回位于位置i处开始的64位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_f64(i)- 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。buf:peek_f64_le(i)- 返回位于位置i处开始的64位浮点型值(LittleEndian),如果超出范围将发生错误。
Client
Section titled “Client”通信代理,由系统创建并传递给async_poll和async_write,用于与设备的通信。
client:clear_recv_buf()- 清空接收缓冲区(可用于在接收数据前清除“脏”数据),对于设备可能主动发送数据的情形应慎用。client:send(buf, clear)- 发送请求帧,不等待响应。buf: 请求帧字节缓冲区 (ByteBufferMut),由设备驱动程序自身创建;clear:可选参数,布尔型值,指示是否在发送前清空接收缓冲区,缺省值false(表明不清空)。client:recv(timeout_ms)- 接收数据帧,如果正确接收到一个完整的数据帧将返回 ByteBuffer。timeout_ms: 可选参数,非负的整型值,等待超时时间(毫秒),缺省值500。client:request(buf, timeout_ms)- 发送请求帧(注意:发送请求前会先清空接收缓冲区)并等待响应,适用于“一问一答”的通信方式(并且设备不会主动发送数据),成功接收到响应帧返回 ByteBuffer 对象。buf: 请求帧字节缓冲区 (ByteBufferMut),由设备驱动程序自身创建;timeout_ms: 可选参数,非负的整型值,等待超时时间(毫秒),缺省值500。
NameValues
Section titled “NameValues”名值对对象。
NameValues.new()- 创建NameValues对象,如:local name_values = NameValues.new()name_values:append(other_name_values)- 将另一个NameValues对象other_name_values内的数据移除并合并到name_values中。name_values:insert_bool(name, value)- 插入布尔型变量。name: 变量名称,字符串值;value - 布尔型值。name_values:insert_i8(name, value)- 插入8位有符号整型变量值。name: 变量名称,字符串值;value - 8位有符号整型值。name_values:insert_u8(name, value)- 插入8位无符号整型变量值。name: 变量名称,字符串值;value - 8位无符号整型值。name_values:insert_i16(name, value)- 插入16位有符号整型变量值。name: 变量名称,字符串值;value - 16位有符号整型值。name_values:insert_u16(name, value)- 插入16位无符号整型变量值。name: 变量名称,字符串值;value - 16位无符号整型值。name_values:insert_i32(name, value)- 插入32位有符号整型变量值。name: 变量名称,字符串值;value - 32位有符号整型值。name_values:insert_u32(name, value)- 插入32位无符号整型变量值。name: 变量名称,字符串值;value - 32位无符号整型值。name_values:insert_f32(name, value)- 插入32位浮点型变量值。name: 变量名称,字符串值;value - 32位浮点型值。name_values:insert_i64(name, value)- 插入64位有符号整型变量值。name: 变量名称,字符串值;value - 64位有符号整型值。name_values:insert_u64(name, value)- 插入64位无符号整型变量值。name: 变量名称,字符串值;value - 64位无符号整型值。name_values:insert_f64(name, value)- 插入64位浮点型变量值。name: 变量名称,字符串值;value - 64位浮点型值。
time 模块
Section titled “time 模块”time.sleep_ms(ms)- 休眠一段时间(暂停执行,让出控制权),ms:无符号整型值,毫秒。这是一个异步函数。time.now()- 返回当前时间(类型为内部的Instance),如:local t = time.now()t:elapsed_ms()- 返回t代表的时刻到当前时刻之间经历的时间(毫秒数),整型值。
modbus 模块(需要手工导入)
Section titled “modbus 模块(需要手工导入)”系统提供对 modbus 客户端的有限支持,目前支持的功能码(十进制):1, 2, 3, 4, 5, 6, 16, 22。
需要手动导入:local modbus = require('modbus')。
modbus.rtu
Section titled “modbus.rtu”-
modbus.rtu.decode_response(buf)- 返回DecodeResult,buf- DecodeBuffer, 用于解码响应帧,常用在sync_decode_frame函数内。 -
modbus.rtu.as_response(buf)- 返回ModbusRtuResponse,buf- ByteBuffer, 将buf解释为 modbus rtu 的响应帧。 -
modbus.rtu.request_fc1(client, slave_id, start_addr, quantity, timeout_ms)- 请求功能码:1(读线圈状态)。 -
modbus.rtu.request_fc2(client, slave_id, start_addr, quantity, timeout_ms)- 请求功能码:2(读离散量输入寄存器状态)。 -
modbus.rtu.request_fc3(client, slave_id, start_addr, quantity, timeout_ms)- 请求功能码:3(读保持寄存器状态)。 -
modbus.rtu.request_fc4(client, slave_id, start_addr, quantity, timeout_ms)- 请求功能码:4(读输入寄存器状态)。 -
modbus.rtu.request_fc5(client, slave_id, addr, value, timeout_ms)- 请求功能码:5(设置单个线圈状态),value- 布尔型值。 -
modbus.rtu.request_fc6_i16(client, slave_id, addr, value, timeout_ms)- 请求功能码:6(写单个保持寄存器的状态),value- 16位有符号整型值(BigEndian)。 -
modbus.rtu.request_fc6_i16_le(client, slave_id, addr, value, timeout_ms)- 请求功能码:6(写单个保持寄存器的状态),value- 16位有符号整型值(LittleEndian)。 -
modbus.rtu.request_fc6_u16(client, slave_id, addr, value, timeout_ms)- 请求功能码:6(写单个保持寄存器的状态),value- 16位无符号整型值(BigEndian)。 -
modbus.rtu.request_fc6_u16_le(client, slave_id, addr, value, timeout_ms)- 请求功能码:6(写单个保持寄存器的状态),value- 16位无符号整型值(LittleEndian)。 -
modbus.rtu.request_fc16_i32(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位有符号整型值(BigEndian)。 -
modbus.rtu.request_fc16_i32_le(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位有符号整型值(LittleEndian)。 -
modbus.rtu.request_fc16_u32(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位无符号整型值(BigEndian)。 -
modbus.rtu.request_fc16_u32_le(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位无符号整型值(LittleEndian)。 -
modbus.rtu.request_fc16_f32(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位浮点型值(BigEndian)。 -
modbus.rtu.request_fc16_f32_le(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位浮点型值(LittleEndian)。 -
modbus.rtu.request_fc16_i64(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位有符号整型值(BigEndian)。 -
modbus.rtu.request_fc16_i64_le(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位有符号整型值(LittleEndian)。 -
modbus.rtu.request_fc16_u64(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位无符号整型值(BigEndian)。 -
modbus.rtu.request_fc16_u64_le(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位无符号整型值(LittleEndian)。 -
modbus.rtu.request_fc16_f64(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位浮点型值(BigEndian)。 -
modbus.rtu.request_fc16_f64_le(client, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位浮点型值(LittleEndian)。 -
modbus.rtu.request_fc22(client, slave_id, addr, and_mask, or_mask, timeout_ms)- 请求功能码:22(掩码写保持寄存器的状态),and_mask- 16位无符号整型值(BigEndian),“与”掩码值;or_mask- 16位无符号整型值(BigEndian),“或”掩码值。
modbus.tcp
Section titled “modbus.tcp”-
modbus.tcp.decode_response(buf)- 返回DecodeResult,buf- DecodeBuffer, 用于解码响应帧,常用在sync_decode_frame函数内。 -
modbus.tcp.as_response(buf)- 返回ModbusTcpResponse,buf- ByteBuffer, 将buf解释为 modbus tcp 的响应帧。 -
modbus.tcp.request_fc1(client, transaction_id, slave_id, start_addr, quantity, timeout_ms)- 请求功能码:1(读线圈状态)。 -
modbus.tcp.request_fc2(client, transaction_id, slave_id, start_addr, quantity, timeout_ms)- 请求功能码:2(读离散量输入寄存器状态)。 -
modbus.tcp.request_fc3(client, transaction_id, slave_id, start_addr, quantity, timeout_ms)- 请求功能码:3(读保持寄存器状态)。 -
modbus.tcp.request_fc4(client, transaction_id, slave_id, start_addr, quantity, timeout_ms)- 请求功能码:4(读输入寄存器状态)。 -
modbus.tcp.request_fc5(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:5(设置单个线圈状态),value- 布尔型值。 -
modbus.tcp.request_fc6_i16(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:6(写单个保持寄存器的状态),value- 16位有符号整型值(BigEndian)。 -
modbus.tcp.request_fc6_i16_le(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:6(写单个保持寄存器的状态),value- 16位有符号整型值(LittleEndian)。 -
modbus.tcp.request_fc6_u16(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:6(写单个保持寄存器的状态),value- 16位无符号整型值(BigEndian)。 -
modbus.tcp.request_fc6_u16_le(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:6(写单个保持寄存器的状态),value- 16位无符号整型值(LittleEndian)。 -
modbus.tcp.request_fc16_i32(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位有符号整型值(BigEndian)。 -
modbus.tcp.request_fc16_i32_le(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位有符号整型值(LittleEndian)。 -
modbus.tcp.request_fc16_u32(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位无符号整型值(BigEndian)。 -
modbus.tcp.request_fc16_u32_le(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位无符号整型值(LittleEndian)。 -
modbus.tcp.request_fc16_f32(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位浮点型值(BigEndian)。 -
modbus.tcp.request_fc16_f32_le(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 32位浮点型值(LittleEndian)。 -
modbus.tcp.request_fc16_i64(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位有符号整型值(BigEndian)。 -
modbus.tcp.request_fc16_i64_le(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位有符号整型值(LittleEndian)。 -
modbus.tcp.request_fc16_u64(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位无符号整型值(BigEndian)。 -
modbus.tcp.request_fc16_u64_le(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位无符号整型值(LittleEndian)。 -
modbus.tcp.request_fc16_f64(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位浮点型值(BigEndian)。 -
modbus.tcp.request_fc16_f64_le(client, transaction_id, slave_id, addr, value, timeout_ms)- 请求功能码:16(写多个保持寄存器的状态),value- 64位浮点型值(LittleEndian)。 -
modbus.tcp.request_fc22(client, transaction_id, slave_id, addr, and_mask, or_mask, timeout_ms)- 请求功能码:22(掩码写保持寄存器的状态),and_mask- 16位无符号整型值(BigEndian),“与”掩码值;or_mask- 16位无符号整型值(BigEndian),“或”掩码值。
ModbusRtuResponse
Section titled “ModbusRtuResponse”modbus.rtu.as_response(buf) 返回的对象, buf - ByteBuffer 对象。
local modbus = require('modbus')local response = modbus.rtu.as_response(buf)response.slave_id- 16位无符号整型值,从机地址。如:local slave_id = response.slave_idresponse.fc- 8位无符号整型值,功能码。如:local fc = response.fcresponse.payload-ByteBuffer对象,响应帧负载(数据)缓冲区(不包含:从机地址,功能码和CRC校验码)response:fc1_data()- 请求功能码1的响应帧的数据,布尔型值数组,元素数目是8的倍数,可能多于实际请求的寄存器的数目,多余的部分没意义。response:fc2_data()- 请求功能码2的响应帧的数据,布尔型值数组,元素数目是8的倍数,可能多于实际请求的寄存器的数目,多余的部分没意义。response:fc3_data()- 请求功能码3的响应帧的数据,16位无符号整型值(BigEndian)数组,元素数目为请求的寄存器的数目。response:fc4_data()- 请求功能码4的响应帧的数据,16位无符号整型值(BigEndian)数组,元素数目为请求的寄存器的数目。response:fc5_data()- 请求功能码5的响应帧的数据,16位无符号整型值(BigEndian)数组,数组包含2个元素,第一个是寄存器地址,第二个是写入的值(0x0000 或 0xFF00)。response:fc6_data()- 请求功能码6的响应帧的数据,16位无符号整型值(BigEndian)数组,数组包含2个元素,第一个是寄存器地址,第二个是写入的值。response:fc16_data()- 请求功能码16的响应帧的数据,16位无符号整型值(BigEndian)数组,数组包含2个元素,第一个是开始寄存器地址,第二个是写入的寄存器的数量。response:fc22_data()- 请求功能码22的响应帧的数据,16位无符号整型值(BigEndian)数组,数组包含3个元素,第一个是寄存器地址,第二个是“与”掩码值,第三个是“或”掩码值。
ModbusTcpResponse
Section titled “ModbusTcpResponse”modbus.tcp.as_response(buf) 返回的对象, buf - ByteBuffer 对象。
local modbus = require('modbus')local response = modbus.tcp.as_response(buf)response.transaction_id- 16位无符号整型值,请求事务ID。如:local transaction_id = response.transaction_idresponse.slave_id- 16位无符号整型值,从机地址。如:local slave_id = response.slave_idresponse.fc- 8位无符号整型值,功能码。如:local fc = response.fcresponse.payload-ByteBuffer对象,响应帧负载(数据)缓冲区(不包含:MEAP头和功能码)response:fc1_data()- 请求功能码1的响应帧的数据,布尔型值数组,元素数目是8的倍数,可能多于实际请求的寄存器的数目,多余的部分没意义。response:fc2_data()- 请求功能码2的响应帧的数据,布尔型值数组,元素数目是8的倍数,可能多于实际请求的寄存器的数目,多余的部分没意义。response:fc3_data()- 请求功能码3的响应帧的数据,16位无符号整型值(BigEndian)数组,元素数目为请求的寄存器的数目。response:fc4_data()- 请求功能码4的响应帧的数据,16位无符号整型值(BigEndian)数组,元素数目为请求的寄存器的数目。response:fc5_data()- 请求功能码5的响应帧的数据,16位无符号整型值(BigEndian)数组,数组包含2个元素,第一个是寄存器地址,第二个是写入的值(0x0000 或 0xFF00)。response:fc6_data()- 请求功能码6的响应帧的数据,16位无符号整型值(BigEndian)数组,数组包含2个元素,第一个是寄存器地址,第二个是写入的值。response:fc16_data()- 请求功能码16的响应帧的数据,16位无符号整型值(BigEndian)数组,数组包含2个元素,第一个是开始寄存器地址,第二个是写入的寄存器的数量。response:fc22_data()- 请求功能码22的响应帧的数据,16位无符号整型值(BigEndian)数组,数组包含3个元素,第一个是寄存器地址,第二个是“与”掩码值,第三个是“或”掩码值。
几个例子(附详细注释)
Section titled “几个例子(附详细注释)”模拟设备驱动程序
Section titled “模拟设备驱动程序”
local i8 = -8local u8 = 8local i16 = -16local u16 = 16local i32 = -32local u32 = 32local f32 = 32.32local f64 = 64.64local bool = false
local auto_inc_i16 = -1local toggle_bool = false
local function async_poll() time.sleep_ms(100) local values = NameValues.new()
values:insert_i8('I8', i8) values:insert_u8('U8', u8) values:insert_i16('I16', i16) values:insert_u16('U16', u16) values:insert_i32('I32', i32) values:insert_u32('U32', u32) values:insert_f32('F32', f32) values:insert_f64('F64', f64) values:insert_bool('Bool', bool)
toggle_bool = not toggle_bool auto_inc_i16 = (auto_inc_i16 + 1) % 32768
values:insert_i16('Random_I16', math.random(-32768, 32767)) values:insert_u16('Random_U16', math.random(0, 65535)) values:insert_i32('Random_I32', math.random(-2147483648, 2147483647)) values:insert_u32('Random_U32', math.random(0, 4294967295)) values:insert_i16('Auto_Inc_I16', auto_inc_i16) values:insert_bool('Toggle_Bool', toggle_bool)
return valuesend
local function async_write(name, value) if name == 'I8' then i8 = value elseif name == 'U8' then u8 = value elseif name == 'I16' then i16 = value elseif name == 'U16' then u16 = value elseif name == 'I32' then i32 = value elseif name == 'U32' then u32 = value elseif name == 'F32' then f32 = value elseif name == 'F64' then f64 = value elseif name == 'Bool' then bool = value else Error(string.format('变量 %q 不支持写操作或不存在', name)) endend
return { poll = async_poll, write = async_write }modbus rtu 设备驱动程序
Section titled “modbus rtu 设备驱动程序”这里假设目标设备支持的寄存器如下:
- 线圈寄存器:地址从 0 开始,共 3 个寄存器依次为:
LED1,LED2,BEEP。均支持写操作。 - 离散量输入寄存器:地址从 0 开始,共 2 个寄存器依次为:
KEY1,KEY2。均只读。 - 保持寄存器:地址从 0x1000 开始,共 4 个寄存器依次为:
U16-1,U16-2,F32(4字节,占用 2 个寄存器)。均支持写操作。
支持的功能码:1, 2, 3, 5, 6, 16。
这里提供两个实现方式:利用系统提供的 modbus 支持实现和直接实现。

示例代码(利用系统提供的 modbus 支持)
Section titled “示例代码(利用系统提供的 modbus 支持)”--[[modbus 是一个内置的模块,但不会自动导入,需要使用 lua 标准库函数 `require` 手工导入]]local modbus = require("modbus")
-- 接收数据帧解码,可以直接调用内置的方法local function sync_decode_frame(buf) return modbus.rtu.decode_response(buf)end
--[[功能码 1 响应帧的数据解析在 async_poll 函数中请求的是 3 个线圈寄存器的状态:`LED1`, `LED2`, `BEEP`]]local function parse_fc1(slave_id, buf, name_values) local names = { "LED1", "LED2", "BEEP" } local response = modbus.rtu.as_response(buf) assert(response.slave_id == slave_id and response.fc == 1, '从机地址或功能码不匹配')
--[[ response:fc1_data() 布尔型数组,元素数目是8的倍数, 可能多于实际请求的寄存器的数目,多余的部分没意义 ]] local data = response:fc1_data()
--[[ 调用 lua 标准库函数 `ipairs` 遍历数组:`i` 是索引(注意这里的索引从 1 开始), `v` 是对应的值 ]] for i, v in ipairs(data) do --[[ 如果 `names[i]` 为真(即索引值位于 1~3之间), 表明该数据是我们需要的,于是将该值插入到 `name_values` 中 ]] if names[i] then name_values:insert_bool(names[i], v) end endend
--[[功能码 2 响应帧的数据解析在 async_poll 函数中请求的是 2 个离散量输入寄存器的状态:`KEY1`, `KEY2`代码与 `parse_fc1` 类似,这里不详细注释。]]local function parse_fc2(slave_id, buf, name_values) local response = modbus.rtu.as_response(buf) assert(response.slave_id == slave_id and response.fc == 2, '从机地址或功能码不匹配')
--[[ response:fc2_data() 布尔型数组,元素数目是8的倍数, 可能多于实际请求的寄存器的数目,多余的部分没意义 ]] local data = response:fc2_data()
-- 这里使用与 parse_fc1 不同的解析方法:不遍历数组 if #data < 2 then error("请求离散量输入寄存器的状态未返回正确数据") end
name_values:insert_bool("KEY1", data[1]) -- 注意:data 的索引从 1 开始 name_values:insert_bool("KEY2", data[2])end
local function parse_fc3(slave_id, buf, name_values) --[[ 这里不能使用 `response:fc3_data()`,因为标准的 modbus rtu 响应帧 的保持寄存器是16位无符号整型值并且采用 BigEndian 编码存储,而这里 包含一个32位的浮点型变量 `F32`(占用两个寄存器的存储空间),因此, 我们直接使用 `response.payload`。如果目标设备的数据是 LittleEndian 编码存储的也不能使用 `response:fc3_data()`,直接使用 `response.payload` ]]
local response = modbus.rtu.as_response(buf) assert(response.slave_id == slave_id and response.fc == 3, '从机地址或功能码不匹配')
-- payload 不包含:从机地址、功能码、CRC校验码,参考说明 local payload = response.payload
-- payload[0]: 返回数据的字节数 assert(payload:len() == 9 and payload[0] == 8, '返回的数据帧错误')
--[[ 2 字节,如果目标设备的数据是 LittleEndian 编码, 需要使用 `payload:peek_u16_le(1)` ]] local u16_1 = payload:peek_u16(1)
--[[ 2 字节,如果目标设备的数据是 LittleEndian 编码, 需要使用 `payload:peek_u16_le(3)` ]] local u16_2 = payload:peek_u16(3)
--[[ 4 字节(占用两个寄存器空间),如果目标设备的数据是 LittleEndian 编码, 需要使用 `payload:peek_f32_le(5)` ]] local f32 = payload:peek_f32(5)
name_values:insert_u16("U16-1", u16_1) name_values:insert_u16("U16-2", u16_2) name_values:insert_f32("F32", f32)end
local function async_poll(client, slave_id) local name_values = NameValues.new()
--[[ modbus 功能码 1, 读线圈寄存器状态:从线圈寄存器地址 0 开始, 共读取 3 个寄存器的状态:'LED1', 'LED2', 'BEEP'。 buf` 是响应的字节流缓冲区,下同 ]] local buf = modbus.rtu.request_fc1(client, slave_id, 0, 3) parse_fc1(slave_id, buf, name_values)
--[[ modbus 功能码 2, 读离散量输入寄存器状态:从离散量输入寄存器地址 0 开始, 共读取 2 个寄存器的状态:'KEY1', 'KEY2'。 这里还指定了超时时间:200毫秒,缺省是 500 毫秒 ]] local buf = modbus.rtu.request_fc2(client, slave_id, 0, 2, 200) parse_fc2(slave_id, buf, name_values)
--[[ modbus 功能码 3, 读保持寄存器状态:从保持寄存器地址 0x1000 开始, 共读取 4 个寄存器的状态:'U16-1', 'U16-2', 'F32'(占用2个寄存器空间) ]] local buf = modbus.rtu.request_fc3(client, slave_id, 0x1000, 4) parse_fc3(slave_id, buf, name_values)
-- 最后返回所有采集到的数据 return name_valuesend
local function async_write(client, slave_id, name, value) local buf local fc = 5
if name == "LED1" then -- 写单个线圈寄存器,功能码 5 buf = modbus.rtu.request_fc5(client, slave_id, 0, value) elseif name == "LED2" then -- 写单个线圈寄存器,功能码 5 buf = modbus.rtu.request_fc5(client, slave_id, 1, value) elseif name == "BEEP" then -- 写单个线圈寄存器,功能码 5 buf = modbus.rtu.request_fc5(client, slave_id, 2, value) elseif name == "U16-1" then -- 写单个保持寄存器,功能码 6,BigEndian buf = modbus.rtu.request_fc6_u16(client, slave_id, 0x1000, value) fc = 6 elseif name == "U16-2" then -- 写单个保持寄存器,功能码 6,BigEndian buf = modbus.rtu.request_fc6_u16(client, slave_id, 0x1001, value) fc = 6 elseif name == "F32" then -- 写多个保持寄存器(2个寄存器),功能码 16,BigEndian buf = modbus.rtu.request_fc16_f32(client, slave_id, 0x1002, value) fc = 16 else Error(string.format("变量 %q 不支持写操作或不存在", name)) end
-- 如果响应帧的功能码大于0x80,`modbus.rtu.as_response` 函数直接返回错误 local response = modbus.rtu.as_response(buf) Assert(response.slave_id == slave_id and response.fc == fc, '从机地址或功能码不匹配')end
return { decode = sync_decode_frame, poll = async_poll, write = async_write }示例代码(不利用系统提供的 modbus 支持)
Section titled “示例代码(不利用系统提供的 modbus 支持)”--[[计算 CRC 校验码`buf` - 可以是 `ByteBufferMut` 和 `DecodeBuffer` 对象`from` - 开始索引号,从 0 开始,包含`to` - 结束索引号,从 0 开始,不包含
返回的是16位无符号整型值(CRC)]]local function modbus_crc16(buf, from, to) local crc = 0xFFFF
from = from or 0 to = to or buf:len()
for i = from, to - 1 do crc = crc ~ buf[i]
for _ = 1, 8 do if (crc & 0x0001) ~= 0 then crc = (crc >> 1) ~ 0xA001 else crc = crc >> 1 end end end
return crcend
-- 支持的功能码:1, 2, 3, 5, 6, 16。local supported_fcs = { [1] = true, [2] = true, [3] = true, [5] = true, [6] = true, [16] = true,}
local function sync_decode_frame(buf) local len = buf:len() if len < 2 then -- 帧未结束,但暂时还无法确定还需要接收多少字节的数据 return DecodeResult.expect_more() end
local fc = buf[1] -- 功能码/异常功能码 -- fc > 0x80: 异常功能码 = fc + 0x80 if not supported_fcs[fc] and not supported_fcs[fc - 0x80] then local fc = fc if fc > 0x80 then fc = fc - 0x80 end error(string.format("不支持的功能码: 0x%02x", fc)) end
-- 任何一个 modbus rtu 响应帧至少应接收 5 字节的数据 if len < 5 then -- 帧未结束,但暂时还无法确定还需要接收多少字节的数据 return DecodeResult.expect_more() end
-- 如果 len >= 5, 计算出还需要接收多少字节的数据 local expected_len if fc >= 1 and fc <= 3 then expected_len = buf[2] + 5 elseif fc == 5 or fc == 6 or fc == 16 then expected_len = 8 elseif fc > 0x80 then expected_len = 5 end
assert(expected_len <= 255, string.format("MODBUS RTU 帧长度 %d 超过最大值 255", expected_len))
if len < expected_len then -- 帧未结束,但已确定还需要接收多少字节的数据 return DecodeResult.expect_more(expected_len - len) end
-- 接收的响应帧完成 local crc = modbus_crc16(buf, 0, expected_len - 2) -- 如果 CRC 检验失败,则应丢弃掉接收的数据并抛出错误 assert(crc == buf:peek_u16_le(expected_len - 2), "CRC 错误")
-- 帧接收完成,并且 CRC 校验正确 return DecodeResult.finished(expected_len)end
local function parse_fc1(slave_id, buf, name_values) -- 可以根据接收到的数据验证数据的有效性,这里是简单示例 -- 缓冲区长度,正常应该为 6(包含尾部的 CRC 校验码) local len = buf:len() if len == 5 and buf[1] == 0x81 then error(string.format("接收到异常码:%d", buf[2])) end
assert(len == 6, "未正确接收到应答帧:帧长度不符") assert(buf[0] == slave_id, "未正确接收到应答帧:从机地址不符") assert(buf[1] == 1, "未正确接收到应答帧:功能码不符") assert(buf[2] == 1, "未正确接收到应答帧:数据字节数不符")
local value = buf[3]
name_values:insert_bool("LED1", (value & 1) == 1) name_values:insert_bool("LED2", (value & 2) == 2) name_values:insert_bool("BEEP", (value & 4) == 4)end
local function parse_fc2(slave_id, buf, name_values) -- 可以根据接收到的数据验证数据的有效性,这里是简单示例 -- 缓冲区长度,正常应该为 6(包含尾部的 CRC 校验码) local len = buf:len() if len == 5 and buf[1] == 0x82 then error(string.format("接收到异常码:%d", buf[2])) end
assert(len == 6, "未正确接收到应答帧:帧长度不符") assert(buf[0] == slave_id, "未正确接收到应答帧:从机地址不符") assert(buf[1] == 2, "未正确接收到应答帧:功能码不符") assert(buf[2] == 1, "未正确接收到应答帧:数据字节数不符")
local value = buf[3]
name_values:insert_bool("KEY1", (value & 1) == 1) name_values:insert_bool("KEY2", (value & 2) == 2)end
local function parse_fc3(slave_id, buf, name_values) -- 可以根据接收到的数据验证数据的有效性,这里是简单示例 -- 缓冲区长度,正常应该为 13(包含尾部的 CRC 校验码) local len = buf:len() if len == 5 and buf[1] == 0x83 then error(string.format("接收到异常码:%d", buf[2])) end
assert(len == 13, "未正确接收到应答帧:帧长度不符") assert(buf[0] == slave_id, "未正确接收到应答帧:从机地址不符") assert(buf[1] == 3, "未正确接收到应答帧:功能码不符") assert(buf[2] == 8, "未正确接收到应答帧:数据字节数不符")
name_values:insert_u16("U16-1", buf:peek_u16(3)) name_values:insert_u16("U16-2", buf:peek_u16(5)) name_values:insert_f32("F32", buf:peek_f32(7))end
-- 功能码 1, 2, 3 具有相同的请求帧格式local function encode_fc1_fc2_fc3(slave_id, fc, start_addr, quantity) -- 创建编码缓冲区对象,能够确定帧总长度为 8 local buf = ByteBufferMut.with_capacity(8)
-- 从机地址,1字节 buf:append_u8(slave_id)
-- 功能码,1字节 buf:append_u8(fc)
-- 开始地址,2字节,BigEndian buf:append_u16(start_addr)
-- 请求的寄存器的数目,2字节,BigEndian buf:append_u16(quantity)
-- 计算 CRC local crc = modbus_crc16(buf)
-- CRC 的值追加到缓冲区尾部,2字节,LittleEndian buf:append_u16_le(crc)
-- 返回请求帧缓冲区对象,共8字节 return bufend
local function async_poll(client, slave_id) local name_values = NameValues.new()
-- 功能码 1, 开始地址 0, 共 3 个线圈寄存器:'LED1', 'LED2', 'BEEP' local buf = encode_fc1_fc2_fc3(slave_id, 1, 0, 3) local buf = client:request(buf) parse_fc1(slave_id, buf, name_values)
-- 功能码 2, 开始地址 0, 共 2 个离散量输入寄存器:'KEY1', 'KEY2' local buf = encode_fc1_fc2_fc3(slave_id, 2, 0, 2) local buf = client:request(buf) parse_fc2(slave_id, buf, name_values)
--[[ 功能码 3, 开始地址 0x1000, 共 4 个保持寄存器: 'U16-1', 'U16-2', 'F32'(占用2个寄存器空间) ]] local buf = encode_fc1_fc2_fc3(slave_id, 3, 0x1000, 4) local buf = client:request(buf) parse_fc3(slave_id, buf, name_values)
return name_valuesend
-- 写单个线圈寄存器,功能码 5local function encode_fc5(slave_id, addr, value) -- 创建编码缓冲区对象,能够确定帧总长度为 8 local buf = ByteBufferMut.with_capacity(8)
-- 从机地址,1字节 buf:append_u8(slave_id)
-- 功能码,1字节 buf:append_u8(5)
-- 地址,2字节,BigEndian buf:append_u16(addr)
local v = 0 if value then v = 0xff00 end
-- 写入的值,2字节,BigEndian buf:append_u16(v)
-- 计算 CRC local crc = modbus_crc16(buf)
-- CRC 的值追加到缓冲区尾部,2字节,LittleEndian buf:append_u16_le(crc)
-- 返回请求帧缓冲区对象,共8字节 return bufend
-- 写单个保持寄存器,功能码 6local function encode_fc6(slave_id, addr, value) -- 创建编码缓冲区对象,能够确定帧总长度为 8 local buf = ByteBufferMut.with_capacity(8)
-- 从机地址,1字节 buf:append_u8(slave_id)
-- 功能码,1字节 buf:append_u8(6)
-- 地址,2字节,BigEndian buf:append_u16(addr)
-- 写入的值,2字节,BigEndian buf:append_u16(value)
-- 计算 CRC local crc = modbus_crc16(buf)
-- CRC 的值追加到缓冲区尾部,2字节,LittleEndian buf:append_u16_le(crc)
-- 返回请求帧缓冲区对象,共8字节 return bufend
-- 写多个保持寄存器(2个寄存器, 32位浮点数)local function encode_fc16_f32(slave_id, addr, value) -- 创建编码缓冲区对象,能够确定帧总长度为 13 local buf = ByteBufferMut.with_capacity(13)
-- 从机地址,1字节 buf:append_u8(slave_id)
-- 功能码,1字节 buf:append_u8(16)
-- 地址,2字节,BigEndian buf:append_u16(addr)
-- 寄存器数量 2,2字节 buf:append_u16(2)
-- 数据占用字节数 4,1字节 buf:append_u8(4)
-- 写入的值,4字节,BigEndian buf:append_f32(value)
-- 计算 CRC local crc = modbus_crc16(buf)
-- CRC 的值追加到缓冲区尾部,2字节,LittleEndian buf:append_u16_le(crc)
-- 返回请求帧缓冲区对象,共13字节 return bufend
local function async_write(client, slave_id, name, value) local buf local fc = 5
if name == "LED1" then buf = encode_fc5(slave_id, 0, value) elseif name == "LED2" then buf = encode_fc5(slave_id, 1, value) elseif name == "BEEP" then buf = encode_fc5(slave_id, 2, value) elseif name == "U16-1" then buf = encode_fc6(slave_id, 0x1000, value) fc = 6 elseif name == "U16-2" then buf = encode_fc6(slave_id, 0x1001, value) fc = 6 elseif name == "F32" then buf = encode_fc16_f32(slave_id, 0x1002, value) fc = 16 else Error(string.format("变量 %q 不支持写操作或不存在", name)) end
-- 发送请求帧,并处理错误 local buf = client:request(buf)
Assert(buf[0] == slave_id, "未正确接收到应答帧:从机地址不符") Assert(fc == buf[1] or fc + 0x80 == buf[1], "未正确接收到应答帧:功能码不符")
if buf[1] > 0x80 then Error(string.format("接收到异常码:%d", buf[2])) endend
return { decode = sync_decode_frame, poll = async_poll, write = async_write }