设备驱动程序开发
设备驱动程序是系统的核心组件,管理员可以在线开发设备驱动程序而不需要专门的开发环境。设备驱动程序使用lua脚本语言编写,可以及时观察运行效果,程序的错误会记录在日志中。
设备驱动程序支持的数据类型:
I8- 8位有符号整数,取值范围:-128 ~ 127U8- 8位无符号整数,取值范围:0 ~ 255I16- 16位有符号整数,取值范围:-32768 ~ 32767U16- 16位无符号整数,取值范围:0 ~ 65535I32- 32位有符号整数,取值范围:-2147483648 ~ 2147483647U32- 32位无符号整数,取值范围:0 ~ 4294967295F32- 32位(单精度)浮点数(IEEE 754),取值范围:-3.40282347e+38 ~ 3.40282347e+38F64- 64位(双精度)浮点数(IEEE 754),取值范围:-1.7976931348623157e+308 ~ 1.7976931348623157e+308Bool- 布尔型变量,只有两个值:false和true
用户自定义的LUA模块(1.5.0+)
Section titled “用户自定义的LUA模块(1.5.0+)”用户可以自定义LUA模块,这些模块可被用于设备驱动程序。一般的模块的基本结构如下:
-- 假设模块名为:demo
local M = {}
function M.area_of_circle(r) return math.pi * r^2end
return M调用的方法:
local m = require(':demo') --注意这里的 ':'
local area = m.area_of_circle(1) -- 半径为1的圆的面积
...定义设备支持的变量
Section titled “定义设备支持的变量”定义供设备驱动程序外部使用的变量,包括变量的名称(外部标识符)、数据类型和是否可写标志。
lua语言编写的设备驱动程序实现,可以直接在代码编辑器中编辑(经过签名的设备驱动程序不会显示代码编辑器)。设备驱动程序的本质是请求帧(发送的帧)的编码和响应帧(接收的帧)的解码和数据解析。
三个主要函数
Section titled “三个主要函数”sync_decode_frame
Section titled “sync_decode_frame”响应帧解码函数,这是一个同步函数,内部不能调用任何异步函数(如
time.sleep_ms)。主要功能是确定响应帧是否接收完整、是否出错。对于模拟设备驱动程序无须实现该函数。当请求帧发送之后等待接收期间,每当有新的数据字节接收到会调用该函数。该函数携带一个由系统提供的参数:储存当前接收到的数据的字节缓冲区(DecodeBuffer),同时返回DecodeResult。
async_poll
Section titled “async_poll”轮询设备数据,这是一个异步函数,内部可以调用异步函数。主要功能是发送请求并且解析接收到的响应,最后返回采集的数据(NameValues)。该函数会不断地被调用(周期不是固定的,决定于总线上的设备数量和每个设备的响应时间)。
函数参数:
对于
模拟设备驱动程序,该函数无参数。而对于非模拟设备驱动程序,该函数携带两个参数,依次是:client和slave_id。client代表发送请求的代理(Client),slave_id代表目标从设备的ID(地址),目前仅支持非负整数的设备地址。
async_write
Section titled “async_write”写操作,这是一个异步函数,内部可以调用异步函数。主要功能是发送写操作请求并返回操作结果。
函数参数:
对于
模拟设备驱动程序,该函数携带两个参数,依次是:name和value。而对于非模拟设备驱动程序,该函数携带四个参数,依次是:client、slave_id、name和value。name是设备驱动程序定义的供外部使用的变量名,value是准备写的值。
代码的最后需要返回这三个函数:return { decode = sync_decode_frame, poll = async_poll, write = async_write }。如果设备不支持写操作,不需要实现 async_write,并且返回:return { decode = sync_decode_frame, poll = async_poll }。
对于模拟设备驱动程序,最后需要这样返回:
- 支持写操作
return { poll = async_poll, write = async_write } - 不支持写操作
return { poll = async_poll }
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: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),如果超出范围将发生错误。
常用校验算法
Section titled “常用校验算法”buf:xor_checksum(from, to)- 返回该缓冲区数据的字节“异或”校验和(8位无符号整型值),逐字节“异或”的结果。buf:xor_checksum_seeded(seed, from, to)- 返回该缓冲区数据的字节“异或”校验和(8位无符号整型值),逐字节“异或”的结果再与seed进行“异或”操作。seed: 种子值,8位无符号整型值。如果seed为 0 则与buf:xor_checksum(from, to)结果相同。buf:checksum8(from, to)- 返回该缓冲区数据的字节“累加”校验和(8位无符号整型值),逐字节相加的结果取低8位。buf:checksum16(from, to)- 返回该缓冲区数据的字节“累加”校验和(16位无符号整型值),逐字节相加的结果取低16位。buf:checksum16_words(from, to)- 返回该缓冲区数据的字“累加”校验和(16位无符号整型值),逐字(连续2字节按BigEndian得到的16位无符号整型值)相加的结果取低16位。如果缓冲区长度为奇数,则最后一个字节的值作为最后一个字的高8位,而低8位为0。buf:crc16_modbus(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 modbus rtu 的 CRC 校验。buf:crc16_dnp(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 dnp 的 CRC 校验。buf:crc16_profibus(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 profibus 的 CRC 校验。
DecodeBuffer
Section titled “DecodeBuffer”系统提供的字节缓冲区对象,只读。传递给 sync_decode_frame 函数。
buf:len()- 缓冲区内存储的字节的数量。
buf[i]- 通过索引号读取相应字节的值,索引值i从 0 开始,如果i超出范围将发生错误。buf:peek_i8(i)- 返回位于位置i处开始的8位有符号整型值,如果超出范围将发生错误。buf:peek_u8(i)- 返回位于位置i处开始的8位无符号整型值,如果超出范围将发生错误。buf:peek_i16(i)- 返回位于位置i处开始的16位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i16_le(i)- 返回位于位置i处开始的16位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u16(i)- 返回位于位置i处开始的16位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u16_le(i)- 返回位于位置i处开始的16位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_i32(i)- 返回位于位置i处开始的32位有符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_i32_le(i)- 返回位于位置i处开始的32位有符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_u32(i)- 返回位于位置i处开始的32位无符号整型值(BigEndian),如果超出范围将发生错误。buf:peek_u32_le(i)- 返回位于位置i处开始的32位无符号整型值(LittleEndian),如果超出范围将发生错误。buf:peek_f32(i)- 返回位于位置i处开始的32位浮点型值(BigEndian),如果超出范围将发生错误。buf:peek_f32_le(i)- 返回位于位置i处开始的32位浮点型值(LittleEndian),如果超出范围将发生错误。buf:peek_f64(i)- 返回位于位置i处开始的64位浮点型值(BigEndian),如果超出范围将发生错误。buf:peek_f64_le(i)- 返回位于位置i处开始的64位浮点型值(LittleEndian),如果超出范围将发生错误。
常用校验算法
Section titled “常用校验算法”buf:xor_checksum(from, to)- 返回该缓冲区数据的字节“异或”校验和(8位无符号整型值),逐字节“异或”的结果。buf:xor_checksum_seeded(seed, from, to)- 返回该缓冲区数据的字节“异或”校验和(8位无符号整型值),逐字节“异或”的结果再与seed进行“异或”操作。seed: 种子值,8位无符号整型值。如果seed为 0 则与buf:xor_checksum(from, to)结果相同。buf:checksum8(from, to)- 返回该缓冲区数据的字节“累加”校验和(8位无符号整型值),逐字节相加的结果取低8位。buf:checksum16(from, to)- 返回该缓冲区数据的字节“累加”校验和(16位无符号整型值),逐字节相加的结果取低16位。buf:checksum16_words(from, to)- 返回该缓冲区数据的字“累加”校验和(16位无符号整型值),逐字(连续2字节按BigEndian得到的16位无符号整型值)相加的结果取低16位。如果缓冲区长度为奇数,则最后一个字节的值作为最后一个字的高8位,而低8位为0。buf:crc16_modbus(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 modbus rtu 的 CRC 校验。buf:crc16_dnp(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 dnp 的 CRC 校验。buf:crc16_profibus(from, to)- 返回该缓冲区数据的“CRC16”校验码(16位无符号整型值),用于 profibus 的 CRC 校验。
DecodeResult
Section titled “DecodeResult”用于 sync_decode_frame 的返回值,代表当前帧解码的状态。如果接收过程中检测到错误,应该调用Error 来结束帧解码; 如果已经接收到完整的响应帧,应该返回 DecodeResult.Finished(bytes),其中bytes是帧的长度;如果没有检测到错误并且没有接收到完整的响应帧,则返回 DecodeResult.ExpectMore(bytes), 其中 bytes 可选,代表还需要接收的字节数,如果能确定还需要接收的字节数则可以提供该参数,否则,直接返回 DecodeResult.ExpectMore()。
DecodedFrameBuffer
Section titled “DecodedFrameBuffer”系统提供的字节缓冲区对象,只读。当接收到一个完整的响应帧(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),如果超出范围将发生错误。
Client
Section titled “Client”通信代理,由系统创建并传递给async_poll和async_write,用于与设备的通信。
client:clear_recv_buf()- 清空接收缓冲区(可用于在接收数据前清除“脏”数据),对于设备可能主动发送数据的情形应慎用。client:send(buf, clear)- 发送请求帧,不等待响应。buf: 请求帧字节缓冲区 (EncodeBuffer),由设备驱动程序自身创建;clear:可选参数,布尔型值,指示是否在发送前清空接收缓冲区,缺省值false(表明不清空)。client:recv(timeout_ms)- 接收数据帧,如果正确接收到一个完整的数据帧将返回 DecodedFrameBuffer。timeout_ms: 可选参数,非负的整型值,等待超时时间(毫秒),缺省值500。client:request(buf, timeout_ms)- 发送请求帧(注意:发送请求前会先清空接收缓冲区)并等待响应,适用于“一问一答”的通信方式(并且设备不会主动发送数据),成功接收到响应帧返回 DecodedFrameBuffer 对象。buf: 请求帧字节缓冲区 (EncodeBuffer),由设备驱动程序自身创建;timeout_ms: 可选参数,非负的整型值,等待超时时间(毫秒),缺省值500。
NameValues
Section titled “NameValues”名值对对象。
NameValues.new()- 创建NameValues对象,如:local name_values = NameValues.new()name_values:append(other_name_values)- 将另一个NameValues对象other_name_values内的数据移除并合并到name_values中。name_values:insert_bool(name, value)- 插入布尔型变量。name: 变量名称,字符串值;value - 布尔型值。name_values:insert_i8(name, value)- 插入8位有符号整型变量值。name: 变量名称,字符串值;value - 8位有符号整型值。name_values:insert_u8(name, value)- 插入8位无符号整型变量值。name: 变量名称,字符串值;value - 8位无符号整型值。name_values:insert_i16(name, value)- 插入16位有符号整型变量值。name: 变量名称,字符串值;value - 16位有符号整型值。name_values:insert_u16(name, value)- 插入16位无符号整型变量值。name: 变量名称,字符串值;value - 16位无符号整型值。name_values:insert_i32(name, value)- 插入32位有符号整型变量值。name: 变量名称,字符串值;value - 32位有符号整型值。name_values:insert_u32(name, value)- 插入32位无符号整型变量值。name: 变量名称,字符串值;value - 32位无符号整型值。name_values:insert_f32(name, value)- 插入32位浮点型变量值。name: 变量名称,字符串值;value - 32位浮点型值。name_values:insert_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 模块(需要手工导入)
Section titled “modbus 模块(需要手工导入)”系统提供对 modbus 客户端的有限支持,目前支持的功能码(十进制):1, 2, 3, 4, 5, 6, 16, 22。
需要手动导入:local modbus = require('modbus')。
modbus.rtu
Section titled “modbus.rtu”-
modbus.rtu.decode_response(buf)- 返回DecodeResult,buf- DecodeBuffer, 用于解码响应帧,常用在sync_decode_frame函数内。 -
modbus.rtu.as_response(buf)- 返回ModbusRtuResponse,buf- 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
Section titled “modbus.tcp”-
modbus.tcp.decode_response(buf)- 返回DecodeResult,buf- DecodeBuffer, 用于解码响应帧,常用在sync_decode_frame函数内。 -
modbus.tcp.as_response(buf)- 返回ModbusTcpResponse,buf- 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),“或”掩码值。
ModbusRtuResponse
Section titled “ModbusRtuResponse”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_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”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_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, 该值每次轮询会翻转
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
return { poll = async_poll, write = async_write }modbus rtu 设备驱动程序
Section titled “modbus rtu 设备驱动程序”这里假设目标设备支持的寄存器如下:
- 线圈寄存器:地址从 0 开始,共 3 个寄存器依次为:
LED1,LED2,BEEP。均支持写操作。 - 离散量输入寄存器:地址从 0 开始,共 2 个寄存器依次为:
KEY1,KEY2。均只读。 - 保持寄存器:地址从 0x1000 开始,共 4 个寄存器依次为:
U16-1,U16-2,F32(4字节,占用 2 个寄存器)。均支持写操作。
支持的功能码:1, 2, 3, 5, 6, 16。
这里提供两个实现方式:利用系统提供的 modbus 支持实现和直接实现。

示例代码(利用系统提供的 modbus 支持)
Section titled “示例代码(利用系统提供的 modbus 支持)”-- modbus 是一个内置的模块,但不会自动导入,需要使用 lua 标准库函数 `require` 手工导入local modbus = require("modbus")
-- 接收数据帧解码,可以直接调用内置的方法local function sync_decode_frame(buf) return modbus.rtu.decode_response(buf)end
--[[功能码 1 响应帧的数据解析在 async_poll 函数中请求的是 3 个线圈寄存器的状态:`LED1`, `LED2`, `BEEP`]]local function parse_fc1(slave_id, buf, name_values) local names = { "LED1", "LED2", "BEEP" } local response = modbus.rtu.as_response(buf) 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 endend
--[[功能码 2 响应帧的数据解析在 async_poll 函数中请求的是 2 个离散量输入寄存器的状态:`KEY1`, `KEY2`代码与 `parse_fc1` 类似,这里不详细注释。]]local function parse_fc2(slave_id, buf, name_values) local response = modbus.rtu.as_response(buf) 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_valuesend
local function async_write(client, slave_id, name, value) local buf local fc = 5
if name == "LED1" then -- 写单个线圈寄存器,功能码 5 buf = modbus.rtu.request_fc5(client, slave_id, 0, value) elseif name == "LED2" then -- 写单个线圈寄存器,功能码 5 buf = modbus.rtu.request_fc5(client, slave_id, 1, value) elseif name == "BEEP" then -- 写单个线圈寄存器,功能码 5 buf = modbus.rtu.request_fc5(client, slave_id, 2, value) elseif name == "U16-1" then -- 写单个保持寄存器,功能码 6,BigEndian buf = modbus.rtu.request_fc6_u16(client, slave_id, 0x1000, value) fc = 6 elseif name == "U16-2" then -- 写单个保持寄存器,功能码 6,BigEndian buf = modbus.rtu.request_fc6_u16(client, slave_id, 0x1001, value) fc = 6 elseif name == "F32" then -- 写多个保持寄存器(2个寄存器),功能码 16,BigEndian buf = modbus.rtu.request_fc16_f32(client, slave_id, 0x1002, value) fc = 16 else Error(string.format("变量 %q 不支持写操作或不存在", name)) -- string.format 是 lua 标准库的函数 end
-- 这里略过检查更多的错误(如返回的数据) local response = modbus.rtu.as_response(buf) -- 如果响应帧的功能码大于0x80,该函数直接返回错误 if response.slave_id ~= slave_id or response.fc ~= fc then Error('从机地址或功能码不匹配') endend
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 crcend
-- 支持的功能码:1, 2, 3, 5, 6, 16。local supported_fcs = { [1] = true, [2] = true, [3] = true, [5] = true, [6] = true, [16] = true,}
local function sync_decode_frame(buf) local len = buf:len() if len < 2 then return DecodeResult.expect_more() -- 帧未结束,但暂时还无法确定还需要接收多少字节的数据 end
local fc = buf[1] -- 功能码/异常功能码 -- fc > 0x80: 异常功能码 = fc + 0x80 if not supported_fcs[fc] and not supported_fcs[fc - 0x80] then local fc = fc if fc > 0x80 then fc = fc - 0x80 end Error(string.format("不支持的功能码: 0x%02x", fc)) end
-- 任何一个 modbus rtu 响应帧至少应接收 5 字节的数据 if len < 5 then return DecodeResult.expect_more() -- 帧未结束,但暂时还无法确定还需要接收多少字节的数据 end
-- 如果 len >= 5, 计算出还需要接收多少字节的数据 local expected_len if fc >= 1 and fc <= 3 then expected_len = buf[2] + 5 elseif fc == 5 or fc == 6 or fc == 16 then expected_len = 8 elseif fc > 0x80 then expected_len = 5 end
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_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 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 }