pymodbus: Modbus RTU считывает регистр вызовов, блокируется и никогда не просыпается, или автоматически переподключается к устройству Modbus RTU - PullRequest
0 голосов
/ 01 февраля 2019

Я пытаюсь создать клиент Modbus RTU, который будет считывать данные из последовательного порта, используя библиотеку pymodbus .Я могу подключиться к Modbus RTU, работающему на COM2 в Windows10, и могу читать данные различных типов, такие как Int32, Float и т. Д.

Проблема:

Через некоторое время я отключил свое устройство и проверил состояние ModbusClient.Мой клиент подключен к порту COM2 и пытается прочитать с устройства, которое недоступно, и вызвать read_holding_registers заблокирован.

Среда:

Python: 3.6.5pymodbus: 2.1.0Windows: 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?

Буду признателен за любую помощь.

Ответы [ 2 ]

0 голосов
/ 19 февраля 2019

Как сказал @grapes, в случае соединения устройства RTU будет работать только формат запроса / ответа.Таким образом, единственный вариант, который у нас есть, это добавить timeout, который закроет транзакцию, как только произойдет тайм-аут чтения.

Из документации Twisted я нашел метод с именем addTimeoutВы можете проверить документы из twisted.internet.defer.Deferred.addTimeout (...) , которые позволяют отменить транзакцию по истечении времени, указанного как timeout.

По истечении времени ожидания он передаст управление объекту errorHandler из Deferred.Когда вы добавляете логику повторного подключения, вызывая connectionMade метод ModbusClientProtocol, в моем примере он называется CustomModbusClientProtocol.

Мой рабочий код:

Ниже представлено мое полное решение для автоматического переподключения к устройству Modbus 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})

Вывод:

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'.

Я надеюсь, что этопомогает кому-то в будущем.

0 голосов
/ 02 февраля 2019

Вы путаете последовательную связь и TCP / IP.Они совершенно разные.При использовании Modbus RTU он работает по последовательным линиям (в основном это промышленный интерфейс RS-485 или RS-232 для целей конфигурации).

В TCP / IP у вас есть логический канал (TCP), которыйотвечает за самодиагностику и удаление ошибок при попытке чтения / записи в неподключенную конечную точку.

С помощью последовательных линий вы просто отправляете данные в порт (что делается независимо от того, слушает ли кто-то на другой сторонеэто) и единственный способ понять, что ваша конечная точка не работает - это тайм-аут в ожидании ответа.

Кстати, есть момент, когда нет ответа, это не означает, что устройство отключено - широковещательные сообщения являются отличным примером,Для некоторых устройств Modbus вы можете передавать информацию о времени на подчиненном устройстве 0, и ответ не будет предоставлен.

Вывод: для устройств rtu процедура connect/disconnect отсутствует, вы говорите только с точки зрения запроса / ответа.

...