跳转到内容

设备驱动程序开发

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

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

  • I8 - 8位有符号整数,取值范围:-128 ~ 127
  • U8 - 8位无符号整数,取值范围:0 ~ 255
  • 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模块,这些模块可被用于设备驱动程序。一般的模块的基本结构如下:

-- 假设模块名为:demo
local M = {}
function M.area_of_circle(r)
return math.pi * r^2
end
return M

调用的方法:

local m = require(':demo') --注意这里的 ':'
local area = m.area_of_circle(1) -- 半径为1的圆的面积
...

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

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

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

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

函数参数:

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

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

函数参数:

对于 模拟 设备驱动程序,该函数携带两个参数,依次是:namevalue。而对于非模拟设备驱动程序,该函数携带四个参数,依次是:clientslave_idnamevaluename 是设备驱动程序定义的供外部使用的变量名,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 }

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

  • 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: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_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_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_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 校验。
  • buf:crc16_dnp(from, to) - 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 dnp 的 CRC 校验。
  • buf:crc16_profibus(from, to) - 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 profibus 的 CRC 校验。

系统提供的字节缓冲区对象,只读。传递给 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_f64(i) - 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。
  • buf:peek_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:crc16_dnp(from, to) - 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 dnp 的 CRC 校验。
  • buf:crc16_profibus(from, to) - 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 profibus 的 CRC 校验。

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

系统提供的字节缓冲区对象,只读。当接收到一个完整的响应帧(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_f64(i) - 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。
  • buf:peek_f64_le(i) - 返回位于位置i处开始的64位浮点型值(LittleEndian),如果超出范围将发生错误。

通信代理,由系统创建并传递给async_pollasync_write,用于与设备的通信。

  • client:clear_recv_buf() - 清空接收缓冲区(可用于在接收数据前清除“脏”数据),对于设备可能主动发送数据的情形应慎用。
  • client:send(buf, clear) - 发送请求帧,不等待响应。buf: 请求帧字节缓冲区 (EncodeBuffer),由设备驱动程序自身创建;clear:可选参数,布尔型值,指示是否在发送前清空接收缓冲区,缺省值false(表明不清空)。
  • client:recv(timeout_ms) - 接收数据帧,如果正确接收到一个完整的数据帧将返回 DecodedFrameBuffertimeout_ms: 可选参数,非负的整型值,等待超时时间(毫秒),缺省值500
  • client:request(buf, timeout_ms) - 发送请求帧(注意:发送请求前会先清空接收缓冲区)并等待响应,适用于“一问一答”的通信方式(并且设备不会主动发送数据),成功接收到响应帧返回 DecodedFrameBuffer 对象。buf: 请求帧字节缓冲区 (EncodeBuffer),由设备驱动程序自身创建;timeout_ms: 可选参数,非负的整型值,等待超时时间(毫秒),缺省值500

名值对对象。

  • 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_f64(name, value) - 插入64位浮点型变量值。 name: 变量名称,字符串值;value - 64位浮点型值。

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

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

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

  • modbus.rtu.decode_response(buf) - 返回 DecodeResultbuf - DecodeBuffer, 用于解码响应帧,常用在 sync_decode_frame 函数内。

  • modbus.rtu.as_response(buf) - 返回 ModbusRtuResponsebuf - DecodedFrameBuffer, 将 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_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.decode_response(buf) - 返回 DecodeResultbuf - DecodeBuffer, 用于解码响应帧,常用在 sync_decode_frame 函数内。

  • modbus.tcp.as_response(buf) - 返回 ModbusTcpResponsebuf - DecodedFrameBuffer, 将 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_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),“或”掩码值。

modbus.rtu.as_response(buf) 返回的对象, buf - DecodedFrameBuffer 对象。

local modbus = require('modbus')
local response = modbus.rtu.as_response(buf)
  • 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个元素,第一个是寄存器地址,第二个是“与”掩码值,第三个是“或”掩码值。

modbus.tcp.as_response(buf) 返回的对象, buf - DecodedFrameBuffer 对象。

local modbus = require('modbus')
local response = modbus.tcp.as_response(buf)
  • 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, 该值每次轮询会翻转
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
return { poll = async_poll, write = async_write }

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

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

示例代码(利用系统提供的 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)
if response.slave_id ~= slave_id or response.fc ~= 1 then
Error('从机地址或功能码不匹配')
end
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
end
--[[
功能码 2 响应帧的数据解析
在 async_poll 函数中请求的是 2 个离散量输入寄存器的状态:`KEY1`, `KEY2`
代码与 `parse_fc1` 类似,这里不详细注释。
]]
local function parse_fc2(slave_id, buf, name_values)
local response = modbus.rtu.as_response(buf)
if response.slave_id ~= slave_id or response.fc ~= 2 then
Error('从机地址或功能码不匹配')
end
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])
end
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)
if response.slave_id ~= slave_id or response.fc ~= 3 then
Error('从机地址或功能码不匹配')
end
local payload = response.payload -- payload 不包含:从机地址、功能码、CRC校验码,参考说明
if payload:len() ~= 9 or payload[0] ~= 8 then -- payload[0]: 返回数据的字节数
Error('返回的数据帧错误')
end
local u16_1 = payload:peek_u16(1) -- 2 字节,如果目标设备的数据是 LittleEndian 编码,需要使用 `payload:peek_u16_le(1)`
local u16_2 = payload:peek_u16(3) -- 2 字节,如果目标设备的数据是 LittleEndian 编码,需要使用 `payload:peek_u16_le(3)`
local f32 = payload:peek_f32(5) -- 4 字节(占用两个寄存器空间),如果目标设备的数据是 LittleEndian 编码,需要使用 `payload:peek_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)
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'
local buf = modbus.rtu.request_fc2(client, slave_id, 0, 2, 200) -- 这里还可以指定超时时间:200毫秒,缺省是 500 毫秒
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_values
end
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)) -- string.format 是 lua 标准库的函数
end
-- 这里略过检查更多的错误(如返回的数据)
local response = modbus.rtu.as_response(buf) -- 如果响应帧的功能码大于0x80,该函数直接返回错误
if response.slave_id ~= slave_id or response.fc ~= fc then
Error('从机地址或功能码不匹配')
end
end
return { decode = sync_decode_frame, poll = async_poll, write = 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 响应帧至少应接收 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
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:peek_u16_le(expected_len - 2) then
Error("CRC 错误")
end
return DecodeResult.finished(expected_len) -- 帧接收完成,并且 CRC 校验正确
end
local function parse_fc1(slave_id, buf, name_values)
-- 可以根据接收到的数据验证数据的有效性,这里是简单示例
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
if buf[0] ~= slave_id then -- 从机地址
Error("未正确接收到数据:从机地址不符")
end
local fc = buf[1] -- 功能码,应该为 1
if fc ~= 1 then
Error("未正确接收到数据:功能码不符")
end
local bytes = buf[2] -- 数据字节数,应该为 1
if bytes ~= 1 then
Error("未正确接收到数据:数据字节数不符")
end
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)
-- 可以根据接收到的数据验证数据的有效性,这里是简单示例
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
if buf[0] ~= slave_id then -- 从机地址
Error("未正确接收到数据:从机地址不符")
end
local fc = buf[1] -- 功能码,应该为 2
if fc ~= 2 then
Error("未正确接收到数据:功能码不符")
end
local bytes = buf[2] -- 数据字节数,应该为 1
if bytes ~= 1 then
Error("未正确接收到数据:数据字节数不符")
end
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)
-- 可以根据接收到的数据验证数据的有效性,这里是简单示例
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
if buf[0] ~= slave_id then -- 从机地址
Error("未正确接收到数据:从机地址不符")
end
local fc = buf[1] -- 功能码,应该为 3
if fc ~= 3 then
Error("未正确接收到数据:功能码不符")
end
local bytes = buf[2] -- 数据字节数,应该为 8
if bytes ~= 8 then
Error("未正确接收到数据:数据字节数不符")
end
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)
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 name_values = NameValues.new()
local buf = encode_fc1_fc2_fc3(slave_id, 1, 0, 3) -- 功能码 1, 开始地址 0, 共 3 个线圈寄存器:'LED1', 'LED2', 'BEEP'
local buf = client:request(buf)
parse_fc1(slave_id, buf, name_values)
local buf = encode_fc1_fc2_fc3(slave_id, 2, 0, 2) -- 功能码 2, 开始地址 0, 共 2 个离散量输入寄存器:'KEY1', 'KEY2'
local buf = client:request(buf)
parse_fc2(slave_id, buf, name_values)
local buf = encode_fc1_fc2_fc3(slave_id, 3, 0x1000, 4) -- 功能码 3, 开始地址 0x1000, 共 4 个保持寄存器:'U16-1', 'U16-2', 'F32'(占用2个寄存器空间)
local buf = client:request(buf)
parse_fc3(slave_id, buf, name_values)
return name_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
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)) -- string.format 是 lua 标准库的函数
end
-- 发送请求帧,并处理错误
local buf = client:request(buf)
if buf[0] ~= slave_id then -- 从机地址
Error("未正确接收到数据:从机地址不符")
end
if fc ~= buf[1] and fc + 0x80 ~= buf[1] then -- 功能码
Error("未正确接收到数据:功能码不符")
end
if buf[1] > 0x80 then
Error(string.format("接收到异常码:%d", buf[2]))
end
-- 这里忽略检查返回数据
end
return { decode = sync_decode_frame, poll = async_poll, write = async_write }