pymodbus:Modbus RTU读取寄存器调用被阻止,并且永远不会唤醒或自动重新连接到Modbus RTU设备

时间:2019-02-01 12:14:36

标签: python-3.x pyserial modbus modbus-tcp pymodbus

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

问题:

  

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

环境:

  

Python:3.6.5
  pymodbus:2.1.0
  Windows:10 64位

根据我的说法,它应该引发如下错误

[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设备的断开和重新连接吗?

任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:0)

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

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

使用串行线路,您只需将数据发送到端口(无论对方是否正在监听它,都可以完成此操作),而了解端点已关闭的唯一方法是等待应答的超时。

顺便说一句,有些时候没有回复并不意味着设备离线-广播消息就是一个很好的例子。对于某些Modbus设备,您可以在从站0上广播时间信息,并且不会给予任何答复。

结论:对于rtu设备,没有connect/disconnect程序,您只能按照请求/答复的方式发言。

答案 1 :(得分:0)

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

  

Twisted的文档中,我发现了名为addTimeout的方法,您可以检查twisted.internet.defer.Deferred.addTimeout(...)中的文档,这些文档允许在timeout给出的时间后取消交易。

请求超时后,它将控制权传递给errorHandler对象的Deferred。通过调用connectionMade的{​​{1}}方法在其中添加重新连接逻辑的地方,在我的示例中,它被命名为ModbusClientProtocol

我的工作代码:

下面是我自动重新连接到Modbus CustomModbusClientProtocol设备的完整解决方案。我试图从RTU设备读取10数据的string个字符的地方。

RTU

输出:

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})

我希望这对以后的人有帮助。