正如@grapes 所说,只有请求/响应格式才能在RTU
设备通信的情况下起作用。因此,我们唯一的选择是添加timeout
一旦发生读取超时将关闭事务的选项。
从Twisted的文档中,我找到了名为addTimeout
You can check docs from twisted.internet.defer.Deferred.addTimeout(...)的方法,该方法允许在给定的时间后取消交易timeout
。
一旦请求超时,它将把控制权交给errorHandler
对象Deferred
。connectionMade
通过调用的方法添加重新连接逻辑的地方ModbusClientProtocol
,在我的示例中,它被命名为CustomModbusClientProtocol
。
我的工作代码:
以下是我自动重新连接到 ModbusRTU
设备的完整解决方案。我试图从设备读取数据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'.
我希望这对将来的人有所帮助。