Repository: pymodbus-dev/pymodbus

Issue:
ReadFileRecordRequest.execute() doesn't wait for complete pdu

### Versions

* Python: 3.8.10
* OS: Ubuntu 20.04, Windows 10
* Pymodbus: 2.3.5
* Modbus Hardware (if used): Hach Company reference implementation - slave

### Pymodbus Specific
* Server: none
* Client: /rtu - sync

### Description

When trying to read a file using the ReadFileRecordRequest (Function code 20), the pymodbus.transaction transitions from WAITING FOR REPLY to PROCESSING REPLY before all of the response has been received. This results in the client application receiving many "Modbus Error: [Input/Output] No Response received from the remote unit/Unable to decode response". The application must continue the retry roulette until it receives a complete record response.

One problem is that there is no get_response_pdu_size() member defined for ReadFileRecordRequest. 

### Code and Logs

```python

             rec = FileRecord(reference_type=self.REFERENCE_TYPE, file_number=file_number, record_number=(i*RECORD_LENGTH), record_length=rec_len)
            records.append(rec)

            request = ReadFileRecordRequest(records=records, unit=self.slave_unit)
            response = self.client.execute(request)


DEBUG:pymodbus.transaction:SEND: 0x1 0x14 0x7 0x6 0x0 0x18 0x0 0x0 0x0 0x1a 0x58 0xed
DEBUG:pymodbus.framer.rtu_framer:Changing state to IDLE - Last Frame End - None, Current Time stamp - 1643687749.313818
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: 0x5e 0x20 0x8e 0x69 0xc2 0x29 0xb2 0x94 0xbf 0xcc 0x5b 0xf6 0x60 0x1e 0x51 0x5b 0xc2 0x5 0xc5 0xba 0x40 0x0
DEBUG:pymodbus.framer.rtu_framer:Frame check failed, ignoring!!
DEBUG:pymodbus.framer.rtu_framer:Resetting frame - Current Frame in buffer - 0x5e 0x20 0x8e 0x69 0xc2 0x29 0xb2 0x94 0xbf 0xcc 0x5b 0xf6 0x60 0x1e 0x51 0x5b 0xc2 0x5 0xc5 0xba 0x40 0x0
DEBUG:pymodbus.transaction:Getting transaction 1
DEBUG:pymodbus.transaction:Changing transaction state from 'PROCESSING REPLY' to 'TRANSACTION_COMPLETE'
WARNING:probe:file read response is Modbus Error: [Input/Output] No Response received from the remote unit/Unable to decode response.


```



Discussion:

Resolving patch:
diff --git a/pymodbus/framer/rtu_framer.py b/pymodbus/framer/rtu_framer.py
--- a/pymodbus/framer/rtu_framer.py
+++ b/pymodbus/framer/rtu_framer.py
@@ -153,11 +153,9 @@ def populateHeader(self, data=None):  # pylint: disable=invalid-name
         `self._buffer` is not yet long enough.
         """
         data = data if data is not None else self._buffer
-        self._header["uid"] = int(data[0])
-        func_code = int(data[1])
-        pdu_class = self.decoder.lookupPduClass(func_code)
-        size = pdu_class.calculateRtuFrameSize(data)
-        self._header["len"] = size
+        self._header['uid'] = int(data[0])
+        size = self.get_expected_response_length(data)
+        self._header['len'] = size
 
         if len(data) < size:
             # crc yet not available
@@ -330,5 +328,15 @@ def getRawFrame(self):  # pylint: disable=invalid-name
         _logger.debug(txt)
         return self._buffer
 
+    def get_expected_response_length(self, data):
+        """Get the expected response length.
+
+        :param data: Message data read so far
+        :raises IndexError: If not enough data to read byte count
+        :return: Total frame size
+        """
+        func_code = int(data[1])
+        pdu_class = self.decoder.lookupPduClass(func_code)
+        return pdu_class.calculateRtuFrameSize(data)
 
 # __END__
diff --git a/pymodbus/transaction.py b/pymodbus/transaction.py
--- a/pymodbus/transaction.py
+++ b/pymodbus/transaction.py
@@ -343,7 +343,7 @@ def _recv(self, expected_response_length, full):  # NOSONAR
             if isinstance(self.client.framer, ModbusSocketFramer):
                 min_size = 8
             elif isinstance(self.client.framer, ModbusRtuFramer):
-                min_size = 2
+                min_size = 4
             elif isinstance(self.client.framer, ModbusAsciiFramer):
                 min_size = 5
             elif isinstance(self.client.framer, ModbusBinaryFramer):
@@ -362,7 +362,7 @@ def _recv(self, expected_response_length, full):  # NOSONAR
                 if isinstance(self.client.framer, ModbusSocketFramer):
                     func_code = int(read_min[-1])
                 elif isinstance(self.client.framer, ModbusRtuFramer):
-                    func_code = int(read_min[-1])
+                    func_code = int(read_min[1])
                 elif isinstance(self.client.framer, ModbusAsciiFramer):
                     func_code = int(read_min[3:5], 16)
                 elif isinstance(self.client.framer, ModbusBinaryFramer):
@@ -378,6 +378,12 @@ def _recv(self, expected_response_length, full):  # NOSONAR
                         )
                         length = struct.unpack(">H", read_min[4:6])[0] - 1
                         expected_response_length = h_size + length
+                    elif expected_response_length is None and isinstance(self.client.framer, ModbusRtuFramer):
+                        try:
+                            expected_response_length = self.client.framer.get_expected_response_length(read_min)
+                        except IndexError:
+                            # Could not determine response length with available bytes
+                            pass
                     if expected_response_length is not None:
                         expected_response_length -= min_size
                         total = expected_response_length + min_size


Test patch: