Repository: pymodbus-dev/pymodbus

Issue:
ModbusSerialClient.diag_query_data does not support byte array argument for msg
### Versions
- Python: 3.10.4
- OS: Windows 10
- Pymodbus: 3.2.2
- Modbus Hardware (if used): Custom device with embedded modbus server software in C.

### Pymodbus Specific
- Server: embedded (C)
- Client: rtu - sync

### Description
ModbusSerialClient.diag_query_data only accept one integer for msg argument.
diag_query_data msg argument is specified to be a bytearray but it calls pdu_diag.ReturnQueryDataRequest that take an integer msg argument. According to Modbus specification, diag_query_data should takes a byte array (lenght limited by the maximum modbus frame lenght).

### Code and Logs
```python
from pymodbus.client import ModbusSerialClient

client.connect()
print ("connected")
rr = client.diag_query_data(msg = 15, slave = 1)
print (rr.message[0])
test_list = [2,3,4]
test_byte_array = bytearray (test_list)
rr = client.diag_query_data(msg = test_byte_array, slave = 1)
print (rr.message[0])
client.close()
```

### Output
Exception has occurred: error
required argument is not an integer
  File "X:\R&D\Autres App\Projets en Cours\A026 - SxIP\Logiciel\Pyhton Lib\query_test.py", line 18, in <module>
    rr = client.diag_query_data(msg = test_byte_array, slave = 1)
struct.error: required argument is not an integer

connected
2023-06-28 12:15:38,348 MainThread      DEBUG    logging        :102      Current transaction state - IDLE
2023-06-28 12:15:38,350 MainThread      DEBUG    logging        :102      Running transaction 1
2023-06-28 12:15:38,351 MainThread      DEBUG    logging        :102      SEND: 0x1 0x8 0x0 0x0 0x0 0xf 0xa0 0xf
2023-06-28 12:15:38,352 MainThread      DEBUG    logging        :102      New Transaction state "SENDING"
2023-06-28 12:15:38,355 MainThread      DEBUG    logging        :102      Changing transaction state from "SENDING" to "WAITING FOR REPLY"
2023-06-28 12:15:38,476 MainThread      DEBUG    logging        :102      Changing transaction state from "WAITING FOR REPLY" to "PROCESSING REPLY"
2023-06-28 12:15:38,476 MainThread      DEBUG    logging        :102      RECV: 0x1 0x8 0x0 0x0 0x0 0xf 0xa0 0xf
2023-06-28 12:15:38,478 MainThread      DEBUG    logging        :102      Getting Frame - 0x8 0x0 0x0 0x0 0xf
2023-06-28 12:15:38,478 MainThread      DEBUG    logging        :102      Factory Response[DiagnosticStatusResponse': 8]
2023-06-28 12:15:38,480 MainThread      DEBUG    logging        :102      Frame advanced, resetting header!!
2023-06-28 12:15:38,481 MainThread      DEBUG    logging        :102      Adding transaction 1
2023-06-28 12:15:38,482 MainThread      DEBUG    logging        :102      Frame - [b'\x01\x08\x00\x00\x00\x0f\xa0\x0f'] not ready
2023-06-28 12:15:38,483 MainThread      DEBUG    logging        :102      Getting transaction 1
2023-06-28 12:15:38,483 MainThread      DEBUG    logging        :102      Changing transaction state from "PROCESSING REPLY" to "TRANSACTION_COMPLETE"
15
2023-06-28 12:15:38,485 MainThread      DEBUG    logging        :102      Current transaction state - TRANSACTION_COMPLETE
2023-06-28 12:15:38,485 MainThread      DEBUG    logging        :102      Running transaction 2



Discussion:
Please submit a pull request if you want change, but please check with the modbus standard if e,g, bytearray is allowed.
Hi janiverse,
In the modbus standad (Modbus application protocol 1_1b3) it is said that the data is Nx2bytes.
I mention a byte array because the diag_query_data function documentation indicate that msg argument should be a byte array but byte arrays return an exception. 
As I wrote feel free to submit a pull request alternatively wait until we come around to investigate it.
Ok! I'm new on Github and my python skills are poor. So I'm not sure to be able to pull a request that would be useful.

Resolving patch:
diff --git a/examples/client_calls.py b/examples/client_calls.py
--- a/examples/client_calls.py
+++ b/examples/client_calls.py
@@ -224,8 +224,13 @@ async def _execute_information_requests(client):
 async def _execute_diagnostic_requests(client):
     """Execute extended diagnostic requests."""
     _logger.info("### Running diagnostic requests.")
-    rr = _check_call(await client.execute(req_diag.ReturnQueryDataRequest(slave=SLAVE)))
-    assert not rr.message[0]
+    message = b"OK"
+    rr = _check_call(
+        await client.execute(
+            req_diag.ReturnQueryDataRequest(message=message, slave=SLAVE)
+        )
+    )
+    assert rr.message == message
 
     _check_call(
         await client.execute(req_diag.RestartCommunicationsOptionRequest(slave=SLAVE))
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -221,5 +221,8 @@ async def run_async_server(args):
 
 
 if __name__ == "__main__":
-    run_args = setup_server(description="Run asynchronous server.")
+    run_args = setup_server(
+        description="Run asynchronous server.",
+        cmdline=["--log", "debug", "--framer", "rtu"],
+    )
     asyncio.run(run_async_server(run_args), debug=True)
diff --git a/pymodbus/diag_message.py b/pymodbus/diag_message.py
--- a/pymodbus/diag_message.py
+++ b/pymodbus/diag_message.py
@@ -48,7 +48,7 @@
 
 from pymodbus.constants import ModbusPlusOperation, ModbusStatus
 from pymodbus.device import ModbusControlBlock
-from pymodbus.exceptions import NotImplementedException
+from pymodbus.exceptions import ModbusException, NotImplementedException
 from pymodbus.pdu import ModbusRequest, ModbusResponse
 from pymodbus.utilities import pack_bitstring
 
@@ -87,7 +87,7 @@ def encode(self):
                 packet += self.message.encode()
             elif isinstance(self.message, bytes):
                 packet += self.message
-            elif isinstance(self.message, list):
+            elif isinstance(self.message, (list, tuple)):
                 for piece in self.message:
                     packet += struct.pack(">H", piece)
             elif isinstance(self.message, int):
@@ -101,8 +101,11 @@ def decode(self, data):
         """
         (
             self.sub_function_code,  # pylint: disable=attribute-defined-outside-init
-            self.message,
-        ) = struct.unpack(">HH", data)
+        ) = struct.unpack(">H", data[:2])
+        if self.sub_function_code == ReturnQueryDataRequest.sub_function_code:
+            self.message = data[2:]
+        else:
+            (self.message,) = struct.unpack(">H", data[2:])
 
     def get_response_pdu_size(self):
         """Get response pdu size.
@@ -158,18 +161,19 @@ def decode(self, data):
 
         :param data: The data to decode into the function code
         """
-        word_len = len(data) // 2
-        if len(data) % 2:
-            word_len += 1
-            data += b"0"
-        data = struct.unpack(">" + "H" * word_len, data)
         (
             self.sub_function_code,  # pylint: disable=attribute-defined-outside-init
-            self.message,
-        ) = (
-            data[0],
-            data[1:],
-        )
+        ) = struct.unpack(">H", data[:2])
+        data = data[2:]
+        if self.sub_function_code == ReturnQueryDataRequest.sub_function_code:
+            self.message = data
+        else:
+            word_len = len(data) // 2
+            if len(data) % 2:
+                word_len += 1
+                data += b"0"
+            data = struct.unpack(">" + "H" * word_len, data)
+            self.message = data
 
 
 class DiagnosticStatusSimpleRequest(DiagnosticStatusRequest):
@@ -231,16 +235,15 @@ class ReturnQueryDataRequest(DiagnosticStatusRequest):
 
     sub_function_code = 0x0000
 
-    def __init__(self, message=0x0000, slave=None, **kwargs):
+    def __init__(self, message=b"\x00\x00", slave=None, **kwargs):
         """Initialize a new instance of the request.
 
         :param message: The message to send to loopback
         """
         DiagnosticStatusRequest.__init__(self, slave=slave, **kwargs)
-        if isinstance(message, list):
-            self.message = message
-        else:
-            self.message = [message]
+        if not isinstance(message, bytes):
+            raise ModbusException(f"message({type(message)}) must be bytes")
+        self.message = message
 
     def execute(self, *_args):
         """Execute the loopback request (builds the response).
@@ -260,16 +263,15 @@ class ReturnQueryDataResponse(DiagnosticStatusResponse):
 
     sub_function_code = 0x0000
 
-    def __init__(self, message=0x0000, **kwargs):
+    def __init__(self, message=b"\x00\x00", **kwargs):
         """Initialize a new instance of the response.
 
         :param message: The message to loopback
         """
         DiagnosticStatusResponse.__init__(self, **kwargs)
-        if isinstance(message, list):
-            self.message = message
-        else:
-            self.message = [message]
+        if not isinstance(message, bytes):
+            raise ModbusException(f"message({type(message)}) must be bytes")
+        self.message = message
 
 
 # ---------------------------------------------------------------------------#


Test patch:
diff --git a/test/test_client.py b/test/test_client.py
--- a/test/test_client.py
+++ b/test/test_client.py
@@ -33,7 +33,7 @@
             {},
             {"address": 0x01},
             {"address": 0x01, "value": False},
-            {"msg": "long message"},
+            {"msg": b"long message"},
             {"toggle": False},
             {"address": 0x01, "values": [False, True]},
             {"address": 0x01, "values": [22, 44]},
diff --git a/test/test_diag_messages.py b/test/test_diag_messages.py
--- a/test/test_diag_messages.py
+++ b/test/test_diag_messages.py
@@ -166,16 +166,16 @@ def test_diagnostic_execute(self):
 
     def test_return_query_data_request(self):
         """Testing diagnostic message execution"""
-        message = ReturnQueryDataRequest([0x0000] * 2)
+        message = ReturnQueryDataRequest(b"\x00\x00\x00\x00")
         assert message.encode() == b"\x00\x00\x00\x00\x00\x00"
-        message = ReturnQueryDataRequest(0x0000)
+        message = ReturnQueryDataRequest(b"\x00\x00")
         assert message.encode() == b"\x00\x00\x00\x00"
 
     def test_return_query_data_response(self):
         """Testing diagnostic message execution"""
-        message = ReturnQueryDataResponse([0x0000] * 2)
+        message = ReturnQueryDataResponse(b"\x00\x00\x00\x00")
         assert message.encode() == b"\x00\x00\x00\x00\x00\x00"
-        message = ReturnQueryDataResponse(0x0000)
+        message = ReturnQueryDataResponse(b"\x00\x00")
         assert message.encode() == b"\x00\x00\x00\x00"
 
     def test_restart_cmmunications_option(self):