跳转到内容

设备驱动程序开发

设备驱动程序是系统的核心组件,管理员可以在线开发设备驱动程序而不需要专门的开发环境。设备驱动程序使用lua脚本语言编写,可以及时观察运行效果,程序的错误会记录在日志中。

设备驱动程序支持的数据类型:

  • I16 - 16位有符号整数,取值范围:-32768 ~ 32767
  • U16 - 16位无符号整数,取值范围:0 ~ 65535
  • I32 - 32位有符号整数,取值范围:-2147483648 ~ 2147483647
  • U32 - 32位无符号整数,取值范围:0 ~ 4294967295
  • F32 - 32位(单精度)浮点数(IEEE 754),取值范围:-3.40282347e+38 ~ 3.40282347e+38
  • F64 - 64位(双精度)浮点数(IEEE 754),取值范围:-1.7976931348623157e+308 ~ 1.7976931348623157e+308
  • Bool - 布尔型变量,只有两个值:falsetrue

定义供驱动程序外部使用的变量,包括变量的名称(外部标识符)、数据类型和是否可写标志。

lua语言编写的驱动程序实现,可以直接在代码编辑器中编辑(经过签名的驱动程序不会显示代码编辑器)。设备驱动程序的本质是请求帧的编码和响应帧的解码和数据解析。

帧解码函数,这是一个同步函数,内部不能调用任何异步函数(如 time.sleep_ms)。主要功能是确定响应帧是否接收完整、是否出错。对于模拟设备驱动程序无须实现该函数。当请求帧发送之后,每当有新的数据字节接收到会调用该函数。该函数携带一个由系统提供的参数:储存当前接收到的数据的字节缓冲区(DecodeBuffer),同时返回 DecodeResult

轮询设备数据,这是一个异步函数,内部可以调用异步函数。主要功能是发送请求并且解析接收到的应答,最后返回采集的数据。该函数会不断地被调用(周期不是固定的,决定于总线上的设备数量和每个设备的响应时间)。

函数参数:

对于 模拟 设备驱动程序,该函数无参数。而对于非模拟设备驱动程序,该函数携带两个参数,依次是:clientslave_idclient代表发送请求的代理(Client), slave_id代表目标从设备的ID(地址),目前仅支持非负整数的设备地址。

写操作,这是一个异步函数,内部可以调用异步函数。主要功能是发送写操作请求并返回操作结果。

函数参数:

对于 模拟 设备驱动程序,该函数携带两个参数,依次是:namevalue。而对于非模拟设备驱动程序,该函数携带四个参数,依次是:clientslave_idnamevaluename 是驱动程序定义的供外部使用的变量名,value是准备写的值。

代码的最后需要依次序返回这三个函数:return sync_decode_frame, async_poll, async_write。这三个函数的名称不重要,但返回次序很重要。如果设备不支持写操作,不需要实现 async_write,并且返回:return sync_decode_frame, async_pollreturn sync_decode_frame, async_poll, nil

对于模拟设备驱动程序,最后需要这样返回:

  • 支持写操作 return async_poll, async_write
  • 不支持写操作 return async_pollreturn async_poll, nil

用于请求帧编码的字节缓冲区,由驱动程序自身创建,可修改。

  • 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),如果超出范围将发生错误。
  • 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 校验。

系统提供的字节缓冲区对象,只读。传递给 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),如果超出范围将发生错误。
  • 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: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()

用于 sync_decode_frame 的返回值,代表当前帧解码的状态。如果接收过程中检测到错误,应该调用Error 来结束帧解码; 如果已经接收到完整的应答帧,应该返回 DecodeResult.Finished(bytes),其中bytes是帧的长度;如果没有检测到错误并且没有接收到完整的应答帧,则返回 DecodeResult.ExpectMore(bytes), 其中 bytes 可选,代表还需要接收的字节数,如果能确定还需要接收的字节数则可以提供该参数,否则,直接返回 DecodeResult.ExpectMore()

系统提供的字节缓冲区对象,只读。当接收到一个完整的数据帧(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),如果超出范围将发生错误。

发送请求的代理,由系统创建并传递给async_pollasync_write

  • client:request(buf, parser, timeout_ms) - 发送请求帧,成功接收到应答帧应该返回 NameValues 对象。buf: 请求帧字节缓冲区 (EncodeBuffer),由驱动程序自身创建; parser: 根据接收到的应答帧解析数据的函数,该函数的参数是一个完整的应答帧字节缓冲区 (DeocodedFrameBuffer),并且可能需要返回 NameValues 对象(如果请求读操作,需要返回该对象,该对象存储的是采集的数据;如果请求写操作,不需要返回该对象,而是根据响应帧确定是否需要抛出错误,如果写操作结果正确不需要做特殊的处理); timeout_ms: 可选参数,等待超时时间(毫秒),缺省值500
  • client:request_oneway(buf) - 发送单向请求帧,不会等待应答。buf: 请求帧字节缓冲区 (EncodeBuffer),由驱动程序自身创建。

名值对对象。

  • 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(msg): msg - 字符串值,错误信息。调用该函数,将会退出当前函数并将错误信息向调用者传递,如果调用者函数是通过lua标准函数pcallxpcall调用的当前函数,则错误信息不会继续向上传递,如果在整个调用链中没有通过pcallxpcall捕获错误,错误信息将传递到顶层并且会记录在日志中。

time 模块(预装载,不需要导入)

Section titled “time 模块(预装载,不需要导入)”
  • time.sleep_ms(ms) - 休眠一段时间(暂停执行,让出控制权),ms:无符号整型值,毫秒。这是一个异步函数。
  • time.now() - 返回当前时间(类型为内部的Instance),如:local t = time.now()
  • t:elapsed_ms() - 返回t代表的时刻到当前时刻之间经历的时间(毫秒数),整型值。

系统提供对 modbus rtu 客户端的有限支持,目前支持的功能码(十进制):1, 2, 3, 4, 5, 6, 16, 22。 需要手动导入:local modbus_rtu = require('modbus_rtu')

  • modbus_rtu.crc16(byte_string) - 返回 16位无符号整型值,CRC 校验码。byte_string - 字节串,如:string.char(1, 3, 0, 0, 0, 4)
  • modbus_rtu.encode.fc1(slave_id, start_addr, quantity) - 返回功能码为1(读线圈状态)的请求帧字节缓冲区(EncodeBuffer,下同), slave_id - 8位无符号整型值,从设备地址;start_addr - 16位无符号整型值,寄存器开始地址;quantity - 16位无符号整型值,请求的寄存器数目。
  • modbus_rtu.encode.fc2(slave_id, start_addr, quantity) - 返回功能码为2(读离散量输入寄存器状态)的请求帧字节缓冲区, slave_id - 8位无符号整型值,从设备地址;start_addr - 16位无符号整型值,寄存器开始地址;quantity - 16位无符号整型值,请求的寄存器数目。
  • modbus_rtu.encode.fc3(slave_id, start_addr, quantity) - 返回功能码为3(读保持寄存器状态)的请求帧字节缓冲区, slave_id - 8位无符号整型值,从设备地址;start_addr - 16位无符号整型值,寄存器开始地址;quantity - 16位无符号整型值,请求的寄存器数目。
  • modbus_rtu.encode.fc4(slave_id, start_addr, quantity) - 返回功能码为4(读输入寄存器状态)的请求帧字节缓冲区, slave_id - 8位无符号整型值,从设备地址;start_addr - 16位无符号整型值,寄存器开始地址;quantity - 16位无符号整型值,请求的寄存器数目。
  • modbus_rtu.encode.fc5(slave_id, addr, value) - 返回功能码为5(设置单个线圈状态)的请求帧字节缓冲区, slave_id - 8位无符号整型值,从设备地址;addr - 16位无符号整型值,寄存器地址;value - 布尔型值,待设置的状态值。
  • 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_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(slave_id, addr, and_mask, or_mask) - 返回功能码为22(掩码写保持寄存器的状态)的请求帧字节缓冲区, slave_id - 8位无符号整型值,从设备地址;addr - 16位无符号整型值,寄存器地址;and_mask - 16位无符号整型值(BigEndian),“与”掩码值;or_mask - 16位无符号整型值(BigEndian),“或”掩码值。

系统提供对 modbus tcp 客户端的有限支持,目前支持的功能码(十进制):1, 2, 3, 4, 5, 6, 16, 22。 需要手动导入:local modbus_tcp = require('modbus_tcp')

  • 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(transaction_id, slave_id, start_addr, quantity) - 返回功能码为2(读离散量输入寄存器状态)的请求帧字节缓冲区,transaction_id - 16位无符号整型值,请求事务ID; slave_id - 8位无符号整型值,从设备地址;start_addr - 16位无符号整型值,寄存器开始地址;quantity - 16位无符号整型值,请求的寄存器数目。
  • 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(transaction_id, slave_id, start_addr, quantity) - 返回功能码为4(读输入寄存器状态)的请求帧字节缓冲区,transaction_id - 16位无符号整型值,请求事务ID; slave_id - 8位无符号整型值,从设备地址;start_addr - 16位无符号整型值,寄存器开始地址;quantity - 16位无符号整型值,请求的寄存器数目。
  • 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_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_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(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),“或”掩码值。

DecodedFrameBuffer 对象的方法 as_modbus_rtu_response() 返回的对象。

-- 这里假设 buf 是 DecodedFrameBuffer 对象
local response = buf:as_modbus_rtu_response()
  • response.slave_id - 16位无符号整型值,从机地址。如:local slave_id = response.slave_id
  • response.fc - 8位无符号整型值,功能码。如:local fc = response.fc
  • response.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个元素,第一个是寄存器地址,第二个是“与”掩码值,第三个是“或”掩码值。

DecodedFrameBuffer 对象的方法 as_modbus_tcp_response() 返回的对象。

-- 这里假设 buf 是 DecodedFrameBuffer 对象
local response = buf:as_modbus_tcp_response()
  • response.transaction_id - 16位无符号整型值,请求事务ID。如:local transaction_id = response.transaction_id
  • response.slave_id - 16位无符号整型值,从机地址。如:local slave_id = response.slave_id
  • response.fc - 8位无符号整型值,功能码。如:local fc = response.fc
  • response.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个元素,第一个是寄存器地址,第二个是“与”掩码值,第三个是“或”掩码值。

local i16 = -16 --定义16位有符号整数,初始值为 -16
local u16 = 16 --定义16位无符号整数,初始值为 16
local i32 = -32 --定义32位有符号整数,初始值为 -32
local u32 = 32 --定义32位无符号整数,初始值为 32
local f32 = 32.32 --定义32位浮点数,初始值为 32.32
local f64 = 64.64 --定义64位浮点数,初始值为 64.64
local bool = false --定义布尔型变量,初始值为 false
local auto_inc_i16 = -1 --定义16位有符号整数,初始值为 -1,该数值每次轮询会自动增1
local 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 标准库的函数
end
end
--[[
最后返回按次序这两个函数(函数名称不重要,但次序重要)
如果不支持写操作但支持轮询操作,可以不实现 async_write 函数:return async_poll 即可,或 return async_poll, nil
如果不支持轮询操作但支持写操作,可以不实现 async_poll 函数:return nil, async_write 即可
如果两者都不支持(实际没意义):return 即可
]]
return async_poll, async_write

这里假设目标设备支持的寄存器如下:

  • 线圈寄存器:地址从 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 rtu 设备驱动程序变量一览表

示例代码(利用系统提供的 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_values
end
--[[
功能码 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_values
end
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 values
end
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 crc
end
-- 支持的功能码: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_values
end
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_values
end
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_values
end
-- 功能码 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 values
end
-- 写单个线圈寄存器,功能码 5
local 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
-- 写单个保持寄存器,功能码 6
local 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