pymodbus:Modbus RTU 读取寄存器调用被阻止且从未唤醒或自动重新连接到 Modbus RTU 设备
pymodbus: Modbus RTU read register call blocked & never woke up or Auto-reconnect to Modbus RTU device
我正在尝试创建 Modbus RTU 客户端,它将使用 pymodbus 库从串口读取数据。我能够在 Windows10 中的 COM2
上连接到 Modbus RTU 运行 并且能够读取不同类型的数据,例如 Int32
、Float
等
问题:
After some time I've disconnected my device & checked the status of
ModbusClient. My client is connected to COM2
port & trying to read
from the device which is not available & call for
read_holding_registers
blocked.
环境:
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 设备的断开和重新连接?
如有任何帮助,我们将不胜感激。
您混淆了串行通信和 TCP/IP 通信。他们是完全不同的。使用 Modbus RTU 时,它通过串行线路工作(工业上大多数是 RS-485 接口,或用于配置目的的 RS-232)。
在 TCP/IP 中,您有一个逻辑通道 (TCP),它负责在尝试 read/write 到未连接的端点时进行自我诊断和丢弃错误。
使用串行线路,您只需将数据发送到端口(无论另一端是否有人在监听它,这都会完成)并且了解您的端点已关闭的唯一方法是超时等待回复。
顺便说一句,在某些情况下,没有回复并不意味着设备处于离线状态 - 广播消息就是一个很好的例子。对于某些 modbus 设备,您可以在从站 0
上广播时间信息,并且不会收到回复。
结论:rtu 设备没有 connect/disconnect
程序,你只说 request/reply。
正如@grapes 所说,在 RTU
设备通信的情况下,只有 request/response 格式可以工作。因此,我们唯一的选择是添加 timeout
,这将在发生读取超时时关闭事务。
From the documentation of Twisted, I found method named addTimeout
You can check docs from twisted.internet.defer.Deferred.addTimeout(...) which allow to cancel transaction after the amount of time given as timeout
.
一旦请求超时,它将把控制权交给Deferred
对象的errorHandler
。通过调用 ModbusClientProtocol
的 connectionMade
方法添加重新连接逻辑的地方,在我的例子中,它被命名为 CustomModbusClientProtocol
.
我的工作代码:
以下是我自动重新连接到 Modbus RTU
设备的完整解决方案。我正在尝试从 RTU
设备读取 string
数据的 10
个字符。
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'.
我希望这对以后的人有所帮助。
我正在尝试创建 Modbus RTU 客户端,它将使用 pymodbus 库从串口读取数据。我能够在 Windows10 中的 COM2
上连接到 Modbus RTU 运行 并且能够读取不同类型的数据,例如 Int32
、Float
等
问题:
After some time I've disconnected my device & checked the status of ModbusClient. My client is connected to
COM2
port & trying to read from the device which is not available & call forread_holding_registers
blocked.
环境:
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 设备的断开和重新连接?
如有任何帮助,我们将不胜感激。
您混淆了串行通信和 TCP/IP 通信。他们是完全不同的。使用 Modbus RTU 时,它通过串行线路工作(工业上大多数是 RS-485 接口,或用于配置目的的 RS-232)。
在 TCP/IP 中,您有一个逻辑通道 (TCP),它负责在尝试 read/write 到未连接的端点时进行自我诊断和丢弃错误。
使用串行线路,您只需将数据发送到端口(无论另一端是否有人在监听它,这都会完成)并且了解您的端点已关闭的唯一方法是超时等待回复。
顺便说一句,在某些情况下,没有回复并不意味着设备处于离线状态 - 广播消息就是一个很好的例子。对于某些 modbus 设备,您可以在从站 0
上广播时间信息,并且不会收到回复。
结论:rtu 设备没有 connect/disconnect
程序,你只说 request/reply。
正如@grapes 所说,在 RTU
设备通信的情况下,只有 request/response 格式可以工作。因此,我们唯一的选择是添加 timeout
,这将在发生读取超时时关闭事务。
From the documentation of Twisted, I found method named
addTimeout
You can check docs from twisted.internet.defer.Deferred.addTimeout(...) which allow to cancel transaction after the amount of time given astimeout
.
一旦请求超时,它将把控制权交给Deferred
对象的errorHandler
。通过调用 ModbusClientProtocol
的 connectionMade
方法添加重新连接逻辑的地方,在我的例子中,它被命名为 CustomModbusClientProtocol
.
我的工作代码:
以下是我自动重新连接到 Modbus RTU
设备的完整解决方案。我正在尝试从 RTU
设备读取 string
数据的 10
个字符。
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'.
我希望这对以后的人有所帮助。