Repository: pymodbus-dev/pymodbus

Issue:
Inconsistent operation with RTU client
<!--
Before opening a new issue, make sure you do the following:

- Check that your issue isn't already filed: https://github.com/pymodbus-dev/pymodbus/issues
- Check the discussions forum https://github.com/pymodbus-dev/pymodbus/discussions
- Prepare a short, runnable example that reproduce the issue with the latest development version of Pymodbus
-->

### Versions

- Python: 3.10.4
- OS: macOS 10.13.6
- Pymodbus: 3.3.2
- Modbus Hardware: USB RS485 adapter communicating with a motor controller

### Pymodbus Specific

- Client: rtu - async

### Description

A motor  controller that I am trying to communicate with recognizes three commands; read holding registers, read input registers, and write holding registers. I have had inconsistent results with all three, sometimes they work, sometimes not. My hunch is that the problem is related to the fact that all the bytes transmitted are always echoed back as received data. The code that I have included reliably reads five input registers, but fails when reading four. I attached a second USB-RS485 adapter in order to monitor all the traffic on the RS485 bus and from that data stream everything looks exactly right. The response from the motor controller is correct, but pymodbus fails with a timeout error.

### Code

```python
import sys
import pymodbus
print('Python version:  ',sys.version.split(' ')[0])
print('pymodbus version:',pymodbus.__version__)
print('macOS High Sierra 10.13.6')

import asyncio
from pymodbus.client.serial import AsyncModbusSerialClient as ModbusClient

import logging
logging.basicConfig()
log = logging.getLogger()
log.setLevel(logging.DEBUG)

async def main():
    client = ModbusClient( method='rtu'
                         , port='/dev/cu.usbserial-AB0KJMPA'
                         , baudrate=19200
                         , bytesize=8, parity='N', stopbits=2
                         , handle_local_echo=True
                         , strict=False
                         , timeout=2
                         )
    await client.connect()
    for n in [5,4]:
        print('\nread %d input registers **********'%n)
        res = await client.read_input_registers( address=0
                                               , count=n
                                               , slave=1 )
        print('input register data:')
        for rv in res.registers:
            print('\t',rv)
    client.close()

if __name__ == "__main__":
    asyncio.run(main())
```

# Log
```
Python version:   3.10.4
pymodbus version: 3.3.2
macOS High Sierra 10.13.6
DEBUG:asyncio:Using selector: KqueueSelector
DEBUG:pymodbus.logging:Connecting to None.
DEBUG:pymodbus.logging:Connecting comm
DEBUG:pymodbus.logging:Connected to comm

read 5 input registers **********
DEBUG:pymodbus.logging:send: 0x1 0x4 0x0 0x0 0x0 0x5 0x30 0x9
DEBUG:pymodbus.logging:Adding transaction 1
DEBUG:pymodbus.logging:recv: 0x1 0x4 0x0 0x0 0x0 0x5 0x30 0x9
DEBUG:pymodbus.logging:recv: 0x1 0x4 0x0 0x0 0x0 0x5 0x30 0x9
DEBUG:pymodbus.logging:Frame check failed, ignoring!!
DEBUG:pymodbus.logging:Resetting frame - Current Frame in buffer - 0x1 0x4 0x0 0x0 0x0 0x5 0x30 0x9
DEBUG:pymodbus.logging:Frame - [b'\x01\x04\x00\x00\x00\x050\t'] not ready
DEBUG:pymodbus.logging:recv: 0x1 0x4 0xa 0xf0 0x0 0xf0 0x1 0xf0 0x2 0x4 0x3 0x0 0x1 0x96 0xfa
DEBUG:pymodbus.logging:recv: 0x1 0x4 0xa 0xf0 0x0 0xf0 0x1 0xf0 0x2 0x4 0x3 0x0 0x1 0x96 0xfa
DEBUG:pymodbus.logging:Frame check failed, ignoring!!
DEBUG:pymodbus.logging:Resetting frame - Current Frame in buffer - 0x0 0x5 0x30 0x9 0x1 0x4 0xa 0xf0 0x0 0xf0 0x1 0xf0 0x2 0x4 0x3 0x0 0x1 0x96 0xfa
DEBUG:pymodbus.logging:Frame check failed, ignoring!!
DEBUG:pymodbus.logging:Resetting frame - Current Frame in buffer - 0x9 0x1 0x4 0xa 0xf0 0x0 0xf0 0x1 0xf0 0x2 0x4 0x3 0x0 0x1 0x96 0xfa
DEBUG:pymodbus.logging:Getting Frame - 0x4 0xa 0xf0 0x0 0xf0 0x1 0xf0 0x2 0x4 0x3 0x0 0x1
DEBUG:pymodbus.logging:Factory Response[ReadInputRegistersResponse': 4]
DEBUG:pymodbus.logging:Frame advanced, resetting header!!
DEBUG:pymodbus.logging:Getting transaction 1
input register data:
	 61440
	 61441
	 61442
	 1027
	 1

read 4 input registers **********
DEBUG:pymodbus.logging:send: 0x1 0x4 0x0 0x0 0x0 0x4 0xf1 0xc9
DEBUG:pymodbus.logging:Adding transaction 1
DEBUG:pymodbus.logging:recv: 0x1 0x4 0x0 0x0 0x0 0x4 0xf1 0xc9
DEBUG:pymodbus.logging:recv: 0x1 0x4 0x0 0x0 0x0 0x4 0xf1 0xc9
DEBUG:pymodbus.logging:Frame check failed, ignoring!!
DEBUG:pymodbus.logging:Resetting frame - Current Frame in buffer - 0x1 0x4 0x0 0x0 0x0 0x4 0xf1 0xc9
DEBUG:pymodbus.logging:Frame - [b'\x01\x04\x00\x00\x00\x04\xf1\xc9'] not ready
DEBUG:pymodbus.logging:recv: 0x1 0x4 0x8 0xf0 0x0 0xf0 0x1 0xf0 0x2 0x3 0xfb 0xd1 0xca
DEBUG:pymodbus.logging:recv: 0x1 0x4 0x8 0xf0 0x0 0xf0 0x1 0xf0 0x2 0x3 0xfb 0xd1 0xca
DEBUG:pymodbus.logging:Frame - [b'\x01\x04\x08\xf0\x00\xf0\x01\xf0\x02\x03\xfb\xd1\xca'] not ready
DEBUG:pymodbus.logging:Connection lost comm due to None
DEBUG:pymodbus.logging:Getting transaction 1
Traceback (most recent call last):
  File "/Applications/AAApplications/mambaforge/envs/mspy2/lib/python3.10/asyncio/tasks.py", line 456, in wait_for
    return fut.result()
asyncio.exceptions.CancelledError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/analog/Projects/Current_Projects/Doug/Brushless DC motor/utilities/code_test.py", line 36, in <module>
    asyncio.run(main())
  File "/Applications/AAApplications/mambaforge/envs/mspy2/lib/python3.10/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Applications/AAApplications/mambaforge/envs/mspy2/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
    return future.result()
  File "/Users/analog/Projects/Current_Projects/Doug/Brushless DC motor/utilities/code_test.py", line 27, in main
    res = await client.read_input_registers( address=0
  File "/Users/analog/Projects/Current_Projects/Doug/Brushless DC motor/utilities/pymodbus/client/base.py", line 198, in async_execute
    resp = await asyncio.wait_for(req, timeout=self.params.timeout)
  File "/Applications/AAApplications/mambaforge/envs/mspy2/lib/python3.10/asyncio/tasks.py", line 458, in wait_for
    raise exceptions.TimeoutError() from exc
asyncio.exceptions.TimeoutError
logout
Saving session...
...copying shared history...
...saving history...truncating history files...
...completed.

[Process completed]


```

# RS485 data stream
```
01 04 00 00 00 05 30 09 01 04 0A F0 00 F0 01 F0 
02 04 03 00 01 96 FA 01 04 00 00 00 04 F1 C9 01 
04 08 F0 00 F0 01 F0 02 03 FB D1 CA
```



Discussion:
Thanks, this looks like a bug in the sync client, we have not tested that for a while (we are working on the async client), but let me test it.
method= do not exist please read the documentation, however you might be in luck that rtu correspond to the default.

Resolving patch:
diff --git a/pymodbus/client/base.py b/pymodbus/client/base.py
--- a/pymodbus/client/base.py
+++ b/pymodbus/client/base.py
@@ -189,7 +189,6 @@ async def async_execute(self, request=None):
         """Execute requests asynchronously."""
         request.transaction_id = self.transaction.getNextTID()
         packet = self.framer.buildPacket(request)
-        Log.debug("send: {}", packet, ":hex")
         self.transport_send(packet)
         req = self._build_response(request.transaction_id)
         if self.params.broadcast_enable and not request.slave_id:
@@ -217,7 +216,6 @@ def callback_data(self, data: bytes, addr: tuple = None) -> int:
 
         returns number of bytes consumed
         """
-        Log.debug("recv: {} addr={}", data, ":hex", addr)
         self.framer.processIncomingPacket(data, self._handle_response, slave=0)
         return len(data)
 
diff --git a/pymodbus/transport/transport.py b/pymodbus/transport/transport.py
--- a/pymodbus/transport/transport.py
+++ b/pymodbus/transport/transport.py
@@ -282,20 +282,15 @@ def data_received(self, data: bytes):
 
         :param data: non-empty bytes object with incoming data.
         """
-        Log.debug("recv: {}", data, ":hex")
-        if self.comm_params.handle_local_echo and self.sent_buffer == data:
-            self.sent_buffer = b""
-            return
-        self.recv_buffer += data
-        cut = self.callback_data(self.recv_buffer)
-        self.recv_buffer = self.recv_buffer[cut:]
+        self.datagram_received(data, None)
 
     def datagram_received(self, data: bytes, addr: tuple):
         """Receive datagram (UDP connections)."""
-        Log.debug("recv: {} addr={}", data, ":hex", addr)
         if self.comm_params.handle_local_echo and self.sent_buffer == data:
+            Log.debug("recv skipping (local_echo): {} addr={}", data, ":hex", addr)
             self.sent_buffer = b""
             return
+        Log.debug("recv: {} addr={}", data, ":hex", addr)
         self.recv_buffer += data
         cut = self.callback_data(self.recv_buffer, addr=addr)
         self.recv_buffer = self.recv_buffer[cut:]


Test patch: