You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Reactor的Pymodbus Modbus RTU客户端回调未触发问题求助

解决Pymodbus异步RTU客户端回调未触发且Transport为Null的问题

我仔细看了你的代码和问题日志,核心问题出在Modbus协议初始化逻辑错误,以及对RTU串口参数的混淆使用,导致Transport无法正确绑定,最终异步请求的回调完全无法触发。咱们一步步拆解问题并修复:

问题根源分析

  1. 协议初始化方式错误:你在connectionMade方法里直接调用ModbusClientProtocol.__init__ModbusRtuFramer.__init__,这完全破坏了Twisted协议与Transport的绑定逻辑,直接导致self.transport为Null,异步请求发不出去,回调自然不会触发。
  2. RTU参数混淆:Modbus RTU是串口通信,不需要IP地址,你把deviceIP和串口路径(比如/dev/ttyUSB0)拼在一起的操作完全不符合RTU的使用逻辑。
  3. 未定义参数遗漏read方法里直接使用startingAddressregisterCount,但这两个变量没有从配置中赋值,后续会引发运行时错误。
  4. Reactor线程风险:单独开线程启动Reactor的方式容易导致事件循环阻塞,影响异步回调的执行。

修正后的完整代码

下面是调整后的可运行代码,我标注了所有关键修改点:

import time
from twisted.internet import reactor, serialport, protocol
from pymodbus.client.asynchronous import ModbusClientProtocol, ModbusClientMixin
from pymodbus.transaction import ModbusRtuFramer
from pymodbus.factory import ClientDecoder
import logging

# 配置日志输出
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def readDevices(modbusRTUDevice):
    serial_port = modbusRTUDevice["serial_port"]
    logger.info("Connecting to Modbus RTU device at port {0}".format(serial_port))
    
    modbusClientFactory = CustomModbusClientFactory()
    modbusClientFactory.modbusDevice = modbusRTUDevice
    
    # 初始化串口客户端:参数顺序为协议、串口路径、Reactor、串口参数
    serialport.SerialPort(
        modbusClientFactory.buildProtocol(),
        serial_port,
        reactor,
        baudrate=9600,
        parity='E',
        bytesize=8,
        stopbits=1,
        timeout=0.2
    )
    reactor.run()

class CustomModbusClientFactory(protocol.ClientFactory, ModbusClientMixin):
    modbusDevice = {}
    
    def buildProtocol(self, addr=None):
        # 直接初始化自定义协议,传入Framer和串口参数
        modbusClientProtocol = CustomModbusClientProtocol(
            framer=ModbusRtuFramer(ClientDecoder()),
            baudrate=9600,
            parity='E',
            bytesize=8,
            stopbits=1,
            timeout=0.2,
            retryOnEmpty=True,
            retries=3
        )
        modbusClientProtocol.factory = self
        modbusClientProtocol.modbusDevice = self.modbusDevice
        return modbusClientProtocol
    
    def clientConnectionLost(self, connector, reason):
        logger.info("Connection lost, reconnecting...")
        connector.connect()
    
    def clientConnectionFailed(self, connector, reason):
        logger.error("Connection failed: {0}".format(reason))
        reactor.stop()

class CustomModbusClientProtocol(ModbusClientProtocol):
    def __init__(self, framer, **kwargs):
        # 用super()正确初始化父类,保证协议与Transport绑定
        super().__init__(framer, **kwargs)
        self.modbusDevice = {}
    
    def connectionMade(self):
        super().connectionMade()
        serial_port = self.modbusDevice["serial_port"]
        logger.info("Modbus RTU device connected at port {0}".format(serial_port))
        # 从配置中读取寄存器参数
        self.startingAddress = self.modbusDevice["starting_address"]
        self.registerCount = self.modbusDevice["register_count"]
        self.read()

    def read(self):
        serial_port = self.modbusDevice["serial_port"]
        slaveAddress = self.modbusDevice["slaveAddress"]
        logger.info("Reading holding registers of Modbus RTU device at port {0}...".format(serial_port))
        deferred = self.read_holding_registers(self.startingAddress, self.registerCount, unit=slaveAddress)
        deferred.addCallbacks(self.requestFetched, self.requestNotFetched)

    def requestNotFetched(self, error):
        logger.error("Error reading registers of Modbus RTU device : {0}".format(error))
        # 用Reactor的延迟调用替代sleep,避免阻塞事件循环
        reactor.callLater(0.5, self.read)

    def requestFetched(self, response):
        logger.info("Successfully fetched registers! Response: {0}".format(response.registers))
        # 在这里添加你的数据处理逻辑
        # Do some other stuff here
        # 定时重复读取
        reactor.callLater(1, self.read)

# 示例调用
if __name__ == "__main__":
    # RTU设备配置:移除无用的IP字段,只保留串口路径、从站地址、寄存器参数
    rtu_device = {
        "serial_port": "/dev/ttyUSB0",  # Windows下替换为"COM3"这类串口名
        "slaveAddress": 1,
        "starting_address": 0,
        "register_count": 10
    }
    readDevices(rtu_device)

关键修改说明

  1. 协议初始化修正:在CustomModbusClientProtocol的构造函数中用super()正确调用父类初始化,保证Framer和Transport的绑定关系。
  2. RTU参数清理:删除了无用的ip字段,改用serial_port表示串口路径,符合RTU串口通信的要求。
  3. 连接状态管理:在工厂类中添加了断线重连和连接失败处理逻辑,提升稳定性。
  4. 异步友好的重试:把sleep(0.5)换成reactor.callLater,避免阻塞Twisted的事件循环。
  5. 参数补全:在connectionMade中初始化寄存器的起始地址和数量,确保读取逻辑能正常执行。

额外检查项

  1. 串口权限:Linux下需要将用户加入dialout组(sudo usermod -aG dialout your_username),否则无法访问串口。
  2. 参数匹配:确保串口参数(波特率、奇偶校验、数据位、停止位)和设备端完全一致,这是RTU通信最常见的坑。
  3. 串口占用:Windows下要确认没有其他程序占用目标串口,否则会连接失败。

内容的提问来源于stack exchange,提问作者OO7

火山引擎 最新活动