From 9a23e6b1ae043aa1aeb5a9ed65a356b46ad24e5a Mon Sep 17 00:00:00 2001 From: Claudio Catterina Date: Tue, 6 Jun 2017 10:51:44 +0200 Subject: [PATCH] Recall socket recv until get a complete response. Socket recv waits until it gets some data from the host but not necessarily the entire response that can be fragmented in many packets. In this case it receives an incomplete frame that is rejected by the frame handler. This commit solves this issue by recalling socket recv until get a complete modbus response. --- pymodbus/transaction.py | 48 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/pymodbus/transaction.py b/pymodbus/transaction.py index f97180088..390b51d4c 100644 --- a/pymodbus/transaction.py +++ b/pymodbus/transaction.py @@ -68,10 +68,38 @@ def _set_adu_size(self): def _calculate_response_length(self, expected_pdu_size): if self.base_adu_size == -1: - return 1024 + return None else: return self.base_adu_size + expected_pdu_size + def _calculate_exception_length(self): + ''' Returns the length of the Modbus Exception Response according to + the type of Framer. + ''' + if isinstance(self.client.framer, ModbusSocketFramer): + return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1) + elif isinstance(self.client.framer, ModbusAsciiFramer): + return self.base_adu_size + 4 # Fcode(2), ExcecptionCode(2) + elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)): + return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1) + + return None + + def _check_response(self, response): + ''' Checks if the response is a Modbus Exception. + ''' + if isinstance(self.client.framer, ModbusSocketFramer): + if len(response) >= 8 and byte2int(response[7]) > 128: + return False + elif isinstance(self.client.framer, ModbusAsciiFramer): + if len(response) >= 5 and int(response[3:5], 16) > 128: + return False + elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)): + if len(response) >= 2 and byte2int(response[1]) > 128: + return False + + return True + def execute(self, request): ''' Starts the producer to send the next request to consumer.write(Frame(request)) @@ -79,11 +107,12 @@ def execute(self, request): retries = self.retries request.transaction_id = self.getNextTID() _logger.debug("Running transaction %d" % request.transaction_id) + + expected_response_length = None if hasattr(request, "get_response_pdu_size"): response_pdu_size = request.get_response_pdu_size() - expected_response_length = self._calculate_response_length(response_pdu_size) - else: - expected_response_length = 1024 + if response_pdu_size: + expected_response_length = self._calculate_response_length(response_pdu_size) while retries > 0: try: @@ -92,11 +121,20 @@ def execute(self, request): if _logger.isEnabledFor(logging.DEBUG): _logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet])) self.client._send(packet) - result = self.client._recv(expected_response_length) + + exception = False + result = self.client._recv(expected_response_length or 1024) + while result and expected_response_length and len(result) < expected_response_length: + if not exception and not self._check_response(result): + exception = True + expected_response_length = self._calculate_exception_length() + continue + result += self.client._recv(expected_response_length - len(result)) if not result and self.retry_on_empty: retries -= 1 continue + if _logger.isEnabledFor(logging.DEBUG): _logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result])) self.client.framer.processIncomingPacket(result, self.addTransaction)