Skip to content

When using asyncio, received message is truncated #500

@marhoy

Description

@marhoy

Versions

  • Python: 3.7.3
  • OS: Linux (Debian 10.3 on a rpi3)
  • Pymodbus: 2.3.0
  • Modbus Hardware: USB <-> serial: FT232 based.

Pymodbus Specific

  • Server: A Flexit ventilation aggregate
  • Client: rtu - async

Description

I'm trying to use the asyncio serial client with an already running event loop.

Using the sync serial client, I have no problems talking to my modbus device. But the async serial client seems to have several issues. I think my problem is related to #486, #385 which are still unresolved.

Problem 1: run_until_complete() is trying to start an already running loop

Since I'm passing an already running event loop, the loop.run_until_complete() on line 106 of pymodbus/client/asynchronous/factory/serial.py causes a RuntimeError: This event loop is already running (because it is trying to start the loop). It needs to be replaced by instead adding a task to the existing loop:

--- a/pymodbus/client/asynchronous/factory/serial.py
+++ b/pymodbus/client/asynchronous/factory/serial.py
@@ -102,8 +102,7 @@ def async_io_factory(port=None, framer=None, **kwargs):
         sys.exit(1)

     client = AsyncioModbusSerialClient(port, proto_cls, framer, loop, **kwargs)
-    coro = client.connect()
-    loop.run_until_complete(coro)
+    asyncio.ensure_future(client.connect())
     return loop, client

For Python 3.7+, asyncio.create_task() is preferred.

(Another option would be to not run .connect() here, and instead leave that to the user of the client.)

Problem 2: Timing issues with the RTU framer

When using the sync client (works 10/10 times), the communication looks like this:

DEBUG:pymodbus.transaction:Current transaction state - IDLE
DEBUG:pymodbus.transaction:Running transaction 1
DEBUG:pymodbus.transaction:SEND: 0x1 0x4 0xb 0xda 0x0 0x1 0x12 0x15
DEBUG:pymodbus.client.sync:New Transaction state 'SENDING'
DEBUG:pymodbus.transaction:Changing transaction state from 'SENDING' to 'WAITING FOR REPLY'
DEBUG:pymodbus.transaction:Changing transaction state from 'WAITING FOR REPLY' to 'PROCESSING REPLY'
DEBUG:pymodbus.transaction:RECV: 0x1 0x4 0x2 0x0 0x3 0xf9 0x31
DEBUG:pymodbus.framer.rtu_framer:Getting Frame - 0x4 0x2 0x0 0x3
DEBUG:pymodbus.factory:Factory Response[ReadInputRegistersResponse: 4]
DEBUG:pymodbus.framer.rtu_framer:Frame advanced, resetting header!!
DEBUG:pymodbus.transaction:Adding transaction 1
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
Result: [3]

With the above patch, my asyncio test-script sometimes works, and sometimes doesn't. When it does work, the communication looks like this:

DEBUG:pymodbus.client.asynchronous.asyncio:send: 0x1 0x4 0xb 0xda 0x0 0x1 0x12 0x15
DEBUG:pymodbus.transaction:Adding transaction 1
INFO:pymodbus.client.asynchronous.asyncio:Connected to /dev/ttyUSB0
DEBUG:pymodbus.client.asynchronous.asyncio:recv: 0x1 0x4 0x2 0x0 0x3 0xf9 0x31
DEBUG:pymodbus.framer.rtu_framer:Getting Frame - 0x4 0x2 0x0 0x3
DEBUG:pymodbus.factory:Factory Response[ReadInputRegistersResponse: 4]
DEBUG:pymodbus.framer.rtu_framer:Frame advanced, resetting header!!
DEBUG:pymodbus.transaction:Getting transaction 1
Result: [3]

When it does not work, it looks like e.g. these three examples, and then the program is just stuck:

DEBUG:pymodbus.client.asynchronous.asyncio:send: 0x1 0x4 0xb 0xda 0x0 0x1 0x12 0x15
DEBUG:pymodbus.transaction:Adding transaction 1
INFO:pymodbus.client.asynchronous.asyncio:Connected to /dev/ttyUSB0
DEBUG:pymodbus.client.asynchronous.asyncio:recv: 0x1 0x4 0x2 0x0 0x3 0xf9
DEBUG:pymodbus.framer.rtu_framer:Frame check failed, ignoring!!
DEBUG:pymodbus.framer.rtu_framer:Resetting frame - Current Frame in buffer - 0x1 0x4 0x2 0x0 0x3 0xf9
DEBUG:pymodbus.client.asynchronous.asyncio:recv: 0x31
DEBUG:pymodbus.framer.rtu_framer:Frame - [b'1'] not ready
DEBUG:pymodbus.client.asynchronous.asyncio:send: 0x1 0x4 0xb 0xda 0x0 0x1 0x12 0x15
DEBUG:pymodbus.transaction:Adding transaction 1
INFO:pymodbus.client.asynchronous.asyncio:Connected to /dev/ttyUSB0
DEBUG:pymodbus.client.asynchronous.asyncio:recv: 0x1 0x4 0x2
DEBUG:pymodbus.framer.rtu_framer:Frame check failed, ignoring!!
DEBUG:pymodbus.framer.rtu_framer:Resetting frame - Current Frame in buffer - 0x1 0x4 0x2
DEBUG:pymodbus.client.asynchronous.asyncio:recv: 0x0 0x3 0xf9 0x31
DEBUG:pymodbus.framer.rtu_framer:Frame check failed, ignoring!!
DEBUG:pymodbus.framer.rtu_framer:Resetting frame - Current Frame in buffer - 0x0 0x3 0xf9 0x31
DEBUG:pymodbus.client.asynchronous.asyncio:send: 0x1 0x4 0xb 0xda 0x0 0x1 0x12 0x15
DEBUG:pymodbus.transaction:Adding transaction 1
INFO:pymodbus.client.asynchronous.asyncio:Connected to /dev/ttyUSB0
DEBUG:pymodbus.client.asynchronous.asyncio:recv: 0x1 0x4 0x2 0x0
DEBUG:pymodbus.framer.rtu_framer:Frame check failed, ignoring!!
DEBUG:pymodbus.framer.rtu_framer:Resetting frame - Current Frame in buffer - 0x1 0x4 0x2 0x0
DEBUG:pymodbus.client.asynchronous.asyncio:recv: 0x3 0xf9 0x31
DEBUG:pymodbus.framer.rtu_framer:Frame check failed, ignoring!!
DEBUG:pymodbus.framer.rtu_framer:Resetting frame - Current Frame in buffer - 0x3 0xf9 0x31

As you can see, it seems that the received message is truncated: The full message is 7 bytes, but in the three failing examples, the client only receives 6, 3 or 4 bytes before moving on.

I believe this to be an issue with the rtu_framer, but I haven't had time to look into what is causing the problem.

Test-script

This is the script I'm using to test the asyncio client. It is a trimmed-down version of one of the examples in the pymodbus repository.

#!/usr/bin/env python
import logging
import asyncio

from pymodbus.client.asynchronous.serial import AsyncModbusSerialClient as ModbusClient
from pymodbus.client.asynchronous import schedulers

from threading import Thread
import time

logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)


UNIT = 0x01


async def read_from_client(client):
    try:
        log.debug("Read input registers")
        # await client.connect()
        rr = await client.protocol.read_input_registers(3034, 1, unit=UNIT)
        print("Result:", rr.registers)
        client.stop()
    except Exception as e:
        log.exception(e)
        client.transport.close()


def start_loop(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()


def create_loop():
    loop = asyncio.new_event_loop()
    t = Thread(target=start_loop, args=[loop])
    t.daemon = True
    t.start()
    assert loop.is_running()
    asyncio.set_event_loop(loop)
    return loop


if __name__ == "__main__":

    loop = create_loop()

    loop, client = ModbusClient(
        scheduler=schedulers.ASYNC_IO,
        loop=loop,
        method="rtu",
        port="/dev/ttyUSB0",
        stopbits=1,
        bytesize=8,
        parity="E",
        baudrate=9600,
    )

    future = asyncio.run_coroutine_threadsafe(read_from_client(client), loop=loop)
    while not future.done():
        time.sleep(0.1)
    loop.stop()

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions