Using Multiple AsyncModbusTcpClient #2395
-
Hi Everybody! I'm attempting to read information from a series of AsyncModbusTcpClient objects but the code fails when using more than one object and I can't figure out why! It connects to the first object fine (and handles reconnecting reasonably well) but the second object never connects. I'm using version 3.7.2 of Pymodbus and Python 3.10. I've created a reproducer script below. Any thoughts would be appreciated! class DeviceManagerAsync:
def __init__(self, settings:Settings):
self.modbus_connections: list[dict[str, typing.Any]] = []
for device in settings.modbus_devices:
self.modbus_connections.append(
{
"name": device.name,
"address": device.modbus_address,
"client": pymodbus.client.AsyncModbusTcpClient(
device.modbus_address,
reconnect_delay=0.5,
reconnect_delay_max=10.0,
timeout=3.0,
retries=2,
),
}
)
self.modbus_byte_order = pymodbus.constants.Endian.BIG
self.modbus_word_order = pymodbus.constants.Endian.LITTLE
self.polling_interval = settings.modbus_polling_interval_ms / 1000.0
self.polling_stop_event = asyncio.Event()
self.polling_stop_event.clear()
self.polling_task: typing.Optional[asyncio.Task[typing.Any]] = None
async def _read_rgister(self, register_id: str, client: pymodbus.client.AsyncModbusTcpClient) -> float:
reg_count = -1
if MODBUS_REGISTER_MAP[register_id]["register_value_type"] is float:
reg_count = 2
elif MODBUS_REGISTER_MAP[register_id]["register_value_type"] is int:
reg_count = 1
elif MODBUS_REGISTER_MAP[register_id]["register_value_type"] is bool:
reg_count = 0
else:
raise RuntimeError(f"The type for register {register_id} is not supported")
if str(MODBUS_REGISTER_MAP[register_id]["register_type"]).lower() == "input":
results = await client.read_input_registers(
MODBUS_REGISTER_MAP[register_id]["register"], reg_count
)
elif str(MODBUS_REGISTER_MAP[register_id]["register_type"]).lower() == "holding":
results = await client.read_holding_registers(
MODBUS_REGISTER_MAP[register_id]["register"], reg_count
)
elif MODBUS_REGISTER_MAP[register_id]["register_type"].lower() == "discrete":
results = await client.read_discrete_inputs(
MODBUS_REGISTER_MAP[register_id]["register"]
)
else:
raise RuntimeError(f"The register type for {register_id} is not supported")
if not results.isError():
if reg_count > 0:
decoder = pymodbus.payload.BinaryPayloadDecoder.fromRegisters(
results.registers,
byteorder=self.modbus_byte_order,
wordorder=self.modbus_word_order,
)
if reg_count > 1:
return_value = decoder.decode_32bit_float()
else:
return_value = decoder.decode_16bit_uint()
else:
return_value = float(results.bits[0])
else:
raise RuntimeError("Error connecting to device!")
return return_value
async def _write_register(
self, register_id: str, client: pymodbus.client.AsyncModbusTcpClient, value: float
) -> None:
payload_builder = pymodbus.payload.BinaryPayloadBuilder(
payload=None, byteorder=self.modbus_byte_order, wordorder=self.modbus_word_order
)
if str(MODBUS_REGISTER_MAP[register_id]["register_type"]).lower() == "holding":
if MODBUS_REGISTER_MAP[register_id]["register_value_type"] is float:
payload_builder.add_32bit_float(float(value))
else:
payload_builder.add_16bit_uint(abs(int(value)))
await client.write_registers(
int(MODBUS_REGISTER_MAP[register_id]["register"]),
payload_builder.build(), # type: ignore[arg-type]
skip_encode=True,
)
else:
raise RuntimeError("Could not write to register type")
async def handle_periodic_read(self) -> None:
while not self.polling_stop_event.is_set():
telemetry_to_insert.clear()
try:
for connection in self.modbus_connections:
try:
if not connection["client"].connected:
await connection["client"].connect()
try:
for register in MODBUS_REGISTER_MAP:
return_value = await self._read_rgister(register, connection["client"])
print(return_value)
except RuntimeError as e:
logging.warning("Received %s while reading %s from %s", e, register, connection["address"])
except pymodbus.ModbusException as e:
logging.error("Received %s while reading %s from %s", e, register, connection["address"])
except Exception as e:
logging.error("Unhandled exception in periodic reading: %s ", e)
await asyncio.sleep(self.polling_interval)
async def __aenter__(self):
for connection in self.modbus_connections:
try:
await connection["client"].connect()
except pymodbus.exceptions.ConnectionException as e:
logging.error("Received %s while connecting to %s. Will retry later", e, connection["address"])
self.polling_task = asyncio.create_task(self.handle_periodic_read())
return self
async def __aexit__(self, *excinfo):
self.polling_stop_event.set()
for connection in self.modbus_connections:
connection["client"].close()
async def run() -> None:
settings = loadSettingsDict()
try:
async with DeviceManagerAsync(settings):
while True:
await asyncio.sleep(1)
def main():
try:
asyncio.run(run())
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main() Truncated Debug Logs:
|
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 6 replies
-
please add debug logs so we can see what happens. We have a number of tests cases using multiple clients, so it should be working. I have not checked your read/write calls. |
Beta Was this translation helpful? Give feedback.
-
Are you connecting to the same server, a lot of devices do not allow multiple connections from the same client (ip address). |
Beta Was this translation helpful? Give feedback.
-
Did you try to reverse to order of the devices, it could be .2 never connects. |
Beta Was this translation helpful? Give feedback.
-
May I suggest you make a simple example. Just connect the 2 client objects, without all the overhead. |
Beta Was this translation helpful? Give feedback.
May I suggest you make a simple example. Just connect the 2 client objects, without all the overhead.