设备驱动程序开发
设备驱动程序是系统的核心组件,管理员可以在线开发设备驱动程序而不需要专门的开发环境。设备驱动程序使用lua脚本语言编写,可以及时观察运行效果,程序的错误会记录在日志中。
设备驱动程序支持的数据类型:
I16- 16位有符号整数,取值范围:-32768 ~ 32767U16- 16位无符号整数,取值范围:0 ~ 65535I32- 32位有符号整数,取值范围:-2147483648 ~ 2147483647U32- 32位无符号整数,取值范围:0 ~ 4294967295F32- 32位(单精度)浮点数(IEEE 754),取值范围:-3.40282347e+38 ~ 3.40282347e+38F64- 64位(双精度)浮点数(IEEE 754),取值范围:-1.7976931348623157e+308 ~ 1.7976931348623157e+308Bool- 布尔型变量,只有两个值:false和true
定义设备支持的变量
Section titled “定义设备支持的变量”定义供驱动程序外部使用的变量,包括变量的名称(外部标识符)、数据类型和是否可写标志。
lua语言编写的驱动程序实现,可以直接在代码编辑器中编辑(经过签名的驱动程序不会显示代码编辑器)。设备驱动程序的本质是请求帧的编码和响应帧的解码和数据解析。
三个主要函数
Section titled “三个主要函数”sync_decode_frame
Section titled “sync_decode_frame”帧解码函数,这是一个同步函数,内部不能调用任何异步函数(如
time.sleep_ms)。主要功能是确定响应帧是否接收完整、是否出错。对于模拟设备驱动程序无须实现该函数。当请求帧发送之后,每当有新的数据字节接收到会调用该函数。该函数携带一个由系统提供的参数:储存当前接收到的数据的字节缓冲区(DecodeBuffer),同时返回DecodeResult。
async_poll
Section titled “async_poll”轮询设备数据,这是一个异步函数,内部可以调用异步函数。主要功能是发送请求并且解析接收到的应答,最后返回采集的数据。该函数会不断地被调用(周期不是固定的,决定于总线上的设备数量和每个设备的响应时间)。
函数参数:
对于
模拟设备驱动程序,该函数无参数。而对于非模拟设备驱动程序,该函数携带两个参数,依次是:client和slave_id。client代表发送请求的代理(Client),slave_id代表目标从设备的ID(地址),目前仅支持非负整数的设备地址。
async_write
Section titled “async_write”写操作,这是一个异步函数,内部可以调用异步函数。主要功能是发送写操作请求并返回操作结果。
函数参数:
对于
模拟设备驱动程序,该函数携带两个参数,依次是:name和value。而对于非模拟设备驱动程序,该函数携带四个参数,依次是:client、slave_id、name和value。name是驱动程序定义的供外部使用的变量名,value是准备写的值。
代码的最后需要依次序返回这三个函数:return sync_decode_frame, async_poll, async_write。这三个函数的名称不重要,但返回次序很重要。如果设备不支持写操作,不需要实现 async_write,并且返回:return sync_decode_frame, async_poll 或 return sync_decode_frame, async_poll, nil。
对于模拟设备驱动程序,最后需要这样返回:
- 支持写操作
return async_poll, async_write - 不支持写操作
return async_poll或return async_poll, nil
EncodeBuffer
Section titled “EncodeBuffer”用于请求帧编码的字节缓冲区,由驱动程序自身创建,可修改。
EncodeBuffer.new()- 创建一个空的字节缓冲区对象,如:local buf = EncodeBuffer.new()EncodeBuffer.with_capacity(cap)- 创建一个空的字节缓冲区对象,同时分配初始的缓冲区容量,cap是待分配的缓冲区的容量(字节数)。如:local buf = EncodeBuffer.with_capacity(8)。如果缓冲区的容量可以确定,使用该函数创建将会减少内存分配的次数(会稍微提高一些性能)
buf:len()- 返回缓冲区内存储的字节的数量。
buf[i]- 通过索引号读取相应字节的值,索引值i从 0 开始,如果i超出范围将发生错误。buf:get_i8(i)- 返回位于位置i处开始的8位有符号整型值,如果超出范围将发生错误。buf:get_u8(i)- 返回位于位置i处开始的8位无符号整型值,如果超出范围将发生错误。buf:get_i16(i)- 返回位于位置i处开始的16位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i16_le(i)- 返回位于位置i处开始的16位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u16(i)- 返回位于位置i处开始的16位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u16_le(i)- 返回位于位置i处开始的16位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_i32(i)- 返回位于位置i处开始的32位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i32_le(i)- 返回位于位置i处开始的32位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u32(i)- 返回位于位置i处开始的32位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u32_le(i)- 返回位于位置i处开始的32位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_i64(i)- 返回位于位置i处开始的64位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i64_le(i)- 返回位于位置i处开始的64位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u64(i)- 返回位于位置i处开始的64位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u64_le(i)- 返回位于位置i处开始的64位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_f32(i)- 返回位于位置i处开始的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:get_f32_le(i)- 返回位于位置i处开始的32位浮点型值(LittleEndian),如果超出范围将发生错误。buf:get_f64(i)- 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。buf:get_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_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_f32(value)- 在缓冲区尾部追加32位浮点型值(BigEndian)。buf:append_f32_le(value)- 在缓冲区尾部追加32位浮点型值(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_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_f32(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:set_f32_le(i, value)- 设置(改写)索引值为i(从0开始)处的值为指定的32位浮点型值(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 校验。
DecodeBuffer
Section titled “DecodeBuffer”系统提供的字节缓冲区对象,只读。传递给 sync_decode_frame 函数。
buf:len()- 缓冲区内存储的字节的数量。
buf[i]- 通过索引号读取相应字节的值,索引值i从 0 开始,如果i超出范围将发生错误。buf:get_i8(i)- 返回位于位置i处开始的8位有符号整型值,如果超出范围将发生错误。buf:get_u8(i)- 返回位于位置i处开始的8位无符号整型值,如果超出范围将发生错误。buf:get_i16(i)- 返回位于位置i处开始的16位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i16_le(i)- 返回位于位置i处开始的16位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u16(i)- 返回位于位置i处开始的16位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u16_le(i)- 返回位于位置i处开始的16位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_i32(i)- 返回位于位置i处开始的32位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i32_le(i)- 返回位于位置i处开始的32位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u32(i)- 返回位于位置i处开始的32位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u32_le(i)- 返回位于位置i处开始的32位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_i64(i)- 返回位于位置i处开始的64位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i64_le(i)- 返回位于位置i处开始的64位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u64(i)- 返回位于位置i处开始的64位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u64_le(i)- 返回位于位置i处开始的64位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_f32(i)- 返回位于位置i处开始的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:get_f32_le(i)- 返回位于位置i处开始的32位浮点型值(LittleEndian),如果超出范围将发生错误。buf:get_f64(i)- 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。buf:get_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 校验。
modbus 支持
Section titled “modbus 支持”buf:decode_as_modbus_rtu_response- 将该DecodeBuffer作为 modbus rtu 应答帧解码帧,返回DecodeResult。如果 modbus rtu 设备支持的功能码不超出以下范围(十进制):1, 2, 3, 4, 5, 6, 16, 22,则sync_decode_frame函数可以直接返回:return buf:decode_as_modbus_rtu_response()。buf:decode_as_modbus_tcp_response- 将该DecodeBuffer作为 modbus tcp 应答帧解码帧,返回DecodeResult。如果 modbus tcp 设备支持的功能码不超出以下范围(十进制):1, 2, 3, 4, 5, 6, 16, 22,则sync_decode_frame函数可以直接返回:return buf:decode_as_modbus_tcp_response()。
DecodeResult
Section titled “DecodeResult”用于 sync_decode_frame 的返回值,代表当前帧解码的状态。如果接收过程中检测到错误,应该调用Error 来结束帧解码; 如果已经接收到完整的应答帧,应该返回 DecodeResult.Finished(bytes),其中bytes是帧的长度;如果没有检测到错误并且没有接收到完整的应答帧,则返回 DecodeResult.ExpectMore(bytes), 其中 bytes 可选,代表还需要接收的字节数,如果能确定还需要接收的字节数则可以提供该参数,否则,直接返回 DecodeResult.ExpectMore()。
DecodedFrameBuffer
Section titled “DecodedFrameBuffer”系统提供的字节缓冲区对象,只读。当接收到一个完整的数据帧(sync_decode_frame 函数返回 DecodeResult.Finished(bytes))后将此对象传递给client:request(buf, parser, timeout_ms)(参考:Client) 的第二个参数(parser函数)作为其参数。
buf:len()- 缓冲区内存储的字节的数量。
buf[i]- 通过索引号读取相应字节的值,索引值i从 0 开始,如果i超出范围将发生错误。buf:get_i8(i)- 返回位于位置i处开始的8位有符号整型值,如果超出范围将发生错误。buf:get_u8(i)- 返回位于位置i处开始的8位无符号整型值,如果超出范围将发生错误。buf:get_i16(i)- 返回位于位置i处开始的16位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i16_le(i)- 返回位于位置i处开始的16位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u16(i)- 返回位于位置i处开始的16位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u16_le(i)- 返回位于位置i处开始的16位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_i32(i)- 返回位于位置i处开始的32位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i32_le(i)- 返回位于位置i处开始的32位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u32(i)- 返回位于位置i处开始的32位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u32_le(i)- 返回位于位置i处开始的32位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_i64(i)- 返回位于位置i处开始的64位有符号整型值(BigEndian),如果超出范围将发生错误。buf:get_i64_le(i)- 返回位于位置i处开始的64位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_u64(i)- 返回位于位置i处开始的64位无符号整型值(BigEndian),如果超出范围将发生错误。buf:get_u64_le(i)- 返回位于位置i处开始的64位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:get_f32(i)- 返回位于位置i处开始的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:get_f32_le(i)- 返回位于位置i处开始的32位浮点型值(LittleEndian),如果超出范围将发生错误。buf:get_f64(i)- 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。buf:get_f64_le(i)- 返回位于位置i处开始的64位浮点型值(LittleEndian),如果超出范围将发生错误。
moubus 支持
Section titled “moubus 支持”buf:as_modbus_rtu_response- 将buf作为ModbusRtuResponse返回。buf:as_modbus_tcp_response- 将buf作为ModbusTcpResponse返回。
Client
Section titled “Client”发送请求的代理,由系统创建并传递给async_poll和async_write。
client:request(buf, parser, timeout_ms)- 发送请求帧,成功接收到应答帧应该返回NameValues对象。buf: 请求帧字节缓冲区 (EncodeBuffer),由驱动程序自身创建;parser: 根据接收到的应答帧解析数据的函数,该函数的参数是一个完整的应答帧字节缓冲区 (DeocodedFrameBuffer),并且可能需要返回NameValues对象(如果请求读操作,需要返回该对象,该对象存储的是采集的数据;如果请求写操作,不需要返回该对象,而是根据响应帧确定是否需要抛出错误,如果写操作结果正确不需要做特殊的处理);timeout_ms: 可选参数,等待超时时间(毫秒),缺省值500。client:request_oneway(buf)- 发送单向请求帧,不会等待应答。buf: 请求帧字节缓冲区 (EncodeBuffer),由驱动程序自身创建。
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_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_f64(name, value)- 插入64位浮点型变量值。name: 变量名称,字符串值;value - 64位浮点型值。
Error 函数
Section titled “Error 函数”Error(msg): msg - 字符串值,错误信息。调用该函数,将会退出当前函数并将错误信息向调用者传递,如果调用者函数是通过lua标准函数pcall或 xpcall调用的当前函数,则错误信息不会继续向上传递,如果在整个调用链中没有通过pcall或 xpcall捕获错误,错误信息将传递到顶层并且会记录在日志中。
time 模块(预装载,不需要导入)
Section titled “time 模块(预装载,不需要导入)”time.sleep_ms(ms)- 休眠一段时间(暂停执行,让出控制权),ms:无符号整型值,毫秒。这是一个异步函数。time.now()- 返回当前时间(类型为内部的Instance),如:local t = time.now()t:elapsed_ms()- 返回t代表的时刻到当前时刻之间经历的时间(毫秒数),整型值。
modbus_rtu 模块(需要手工导入)
Section titled “modbus_rtu 模块(需要手工导入)”系统提供对 modbus rtu 客户端的有限支持,目前支持的功能码(十进制):1, 2, 3, 4, 5, 6, 16, 22。
需要手动导入:local modbus_rtu = require('modbus_rtu')。
modbus_rtu.crc16
Section titled “modbus_rtu.crc16”modbus_rtu.crc16(byte_string)- 返回 16位无符号整型值,CRC 校验码。byte_string- 字节串,如:string.char(1, 3, 0, 0, 0, 4)。
modbus_rtu.encode.fc1
Section titled “modbus_rtu.encode.fc1”modbus_rtu.encode.fc1(slave_id, start_addr, quantity)- 返回功能码为1(读线圈状态)的请求帧字节缓冲区(EncodeBuffer,下同),slave_id- 8位无符号整型值,从设备地址;start_addr- 16位无符号整型值,寄存器开始地址;quantity- 16位无符号整型值,请求的寄存器数目。
modbus_rtu.encode.fc2
Section titled “modbus_rtu.encode.fc2”modbus_rtu.encode.fc2(slave_id, start_addr, quantity)- 返回功能码为2(读离散量输入寄存器状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;start_addr- 16位无符号整型值,寄存器开始地址;quantity- 16位无符号整型值,请求的寄存器数目。
modbus_rtu.encode.fc3
Section titled “modbus_rtu.encode.fc3”modbus_rtu.encode.fc3(slave_id, start_addr, quantity)- 返回功能码为3(读保持寄存器状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;start_addr- 16位无符号整型值,寄存器开始地址;quantity- 16位无符号整型值,请求的寄存器数目。
modbus_rtu.encode.fc4
Section titled “modbus_rtu.encode.fc4”modbus_rtu.encode.fc4(slave_id, start_addr, quantity)- 返回功能码为4(读输入寄存器状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;start_addr- 16位无符号整型值,寄存器开始地址;quantity- 16位无符号整型值,请求的寄存器数目。
modbus_rtu.encode.fc5
Section titled “modbus_rtu.encode.fc5”modbus_rtu.encode.fc5(slave_id, addr, value)- 返回功能码为5(设置单个线圈状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 布尔型值,待设置的状态值。
modbus_rtu.encode.fc6
Section titled “modbus_rtu.encode.fc6”modbus_rtu.encode.fc6_i16(slave_id, addr, value)- 返回功能码为6(写单个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 16位有符号整型值(BigEndian),待设置的状态值。modbus_rtu.encode.fc6_i16_le(slave_id, addr, value)- 返回功能码为6(写单个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 16位有符号整型值(LittleEndian),待设置的状态值。modbus_rtu.encode.fc6_u16(slave_id, addr, value)- 返回功能码为6(写单个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 16位无符号整型值(BigEndian),待设置的状态值。modbus_rtu.encode.fc6_u16_le(slave_id, addr, value)- 返回功能码为6(写单个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 16位无符号整型值(LittleEndian),待设置的状态值。
modbus_rtu.encode.fc16
Section titled “modbus_rtu.encode.fc16”modbus_rtu.encode.fc16_i32(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位有符号整型值(BigEndian),待设置的状态值。modbus_rtu.encode.fc16_i32_le(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位有符号整型值(LittleEndian),待设置的状态值。modbus_rtu.encode.fc16_u32(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位无符号整型值(BigEndian),待设置的状态值。modbus_rtu.encode.fc16_u32_le(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位无符号整型值(LittleEndian),待设置的状态值。modbus_rtu.encode.fc16_i64(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位有符号整型值(BigEndian),待设置的状态值。modbus_rtu.encode.fc16_i64_le(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位有符号整型值(LittleEndian),待设置的状态值。modbus_rtu.encode.fc16_u64(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位无符号整型值(BigEndian),待设置的状态值。modbus_rtu.encode.fc16_u64_le(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位无符号整型值(LittleEndian),待设置的状态值。modbus_rtu.encode.fc16_f32(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位浮点型值(BigEndian),待设置的状态值。modbus_rtu.encode.fc16_f32_le(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位浮点型值(LittleEndian),待设置的状态值。modbus_rtu.encode.fc16_f64(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位浮点型值(BigEndian),待设置的状态值。modbus_rtu.encode.fc16_f64_le(slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位浮点型值(LittleEndian),待设置的状态值。
modbus_rtu.encode.fc22
Section titled “modbus_rtu.encode.fc22”modbus_rtu.encode.fc22(slave_id, addr, and_mask, or_mask)- 返回功能码为22(掩码写保持寄存器的状态)的请求帧字节缓冲区,slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;and_mask- 16位无符号整型值(BigEndian),“与”掩码值;or_mask- 16位无符号整型值(BigEndian),“或”掩码值。
modbus_tcp 模块(需要手工导入)
Section titled “modbus_tcp 模块(需要手工导入)”系统提供对 modbus tcp 客户端的有限支持,目前支持的功能码(十进制):1, 2, 3, 4, 5, 6, 16, 22。
需要手动导入:local modbus_tcp = require('modbus_tcp')。
modbus_tcp.encode.fc1
Section titled “modbus_tcp.encode.fc1”modbus_tcp.encode.fc1(transaction_id, slave_id, start_addr, quantity)- 返回功能码为1(读线圈状态)的请求帧字节缓冲区(EncodeBuffer,下同),transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;start_addr- 16位无符号整型值,寄存器开始地址;quantity- 16位无符号整型值,请求的寄存器数目。
modbus_tcp.encode.fc2
Section titled “modbus_tcp.encode.fc2”modbus_tcp.encode.fc2(transaction_id, slave_id, start_addr, quantity)- 返回功能码为2(读离散量输入寄存器状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;start_addr- 16位无符号整型值,寄存器开始地址;quantity- 16位无符号整型值,请求的寄存器数目。
modbus_tcp.encode.fc3
Section titled “modbus_tcp.encode.fc3”modbus_tcp.encode.fc3(transaction_id, slave_id, start_addr, quantity)- 返回功能码为3(读保持寄存器状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;start_addr- 16位无符号整型值,寄存器开始地址;quantity- 16位无符号整型值,请求的寄存器数目。
modbus_tcp.encode.fc4
Section titled “modbus_tcp.encode.fc4”modbus_tcp.encode.fc4(transaction_id, slave_id, start_addr, quantity)- 返回功能码为4(读输入寄存器状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;start_addr- 16位无符号整型值,寄存器开始地址;quantity- 16位无符号整型值,请求的寄存器数目。
modbus_tcp.encode.fc5
Section titled “modbus_tcp.encode.fc5”modbus_tcp.encode.fc5(transaction_id, slave_id, addr, value)- 返回功能码为5(设置单个线圈状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 布尔型值,待设置的状态值。
modbus_tcp.encode.fc6
Section titled “modbus_tcp.encode.fc6”modbus_tcp.encode.fc6_i16(transaction_id, slave_id, addr, value)- 返回功能码为6(写单个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 16位有符号整型值(BigEndian),待设置的状态值。modbus_tcp.encode.fc6_i16_le(transaction_id, slave_id, addr, value)- 返回功能码为6(写单个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 16位有符号整型值(LittleEndian),待设置的状态值。modbus_tcp.encode.fc6_u16(transaction_id, slave_id, addr, value)- 返回功能码为6(写单个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 16位无符号整型值(BigEndian),待设置的状态值。modbus_tcp.encode.fc6_u16_le(transaction_id, slave_id, addr, value)- 返回功能码为6(写单个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 16位无符号整型值(LittleEndian),待设置的状态值。
modbus_tcp.encode.fc16
Section titled “modbus_tcp.encode.fc16”modbus_tcp.encode.fc16_i32(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位有符号整型值(BigEndian),待设置的状态值。modbus_tcp.encode.fc16_i32_le(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位有符号整型值(LittleEndian),待设置的状态值。modbus_tcp.encode.fc16_u32(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位无符号整型值(BigEndian),待设置的状态值。modbus_tcp.encode.fc16_u32_le(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位无符号整型值(LittleEndian),待设置的状态值。modbus_tcp.encode.fc16_i64(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位有符号整型值(BigEndian),待设置的状态值。modbus_tcp.encode.fc16_i64_le(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位有符号整型值(LittleEndian),待设置的状态值。modbus_tcp.encode.fc16_u64(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位无符号整型值(BigEndian),待设置的状态值。modbus_tcp.encode.fc16_u64_le(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位无符号整型值(LittleEndian),待设置的状态值。modbus_tcp.encode.fc16_f32(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位浮点型值(BigEndian),待设置的状态值。modbus_tcp.encode.fc16_f32_le(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 32位浮点型值(LittleEndian),待设置的状态值。modbus_tcp.encode.fc16_f64(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位浮点型值(BigEndian),待设置的状态值。modbus_tcp.encode.fc16_f64_le(transaction_id, slave_id, addr, value)- 返回功能码为16(写多个保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;value- 64位浮点型值(LittleEndian),待设置的状态值。
modbus_tcp.encode.fc22
Section titled “modbus_tcp.encode.fc22”modbus_tcp.encode.fc22(transaction_id, slave_id, addr, and_mask, or_mask)- 返回功能码为22(掩码写保持寄存器的状态)的请求帧字节缓冲区,transaction_id- 16位无符号整型值,请求事务ID;slave_id- 8位无符号整型值,从设备地址;addr- 16位无符号整型值,寄存器地址;and_mask- 16位无符号整型值(BigEndian),“与”掩码值;or_mask- 16位无符号整型值(BigEndian),“或”掩码值。
ModbusRtuResponse
Section titled “ModbusRtuResponse”DecodedFrameBuffer 对象的方法 as_modbus_rtu_response() 返回的对象。
-- 这里假设 buf 是 DecodedFrameBuffer 对象local response = buf:as_modbus_rtu_response()response.slave_id- 16位无符号整型值,从机地址。如:local slave_id = response.slave_idresponse.fc- 8位无符号整型值,功能码。如:local fc = response.fcresponse.payload-DecodedFrameBuffer对象,响应帧负载(数据)缓冲区(不包含:从机地址,功能码和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”DecodedFrameBuffer 对象的方法 as_modbus_tcp_response() 返回的对象。
-- 这里假设 buf 是 DecodedFrameBuffer 对象local response = buf:as_modbus_tcp_response()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-DecodedFrameBuffer对象,响应帧负载(数据)缓冲区(不包含: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 i16 = -16 --定义16位有符号整数,初始值为 -16local u16 = 16 --定义16位无符号整数,初始值为 16local i32 = -32 --定义32位有符号整数,初始值为 -32local u32 = 32 --定义32位无符号整数,初始值为 32local f32 = 32.32 --定义32位浮点数,初始值为 32.32local f64 = 64.64 --定义64位浮点数,初始值为 64.64local bool = false --定义布尔型变量,初始值为 false
local auto_inc_i16 = -1 --定义16位有符号整数,初始值为 -1,该数值每次轮询会自动增1local toggle_bool = false --定义布尔型变量,初始值为 false, 该值每次轮询会翻转
--[[轮询函数,该函数是一个异步函数。因此可以调用别的异步函数,如 time.sleep_ms。]]local function async_poll() time.sleep_ms(100) -- 延时 100 毫秒,轮询周期大约为 100 毫秒。time 是内置的模块名,不需要导入直接使用,sleep_ms 是其中的一个函数 local values = NameValues.new() -- 创建一个 '键值对' 对象,用于存储变量名和其值
-- 注意:调用 values 的方法需要使用 ':' 符号分割,而不是 '.',这有别于调用模块的函数 values:insert_i16('I16', i16) -- 插入一个16位整数值, '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 -- 翻转布尔型值 values:insert_i16('Auto_Inc_I16', auto_inc_i16)
auto_inc_i16 = (auto_inc_i16 + 1) % 32768 -- 自动增1,并且保证其值位于 0~32767 区间(16位有符号数的非负区间) values:insert_bool('Toggle_Bool', toggle_bool)
values:insert_i16('Random_I16', math.random(-32768, 32767)) -- -32768~32767 之间的随机数,以下类似 values:insert_u16('Random_U16', math.random(0, 65535))
return values -- 最后返回这个 '键值对' 对象end
--[[写操作函数,这是一个异步函数。该函数比较容易理解,根据外部变量名称的不同将值赋值给相应的内部变量。]]local function async_write(name, value) if 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)) -- string.format 是 lua 标准库的函数 endend
--[[最后返回按次序这两个函数(函数名称不重要,但次序重要)如果不支持写操作但支持轮询操作,可以不实现 async_write 函数:return async_poll 即可,或 return async_poll, nil如果不支持轮询操作但支持写操作,可以不实现 async_poll 函数:return nil, async_write 即可如果两者都不支持(实际没意义):return 即可]]return async_poll, async_writemodbus rtu 设备驱动程序
Section titled “modbus rtu 设备驱动程序”这里假设目标设备支持的寄存器如下:
- 线圈寄存器:地址从 0 开始,共 3 个寄存器依次为:
LED1,LED2,BEEP。均支持写操作。 - 离散量输入寄存器:地址从 0 开始,共 2 个寄存器依次为:
KEY1,KEY2。均只读。 - 保持寄存器:地址从 0 开始,共 4 个寄存器依次为:
U16-1,U16-2,F32(4字节,占用 2 个寄存器)。均支持写操作。
支持的功能码:1, 2, 3, 5, 6, 16。
这里提供两个实现方式:利用系统提供的 modbus 支持实现和直接实现。

示例代码(利用系统提供的 modbus 支持)
Section titled “示例代码(利用系统提供的 modbus 支持)”-- modbus_rtu 是一个内置的模块,但不会自动导入,需要使用 lua 标准库函数 `require` 手工导入local modbus_rtu = require("modbus_rtu")
-- 接收数据帧解码,可以直接调用内置的方法local function sync_decode_frame(buf) return buf:decode_as_modbus_rtu_response()end
--[[功能码 1 响应帧的数据解析在 async_poll 函数中请求的是 3 个线圈寄存器的状态:`LED1`, `LED2`, `BEEP`]]local function parse_fc1(buf) local name_values = NameValues.new() local names = { "LED1", "LED2", "BEEP" } --[[ 这里的 `pcall` 是 lua 标准库的函数(受保护的调用),用于捕捉错误,防止错误向调用链的上级传递。 注意这里的调用格式,不能直接使用 `buf:as_modbus_rtu_response`,如果不使用 `pcall` 可以直接写成 `local response = buf:as_modbus_rtu_response()`, 这样如果发生错误,将会退出当前函数并将错误向上传递。 ]] local ok, response = pcall(buf.as_modbus_rtu_response, buf) -- 如果 `ok` 为 `true`,则 `response` 包含正确的响应帧对象 (`ModbusRtuResponse`),否则,`resposne` 为错误对象 if ok then local data = response:fc1_data() -- 布尔型数组(元素数目是8的倍数,可能多于实际请求的寄存器的数目,多余的部分没意义) -- 调用 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 end else -- 如果 not ok,response 为错误对象 -- 这里继续抛出错误(也可以忽略) Error(tostring(response)) -- tostring 是 lua 标准库的函数 end
-- 最后,返回解析出的数据 return name_valuesend
--[[功能码 2 响应帧的数据解析在 async_poll 函数中请求的是 2 个离散量输入寄存器的状态:`KEY1`, `KEY2`代码与 `parse_fc1` 类似,这里不详细注释。只是这里没有用 `pcall` 而是直接 `local response = buf:as_modbus_rtu_response()``buf:as_modbus_rtu_response()` 可能会发生错误(比如 buf 里包含的数据不是合法的响应帧数据),因此,如果发生错误,将不会执行后续的代码,并将错误向调用链的上级传递]]local function parse_fc2(buf) local name_values = NameValues.new() local response = buf:as_modbus_rtu_response() local data = response:fc2_data() -- 布尔型数组(元素数目是8的倍数,可能多于实际请求的寄存器的数目,多余的部分没意义)
-- 这里使用与 parse_fc1 不同的解析方法:不遍历数组 if #data < 2 then -- `#data` - 是数组的元素数目 Error("请求离散量输入寄存器的状态未返回正确数据") end
name_values:insert_bool("KEY1", data[1]) -- 注意:data 的索引从 1 开始 name_values:insert_bool("KEY2", data[2])
return name_valuesend
local function async_poll(client, slave_id) local values = NameValues.new()
-- modbus 功能码 1, 读线圈寄存器状态:从线圈寄存器地址 0 开始,共读取 3 个寄存器的状态:'LED1', 'LED2', 'BEEP' -- 编码成请求帧的字节流,`buf` 即是请求的字节流缓冲区,下同 local buf = modbus_rtu.encode.fc1(slave_id, 0, 3) --[[ 发送请求,应答帧的字节流缓冲区将作为函数 `parse_fc1` 的参数, `parse_fc1` 返回的是解析出的“名值对”数据,再作为 `client:request` 的返回值, 再将返回的数据移动到 `values` 中,下同 ]] values:append(client:request(buf, parse_fc1))
-- modbus 功能码 2, 读离散量输入寄存器状态:从离散量输入寄存器地址 0 开始,共读取 2 个寄存器的状态:'KEY1', 'KEY2' local buf = modbus_rtu.encode.fc2(slave_id, 0, 2) values:append(client:request(buf, parse_fc2, 200)) -- 这里还可以指定超时时间:200毫秒,缺省是 500 毫秒
-- modbus 功能码 3, 读保持寄存器状态:从保持寄存器地址 0 开始,共读取 4 个寄存器的状态:'U16-1', 'U16-2', 'F32'(占用2个寄存器空间) local buf = modbus_rtu.encode.fc3(slave_id, 0, 4) --[[ 这里也可以直接将解析函数直接使用一个匿名函数嵌入到参数位置,注意,匿名函数的参数 `buf` 虽然与 `client:request` 的第一个参数同名,但它们不是同一个对象 ]] values:append(client:request(buf, function(buf) local name_values = NameValues.new() --[[ 这里不能使用 `response:fc3_data()`,因为标准的 modbus rtu 响应帧的保持寄存器是16位无符号整型值 并且采用 BigEndian 编码存储,而这里包含一个32位的浮点型变量 `F32`(占用两个寄存器的存储空间), 因此,我们直接使用 `response.payload`。如果目标设备的数据是 LittleEndian 编码存储的也不能使用 `response:fc3_data()`,直接使用 `response.payload` ]] local ok, response = pcall(buf.as_modbus_rtu_response, buf) if ok then local payload = response.payload -- payload 不包含:从机地址、功能码、CRC校验码,参考说明 local _bytes = payload[0] -- 返回数据的字节数,这里不用 local u16_1 = payload:get_u16(1) -- 2 字节,如果目标设备的数据是 LittleEndian 编码,需要使用 `payload:get_u16_le(1)` local u16_2 = payload:get_u16(3) -- 2 字节,如果目标设备的数据是 LittleEndian 编码,需要使用 `payload:get_u16_le(3)` local f32 = payload:get_f32(5) -- 4 字节(占用两个寄存器空间),如果目标设备的数据是 LittleEndian 编码,需要使用 `payload:get_f32_le(5)`
name_values:insert_u16("U16-1", u16_1) name_values:insert_u16("U16-2", u16_2) name_values:insert_f32("F32", f32) else -- 这里忽略错误,也可以继续抛出错误;如果不能忽略错误,不要使用上面的 `pcall` 调用, -- 而直接这样使用:local response = buf:as_modbus_rtu_response() end
return name_values end))
-- 最后返回所有采集到的数据 return valuesend
local function async_write(client, slave_id, name, value) local buf
if name == "LED1" then -- 写单个线圈寄存器,功能码 5 buf = modbus_rtu.encode.fc5(slave_id, 0, value) elseif name == "LED2" then -- 写单个线圈寄存器,功能码 5 buf = modbus_rtu.encode.fc5(slave_id, 1, value) elseif name == "BEEP" then -- 写单个线圈寄存器,功能码 5 buf = modbus_rtu.encode.fc5(slave_id, 2, value) elseif name == "U16-1" then -- 写单个保持寄存器,功能码 6,BigEndian buf = modbus_rtu.encode.fc6_u16(slave_id, 0, value) elseif name == "U16-2" then -- 写单个保持寄存器,功能码 6,BigEndian buf = modbus_rtu.encode.fc6_u16(slave_id, 1, value) elseif name == "F32" then -- 写多个保持寄存器(2个寄存器),功能码 16,BigEndian buf = modbus_rtu.encode.fc16_f32(slave_id, 2, value) else Error(string.format("变量 %q 不支持写操作或不存在", name)) -- string.format 是 lua 标准库的函数 end
-- 发送请求帧,并处理错误 client:request(buf, function(buf) -- 这里捕获错误 local ok, response = pcall(buf.as_modbus_rtu_response, buf) if not ok then Error(tostring(response)) -- tostring 是 lua 标准库的函数 end --[[ 如果不需要捕获错误,去掉上面的代码并去掉下面一行代码的注释。 如果返回异常,buf:as_modbus_rtu_response()将会产生错误并向上传递 并最终传递到客户端 ]] -- local _response = buf:as_modbus_rtu_response() end)end
return sync_decode_frame, async_poll, async_write示例代码(不利用系统提供的 modbus 支持)
Section titled “示例代码(不利用系统提供的 modbus 支持)”--[[计算 CRC 校验码`buf` - 可以是 `EncodeBuffer` 和 `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 应答帧至少应接收 3 字节的数据 if len < 3 then return DecodeResult.expect_more() -- 帧未结束,但暂时还无法确定还需要接收多少字节的数据 end
-- 如果 len >= 3, 计算出还需要接收多少字节的数据 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
if expected_len > 255 then Error(string.format("MODBUS RTU 帧长度 %d 超过最大值 255", expected_len)) end
if len < expected_len then return DecodeResult.expect_more(expected_len - len) -- 帧未结束,但已确定还需要接收多少字节的数据 end
-- 接收的应答帧完成 local crc = modbus_crc16(buf, 0, expected_len - 2) -- 如果 CRC 检验失败,则应丢弃掉接收的数据并抛出错误 if crc ~= buf:get_u16_le(expected_len - 2) then Error("CRC 错误") end
return DecodeResult.finished(expected_len) -- 帧接收完成,并且 CRC 校验正确end
local function parse_fc1(buf) local name_values = NameValues.new()
-- 可以根据接收到的数据验证数据的有效性,这里是简单示例 local len = buf:len() -- 缓冲区长度,正常应该为 6(包含尾部的 CRC 校验码) if len == 5 and buf[1] == 0x81 then Error(string.format("接收到异常码:%d", buf[2])) end
if len ~= 6 then Error("未正确接收到数据:帧长度不符") end
local _slave_id = buf[0] -- 从机地址
local fc = buf[1] -- 功能码,应该为 1 if fc ~= 1 then Error("未正确接收到数据:功能码不符") end
local _bytes = 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)
return name_valuesend
local function parse_fc2(buf) local name_values = NameValues.new()
-- 可以根据接收到的数据验证数据的有效性,这里是简单示例 local len = buf:len() -- 缓冲区长度,正常应该为 6(包含尾部的 CRC 校验码) if len == 5 and buf[1] == 0x82 then Error(string.format("接收到异常码:%d", buf[2])) end
if len ~= 6 then Error("未正确接收到数据:帧长度不符") end
local _slave_id = buf[0] -- 从机地址
local fc = buf[1] -- 功能码,应该为 2 if fc ~= 2 then Error("未正确接收到数据:功能码不符") end
local _bytes = buf[2] -- 字节数,应该为 1
local value = buf[3]
name_values:insert_bool("KEY1", (value & 1) == 1) name_values:insert_bool("KEY2", (value & 2) == 2)
return name_valuesend
local function parse_fc3(buf) local name_values = NameValues.new()
-- 可以根据接收到的数据验证数据的有效性,这里是简单示例 local len = buf:len() -- 缓冲区长度,正常应该为 13(包含尾部的 CRC 校验码) if len == 5 and buf[1] == 0x83 then Error(string.format("接收到异常码:%d", buf[2])) end
if len ~= 13 then Error("未正确接收到数据:帧长度不符") end
local _slave_id = buf[0] -- 从机地址
local fc = buf[1] -- 功能码,应该为 3 if fc ~= 3 then Error("未正确接收到数据:功能码不符") end
local _bytes = buf[2] -- 字节数,应该为 8
name_values:insert_u16("U16-1", buf:get_u16(3)) name_values:insert_u16("U16-2", buf:get_u16(5)) name_values:insert_f32("F32", buf:get_f32(7))
return name_valuesend
-- 功能码 1, 2, 3 具有相同的请求帧格式local function encode_fc1_fc2_fc3(slave_id, fc, start_addr, quantity) local buf = EncodeBuffer.with_capacity(8) -- 创建编码缓冲区对象,能够确定帧总长度为 8 buf:append_u8(slave_id) -- 从机地址,1字节 buf:append_u8(fc) -- 功能码,1字节 buf:append_u16(start_addr) -- 开始地址,2字节,BigEndian buf:append_u16(quantity) -- 请求的寄存器的数目,2字节,BigEndian local crc = modbus_crc16(buf) -- 计算 CRC buf:append_u16_le(crc) -- CRC 的值追加到缓冲区尾部,2字节,LittleEndian
return buf -- 返回请求帧缓冲区对象,共8字节end
local function async_poll(client, slave_id) local values = NameValues.new()
local buf = encode_fc1_fc2_fc3(slave_id, 1, 0, 3) -- 功能码 1, 开始地址 0, 共 3 个线圈寄存器:'LED1', 'LED2', 'BEEP' values:append(client:request(buf, parse_fc1))
local buf = encode_fc1_fc2_fc3(slave_id, 2, 0, 2) -- 功能码 2, 开始地址 0, 共 2 个离散量输入寄存器:'KEY1', 'KEY2' values:append(client:request(buf, parse_fc2))
local buf = encode_fc1_fc2_fc3(slave_id, 3, 0, 4) -- 功能码 3, 开始地址 0, 共 4 个保持寄存器:'U16-1', 'U16-2', 'F32'(占用2个寄存器空间) values:append(client:request(buf, parse_fc3))
return valuesend
-- 写单个线圈寄存器,功能码 5local function encode_fc5(slave_id, addr, value) local buf = EncodeBuffer.with_capacity(8) -- 创建编码缓冲区对象,能够确定帧总长度为 8 buf:append_u8(slave_id) -- 从机地址,1字节 buf:append_u8(5) -- 功能码,1字节 buf:append_u16(addr) -- 地址,2字节,BigEndian local v = 0 if value then v = 0xff00 end buf:append_u16(v) -- 写入的值,2字节,BigEndian local crc = modbus_crc16(buf) -- 计算 CRC buf:append_u16_le(crc) -- CRC 的值追加到缓冲区尾部,2字节,LittleEndian
return buf -- 返回请求帧缓冲区对象,共8字节end
-- 写单个保持寄存器,功能码 6local function encode_fc6(slave_id, addr, value) local buf = EncodeBuffer.with_capacity(8) -- 创建编码缓冲区对象,能够确定帧总长度为 8 buf:append_u8(slave_id) -- 从机地址,1字节 buf:append_u8(6) -- 功能码,1字节 buf:append_u16(addr) -- 地址,2字节,BigEndian buf:append_u16(value) -- 写入的值,2字节,BigEndian local crc = modbus_crc16(buf) -- 计算 CRC buf:append_u16_le(crc) -- CRC 的值追加到缓冲区尾部,2字节,LittleEndian
return buf -- 返回请求帧缓冲区对象,共8字节end
-- 写多个保持寄存器(2个寄存器, 32位浮点数)local function encode_fc16_f32(slave_id, addr, value) local buf = EncodeBuffer.with_capacity(13) -- 创建编码缓冲区对象,能够确定帧总长度为 13 buf:append_u8(slave_id) -- 从机地址,1字节 buf:append_u8(16) -- 功能码,1字节 buf:append_u16(addr) -- 地址,2字节,BigEndian buf:append_u16(2) -- 寄存器数量 2,2字节 buf:append_u8(4) -- 数据占用字节数 4,1字节 buf:append_f32(value) -- 写入的值,4字节,BigEndian local crc = modbus_crc16(buf) -- 计算 CRC buf:append_u16_le(crc) -- CRC 的值追加到缓冲区尾部,2字节,LittleEndian
return buf -- 返回请求帧缓冲区对象,共13字节end
local function async_write(client, slave_id, name, value) local buf
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, 0, value) elseif name == "U16-2" then buf = encode_fc6(slave_id, 1, value) elseif name == "F32" then buf = encode_fc16_f32(slave_id, 2, value) else Error(string.format("变量 %q 不支持写操作或不存在", name)) -- string.format 是 lua 标准库的函数 end
-- 发送请求帧,并处理错误 client:request(buf, function(buf) -- 这里捕获错误 local ok, response = pcall(buf.as_modbus_rtu_response, buf) if not ok then Error(tostring(response)) -- tostring 是 lua 标准库的函数 end --[[ 如果不需要捕获错误,去掉上面的代码并去掉下面一行代码的注释。 如果返回异常,buf:as_modbus_rtu_response()将会产生错误并向上传递 并最终传递到客户端 ]] -- local _response = buf:as_modbus_rtu_response() end)end
return sync_decode_frame, async_poll, async_write