From 5cb3d5271537f62d0deef0e1c4b2aaed9e867cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Wed, 10 Mar 2021 09:47:15 +0100 Subject: [PATCH 1/2] Raise exception for unexpected slave or function code in response. For RTU links, the specification (section 2.4.1) says the following: "When a reply is received, the Master checks the reply before starting the data processing. The checking may result in an error, for example a reply from an unexpected slave, or an error in the received frame." Technically, such frame errors include a mismatch in the response CRC or slave address. The CRC is handled by uModbus, but the slave address is not checked. If the function code does not match the request, uModbus will also happily parse it and return data which is valid, but unrelated to the application's request. The latter two should be regarded as a failure in send_message() as well, which this change does by throwing a specific exception. The standard goes on to specify that the "Response time-out" should be kept running if a response comes from the wrong slave. However, no such mechanism exists in uModbus yet (relying on the serial port RX timeout). This simple approach does not fix that inconsistency with the spec, but still allows better error handling in the application. --- umodbus/client/serial/rtu.py | 5 +++++ umodbus/exceptions.py | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/umodbus/client/serial/rtu.py b/umodbus/client/serial/rtu.py index 4c01580..770b5fc 100644 --- a/umodbus/client/serial/rtu.py +++ b/umodbus/client/serial/rtu.py @@ -53,6 +53,7 @@ WriteSingleRegister, WriteMultipleCoils, WriteMultipleRegisters) from umodbus.utils import recv_exactly +from umodbus.exceptions import ModbusFrameError def _create_request_adu(slave_id, req_pdu): @@ -217,6 +218,10 @@ def send_message(adu, serial_port): response_error_adu = recv_exactly(serial_port.read, exception_adu_size) raise_for_exception_adu(response_error_adu) + if response_error_adu[0:2] != adu[0:2]: + # Mismatch in response's slave address or function code + raise ModbusFrameError(response_error_adu) + expected_response_size = \ expected_response_pdu_size_from_request_pdu(adu[1:-2]) + 3 response_remainder = recv_exactly( diff --git a/umodbus/exceptions.py b/umodbus/exceptions.py index 85a9a56..57f74f8 100644 --- a/umodbus/exceptions.py +++ b/umodbus/exceptions.py @@ -3,6 +3,12 @@ class ModbusError(Exception): pass +class ModbusFrameError(ModbusError): + """Reply from an unexpected slave, or an error in the received frame.""" + + pass + + class IllegalFunctionError(ModbusError): """ The function code received in the request is not an allowable action for the server. From e28591ea220264b74bba12020ff575adb2d0ef6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Colomb?= Date: Wed, 10 Mar 2021 10:08:28 +0100 Subject: [PATCH 2/2] Fix typos in parse_response_adu() docstring. --- umodbus/client/serial/rtu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/umodbus/client/serial/rtu.py b/umodbus/client/serial/rtu.py index 770b5fc..c94ed20 100644 --- a/umodbus/client/serial/rtu.py +++ b/umodbus/client/serial/rtu.py @@ -174,9 +174,9 @@ def write_multiple_registers(slave_id, starting_address, values): def parse_response_adu(resp_adu, req_adu=None): """ Parse response ADU and return response data. Some functions require - request ADU to fully understand request ADU. + request ADU to fully understand response ADU. - :param resp_adu: Resonse ADU. + :param resp_adu: Response ADU. :param req_adu: Request ADU, default None. :return: Response data. """