Browse Source

add:上传 exmodbus 扩展库代码

mengyang 1 month ago
parent
commit
fa4c70c13a
3 changed files with 1936 additions and 0 deletions
  1. 386 0
      script/libs/exmodbus.lua
  2. 1071 0
      script/libs/exmodbus_rtu_ascii.lua
  3. 479 0
      script/libs/exmodbus_tcp.lua

+ 386 - 0
script/libs/exmodbus.lua

@@ -0,0 +1,386 @@
+--[[
+@module exmodbus
+@summary exmodbus 控制Modbus RTU/ASCII/TCP主站/从站通信
+@version 1.0
+@date    2025.
+@author  马梦阳
+@usage
+本文件的对外接口有 5 个:
+1、exmodbus.create(config):创建 modbus 主站/从站,支持 RTU、ASCII、TCP 三种通信模式
+2、modbus:read(config):主站向从站发起读取请求(仅适用于 RTU、ASCII、TCP 主站模式)
+3、modbus:write(config):主站向从站发起写入请求(仅适用于 RTU、ASCII、TCP 主站模式)
+4、modbus:destroy():销毁 modbus 主站/从站实例对象
+5、modbus:on(callback):从站注册回调接口,用于处理主站发起的请求(仅适用于 RTU、ASCII、TCP 从站模式)
+]]
+local exmodbus = {}
+
+-- 定义通信模式常量
+exmodbus.RTU_MASTER = 0   -- RTU 主站模式
+exmodbus.RTU_SLAVE = 1    -- RTU 从站模式
+exmodbus.ASCII_MASTER = 2 -- ASCII 主站模式
+exmodbus.ASCII_SLAVE = 3  -- ASCII 从站模式
+exmodbus.TCP_MASTER = 4   -- TCP 主站模式
+exmodbus.TCP_SLAVE = 5    -- TCP 从站模式
+
+-- 定义数据类型常量
+exmodbus.COIL_STATUS = 0      -- 线圈状态
+exmodbus.INPUT_STATUS = 1     -- 离散输入状态
+exmodbus.HOLDING_REGISTER = 4 -- 保持寄存器
+exmodbus.INPUT_REGISTER = 3   -- 输入寄存器
+
+-- 定义操作类型常量
+exmodbus.READ_COILS = 0x01                       -- 读线圈状态
+exmodbus.READ_DISCRETE_INPUTS = 0x02             -- 读离散输入状态
+exmodbus.READ_HOLDING_REGISTERS = 0x03           -- 读保持寄存器
+exmodbus.READ_INPUT_REGISTERS = 0x04             -- 读输入寄存器
+exmodbus.WRITE_SINGLE_COIL = 0x05                -- 写单个线圈状态
+exmodbus.WRITE_SINGLE_HOLDING_REGISTER = 0x06    -- 写单个保持寄存器
+exmodbus.WRITE_MULTIPLE_HOLDING_REGISTERS = 0x10 -- 写多个保持寄存器
+exmodbus.WRITE_MULTIPLE_COILS = 0x0F             -- 写多个线圈状态
+
+-- 定义响应结果常量
+exmodbus.STATUS_SUCCESS = 0      -- 收到响应数据且数据有效
+exmodbus.STATUS_DATA_INVALID = 1 -- 收到响应数据但数据损坏/校验失败
+exmodbus.STATUS_EXCEPTION = 2    -- 收到标准异常响应码
+exmodbus.STATUS_TIMEOUT = 3      -- 超时未收到响应
+
+-- 异常响应码常量
+exmodbus.ILLEGAL_FUNCTION = 0x01           -- 不支持请求的功能码
+exmodbus.ILLEGAL_DATA_ADDRESS = 0x02       -- 请求的数据地址无效或超出范围
+exmodbus.ILLEGAL_DATA_VALUE = 0x03         -- 请求的数据值无效
+exmodbus.SLAVE_DEVICE_FAILURE = 0x04       -- 从站在执行操作时发生内部错误
+exmodbus.ACKNOWLEDGE = 0x05                -- 请求已接受,但需要长时间处理
+exmodbus.SLAVE_DEVICE_BUSY = 0x06          -- 从站正忙,无法处理请求
+exmodbus.NEGATIVE_ACKNOWLEDGE = 0x07       -- 无法执行编程功能
+exmodbus.MEMORY_PARITY_ERROR = 0x08        -- 内存奇偶校验错误
+exmodbus.GATEWAY_PATH_UNAVAILABLE = 0x0A   -- 网关路径不可用
+exmodbus.GATEWAY_TARGET_NO_RESPONSE = 0x0B -- 网关目标设备无响应
+
+-- 全局队列与调度器;
+local request_queue = {}
+local next_request_id = 1
+local scheduler_started = false
+
+-- 生成唯一请求 ID;
+local function gen_request_id()
+    local id = next_request_id
+    next_request_id = next_request_id + 1
+    -- 确保请求 ID 在 32 位有符号整数范围内;
+    if next_request_id == 0x7FFFFFFF then next_request_id = 1 end
+    return id
+end
+
+-- 处理队列中的请求;
+local function process_request_queue()
+    while true do
+        if #request_queue > 0 then
+            local req = table.remove(request_queue, 1)
+            local instance = req.instance
+            local config = req.config
+            local is_read = req.is_read
+            local req_id = req.request_id
+
+            local result
+            if is_read then
+                result = instance:read_internal(config)
+            else
+                result = instance:write_internal(config)
+            end
+
+            sys.publish("exmodbus/resp/" .. req_id, result)
+        else
+            sys.waitUntil("start_scheduler")
+        end
+    end
+end
+
+-- 启动调度器;
+local function start_scheduler()
+    if scheduler_started then return end
+    scheduler_started = true
+    sys.taskInit(process_request_queue)
+end
+
+-- 入队请求并等待响应;(内部使用)
+function exmodbus.enqueue_request(instance, config, is_read)
+    -- 生成唯一请求 ID;
+    local req_id = gen_request_id()
+
+    -- 检查队列是否为空;
+    -- 如果为空,先入队,然后发布主题告知调度器开始处理;
+    -- 如果不为空,则直接入队,不用告知调度器;
+    if #request_queue == 0 then
+        -- 入队请求;
+        table.insert(request_queue, {
+            instance = instance,
+            config = config,
+            is_read = is_read,
+            request_id = req_id
+        })
+
+        sys.publish("start_scheduler")
+    else
+        -- 入队请求;
+        table.insert(request_queue, {
+            instance = instance,
+            config = config,
+            is_read = is_read,
+            request_id = req_id
+        })
+    end
+
+    -- 启动调度器;
+    start_scheduler()
+    local ok, result = sys.waitUntil("exmodbus/resp/" .. req_id)
+
+    return result
+end
+
+--[[
+创建一个新的实例;
+@api exmodbus.create(config)
+@param config table 配置参数表,包含以下字段:
+    mode number 通信模式,必须是 exmodbus 模块定义的常量(如 exmodbus.RTU_MASTER)
+    uart_id number 串口 ID,uart0 写 0,uart1 写 1,以此类推
+    baud_rate number 波特率
+    data_bits number 数据位
+    stop_bits number 停止位
+    parity_bits number 校验位
+    byte_order number 字节顺序
+    rs485_dir_gpio number RS485 方向转换 GPIO 引脚
+    rs485_dir_rx_level number RS485 接收方向电平
+    adapter number 网卡 ID
+    ip_address string 服务器 IP 地址
+    port number 服务器端口号
+    is_udp boolean 是否使用 UDP 协议
+    is_tls boolean 是否使用加密传输
+    keep_idle number 连接空闲多长时间后,开始发送第一个 keepalive 探针报文,单位:秒
+    keep_interval number 发送第一个探针后,如果没收到 ACK 回复,间隔多久再发送下一个探针,单位:秒
+    keep_cnt number 总共发送多少次探针后,如果依然没有回复,则判断连接已断开
+    server_cert string TCP 模式下的服务器 CA 证书数据,UDP 模式下的 PSK
+    client_cert string TCP 模式下的客户端证书数据,UDP 模式下的 PSK-ID
+    client_key string TCP 模式下的客户端私钥加密数据
+    client_password string TCP 模式下的客户端私钥口令数据
+@return table/nil 成功时返回实例对象,失败时返回 nil
+@usage
+RTU/ASCII 通信模式:
+local config = {
+    mode = exmodbus.RTU_MASTER, -- 通信模式:RTU 主站
+    uart_id = 1,                -- 串口 ID:uart1
+    baud_rate = 115200,         -- 波特率:115200
+    data_bits = 8,              -- 数据位:8
+    stop_bits = 1,              -- 停止位:1
+    parity_bits = uart.None,    -- 校验位:无校验
+    byte_order = uart.LSB,      -- 字节顺序:小端序
+    rs485_dir_gpio = 23,        -- RS485 方向转换 GPIO 引脚
+    rs485_dir_rx_level = 0      -- RS485 接收方向电平:0 为低电平,1 为高电平
+}
+local rtu_master = exmodbus.create(config)
+
+TCP 通信模式:
+local config = {
+    mode = exmodbus.TCP_MASTER, -- 通信模式:TCP 主站
+    adapter = socket.LWIP_ETH,  -- 网卡 ID:LwIP 协议栈的以太网卡
+    ip_address = "192.168.1.100", -- 服务器 IP 地址:192.168.1.100(主站:服务器 IP;从站:本地 IP,从站可以不用填此参数)
+    port = 502,                 -- 服务器端口号:502(主站:服务器端口;从站:本地端口)
+    is_udp = false,             -- 是否使用 UDP 协议:不使用 UDP 协议,false/nil 表示使用 TCP 协议
+    is_tls = false,             -- 是否使用加密传输:不使用加密传输,false/nil 表示不使用加密
+    keep_idle = 300,            -- 连接空闲多长时间后,开始发送第一个 keepalive 探针报文:300 秒
+    keep_interval = 10,         -- 发送第一个探针后,如果没收到 ACK 回复,间隔多久再发送下一个探针:10 秒
+    keep_cnt = 3,               -- 总共发送多少次探针后,如果依然没有回复,则判断连接已断开:3 次
+    server_cert = nil,          -- TCP 模式下的服务器 CA 证书数据,UDP 模式下的 PSK:如果客户端不需要验证服务器证书,则设为 nil 或空着
+    client_cert = nil,          -- TCP 模式下的客户端证书数据,UDP 模式下的 PSK-ID:如果服务器不需要验证客户端证书,则设为 nil 或空着
+    client_key = nil,           -- TCP 模式下的客户端私钥加密数据:如果服务器不需要验证客户端私钥,则设为 nil 或空着
+    client_password = nil       -- TCP 模式下的客户端私钥口令数据:如果服务器不需要验证客户端私钥口令,则设为 nil 或空着
+}
+local tcp_master = exmodbus.create(config)
+--]]
+function exmodbus.create(config)
+    -- 检查配置参数是否有效;
+    if not config or type(config) ~= "table" then
+        log.error("exmodbus", "配置必须是表格类型")
+        return false
+    end
+
+    -- 根据通信模式加载对应的模块;
+    if config.mode == exmodbus.RTU_MASTER or config.mode == exmodbus.RTU_SLAVE or
+        config.mode == exmodbus.ASCII_MASTER or config.mode == exmodbus.ASCII_SLAVE then
+        local result, mod = pcall(require, "exmodbus_rtu_ascii")
+        if not result then
+            log.error("exmodbus", "加载 RTU/ASCII 模块失败")
+            return false
+        end
+        return mod.create(config, exmodbus, gen_request_id)
+    elseif config.mode == exmodbus.TCP_MASTER or config.mode == exmodbus.TCP_SLAVE then
+        local result, mod = pcall(require, "exmodbus_tcp")
+        if not result then
+            log.error("exmodbus", "加载 TCP 模块失败")
+            return false
+        end
+        return mod.create(config, exmodbus, gen_request_id)
+    else
+        log.error("exmodbus", "通信模式不支持")
+        return false
+    end
+end
+
+--[[
+主站向从站发送读取请求(仅适用于 RTU、ASCII、TCP 主站模式)
+@api modbus:read(config)
+@param config table 配置参数表,包含以下字段:
+    slave_id number 从站 ID
+    reg_type number 寄存器类型
+    start_addr number 寄存器起始地址
+    reg_count number 寄存器数量
+    raw_request string 原始请求帧
+    timeout number 超时时间,单位:毫秒
+@return table 包含以下字段:
+    status number 响应结果状态码,参考 exmodbus 模块定义的常量(如 exmodbus.STATUS_SUCCESS)
+    execption_code number 异常码,仅在 status 为 exmodbus.STATUS_EXCEPTION 时有效
+    data table 寄存器数值,仅在 status 为 exmodbus.STATUS_SUCCESS 时有效,包含以下字段
+        [start_addr] number 寄存器数值,索引为寄存器地址,值为寄存器数值
+        ...
+    raw_response string 原始响应帧
+@usage
+用户在传入 config 参数时,有 原始帧 和 字段参数 两种方式
+1. 原始帧方式
+local read_config = {
+    raw_request = "010300000002C40B", -- 原始请求帧:01 03 00 00 00 02 C4 0B(读取保持寄存器 0x0000 开始的 2 个寄存器)
+    timeout = 1000                    -- 超时时间:1000 毫秒
+}
+local result = modbus:read(read_config)
+if result.status == exmodbus.STATUS_SUCCESS then
+    log.info("exmodbus_test", "读取成功,原始响应帧: ", table.concat(result.raw_response, ", "))
+elseif result.status == exmodbus.STATUS_TIMEOUT then
+    log.error("exmodbus_test", "读取请求超时")
+else
+    log.error("exmodbus_test", "读取失败")
+end
+
+2. 字段参数方式
+local read_config = {
+    slave_id = 1,                         -- 从站 ID:1
+    reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
+    start_addr = 0x0000,                  -- 寄存器起始地址:0
+    reg_count = 0x0002,                   -- 寄存器数量:2
+    timeout = 1000                        -- 超时时间:1000 毫秒
+}
+
+local result = modbus:read(read_config)
+-- 根据返回状态处理结果
+if result.status == exmodbus.STATUS_SUCCESS then
+    -- 数据解析:
+    log.info("exmodbus_test", "成功读取到从站 1 保持寄存器 0-2 的值,寄存器 0 数值:", result.data[result.start_addr],
+        ",寄存器 1 数值:", result.data[result.start_addr + 1])
+elseif result.status == exmodbus.STATUS_DATA_INVALID then
+    log.info("exmodbus_test", "收到从站 1 的响应数据但数据损坏/校验失败")
+elseif result.status == exmodbus.STATUS_EXCEPTION then
+    log.info("exmodbus_test", "收到从站 1 的 modbus 标准异常响应,异常码为", result.execption_code)
+elseif result.status == exmodbus.STATUS_TIMEOUT then
+    log.info("exmodbus_test", "未收到从站 1 的响应(超时)")
+end
+--]]
+-- 该接口在各个子文件中,此处仅用作注释
+-- function modbus:read(config) end
+
+
+--[[
+主站向从站发送写入请求(仅适用于 RTU、ASCII、TCP 主站模式)
+@api modbus:write(config)
+@param config table 配置参数表,包含以下字段:
+    slave_id number 从站 ID
+    reg_type number 寄存器类型
+    start_addr number 寄存器起始地址
+    reg_count number 寄存器数量
+    data table 寄存器数值,包含以下字段:
+        [start_addr] number 寄存器数值,索引为寄存器地址,值为寄存器数值
+        ...
+    force_multiple boolean 是否强制使用写多个功能码进行写入单个寄存器操作
+    raw_request string 原始请求帧
+    timeout number 超时时间,单位:毫秒
+@return table 包含以下字段:
+    status number 响应结果状态码,参考 exmodbus 模块定义的常量(如 exmodbus.STATUS_SUCCESS)
+    execption_code number 异常码,仅在 status 为 exmodbus.STATUS_EXCEPTION 时有效
+    raw_response string 原始响应帧
+@usage
+用户在传入 config 参数时,有 原始帧 和 字段参数 两种方式
+1. 原始帧方式
+local write_config = {
+    raw_request = "011000000002007B01592471", -- 原始请求帧:01 10 00 00 00 02 00 7B 01 59 24 71(写入保持寄存器 0x0000 开始的 2 个寄存器,值为 0x007B 和 0x0159)
+    timeout = 1000                            -- 超时时间:1000 毫秒
+}
+local result = modbus:write(write_config)
+if result.status == exmodbus.STATUS_SUCCESS then
+    log.info("exmodbus_test", "写入成功,原始响应帧: ", table.concat(result.raw_response, ", "))
+elseif result.status == exmodbus.STATUS_TIMEOUT then
+    log.error("exmodbus_test", "写入请求超时")
+else
+    log.error("exmodbus_test", "写入失败")
+end
+
+2. 字段参数方式
+local write_config = {
+    slave_id = 1,                         -- 从站 ID:1
+    reg_type = exmodbus.HOLDING_REGISTER, -- 寄存器类型:保持寄存器
+    start_addr = 0x0000,                  -- 寄存器起始地址:0
+    reg_count = 0x0002,                   -- 寄存器数量:2
+    data = {
+        [0x0000] = 0x007B,                -- 寄存器 0 数值:0x007B
+        [0x0001] = 0x0159,                -- 寄存器 1 数值:0x0159
+    },
+    timeout = 1000                        -- 超时时间:1000 毫秒
+}
+
+local result = modbus:write(write_config)
+-- 根据返回状态处理结果
+if result.status == exmodbus.STATUS_SUCCESS then
+    log.info("exmodbus_test", "成功写入从站 1 保持寄存器 0-2 的值")
+elseif result.status == exmodbus.STATUS_DATA_INVALID then
+    log.info("exmodbus_test", "收到从站 1 的响应数据但数据损坏/校验失败")
+elseif result.status == exmodbus.STATUS_EXCEPTION then
+    log.info("exmodbus_test", "收到从站 1 的 modbus 标准异常响应,异常码为", result.execption_code)
+elseif result.status == exmodbus.STATUS_TIMEOUT then
+    log.info("exmodbus_test", "未收到从站 1 的响应(超时)")
+end
+--]]
+-- 该接口在各个子文件中,此处仅用作注释
+-- function modbus:write(config) end
+
+
+--[[
+销毁 modbus 主站/从站实例对象
+@api modbus:destroy()
+@return nil
+@usage
+modbus:destroy()
+--]]
+-- 该接口在各个子文件中,此处仅用作注释
+-- function modbus:destroy() end
+
+
+--[[
+从站注册回调接口,用于处理主站发起的请求(仅适用于 RTU、ASCII、TCP 从站模式)
+@api modbus:on(callback)
+@param callback function 回调函数,格式为:
+    function callback(request)
+        -- 用户代码
+    end
+    该回调函数接收 requset 一个参数,该参数为 table 类型,包含以下字段:
+        slave_id number 从站 ID
+        func_code number 功能码
+        reg_type number 寄存器类型
+        start_addr number 寄存器起始地址
+        reg_count number 寄存器数量
+        data table 寄存器数值,包含以下字段:
+            [start_addr] number 寄存器数值,索引为寄存器地址,值为寄存器数值
+            ...
+@return nil
+@usage
+function callback(request)
+    -- 用户处理代码
+end
+--]]
+-- 该接口在各个子文件中,此处仅用作注释
+-- modbus:on(callback)
+
+return exmodbus

+ 1071 - 0
script/libs/exmodbus_rtu_ascii.lua

@@ -0,0 +1,1071 @@
+-- 定义类结构;
+local modbus = {}                            -- 定义 modbus 实例的元表;
+modbus.__index = modbus                      -- 定义 modbus 实例的索引元方法,用于访问实例的属性;
+modbus.__metatable = "instance is protected" -- 定义 modbus 实例的元表,防止外部修改;
+
+-- 模块级变量:依赖注入的引用;
+local exmodbus_ref -- 主模块引用,用于访问enqueue_request等核心功能;
+local gen_id_func  -- ID生成函数引用,用于生成唯一请求ID;
+
+-- 创建 modbus 实例的构造函数;
+function modbus:new(config)
+    local obj = {
+        mode = config.mode,                             -- 通信模式
+        uart_id = config.uart_id,                       -- 串口 ID
+        baud_rate = config.baud_rate,                   -- 波特率
+        data_bits = config.data_bits,                   -- 数据位
+        stop_bits = config.stop_bits,                   -- 停止位
+        parity_bits = config.parity_bits,               -- 校验位
+        byte_order = config.byte_order,                 -- 字节序
+        rs485_dir_gpio = config.rs485_dir_gpio,         -- RS485 方向控制 GPIO 引脚
+        rs485_dir_rx_level = config.rs485_dir_rx_level, -- RS485 方向控制接收电平
+    }
+
+    -- 串口是否已初始化;
+    obj.uart_initialized = false
+    -- 当前等待的主题;
+    obj.current_wait_request_id = nil
+    -- 从站请求处理回调函数;
+    obj.slaveHandler = nil
+
+    -- 设置原表;
+    setmetatable(obj, modbus)
+    -- 返回实例;
+    return obj
+end
+
+-- 解析 Modbus RTU 请求帧(从站使用);
+local function parse_rtu_request(frame)
+    -- 校验请求帧长度是否至少为 4 字节(包含从站地址、功能码和 CRC);
+    local frame_len = #frame
+    if frame_len < 4 then
+        return nil
+    end
+
+    -- 仅校验 CRC(格式基础校验);
+    local calc_crc = crypto.crc16_modbus(frame:sub(1, -3))
+    local recv_crc = string.byte(frame, -2) + bit.lshift(string.byte(frame, -1), 8)
+    if calc_crc ~= recv_crc then
+        -- log.warn("exmodbus", "请求帧 CRC 校验失败")
+        return nil
+    end
+
+    -- 提取从站地址和功能码;
+    local slave_id = string.byte(frame, 1)
+    local func_code = string.byte(frame, 2)
+
+    -- 所有字段尽可能提取,即使值可能非法;
+    local request_data = {
+        slave_id = slave_id,
+        func_code = func_code,
+        reg_type = nil,
+        start_addr = nil,
+        reg_count = nil,
+        data = {},
+    }
+
+    -- 读请求和单写请求;
+    -- 校验请求帧长度是否为 8 字节(包含从站地址、功能码、起始地址、寄存器数量/寄存器值和 CRC);
+    if frame_len == 8 then
+        request_data.start_addr = bit.lshift(string.byte(frame, 3), 8) + string.byte(frame, 4)
+        request_data.reg_count = bit.lshift(string.byte(frame, 5), 8) + string.byte(frame, 6)
+
+        -- 写单个线圈;
+        if func_code == exmodbus_ref.WRITE_SINGLE_COIL then
+            local coil_val = bit.lshift(string.byte(frame, 5), 8) + string.byte(frame, 6)
+            request_data.reg_count = 1
+            request_data.data[request_data.start_addr] = (coil_val == 0xFF00) and 1 or 0
+        -- 写单个保持寄存器;
+        elseif func_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
+            local reg_val = bit.lshift(string.byte(frame, 5), 8) + string.byte(frame, 6)
+            request_data.reg_count = 1
+            request_data.data[request_data.start_addr] = reg_val
+        end
+    -- 多写请求;
+    -- 校验请求帧长度是否至少为 9 字节(包含从站地址、功能码、起始地址、寄存器数量、字节数量、数据和 CRC);
+    elseif frame_len >= 9 then
+        request_data.start_addr = bit.lshift(string.byte(frame, 3), 8) + string.byte(frame, 4)
+        request_data.reg_count = bit.lshift(string.byte(frame, 5), 8) + string.byte(frame, 6)
+
+        -- 写多个线圈;
+        if func_code == exmodbus_ref.WRITE_MULTIPLE_COILS then
+            for i = 0, request_data.reg_count - 1 do
+                local byte_idx = 8 + math.floor(i / 8)
+                if byte_idx > frame_len - 2 then break end -- 防止越界;
+                local bit_idx = i % 8
+                local byte_val = string.byte(frame, byte_idx)
+                local bit_val = bit.band(byte_val, bit.lshift(1, bit_idx)) ~= 0 and 1 or 0
+                request_data.data[request_data.start_addr + i] = bit_val
+            end
+        -- 写多个保持寄存器;
+        elseif func_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
+            for i = 0, request_data.reg_count - 1 do
+                local pos = 8 + i * 2
+                if pos + 1 > frame_len - 2 then break end -- 防止越界;
+                local val = bit.lshift(string.byte(frame, pos), 8) + string.byte(frame, pos + 1)
+                request_data.data[request_data.start_addr + i] = val
+            end
+        end
+    end
+
+    -- 对于读请求,data 为 nil,由用户处理读逻辑;
+    if not request_data.data and (
+        func_code == exmodbus_ref.READ_COILS or
+        func_code == exmodbus_ref.READ_DISCRETE_INPUTS or
+        func_code == exmodbus_ref.READ_HOLDING_REGISTERS or
+        func_code == exmodbus_ref.READ_INPUT_REGISTERS
+    ) then
+        request_data.data = nil -- request_data.data 保持 nil,由用户处理读逻辑;
+    end
+
+    return request_data
+end
+
+-- 构建 Modbus RTU 响应帧(从站使用);
+local function build_rtu_response(request, user_return)
+    local slave_id = request.slave_id
+    local func_code = request.func_code
+
+    -- 用户返回异常码 -> 异常响应;
+    if type(user_return) == "number" then
+        local exception_code = user_return
+        local frame = string.char(slave_id, bit.bor(func_code, 0x80), exception_code)
+        local crc = crypto.crc16_modbus(frame)
+        return frame .. string.char(crc & 0xFF, (crc >> 8) & 0xFF)
+    end
+
+    -- 用户返回表 -> 正常响应;
+    if type(user_return) ~= "table" then
+        log.error("exmodbus", "从站回调必须返回 table 或 number,实际类型: ", type(user_return))
+        return nil
+    end
+
+    local data_bytes = ""
+
+    -- 处理读线圈和读离散输入响应;
+    if func_code == exmodbus_ref.READ_COILS or func_code == exmodbus_ref.READ_DISCRETE_INPUTS then
+        local reg_count = request.reg_count
+
+        -- 校验 reg_count 是否有效;
+        if not reg_count or reg_count <= 0 then
+            log.error("exmodbus", "请求中 reg_count 无效")
+            return nil
+        end
+
+        local byte_count = math.ceil(reg_count / 8)
+        local values = {}
+
+        for i = 0, reg_count - 1 do
+            local addr = request.start_addr + i
+            local bit_val = user_return[addr]
+            if bit_val == nil then
+                log.error("exmodbus", "读线圈/离散输入回调未返回地址 ", addr, " 的数据")
+                return nil
+            end
+            if bit_val ~= 0 and bit_val ~= 1 then
+                log.error("exmodbus", "地址 ", addr, " 的值必须为 0 或 1,实际: ", bit_val)
+                return nil
+            end
+
+            local byte_idx = math.floor(i / 8)
+            if not values[byte_idx] then values[byte_idx] = 0 end
+            if bit_val == 1 then
+                values[byte_idx] = bit.bor(values[byte_idx], bit.lshift(1, i % 8))
+            end
+        end
+
+        data_bytes = string.char(byte_count)
+        for i = 0, byte_count - 1 do
+            data_bytes = data_bytes .. string.char(values[i] or 0)
+        end
+    -- 处理读保持寄存器和读输入寄存器响应;
+    elseif func_code == exmodbus_ref.READ_HOLDING_REGISTERS or func_code == exmodbus_ref.READ_INPUT_REGISTERS then
+        local reg_count = request.reg_count
+        -- 校验 reg_count 是否有效;
+        if not reg_count or reg_count <= 0 then
+            log.error("exmodbus", "请求中 reg_count 无效")
+            return nil
+        end
+
+        local values = ""
+        for i = 0, reg_count - 1 do
+            local addr = request.start_addr + i
+            local val = user_return[addr]
+            if val == nil then
+                log.error("exmodbus", "读保持寄存器/输入寄存器回调未返回地址 ", addr, " 的数据")
+                return nil
+            end
+            if type(val) ~= "number" or val ~= math.floor(val) or val < 0 or val > 65535 then
+                log.error("exmodbus", "地址 ", addr, " 的值必须为 0~65535 的整数,实际: ", val)
+                return nil
+            end
+            values = values .. string.char((val >> 8) & 0xFF, val & 0xFF)
+        end
+        data_bytes = string.char(#values) .. values
+    -- 处理写单个线圈响应;
+    elseif func_code == exmodbus_ref.WRITE_SINGLE_COIL then
+        local addr = request.start_addr
+        -- 校验 start_addr 是否有效;
+        if addr == nil then
+            log.error("exmodbus", "请求中 start_addr 无效")
+            return nil
+        end
+        local coil_val = (request.data and request.data[addr]) or 0
+        local resp_val = (coil_val ~= 0) and 0xFF00 or 0x0000
+        data_bytes = string.char(
+            (addr >> 8) & 0xFF, addr & 0xFF,
+            (resp_val >> 8) & 0xFF, resp_val & 0xFF
+        )
+    -- 处理写单个保持寄存器响应;
+    elseif func_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
+        local addr = request.start_addr
+        -- 校验 start_addr 是否有效;
+        if addr == nil then
+            log.error("exmodbus", "请求中 start_addr 无效")
+            return nil
+        end
+        local reg_val = (request.data and request.data[addr]) or 0
+        -- 校验 reg_val 是否有效;
+        if type(reg_val) ~= "number" or reg_val ~= math.floor(reg_val) or reg_val < 0 or reg_val > 65535 then
+            log.error("exmodbus", "地址 ", addr, " 的值必须为 0~65535 的整数,实际: ", reg_val)
+            return nil
+        end
+        data_bytes = string.char(
+            (addr >> 8) & 0xFF, addr & 0xFF,
+            (reg_val >> 8) & 0xFF, reg_val & 0xFF
+        )
+    -- 处理写多个线圈/保持寄存器响应;
+    elseif func_code == exmodbus_ref.WRITE_MULTIPLE_COILS or func_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
+        local start_addr = request.start_addr
+        local reg_count = request.reg_count
+        -- 校验 start_addr 和 reg_count 是否有效;
+        if not start_addr or not reg_count or reg_count <= 0 then
+            log.error("exmodbus", "请求中 start_addr 或 reg_count 无效")
+            return nil
+        end
+        data_bytes = string.char(
+            (start_addr >> 8) & 0xFF, start_addr & 0xFF,
+            (reg_count >> 8) & 0xFF, reg_count & 0xFF
+        )
+    -- 处理未知功能码,视为错误;
+    else
+        log.error("exmodbus", "不支持的功能码,且未返回异常码: ", func_code)
+        return nil
+    end
+
+    local frame = string.char(slave_id, func_code) .. data_bytes
+    local crc = crypto.crc16_modbus(frame)
+    return frame .. string.char(crc & 0xFF, (crc >> 8) & 0xFF)
+end
+
+-- 初始化串口;
+local function init_uart(instance)
+    -- 检查串口是否已被初始化;
+    if instance.uart_initialized then
+        log.warn("exmodbus", "串口 ", instance.uart_id, " 已经初始化,无需重复初始化")
+        return true
+    end
+
+    -- 配置串口参数,并开启串口功能;
+    local result = uart.setup(
+        instance.uart_id,           -- 串口 ID
+        instance.baud_rate,         -- 波特率
+        instance.data_bits,         -- 数据位
+        instance.stop_bits,         -- 停止位
+        instance.parity_bits,       -- 校验位
+        instance.byte_order,        -- 字节序
+        nil,                        -- 缓冲区大小
+        instance.rs485_dir_gpio,    -- RS485 方向控制 GPIO 引脚
+        instance.rs485_dir_rx_level -- RS485 方向控制接收电平
+    )
+    -- 检查串口是否初始化成功;
+    -- 成功时返回 0,其他返回值表示失败;
+    if result ~= 0 then
+        log.error("exmodbus", "串口 ", instance.uart_id, " 初始化失败")
+        return false
+    end
+
+    -- 定义发送完成回调函数;
+    -- 当串口发送完成时,发布一个主题,通知其他任务;
+    local function on_sent(uart_id)
+        sys.publish("exmodbus/sent/" .. uart_id, true)
+    end
+
+    -- 定义接收完成回调函数;
+    -- 当串口接收完成时,对接收数据进行处理;
+    -- 处理成功时,发布一个主题,通知其他任务;
+    -- 处理失败时,不做任何处理;
+    local function on_receive(uart_id, data_len)
+        local data = uart.read(uart_id, data_len)
+        if not data or #data == 0 then return end
+
+        -- 处理 RTU 主站模式下的接收数据;
+        if instance.mode == exmodbus_ref.RTU_MASTER then
+            -- 校验等待主题是否存在;
+            if instance.current_wait_request_id then
+                -- 发布主题,通知其他任务;
+                sys.publish("exmodbus/rtu_resp/" .. instance.current_wait_request_id, data)
+                -- 发布后,清除等待主题;
+                instance.current_wait_request_id = nil
+                return
+            end
+        -- 处理 RTU 从站模式下的接收数据;
+        elseif instance.mode == exmodbus_ref.RTU_SLAVE then
+            -- 解析 RTU 请求帧;
+            local request = parse_rtu_request(data)
+            if request then
+                -- 广播地址(0)不响应;
+                if request.slave_id == 0 then
+                    -- 调用回调以允许用户记录或处理广播命令(如写寄存器);
+                    if instance.slaveHandler then
+                        instance.slaveHandler(request)
+                        -- 注意:即使回调返回数据,也不发送响应;
+                    end
+                    -- 广播请求处理完毕,不回复;
+                    return
+                end
+                if instance.slaveHandler then
+                    local user_return = instance.slaveHandler(request)
+                    local response_frame = build_rtu_response(request, user_return)
+                    if response_frame then
+                        uart.write(uart_id, response_frame)
+                    else
+                        log.error("exmodbus", "构建响应帧失败,从站地址:", request.slave_id)
+                    end
+                else
+                    log.warn("exmodbus", "收到主站请求,但未注册回调函数")
+                end
+            else
+                log.debug("exmodbus", "无效 RTU 请求帧(CRC 或格式错误)")
+            end
+            return
+        end
+    end
+
+    -- 注册发送完成和接收完成回调函数;
+    uart.on(instance.uart_id, "sent", on_sent)
+    uart.on(instance.uart_id, "receive", on_receive)
+
+    -- 初始化成功,设置标志位为 true;
+    instance.uart_initialized = true
+    log.info("exmodbus", "串口 " .. instance.uart_id .. " 初始化成功,波特率 " .. instance.baud_rate)
+    return true
+end
+
+-- 创建一个新的实例;
+local function create(config, exmodbus, gen_request_id)
+    exmodbus_ref = exmodbus
+    gen_id_func = gen_request_id
+
+    -- 创建一个新的实例;
+    local instance = modbus:new(config)
+    -- 检查实例是否创建成功;
+    if not instance then
+        log.error("exmodbus", "创建 Modbus 实例失败")
+        return false
+    end
+
+    -- 初始化串口;
+    local result = init_uart(instance)
+    -- 检查串口初始化结果;
+    if not result then
+        -- 销毁已创建的实例,释放资源;
+        instance:destroy()
+        return false
+    end
+
+    -- 返回实例;
+    return instance
+end
+
+-- 销毁已创建的实例,释放资源;
+function modbus:destroy()
+    -- 检查实例是否已被销毁;
+    if not self then
+        log.error("exmodbus", "实例对象已被销毁,无需重复销毁")
+        return
+    end
+
+    -- 关闭串口;
+    if self.uart_initialized then
+        uart.close(self.uart_id)
+        uart.on(self.uart_id, "sent", nil)
+        uart.on(self.uart_id, "receive", nil)
+    end
+
+    -- 释放GPIO资源;
+    if self.rs485_dir_gpio then
+        gpio.close(self.rs485_dir_gpio)
+    end
+
+    -- 销毁已创建的实例;
+    setmetatable(self, nil)
+
+    log.info("exmodbus", "实例对象已销毁")
+end
+
+-- 构建 Modbus RTU 帧的函数,支持读取和写入操作;(主站使用)
+local function build_rtu_frame(opt_type, config)
+    -- 参数验证;
+    if not config or type(config) ~= "table" then
+        log.error("exmodbus", "配置必须是表格类型")
+        return false
+    end
+
+    -- 验证必要参数;
+    if not config.slave_id then
+        log.error("exmodbus", "缺少必要参数: slave_id")
+        return false
+    end
+
+    if not config.reg_type then
+        log.error("exmodbus", "缺少必要参数: reg_type")
+        return false
+    end
+
+    if not config.start_addr then
+        log.error("exmodbus", "缺少必要参数: start_addr")
+        return false
+    end
+
+    if not config.reg_count then
+        log.error("exmodbus", "缺少必要参数: reg_count")
+        return false
+    end
+
+    if opt_type == "write" then
+        if not config.data then
+            log.error("exmodbus", "缺少写入请求必要参数: data")
+            return false
+        end
+    end
+
+    -- 参数范围验证;
+    if type(config.slave_id) ~= "number" or config.slave_id < 1 or config.slave_id > 247 then
+        log.error("exmodbus", "从站地址必须在 1-247 范围内")
+        return false
+    end
+
+    if type(config.start_addr) ~= "number" or config.start_addr < 0 or config.start_addr > 65535 then
+        log.error("exmodbus", "起始地址必须在 0-65535 范围内")
+        return false
+    end
+
+    if config.reg_type ~= exmodbus_ref.COIL_STATUS and config.reg_type ~= exmodbus_ref.INPUT_STATUS and
+        config.reg_type ~= exmodbus_ref.HOLDING_REGISTER and config.reg_type ~= exmodbus_ref.INPUT_REGISTER then
+        log.error("exmodbus", "无效的寄存器类型: " .. tostring(config.reg_type))
+        return false
+    end
+
+    -- 根据操作类型和寄存器类型确定功能码;
+    local function_code
+    if opt_type == "write" then
+        -- 校验每一个地址是否有数据,且数据是否为数字类型;
+        for i = 0, config.reg_count - 1 do
+            local addr = config.start_addr + i
+            if config.data[addr] == nil then
+                log.error("exmodbus", "缺少寄存器数据", "address:", addr)
+                return false
+            end
+            if type(config.data[addr]) ~= "number" then
+                log.error("exmodbus", "寄存器数据必须是数字类型", "address:", addr)
+                return false
+            end
+        end
+
+        -- 判断是否强制使用写多个功能码;
+        local use_multiple = config.force_multiple
+
+        if config.reg_count == 1 then
+            -- 写入单个线圈或单个保持寄存器;
+            if not use_multiple then -- 使用写单个功能码;
+                if config.reg_type == exmodbus_ref.COIL_STATUS then
+                    function_code = exmodbus_ref.WRITE_SINGLE_COIL
+                elseif config.reg_type == exmodbus_ref.HOLDING_REGISTER then
+                    function_code = exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER
+                end
+            else -- 使用写多个功能码;
+                if config.reg_type == exmodbus_ref.COIL_STATUS then
+                    function_code = exmodbus_ref.WRITE_MULTIPLE_COILS
+                elseif config.reg_type == exmodbus_ref.HOLDING_REGISTER then
+                    function_code = exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS
+                end
+            end
+        elseif config.reg_count > 1 then
+            -- 写入多个线圈或寄存器;
+            if config.reg_type == exmodbus_ref.COIL_STATUS then
+                function_code = exmodbus_ref.WRITE_MULTIPLE_COILS
+            elseif config.reg_type == exmodbus_ref.HOLDING_REGISTER then
+                function_code = exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS
+            end
+        end
+    elseif opt_type == "read" then
+        -- 读线圈状态;
+        if config.reg_type == exmodbus_ref.COIL_STATUS then
+            function_code = exmodbus_ref.READ_COILS
+        -- 读离散输入状态;
+        elseif config.reg_type == exmodbus_ref.INPUT_STATUS then
+            function_code = exmodbus_ref.READ_DISCRETE_INPUTS
+        -- 读保持寄存器;
+        elseif config.reg_type == exmodbus_ref.HOLDING_REGISTER then
+            function_code = exmodbus_ref.READ_HOLDING_REGISTERS
+        -- 读输入寄存器;
+        elseif config.reg_type == exmodbus_ref.INPUT_REGISTER then
+            function_code = exmodbus_ref.READ_INPUT_REGISTERS
+        end
+    end
+
+    local data_bytes
+    -- 功能码 0x01 和 0x02:读取线圈状态和离散输入状态;
+    if function_code == exmodbus_ref.READ_COILS or function_code == exmodbus_ref.READ_DISCRETE_INPUTS then
+        -- 验证数量范围;
+        if config.reg_count < 1 or config.reg_count > 2000 then
+            log.error("exmodbus", "线圈/离散输入读取数量超出范围: " .. config.reg_count .. " (范围: 1-2000)")
+            return false
+        end
+        
+        -- 构建数据部分(起始地址 + 数量)(大端序);
+        data_bytes = string.char(
+            (config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF,
+            (config.reg_count >> 8) & 0xFF, config.reg_count & 0xFF
+        )
+
+    -- 功能码 0x03 和 0x04:读取保持寄存器和输入寄存器;
+    elseif function_code == exmodbus_ref.READ_HOLDING_REGISTERS or function_code == exmodbus_ref.READ_INPUT_REGISTERS then
+        -- 验证数量范围;
+        if config.reg_count < 1 or config.reg_count > 125 then
+            log.error("exmodbus", "寄存器读取数量超出范围: " .. config.reg_count .. " (范围: 1-125)")
+            return false
+        end
+
+        -- 构建数据部分(起始地址 + 数量)(字节序为大端序);
+        data_bytes = string.char(
+            (config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF,
+            (config.reg_count >> 8) & 0xFF, config.reg_count & 0xFF
+        )
+
+    -- 功能码 0x05:写入单个线圈;
+    elseif function_code == exmodbus_ref.WRITE_SINGLE_COIL then
+        -- 写入单个线圈,值必须是 0xFF00 (ON) 或 0x0000 (OFF);
+        local value = config.data[config.start_addr] ~= 0 and 0xFF00 or 0x0000
+        
+        -- 构建数据部分(起始地址 + 值)(字节序为大端序);
+        data_bytes = string.char(
+            (config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF,
+            (value >> 8) & 0xFF, value & 0xFF
+        )
+
+    -- 功能码 0x06:写入单个保持寄存器;
+    elseif function_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
+        -- 写入单个保持寄存器;
+        local value = config.data[config.start_addr]
+
+        -- 验证寄存器值范围(16 位无符号整数);
+        if value < 0 or value > 65535 or value ~= math.floor(value) then
+            log.error("exmodbus", "寄存器值必须是 0~65535 范围内的整数,实际值: ", value)
+            return false
+        end
+        
+        -- 构建数据部分(起始地址 + 值)(字节序为大端序);
+        data_bytes = string.char(
+            (config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF,
+            (value >> 8) & 0xFF, value & 0xFF
+        )
+
+    -- 功能码 0x0F:写入多个线圈;
+    elseif function_code == exmodbus_ref.WRITE_MULTIPLE_COILS then
+        -- 验证数量范围;
+        if config.reg_count < 1 or config.reg_count > 1968 then
+            log.error("exmodbus", "线圈写入数量超出范围: " .. config.reg_count .. " (范围: 1-1968)")
+            return false
+        end
+
+        -- 计算字节数;
+        local byte_count = math.ceil(config.reg_count / 8)
+        local values_bytes = ""
+
+        -- 构建线圈数据(字节序为大端序);
+        for i = 0, byte_count - 1 do
+            local byte_value = 0
+            -- 遍历当前字节的 8 个位;
+            for j = 0, 7 do
+                local bit_index = i * 8 + j -- 计算当前比特在整个线圈序列中的全局索引(从 0 开始);
+                -- 检查当前比特是否在有效范围内;
+                if bit_index < config.reg_count then
+                    local addr = config.start_addr + bit_index -- 根据起始地址和全局索引计算实际的线圈地址;
+                    local bit_val = config.data[addr] -- 获取当前线圈的状态值(0 或 1);
+                    if bit_val ~= nil and bit_val ~= 0 then
+                        byte_value = byte_value | (1 << j) -- 如果状态为 1,则将当前位设置为 1;
+                    end
+                end
+            end
+            values_bytes = values_bytes .. string.char(byte_value)
+        end
+
+        -- 构建数据部分(起始地址 + 数量 + 字节数 + 线圈数据)(字节序为大端序);
+        data_bytes = string.char(
+            (config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF,
+            (config.reg_count >> 8) & 0xFF, config.reg_count & 0xFF,
+            byte_count
+        ) .. values_bytes
+
+    -- 功能码 0x10:写入多个保持寄存器;
+    elseif function_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
+        -- 验证数量范围;
+        if config.reg_count < 1 or config.reg_count > 123 then
+            log.error("exmodbus", "寄存器写入数量超出范围: " .. config.reg_count .. " (范围: 1-123)")
+            return false
+        end
+
+        -- 计算字节数;
+        local byte_count = config.reg_count * 2
+        local values_bytes = ""
+
+        -- 构建寄存器数据(字节序为大端序);
+        for i = 0, config.reg_count - 1 do
+            local addr = config.start_addr + i
+            local value = config.data[addr]
+            values_bytes = values_bytes .. string.char(
+                (value >> 8) & 0xFF, value & 0xFF
+            )
+        end
+
+        -- 构建数据部分(起始地址 + 数量 + 字节数 + 寄存器数据)(字节序为大端序);
+        data_bytes = string.char(
+            (config.start_addr >> 8) & 0xFF, config.start_addr & 0xFF,
+            (config.reg_count >> 8) & 0xFF, config.reg_count & 0xFF,
+            byte_count
+        ) .. values_bytes
+
+    -- 未知功能码;
+    else
+        log.error("exmodbus", "不支持的功能码构建: " .. function_code)
+        return false
+    end
+
+    -- 构建 Modbus RTU 帧(从站地址 + 功能码 + 数据);
+    local frame = string.char(config.slave_id, function_code) .. data_bytes
+
+    -- 计算 CRC16 校验并添加到帧末尾(小端序);
+    local crc = crypto.crc16_modbus(frame)
+    frame = frame .. string.char(crc & 0xFF, (crc >> 8) & 0xFF)
+
+    return frame, function_code
+end
+
+-- 发送 Modbus 请求并等待响应;
+local function sendRequest_waitResponse(instance, request_frame, config)
+    -- 生成唯一请求ID;
+    local req_id = gen_id_func()
+    instance.current_wait_request_id = req_id
+
+    -- 执行发送请求;
+    uart.write(instance.uart_id, request_frame)
+
+    -- 等待发送完成;
+    local sent_ok = sys.waitUntil("exmodbus/sent/" .. instance.uart_id, 200)
+    if not sent_ok then
+        log.error("exmodbus", "数据发送失败")
+        instance.current_wait_request_id = nil
+        return false, nil
+    end
+
+    -- -- 显示发送的HEX数据;
+    -- local hex_str = ""
+    -- for i = 1, #request_frame do
+    --     hex_str = hex_str .. string.format("%02X ", string.byte(request_frame, i))
+    -- end
+    -- log.info("exmodbus", "发送请求命令成功, HEX: " .. hex_str:sub(1, -2))
+
+    -- 等待接收响应;
+    local ok, response = sys.waitUntil("exmodbus/rtu_resp/" .. req_id, config.timeout or 1000)
+
+    -- 清除当前等待的请求ID;
+    instance.current_wait_request_id = nil
+
+    -- 显示接收的HEX数据;
+    if ok then
+        -- hex_str = ""
+        -- for i = 1, #response do
+        --     hex_str = hex_str .. string.format("%02X ", string.byte(response, i))
+        -- end
+        -- log.info("exmodbus", "接收响应成功, HEX: " .. hex_str:sub(1, -2))
+        return true, response
+    else
+        -- log.error("exmodbus", "接收响应失败或超时")
+        return false, nil
+    end
+end
+
+-- 解析 Modbus RTU 响应报文(主站使用);
+local function parse_rtu_response(response, config, function_code)
+    -- 定义返回数据结构;
+    local return_data = {
+        status = false,
+        execption_code = nil,
+        data = {},
+    }
+
+    -- 验证响应是否为空;
+    if not response or #response == 0 then
+        log.error("exmodbus", "响应报文为空")
+        return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+        return return_data
+    end
+
+    -- 验证响应长度(最小长度:从站地址 + 功能码 + CRC = 4 字节);
+    if not response or #response < 4 then
+        log.error("exmodbus", "响应报文长度不足")
+        return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+        return return_data
+    end
+
+    -- 提取响应中的字段;
+    local actual_slave_id = string.byte(response, 1)
+    local actual_function_code = string.byte(response, 2)
+    local response_length = #response
+
+    -- 验证从站地址是否匹配;
+    if actual_slave_id ~= config.slave_id then
+        log.error("exmodbus", "从站地址不匹配,期望:", config.slave_id, "实际:", actual_slave_id)
+        return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+        return return_data
+    end
+
+    -- 检查是否为异常响应(功能码最高位为 1);
+    if bit.band(actual_function_code, 0x80) ~= 0 then
+        -- 异常响应格式:从站地址(1 字节) + 功能码(1 字节) + 异常码(1 字节) + CRC(2 字节);
+        if response_length ~= 5 then
+            log.error("exmodbus", "异常响应报文长度不正确,期望: 5 字节,实际:", response_length, "字节")
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 提取异常码(第 3 字节);
+        local exception_code = string.byte(response, 3)
+        log.error("exmodbus", "接收到 Modbus 异常响应,功能码:", actual_function_code, "异常码:", exception_code)
+
+        return_data.status = exmodbus_ref.STATUS_EXCEPTION
+        return_data.execption_code = exception_code
+        return return_data
+    end
+
+    -- 验证功能码是否匹配;
+    if actual_function_code ~= function_code then
+        log.error("exmodbus", "功能码不匹配,期望:", function_code, "实际:", actual_function_code)
+        return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+        return return_data
+    end
+
+    -- 根据不同的功能码解析数据;
+    local parsed_data = {}
+
+    -- 功能码 0x01 和 0x02:读取线圈状态和离散输入状态;
+    if function_code == exmodbus_ref.READ_COILS or function_code == exmodbus_ref.READ_DISCRETE_INPUTS then
+        -- 提取数据部分(不包括CRC);
+        local data_length = string.byte(response, 3)
+        local data_start_pos = 4
+        local data_end_pos = response_length - 2 -- 减去CRC长度
+
+        -- 验证数据长度是否正确;
+        -- 注意:这里只验证响应报文中声明的数据长度与实际数据长度是否一致;
+        if data_end_pos - data_start_pos + 1 ~= data_length then
+            log.error("exmodbus", "数据长度不匹配,期望:", data_length, "实际:", data_end_pos - data_start_pos + 1)
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 验证字节数是否足够表示指定数量的位;
+        local expected_bytes = math.ceil(config.reg_count / 8)
+        if data_length < expected_bytes then
+            log.error("exmodbus", "数据字节数不足,无法表示所有位")
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 解析位数据;
+        for i = 0, config.reg_count - 1 do
+            local modbus_addr = config.start_addr + i           -- 计算当前位对应的 Modbus 地址;
+            local byte_pos = data_start_pos + math.floor(i / 8) -- 计算当前位对应的字节位置;
+            local bit_pos = i % 8                               -- 计算当前位对应的位位置;
+            local byte_value = string.byte(response, byte_pos)
+            parsed_data[modbus_addr] = bit.band(byte_value, bit.lshift(1, bit_pos)) ~= 0 and 1 or 0
+        end
+
+    -- 功能码 0x03 和 0x04:读取保持寄存器和输入寄存器;
+    elseif function_code == exmodbus_ref.READ_HOLDING_REGISTERS or function_code == exmodbus_ref.READ_INPUT_REGISTERS then
+        -- 提取数据部分(不包括CRC);
+        local data_length = string.byte(response, 3)
+        local data_start_pos = 4
+        local data_end_pos = response_length - 2 -- 减去CRC长度
+
+        -- 验证数据长度是否正确;
+        if data_end_pos - data_start_pos + 1 ~= data_length then
+            log.error("exmodbus", "数据长度不匹配,期望:", data_length, "实际:", data_end_pos - data_start_pos + 1)
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 验证字节数是否足够表示指定数量的寄存器;
+        local expected_bytes = config.reg_count * 2
+        if data_length < expected_bytes then
+            log.error("exmodbus", "数据字节数不足,无法表示所有寄存器")
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 解析寄存器数据(大端序);
+        for i = 0, config.reg_count - 1 do
+            local modbus_addr = config.start_addr + i -- 计算当前寄存器对应的 Modbus 地址;
+            local reg_pos = data_start_pos + i * 2    -- 计算当前寄存器对应的字节位置;
+            parsed_data[modbus_addr] = bit.lshift(string.byte(response, reg_pos), 8) + string.byte(response, reg_pos + 1)
+        end
+
+    -- 功能码 0x05:写入单个线圈;
+    elseif function_code == exmodbus_ref.WRITE_SINGLE_COIL then
+        -- 写入单个线圈响应格式:从站地址(1 字节) + 功能码(1 字节) + 线圈地址(2 字节) + 线圈值(2 字节) + CRC(2 字节);
+        if response_length ~= 8 then
+            log.error("exmodbus", "写入单个线圈响应报文长度不正确")
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 解析线圈地址和值;
+        local coil_addr = bit.lshift(string.byte(response, 3), 8) + string.byte(response, 4)
+        local coil_value = bit.lshift(string.byte(response, 5), 8) + string.byte(response, 6)
+
+        -- 验证地址是否匹配请求;
+        if config.start_addr and coil_addr ~= config.start_addr then
+            log.error("exmodbus", "线圈地址不匹配,期望:", config.start_addr, "实际:", coil_addr)
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 线圈值应该是 0x0000(OFF) 或 0xFF00(ON);
+        local normalized_value = (coil_value == 0x0000) and 0 or 1
+        parsed_data[coil_addr] = normalized_value
+
+    -- 功能码 0x06:写入单个保持寄存器;
+    elseif function_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
+        -- 写入单个保持寄存器响应格式:从站地址(1 字节) + 功能码(1 字节) + 寄存器地址(2 字节) + 寄存器值(2 字节) + CRC(2 字节);
+        if response_length ~= 8 then
+            log.error("exmodbus", "写入单个保持寄存器响应报文长度不正确")
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 解析寄存器地址和值;
+        local reg_addr = bit.lshift(string.byte(response, 3), 8) + string.byte(response, 4)
+        local reg_value = bit.lshift(string.byte(response, 5), 8) + string.byte(response, 6)
+
+        -- 验证地址是否匹配请求;
+        if config.start_addr and reg_addr ~= config.start_addr then
+            log.error("exmodbus", "单个保持寄存器地址不匹配,期望:", config.start_addr, "实际:", reg_addr)
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        parsed_data[reg_addr] = reg_value
+
+    -- 功能码 0x0F:写入多个线圈;
+    elseif function_code == exmodbus_ref.WRITE_MULTIPLE_COILS then
+        -- 写入多个线圈响应格式:从站地址(1 字节) + 功能码(1 字节) + 起始地址(2 字节) + 线圈数量(2 字节) + CRC(2 字节);
+        if response_length ~= 8 then
+            log.error("exmodbus", "写入多个线圈响应报文长度不正确")
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 解析起始地址和线圈数量;
+        local start_addr = bit.lshift(string.byte(response, 3), 8) + string.byte(response, 4)
+        local coil_count = bit.lshift(string.byte(response, 5), 8) + string.byte(response, 6)
+
+        -- 验证地址和数量是否匹配请求;
+        if config.start_addr and start_addr ~= config.start_addr then
+            log.error("exmodbus", "线圈起始地址不匹配,期望:", config.start_addr, "实际:", start_addr)
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        if config.reg_count and coil_count ~= config.reg_count then
+            log.error("exmodbus", "线圈数量不匹配,期望:", config.reg_count, "实际:", coil_count)
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 在返回数据中记录操作成功的起始地址和数量;
+        parsed_data.start_addr = start_addr
+        parsed_data.count = coil_count
+
+    -- 功能码 0x10:写入多个保持寄存器;
+    elseif function_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
+        -- 写入多个保持寄存器响应格式:从站地址(1 字节) + 功能码(1 字节) + 起始地址(2 字节) + 寄存器数量(2 字节) + CRC(2 字节);
+        if response_length ~= 8 then
+            log.error("exmodbus", "写入多个保持寄存器响应报文长度不正确")
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 解析起始地址和寄存器数量;
+        local start_addr = bit.lshift(string.byte(response, 3), 8) + string.byte(response, 4)
+        local reg_count = bit.lshift(string.byte(response, 5), 8) + string.byte(response, 6)
+
+        -- 验证地址和数量是否匹配请求;
+        if config.start_addr and start_addr ~= config.start_addr then
+            log.error("exmodbus", "寄存器起始地址不匹配,期望:", config.start_addr, "实际:", start_addr)
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        if config.reg_count and reg_count ~= config.reg_count then
+            log.error("exmodbus", "寄存器数量不匹配,期望:", config.reg_count, "实际:", reg_count)
+            return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+            return return_data
+        end
+
+        -- 在返回数据中记录操作成功的起始地址和数量;
+        parsed_data.start_addr = start_addr
+        parsed_data.count = reg_count
+
+    -- 未知功能码;
+    else
+        log.error("exmodbus", "不支持的功能码解析:", function_code)
+        return_data.status = exmodbus_ref.STATUS_DATA_INVALID
+        return return_data
+    end
+
+    -- 成功解析响应数据;
+    -- log.info("exmodbus", "响应解析成功,功能码:", function_code, "数据:", json.encode(parsed_data))
+    return_data.status = exmodbus_ref.STATUS_SUCCESS
+    return_data.data = parsed_data
+    return return_data
+end
+
+-- 主站读取请求函数;(内部使用)
+function modbus:read_internal(config)
+    -- 处理响应结果;
+    local parsed_data = {}
+
+    -- 检查通信模式是否有效;
+    if self.mode == exmodbus_ref.RTU_MASTER then
+
+        -- 检查是否同时指定了 slave_id 和 raw_request;
+        if config.slave_id and config.raw_request then
+            log.error("exmodbus", "禁止同时指定 slave_id 和 raw_request")
+            return false
+        end
+
+        -- 用户传入字段式请求帧;
+        if config.slave_id then
+            -- 构建 Modbus RTU 帧;
+            local request_frame, function_code = build_rtu_frame("read", config)
+            if not request_frame then
+                parsed_data.status = exmodbus_ref.STATUS_DATA_INVALID
+                return parsed_data
+            end
+
+            -- 发送请求并等待响应;
+            local result, response = sendRequest_waitResponse(self, request_frame, config)
+            if not result then
+                parsed_data.status = exmodbus_ref.STATUS_TIMEOUT
+            else
+                -- 解析响应数据;
+                parsed_data = parse_rtu_response(response, config, function_code)
+            end
+
+        -- 用户传入原始请求帧;
+        elseif config.raw_request then
+            -- 发送请求并等待响应;
+            local result, response = sendRequest_waitResponse(self, config.raw_request, config)
+            if not result then
+                parsed_data.status = exmodbus_ref.STATUS_TIMEOUT
+            else
+                -- 直接返回响应结果和原始响应数据;
+                parsed_data.status = exmodbus_ref.STATUS_SUCCESS
+                parsed_data.raw_response = response
+            end
+        end
+
+        return parsed_data
+    else
+        log.error("exmodbus", "通信模式不支持")
+        return false
+    end
+end
+
+-- 主站写入请求的函数;
+function modbus:write_internal(config)
+
+    -- 处理响应结果;
+    local parsed_data = {}
+
+    -- 检查通信模式是否有效;
+    if self.mode == exmodbus_ref.RTU_MASTER then
+
+        -- 检查是否同时指定了 slave_id 和 raw_request;
+        if config.slave_id and config.raw_request then
+            log.error("exmodbus", "禁止同时指定 slave_id 和 raw_request")
+            return false
+        end
+
+        -- 用户传入字段式请求帧;
+        if config.slave_id then
+            -- 构建 Modbus RTU 帧;
+            local request_frame, function_code = build_rtu_frame("write", config)
+            if not request_frame then
+                parsed_data.status = exmodbus_ref.STATUS_DATA_INVALID
+                return parsed_data
+            end
+
+            -- 发送请求并等待响应;
+            local result, response = sendRequest_waitResponse(self, request_frame, config)
+            if not result then
+                -- log.error("exmodbus", "接收响应失败或超时")
+                parsed_data.status = exmodbus_ref.STATUS_TIMEOUT
+            else
+                -- 解析响应数据;
+                parsed_data = parse_rtu_response(response, config, function_code)
+            end
+
+        -- 用户传入原始请求帧;
+        elseif config.raw_request then
+            -- 发送请求并等待响应;
+            local result, response = sendRequest_waitResponse(self, config.raw_request, config)
+            if not result then
+                parsed_data.status = exmodbus_ref.STATUS_TIMEOUT
+            else
+                -- 直接返回响应结果和原始响应数据;
+                parsed_data.status = exmodbus_ref.STATUS_SUCCESS
+                parsed_data.raw_response = response
+            end
+        end
+
+        return parsed_data
+    else
+        log.error("exmodbus", "通信模式不支持")
+        return false
+    end
+end
+
+-- 主站读取请求的函数;
+function modbus:read(config)
+    return exmodbus_ref.enqueue_request(self, config, true)
+end
+
+-- 主站写入请求的函数;
+function modbus:write(config)
+    return exmodbus_ref.enqueue_request(self, config, false)
+end
+
+-- 注册从站请求处理回调函数;
+function modbus:on(callback)
+    if type(callback) ~= "function" then
+        log.error("exmodbus", "on(callback) 的参数必须是一个函数")
+        return false
+    end
+    self.slaveHandler = callback
+    log.info("exmodbus", "已注册从站请求处理回调函数")
+    return true
+end
+
+return { create = create }

+ 479 - 0
script/libs/exmodbus_tcp.lua

@@ -0,0 +1,479 @@
+-- 定义类结构;
+local modbus = {}                            -- 定义 modbus 实例的元表;
+modbus.__index = modbus                      -- 定义 modbus 实例的索引元方法,用于访问实例的属性;
+modbus.__metatable = "instance is protected" -- 定义 modbus 实例的元表,防止外部修改;
+
+-- 模块级变量:依赖注入的引用;
+local exmodbus_ref -- 主模块引用,用于访问enqueue_request等核心功能;
+local gen_id_func  -- ID生成函数引用,用于生成唯一请求ID;
+
+-- Modbus TCP 协议头长度
+local MODBUS_TCP_HEADER_LEN = 7
+
+local libnet = require "libnet"
+
+-- 创建 modbus 实例的构造函数;
+function modbus:new(config, TASK_NAME)
+    local obj = {
+        mode = config.mode,                       -- 通信模式
+        adapter = config.adapter,                 -- 网络适配器
+        ip_address = config.ip_address,           -- IP 地址
+        port = config.port,                       -- 端口号
+        is_udp = config.is_udp,                   -- 是否使用 UDP 协议
+        is_tls = config.is_tls,                   -- 是否使用 TLS 加密
+        keep_idle = config.keep_idle,             -- 连接空闲多长时间后,开始发送第一个 keepalive 探针报文(秒)
+        keep_interval = config.keep_interval,     -- 发送第一个探针后,如果没收到 ACK 回复,间隔多久再发送下一个探针(秒)
+        keep_cnt = config.keep_cnt,               -- 总共发送多少次探针后,如果依然没有回复,则判定连接已断开
+        server_cert = config.server_cert,         -- TCP模式下的服务器ca证书数据,UDP模式下的PSK
+        client_cert = config.client_cert,         -- TCP模式下的客户端证书数据,UDP模式下的PSK-ID
+        client_key = config.client_key,           -- TCP模式下的客户端私钥加密数据
+        client_password = config.client_password, -- TCP模式下的客户端私钥口令数据
+    }
+
+    -- 从站请求处理回调函数;
+    obj.slaveHandler = nil
+    -- 任务名称
+    obj.TASK_NAME = TASK_NAME
+    -- 接收数据缓冲区
+    obj.recv_buff = nil
+
+    -- 设置原表;
+    setmetatable(obj, modbus)
+    -- 返回实例;
+    return obj
+end
+
+-- 解析 TCP 请求帧(从站使用)
+local function parse_tcp_request(data)
+    -- 检查请求帧长度,至少包含 MBAP 头和功能码
+    if #data < MODBUS_TCP_HEADER_LEN + 1 then
+        log.error("exmodbus", "请求帧长度不足")
+        return nil, "请求帧长度不足"
+    end
+
+    -- 解析 MBAP 头(事务标识符(2)、协议标识符(2)、数据长度(2)、从站地址(1))
+    local transaction_id = string.unpack(">H", data, 1)
+    local protocol_id = string.unpack(">H", data, 3)
+    local length = string.unpack(">H", data, 5)
+    local slave_id = string.unpack("B", data, 7)
+
+    -- 检查数据长度是否与实际长度匹配
+    if #data ~= 6 + length then
+        log.error("exmodbus", "数据长度与实际长度不匹配")
+        return nil, "数据长度与实际长度不匹配"
+    end
+
+    -- 检查协议 ID(Modbus TCP 协议 ID 必须为 0)
+    if protocol_id ~= 0 then
+        log.error("exmodbus", "无效的协议 ID")
+        return nil, "无效的协议 ID"
+    end
+
+    -- 解析功能码
+    local func_code = string.unpack("B", data, 8)
+    local request = {
+        transaction_id = transaction_id,
+        protocol_id = protocol_id,
+        length = length,
+        slave_id = slave_id,
+        func_code = func_code,
+        reg_type = nil,
+        start_addr = nil,
+        reg_count = nil,
+        data = {},
+    }
+
+    -- 根据功能码解析请求内容
+    if func_code == exmodbus_ref.READ_COILS or func_code == exmodbus_ref.READ_DISCRETE_INPUTS then
+        -- 读线圈或离散输入
+        request.reg_type = func_code == exmodbus_ref.READ_COILS and exmodbus_ref.COIL_STATUS or exmodbus_ref.DISCRETE_INPUT_STATUS
+        request.start_addr = string.unpack(">H", data, 9)
+        request.reg_count = string.unpack(">H", data, 11)
+    elseif func_code == exmodbus_ref.READ_HOLDING_REGISTERS or func_code == exmodbus_ref.READ_INPUT_REGISTERS then
+        -- 读保持寄存器或输入寄存器
+        request.reg_type = func_code == exmodbus_ref.READ_HOLDING_REGISTERS and exmodbus_ref.HOLDING_REGISTER or exmodbus_ref.INPUT_REGISTER
+        request.start_addr = string.unpack(">H", data, 9)
+        request.reg_count = string.unpack(">H", data, 11)
+    elseif func_code == exmodbus_ref.WRITE_SINGLE_COIL then
+        -- 写单个线圈
+        request.reg_type = exmodbus_ref.COIL_STATUS
+        request.start_addr = string.unpack(">H", data, 9)
+        request.reg_count = 1
+        local value = string.unpack(">H", data, 11)
+        request.data = { [request.start_addr] = value == 0xFF00 and 1 or 0 }
+    elseif func_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
+        -- 写单个寄存器
+        request.reg_type = exmodbus_ref.HOLDING_REGISTER
+        request.start_addr = string.unpack(">H", data, 9)
+        request.reg_count = 1
+        local value = string.unpack(">H", data, 11)
+        request.data = { [request.start_addr] = value }
+    elseif func_code == exmodbus_ref.WRITE_MULTIPLE_COILS then
+        -- 写多个线圈
+        request.reg_type = exmodbus_ref.COIL_STATUS
+        request.start_addr = string.unpack(">H", data, 9)
+        request.reg_count = string.unpack(">H", data, 11)
+        -- local byte_count = string.unpack("B", data, 13)
+        request.data = {}
+        for i = 0, request.reg_count - 1 do
+            local byte_pos = 13 + 1 + math.floor(i / 8)
+            local bit_pos = i % 8
+            local byte_value = string.unpack("B", data, byte_pos)
+            local bit_value = bit.band(byte_value, bit.lshift(1, bit_pos)) > 0 and 1 or 0
+            request.data[request.start_addr + i] = bit_value
+        end
+    elseif func_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
+        -- 写多个寄存器
+        request.reg_type = exmodbus_ref.HOLDING_REGISTER
+        request.start_addr = string.unpack(">H", data, 9)
+        request.reg_count = string.unpack(">H", data, 11)
+        -- local byte_count = string.unpack("B", data, 13)
+        request.data = {}
+        for i = 0, request.reg_count - 1 do
+            local value = string.unpack(">H", data, 13 + 1 + i * 2)
+            request.data[request.start_addr + i] = value
+        end
+    else
+        log.error("exmodbus", "不支持的功能码:", func_code)
+    end
+
+    return request
+end
+
+-- 构建 Modbus TCP 响应帧(从站使用);
+local function build_tcp_response(request, user_return)
+    local slave_id = request.slave_id
+    local func_code = request.func_code
+
+    -- 用户返回异常码 -> 异常响应;
+    if type(user_return) == "number" then
+        local exception_code = user_return
+        local response_payload = string.char(slave_id, bit.bor(func_code, 0x80), exception_code)
+        
+        -- 构建完整的 TCP 响应帧
+        local length = #response_payload
+        local response = string.pack(">H", request.transaction_id) .. -- 事务 ID
+            string.pack(">H", 0) ..                                   -- 协议 ID
+            string.pack(">H", length) ..                              -- 长度
+            response_payload
+        
+        return response
+    end
+
+    -- 用户返回表 -> 正常响应;
+    if type(user_return) ~= "table" then
+        log.error("exmodbus", "从站回调必须返回 table 或 number,实际类型: ", type(user_return))
+        return nil
+    end
+
+    local response_payload = ""
+
+    -- 处理读线圈和读离散输入响应;
+    if func_code == exmodbus_ref.READ_COILS or func_code == exmodbus_ref.READ_DISCRETE_INPUTS then
+        local reg_count = request.reg_count
+
+        -- 校验 reg_count 是否有效;
+        if not reg_count or reg_count <= 0 then
+            log.error("exmodbus", "请求中 reg_count 无效")
+            return nil
+        end
+
+        local byte_count = math.ceil(reg_count / 8)
+        local values = {}
+
+        for i = 0, reg_count - 1 do
+            local addr = request.start_addr + i
+            local bit_val = user_return[addr]
+            if bit_val == nil then
+                log.error("exmodbus", "读线圈/离散输入回调未返回地址 ", addr, " 的数据")
+                return nil
+            end
+            if bit_val ~= 0 and bit_val ~= 1 then
+                log.error("exmodbus", "地址 ", addr, " 的值必须为 0 或 1,实际: ", bit_val)
+                return nil
+            end
+
+            local byte_idx = math.floor(i / 8)
+            if not values[byte_idx] then values[byte_idx] = 0 end
+            if bit_val == 1 then
+                values[byte_idx] = bit.bor(values[byte_idx], bit.lshift(1, i % 8))
+            end
+        end
+
+        response_payload = string.char(slave_id, func_code, byte_count)
+        for i = 0, byte_count - 1 do
+            response_payload = response_payload .. string.char(values[i] or 0)
+        end
+    -- 处理读保持寄存器和读输入寄存器响应;
+    elseif func_code == exmodbus_ref.READ_HOLDING_REGISTERS or func_code == exmodbus_ref.READ_INPUT_REGISTERS then
+        local reg_count = request.reg_count
+        -- 校验 reg_count 是否有效;
+        if not reg_count or reg_count <= 0 then
+            log.error("exmodbus", "请求中 reg_count 无效")
+            return nil
+        end
+
+        local values = ""
+        for i = 0, reg_count - 1 do
+            local addr = request.start_addr + i
+            local val = user_return[addr]
+            if val == nil then
+                log.error("exmodbus", "读保持寄存器/输入寄存器回调未返回地址 ", addr, " 的数据")
+                return nil
+            end
+            if type(val) ~= "number" or val ~= math.floor(val) or val < 0 or val > 65535 then
+                log.error("exmodbus", "地址 ", addr, " 的值必须为 0~65535 的整数,实际: ", val)
+                return nil
+            end
+            values = values .. string.char((val >> 8) & 0xFF, val & 0xFF)
+        end
+        response_payload = string.char(slave_id, func_code, #values) .. values
+    -- 处理写单个线圈响应;
+    elseif func_code == exmodbus_ref.WRITE_SINGLE_COIL then
+        local addr = request.start_addr
+        -- 校验 start_addr 是否有效;
+        if addr == nil then
+            log.error("exmodbus", "请求中 start_addr 无效")
+            return nil
+        end
+        local coil_val = (request.data and request.data[addr]) or 0
+        local resp_val = (coil_val ~= 0) and 0xFF00 or 0x0000
+        response_payload = string.char(slave_id, func_code) ..
+            string.char((addr >> 8) & 0xFF, addr & 0xFF,
+            (resp_val >> 8) & 0xFF, resp_val & 0xFF)
+    -- 处理写单个保持寄存器响应;
+    elseif func_code == exmodbus_ref.WRITE_SINGLE_HOLDING_REGISTER then
+        local addr = request.start_addr
+        -- 校验 start_addr 是否有效;
+        if addr == nil then
+            log.error("exmodbus", "请求中 start_addr 无效")
+            return nil
+        end
+        local reg_val = (request.data and request.data[addr]) or 0
+        -- 校验 reg_val 是否有效;
+        if type(reg_val) ~= "number" or reg_val ~= math.floor(reg_val) or reg_val < 0 or reg_val > 65535 then
+            log.error("exmodbus", "地址 ", addr, " 的值必须为 0~65535 的整数,实际: ", reg_val)
+            return nil
+        end
+        response_payload = string.char(slave_id, func_code) ..
+            string.char((addr >> 8) & 0xFF, addr & 0xFF,
+            (reg_val >> 8) & 0xFF, reg_val & 0xFF)
+    -- 处理写多个线圈/保持寄存器响应;
+    elseif func_code == exmodbus_ref.WRITE_MULTIPLE_COILS or func_code == exmodbus_ref.WRITE_MULTIPLE_HOLDING_REGISTERS then
+        local start_addr = request.start_addr
+        local reg_count = request.reg_count
+        -- 校验 start_addr 和 reg_count 是否有效;
+        if not start_addr or not reg_count or reg_count <= 0 then
+            log.error("exmodbus", "请求中 start_addr 或 reg_count 无效")
+            return nil
+        end
+        response_payload = string.char(slave_id, func_code) ..
+            string.char((start_addr >> 8) & 0xFF, start_addr & 0xFF,
+            (reg_count >> 8) & 0xFF, reg_count & 0xFF)
+    -- 处理未知功能码,视为错误;
+    else
+        log.error("exmodbus", "不支持的功能码,且未返回异常码: ", func_code)
+        return nil
+    end
+
+    -- 构建完整的 TCP 响应帧
+    local length = #response_payload  -- 长度包含从站ID
+    local response = string.pack(">H", request.transaction_id) .. -- 事务 ID
+        string.pack(">H", 0) ..                                   -- 协议 ID
+        string.pack(">H", length) ..                              -- 长度(包含从站ID)
+        response_payload                                          -- 从站ID + PDU数据
+
+    return response
+end
+
+-- TCP 从站接收数据处理函数;
+local function tcp_receiver(netc, instance)
+    -- 如果数据接收缓冲区还没有申请过空间,则先申请内存空间
+    if instance.recv_buff == nil then
+        instance.recv_buff = zbuff.create(1024)
+    end
+
+    -- 循环从内核的缓冲区读取接收到的数据
+    while true do
+        -- 从内核的缓冲区中读取数据到 instance.recv_buff 中
+        local succ, param = socket.rx(netc, instance.recv_buff)
+
+        -- 读取数据失败
+        if not succ then
+            log.info("exmodbus", "读取数据失败,已接收数据长度", param)
+            return false
+        end
+
+        -- 如果读取到了数据
+        if instance.recv_buff:used() > 0 then
+            -- log.info("exmodbus", "已接收数据长度", instance.recv_buff:used())
+            
+            -- 读取数据
+            local data = instance.recv_buff:query()
+            
+            -- 解析 TCP 请求帧
+            local request, err = parse_tcp_request(data)
+            if request then
+                -- 广播地址(0)不响应;
+                if request.slave_id == 0 then
+                    -- 调用回调以允许用户记录或处理广播命令(如写寄存器);
+                    if instance.slaveHandler then
+                        instance.slaveHandler(request)
+                        -- 注意:即使回调返回数据,也不发送响应;
+                    end
+                    -- 广播请求处理完毕,清除对应的报文数据
+                    local expected_len = request.length + MODBUS_TCP_HEADER_LEN - 1
+                    instance.recv_buff:del(0, expected_len)
+                    -- log.info("exmodbus", "广播请求处理完毕,清除报文长度:", expected_len)
+                    -- 广播请求处理完毕,不回复;
+                    break
+                end
+                if instance.slaveHandler then
+                    local user_return = instance.slaveHandler(request)
+                    local response = build_tcp_response(request, user_return)
+                    if response then
+                        libnet.tx(instance.TASK_NAME, 0, netc, response)
+                        sys.sendMsg(instance.TASK_NAME, socket.EVENT, 0)
+                    else
+                        log.error("exmodbus", "构建响应帧失败,从站地址:", request.slave_id)
+                    end
+                    
+                    -- 清除当前请求数据
+                    local expected_len = request.length + MODBUS_TCP_HEADER_LEN - 1
+                    instance.recv_buff:del(0, expected_len)
+                    -- log.info("exmodbus", "请求处理完毕,清除报文长度:", expected_len)
+                else
+                    log.warn("exmodbus", "收到主站请求,但未注册回调函数")
+                    -- 清除当前请求数据
+                    local expected_len = request.length + MODBUS_TCP_HEADER_LEN - 1
+                    instance.recv_buff:del(0, expected_len)
+                    log.info("exmodbus", "清除报文长度:", expected_len)
+                end
+            else
+                if err == "请求帧长度不足" then
+                    -- 请求帧长度不足,等待更多数据
+                    -- log.info("exmodbus", "请求帧长度不足,等待更多数据")
+                    break
+                elseif err == "数据长度与实际长度不匹配" then
+                    -- 数据长度与实际长度不匹配,清空缓冲区
+                    -- log.warn("exmodbus", "数据长度与实际长度不匹配,清空缓冲区")
+                    instance.recv_buff:del()
+                    break
+                elseif err == "协议 ID 错误" then
+                    -- 协议 ID 错误,清空缓冲区
+                    -- log.warn("exmodbus", "协议 ID 错误,清空缓冲区")
+                    instance.recv_buff:del()
+                    break
+                end
+            end
+        else
+            -- 没有数据可读
+            break
+        end
+    end
+
+    return true
+end
+
+local function tcp_slave_main_task_func(instance)
+    local netc = nil
+    local result, param
+
+    while true do
+        -- 创建 TCP 服务器
+        netc = socket.create(instance.adapter, instance.TASK_NAME)
+        if not netc then
+            log.error("exmodbus", "创建 TCP 服务器失败")
+            goto EXCEPTION_PROC
+        end
+
+        -- 配置服务器
+        result = socket.config(netc, instance.port)
+        if not result then
+            log.error("exmodbus", "配置 TCP 服务器失败")
+            goto EXCEPTION_PROC
+        end
+
+        -- 监听端口
+        result = libnet.listen(instance.TASK_NAME, 0, netc)
+        if not result then
+            log.error("exmodbus", "监听端口失败")
+            goto EXCEPTION_PROC
+        end
+
+        log.info("exmodbus", "TCP 从站已启动,监听端口:", instance.port)
+
+        -- 处理连接和数据
+        while true do
+            -- 处理接收数据
+            if not tcp_receiver(netc, instance) then
+                log.info("exmodbus", "接收数据处理失败")
+                break
+            end
+
+            -- 等待事件
+            result, param = libnet.wait(instance.TASK_NAME, 0, netc)
+            if not result then
+                log.info("exmodbus", "客户端断开连接")
+                break
+            end
+        end
+
+        -- 异常处理
+        ::EXCEPTION_PROC::
+
+        -- 关闭连接
+        if netc then
+            libnet.close(instance.TASK_NAME, 5000, netc)
+            socket.release(netc)
+            netc = nil
+        end
+
+        -- 等待 5 秒后重试
+        sys.wait(5000)
+    end
+end
+
+-- 创建一个新的实例;
+local function create(config, exmodbus, gen_request_id)
+    exmodbus_ref = exmodbus
+    gen_id_func = gen_request_id
+    local TASK_NAME = "exmodbus_tcp_task_"..gen_id_func()
+
+    -- 创建一个新的实例;
+    local instance = modbus:new(config, TASK_NAME)
+    -- 检查实例是否创建成功;
+    if not instance then
+        log.error("exmodbus", "创建 Modbus 实例失败")
+        return false
+    end
+
+    -- 启动任务
+    sys.taskInitEx(tcp_slave_main_task_func, TASK_NAME, nil, instance)
+
+    -- 返回实例;
+    return instance
+end
+
+function modbus:destroy()
+    -- 停止任务
+    sys.taskDel(self.TASK_NAME)
+    -- 释放缓冲区
+    if self.recv_buff then
+        self.recv_buff:free()
+        self.recv_buff = nil
+    end
+end
+
+-- 注册从站请求处理回调函数;
+function modbus:on(callback)
+    if type(callback) ~= "function" then
+        log.error("exmodbus", "on(callback) 的参数必须是一个函数")
+        return false
+    end
+    self.slaveHandler = callback
+    log.info("exmodbus", "已注册从站请求处理回调函数")
+    return true
+end
+
+return { create = create }