Repository: pymodbus-dev/pymodbus

Issue:
AsyncioModbusSerialClient TypeError Coroutine
### Versions

* Python: 3.9
* OS: Ubuntu 20.04
* Pymodbus: `3.0.0dev4`
* Modbus Hardware (if used): 

### Pymodbus Specific
* Server: None
* Client: rtu - async

### Description

When I try  `3.0.0dev4` and the latest commit as of today. I am getting a type error that variable `coro` is not a coroutine in file `serial.py`. I am trying to create  `AsyncModbusSerialClient(schedulers.ASYNC_IO, port=connPort, baudrate=connSpeed, method=connMethod, timeout=commTimeout)` in an existing running loop.

I don't think the coroutine was created correctly. What do you think?

Old:
`future = asyncio.run_coroutine_threadsafe(coro, loop=loop)` 

Proposed:
` future = asyncio.run_coroutine_threadsafe(coro(), loop=loop)`
   """Create asyncio based asynchronous serial clients.
    :param port:  Serial port
    :param framer: Modbus Framer
    :param kwargs: Serial port options
    :return: asyncio event loop and serial client
    """
    try:
        loop = kwargs.pop("loop", None) or asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()

    proto_cls = kwargs.get("proto_cls") or ModbusClientProtocol

    client = AsyncioModbusSerialClient(port, proto_cls, framer, loop, **kwargs)
    coro = client.connect
    if not loop.is_running():
        loop.run_until_complete(coro())
    else:  # loop is not asyncio.get_event_loop():
        future = asyncio.run_coroutine_threadsafe(coro, loop=loop) <- `Fails here`
        future.result()

    return loop, client
```
``` py
def async_io_factory(port=None, framer=None, **kwargs):
    """Create asyncio based asynchronous serial clients.
    :param port:  Serial port
    :param framer: Modbus Framer
    :param kwargs: Serial port options
    :return: asyncio event loop and serial client
    """
    try:
        loop = kwargs.pop("loop", None) or asyncio.get_running_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()

    proto_cls = kwargs.get("proto_cls") or ModbusClientProtocol

    client = AsyncioModbusSerialClient(port, proto_cls, framer, loop, **kwargs)
    coro = client.connect
    if not loop.is_running():
        loop.run_until_complete(coro())
    else:  # loop is not asyncio.get_event_loop():
        future = asyncio.run_coroutine_threadsafe(coro, loop=loop) <- `Fails here`
        future.result()

    return loop, client
```



Discussion:
Seems there are a problem, but your suggestion is not correct. You tell that the co-routine returns a pointer to a co-routine, that makes little sense.

Will look into it later.

Resolving patch:
diff --git a/pymodbus/client/asynchronous/factory/serial.py b/pymodbus/client/asynchronous/factory/serial.py
--- a/pymodbus/client/asynchronous/factory/serial.py
+++ b/pymodbus/client/asynchronous/factory/serial.py
@@ -89,7 +89,7 @@ def async_io_factory(port=None, framer=None, **kwargs):
     if not loop.is_running():
         loop.run_until_complete(coro())
     else:  # loop is not asyncio.get_event_loop():
-        future = asyncio.run_coroutine_threadsafe(coro, loop=loop)
+        future = asyncio.run_coroutine_threadsafe(coro(), loop=loop)
         future.result()
 
     return loop, client


Test patch:
diff --git a/test/test_client_async.py b/test/test_client_async.py
--- a/test/test_client_async.py
+++ b/test/test_client_async.py
@@ -93,7 +93,8 @@ def test_udp_asyncio_client(
             ("ascii", ModbusAsciiFramer),
         ],
     )
-    def test_serial_asyncio_client(  # pylint: disable=no-self-use
+    @pytest.mark.asyncio
+    async def test_serial_asyncio_client(  # pylint: disable=no-self-use
         self,
         mock_gather,  # pylint: disable=unused-argument
         mock_event_loop,
@@ -120,6 +121,7 @@ def test_serial_asyncio_client(  # pylint: disable=no-self-use
         assert client.parity == "E"  # nosec
         assert client.stopbits == 2  # nosec
         assert client.bytesize == 7  # nosec
+        asyncio.wait_for(client.connect(), timeout=1)
         client.stop()
         loop.stop()