0

我正在尝试创建 Modbus RTU 客户端,它将使用pymodbus库从串行端口读取数据。我能够连接到COM2在 Windows10 上运行的 Modbus RTU 并能够读取不同类型的数据,如Int32,Float等。

问题:

一段时间后,我断开了我的设备并检查了 ModbusClient 的状态。我的客户端已连接到COM2端口并尝试从不可用的设备中读取并要求 read_holding_registers阻止。

环境:

Python:3.6.5
pymodbus:2.1.0
Windows:10 64bit

据我说,它应该抛出一个类似下面的错误

[Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionRefusedError'>: Connection was refused by other side: 10061: No connection could be made because the target machine actively refused it.

OR

[Failure instance: Traceback (failure with no frames): <class 'pymodbus.exceptions.ConnectionException'>: Modbus Error: [Connection] Client is not connected

与 Modbus TCP 设备断开连接时出现上述错误。但是,在 Modbus RTU 的情况下不执行任何操作。

下面的代码处理连接丢失和失败事件:

from pymodbus.client.common import ModbusClientMixin
from twisted.internet import reactor, protocol

class CustomModbusClientFactory(protocol.ClientFactory, ModbusClientMixin):

    def buildProtocol(self, addr=None):
        modbusClientProtocol = CustomModbusClientProtocol()
        modbusClientProtocol.factory = self
        return modbusClientProtocol

    def clientConnectionLost(self, connector, reason):
        logger.critical("Connection lost with device running on {0}:{1}.".format(modbusTcpDeviceIP, modbusTcpDevicePort))
        logger.critical("Root Cause : {0}".format(reason))
        connector.connect()

    def clientConnectionFailed(self, connector, reason):
        logger.critical("Connection failed with device running on {0}:{1}.".format(modbusTcpDeviceIP, modbusTcpDevicePort))
        logger.critical("Root Cause : {0}".format(reason))
        connector.connect()

我的完整代码在这里给出ModbusRTUClient.py

我已确保 Modbus RTU 设备的可用性并在与设备通信有任何问题时发出警报。

有谁知道如何处理 Modbus RTU 设备的断开和重新连接?

任何帮助,将不胜感激。

4

2 回答 2

0

您混淆了串行通信和 TCP/IP 通信。它们完全不同。使用 Modbus RTU 时,它通过串行线路工作(工业上主要是 RS-485 接口,或用于配置目的的 RS-232 接口)。

在 TCP/IP 中,您有一个逻辑通道 (TCP),它负责在尝试读取/写入未连接的端点时进行自我诊断和丢弃错误。

使用串行线路,您只需将数据发送到端口(无论对方是否正在监听它都会完成),并且了解您的端点已关闭的唯一方法是超时等待回复。

顺便说一句,在某些情况下,没有回复并不意味着设备处于离线状态 - 广播消息就是一个很好的例子。对于某些 modbus 设备,您可以在 slave 上广播时间信息,0并且不会给予回复。

结论:使用 rtu 设备没有connect/disconnect程序,您只能根据请求/回复说话。

于 2019-02-02T10:46:01.380 回答
0

正如@grapes 所说,只有请求/响应格式才能在RTU设备通信的情况下起作用。因此,我们唯一的选择是添加timeout一旦发生读取超时将关闭事务的选项。

Twisted的文档中,我找到了名为addTimeoutYou can check docs from twisted.internet.defer.Deferred.addTimeout(...)的方法,该方法允许在给定的时间后取消交易timeout

一旦请求超时,它将把控制权交给errorHandler对象DeferredconnectionMade通过调用的方法添加重新连接逻辑的地方ModbusClientProtocol,在我的示例中,它被命名为CustomModbusClientProtocol

我的工作代码:

以下是我自动重新连接到 ModbusRTU设备的完整解决方案。我试图从设备读取数据10字符的地方。stringRTU

import logging
from threading import Thread
from time import sleep

from pymodbus.client.async.twisted import ModbusClientProtocol
from pymodbus.constants import Endian
from pymodbus.factory import ClientDecoder
from pymodbus.payload import BinaryPayloadDecoder
from pymodbus.transaction import ModbusRtuFramer
from serial import EIGHTBITS
from serial import PARITY_EVEN
from serial import STOPBITS_ONE
from twisted.internet import protocol
from twisted.internet import serialport, reactor

FORMAT = ('%(asctime)-15s %(threadName)-15s '
      '%(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
logger = logging.getLogger()
logger.setLevel(logging.INFO)


def readDevices(modbusRTUDevice):
    deviceIP = modbusRTUDevice["ip"]
    devicePort = modbusRTUDevice["port"]
    logger.info("Connecting to Modbus RTU device at address {0}".format(deviceIP + ":" + str(devicePort)))
    modbusClientFactory = CustomModbusClientFactory()
    modbusClientFactory.address = deviceIP
    modbusClientFactory.modbusDevice = modbusRTUDevice
    SerialModbusClient(modbusClientFactory, devicePort, reactor, baudrate=9600, bytesize=EIGHTBITS,
                   parity=PARITY_EVEN, stopbits=STOPBITS_ONE, xonxoff=0, rtscts=0)
    Thread(target=reactor.run, args=(False,)).start()  # @UndefinedVariable


class SerialModbusClient(serialport.SerialPort):

    def __init__(self, factory, *args, **kwargs):
        serialport.SerialPort.__init__(self, factory.buildProtocol(), *args, **kwargs)


class CustomModbusClientFactory(protocol.ClientFactory):
    modbusDevice = {}

    def buildProtocol(self, addr=None):
        modbusClientProtocol = CustomModbusClientProtocol()
        modbusClientProtocol.factory = self
        modbusClientProtocol.modbusDevice = self.modbusDevice
        return modbusClientProtocol

    def clientConnectionLost(self, connector, reason):
        modbusTcpDeviceIP = self.modbusDevice["ip"]
        modbusTcpDevicePort = self.modbusDevice["port"]
        logger.critical("Connection lost with device running on {0}:{1}.".format(modbusTcpDeviceIP, modbusTcpDevicePort))
        logger.critical("Root Cause : {0}".format(reason))
        connector.connect()

    def clientConnectionFailed(self, connector, reason):
        modbusTcpDeviceIP = self.modbusDevice["ip"]
        modbusTcpDevicePort = self.modbusDevice["port"]
        logger.critical("Connection failed with device running on {0}:{1}.".format(modbusTcpDeviceIP, modbusTcpDevicePort))
        logger.critical("Root Cause : {0}".format(reason))
        connector.connect()


class CustomModbusClientProtocol(ModbusClientProtocol):

    def connectionMade(self):
        framer = ModbusRtuFramer(ClientDecoder(), client=None)
        ModbusClientProtocol.__init__(self, framer)
        ModbusClientProtocol.connectionMade(self)
        deviceIP = self.modbusDevice["ip"]
        devicePort = self.modbusDevice["port"]
        logger.info("Modbus RTU device connected at address {0}".format(deviceIP + ":" + str(devicePort)))
        reactor.callLater(5, self.read)  # @UndefinedVariable

    def read(self):
        deviceIP = self.modbusDevice["ip"]
        devicePort = self.modbusDevice["port"]
        slaveAddress = self.modbusDevice["slaveAddress"]
        deviceReadTimeout = self.modbusDevice["readTimeoutInSeconds"]
        logger.info("Reading holding registers of Modbus RTU device at address {0}...".format(deviceIP + ":" + str(devicePort)))
        deferred = self.read_holding_registers(0, 5, unit=slaveAddress)
        deferred.addCallbacks(self.requestFetched, self.requestNotFetched)
        deferred.addTimeout(deviceReadTimeout, reactor)

    def requestNotFetched(self, error):
        logger.info("Error reading registers of Modbus RTU device : {0}".format(error))
        logger.error("Trying reconnect in next {0} seconds...".format(5))
        reactor.callLater(5, self.connectionMade)  # @UndefinedVariable

    def requestFetched(self, response):
        logger.info("Inside request fetched...")
        decoder = BinaryPayloadDecoder.fromRegisters(response.registers, byteorder=Endian.Big, wordorder=Endian.Big)
        skipBytesCount = 0
        decoder.skip_bytes(skipBytesCount)
        registerValue = decoder.decode_string(10).decode()
        skipBytesCount += 10
        logger.info("Sensor updated to value '{0}'.".format(registerValue))
        reactor.callLater(5, self.read)  # @UndefinedVariable


readDevices({"ip": "127.0.0.1", "port": "COM2", "slaveAddress": 1, "readTimeoutInSeconds": 30})

输出:

2019-02-19 15:40:02,533 MainThread      INFO     TestRTU:26       Connecting to Modbus RTU device at address 127.0.0.1:COM2
2019-02-19 15:40:02,536 MainThread      INFO     TestRTU:73       Modbus RTU device connected at address 127.0.0.1:COM2
2019-02-19 15:40:07,541 Thread-2        INFO     TestRTU:81       Reading holding registers of Modbus RTU device at address 127.0.0.1:COM2...
2019-02-19 15:40:07,662 Thread-2        INFO     TestRTU:92       Inside request fetched...
2019-02-19 15:40:07,662 Thread-2        INFO     TestRTU:98       Sensor updated to value 'abcdefghij'.


2019-02-19 15:40:12,662 Thread-2        INFO     TestRTU:81       Reading holding registers of Modbus RTU device at address 127.0.0.1:COM2...
2019-02-19 15:40:12,773 Thread-2        INFO     TestRTU:92       Inside request fetched...
2019-02-19 15:40:12,773 Thread-2        INFO     TestRTU:98       Sensor updated to value 'abcdefghij'.


2019-02-19 15:40:17,773 Thread-2        INFO     TestRTU:81       Reading holding registers of Modbus RTU device at address 127.0.0.1:COM2...
2019-02-19 15:40:47,773 Thread-2        INFO     TestRTU:87       Error reading registers of Modbus RTU device : [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.defer.CancelledError'>:]
2019-02-19 15:40:47,773 Thread-2        ERROR    TestRTU:88       Trying to reconnect in next 5 seconds...


2019-02-19 15:40:52,780 Thread-2        INFO     TestRTU:73       Modbus RTU device connected at address logger127.0.0.1:COM2
2019-02-19 15:40:57,784 Thread-2        INFO     TestRTU:81       Reading holding registers of Modbus RTU device at address 127.0.0.1:COM2...
2019-02-19 15:40:57,996 Thread-2        INFO     TestRTU:92       Inside request fetched...
2019-02-19 15:40:57,996 Thread-2        INFO     TestRTU:98       Sensor updated to value 'abcdefghij'.

我希望这对将来的人有所帮助。

于 2019-02-19T10:19:08.037 回答