Как сказал @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'.
Я надеюсь, что этопомогает кому-то в будущем.