Skip to content

Commit 8b1b9b9

Browse files
ccatterinadhoomakethu
authored andcommitted
Recall socket recv until get a complete response. (#180)
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.
1 parent 96627c9 commit 8b1b9b9

File tree

1 file changed

+43
-5
lines changed

1 file changed

+43
-5
lines changed

pymodbus/transaction.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,51 @@ def _set_adu_size(self):
6868

6969
def _calculate_response_length(self, expected_pdu_size):
7070
if self.base_adu_size == -1:
71-
return 1024
71+
return None
7272
else:
7373
return self.base_adu_size + expected_pdu_size
7474

75+
def _calculate_exception_length(self):
76+
''' Returns the length of the Modbus Exception Response according to
77+
the type of Framer.
78+
'''
79+
if isinstance(self.client.framer, ModbusSocketFramer):
80+
return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1)
81+
elif isinstance(self.client.framer, ModbusAsciiFramer):
82+
return self.base_adu_size + 4 # Fcode(2), ExcecptionCode(2)
83+
elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)):
84+
return self.base_adu_size + 2 # Fcode(1), ExcecptionCode(1)
85+
86+
return None
87+
88+
def _check_response(self, response):
89+
''' Checks if the response is a Modbus Exception.
90+
'''
91+
if isinstance(self.client.framer, ModbusSocketFramer):
92+
if len(response) >= 8 and byte2int(response[7]) > 128:
93+
return False
94+
elif isinstance(self.client.framer, ModbusAsciiFramer):
95+
if len(response) >= 5 and int(response[3:5], 16) > 128:
96+
return False
97+
elif isinstance(self.client.framer, (ModbusRtuFramer, ModbusBinaryFramer)):
98+
if len(response) >= 2 and byte2int(response[1]) > 128:
99+
return False
100+
101+
return True
102+
75103
def execute(self, request):
76104
''' Starts the producer to send the next request to
77105
consumer.write(Frame(request))
78106
'''
79107
retries = self.retries
80108
request.transaction_id = self.getNextTID()
81109
_logger.debug("Running transaction %d" % request.transaction_id)
110+
111+
expected_response_length = None
82112
if hasattr(request, "get_response_pdu_size"):
83113
response_pdu_size = request.get_response_pdu_size()
84-
expected_response_length = self._calculate_response_length(response_pdu_size)
85-
else:
86-
expected_response_length = 1024
114+
if response_pdu_size:
115+
expected_response_length = self._calculate_response_length(response_pdu_size)
87116

88117
while retries > 0:
89118
try:
@@ -92,11 +121,20 @@ def execute(self, request):
92121
if _logger.isEnabledFor(logging.DEBUG):
93122
_logger.debug("send: " + " ".join([hex(byte2int(x)) for x in packet]))
94123
self.client._send(packet)
95-
result = self.client._recv(expected_response_length)
124+
125+
exception = False
126+
result = self.client._recv(expected_response_length or 1024)
127+
while result and expected_response_length and len(result) < expected_response_length:
128+
if not exception and not self._check_response(result):
129+
exception = True
130+
expected_response_length = self._calculate_exception_length()
131+
continue
132+
result += self.client._recv(expected_response_length - len(result))
96133

97134
if not result and self.retry_on_empty:
98135
retries -= 1
99136
continue
137+
100138
if _logger.isEnabledFor(logging.DEBUG):
101139
_logger.debug("recv: " + " ".join([hex(byte2int(x)) for x in result]))
102140
self.client.framer.processIncomingPacket(result, self.addTransaction)

0 commit comments

Comments
 (0)