Skip to content

Commit 15942e8

Browse files
committed
Retry updates on empty or error response for sync clients
1 parent 3ed12d5 commit 15942e8

File tree

3 files changed

+74
-14
lines changed

3 files changed

+74
-14
lines changed

pymodbus/framer/rtu_framer.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,11 @@ def sendPacket(self, message):
282282
# Recovering from last error ??
283283
time.sleep(self.client.silent_interval)
284284
self.client.state = ModbusTransactionState.IDLE
285+
elif self.client.state == ModbusTransactionState.RETRYING:
286+
# Simple lets settle down!!!
287+
# To check for higher baudrates
288+
time.sleep(self.client.timeout)
289+
break
285290
else:
286291
if time.time() > timeout:
287292
_logger.debug("Spent more time than the read time out, "

pymodbus/transaction.py

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ def __init__(self, client, **kwargs):
6464
self.tid = Defaults.TransactionId
6565
self.client = client
6666
self.backoff = kwargs.get('backoff', Defaults.Backoff) or 0.3
67-
self.retry_on_empty = kwargs.get('retry_on_empty', Defaults.RetryOnEmpty)
68-
self.retry_on_invalid = kwargs.get('retry_on_invalid', Defaults.RetryOnInvalid)
67+
self.retry_on_empty = kwargs.get('retry_on_empty',
68+
Defaults.RetryOnEmpty)
69+
self.retry_on_invalid = kwargs.get('retry_on_invalid',
70+
Defaults.RetryOnInvalid)
6971
self.retries = kwargs.get('retries', Defaults.Retries) or 1
7072
self._transaction_lock = RLock()
7173
self._no_response_devices = []
@@ -108,6 +110,25 @@ def _calculate_exception_length(self):
108110

109111
return None
110112

113+
def _validate_response(self, request, response, exp_resp_len):
114+
"""
115+
Validate Incoming response against request
116+
:param request: Request sent
117+
:param response: Response received
118+
:param exp_resp_len: Expected response length
119+
:return: New transactions state
120+
"""
121+
if not response:
122+
return False
123+
124+
mbap = self.client.framer.decode_data(response)
125+
if mbap.get('unit') != request.unit_id or mbap.get('fcode') != request.function_code:
126+
return False
127+
128+
if 'length' in mbap and exp_resp_len:
129+
return mbap.get('length') == exp_resp_len
130+
return True
131+
111132
def execute(self, request):
112133
""" Starts the producer to send the next request to
113134
consumer.write(Frame(request))
@@ -132,6 +153,7 @@ def execute(self, request):
132153
self._transact(request, None, broadcast=True)
133154
response = b'Broadcast write sent - no response expected'
134155
else:
156+
invalid_response = False
135157
expected_response_length = None
136158
if not isinstance(self.client.framer, ModbusSocketFramer):
137159
if hasattr(request, "get_response_pdu_size"):
@@ -149,35 +171,47 @@ def execute(self, request):
149171
full = True
150172
if not expected_response_length:
151173
expected_response_length = Defaults.ReadSize
152-
retries += 1
174+
# retries += 1
153175
while retries > 0:
154176
response, last_exception = self._transact(
155177
request,
156178
expected_response_length,
157179
full=full,
158180
broadcast=broadcast
159181
)
160-
if not response and (
161-
request.unit_id not in self._no_response_devices):
182+
valid_response = self._validate_response(
183+
request, response, expected_response_length
184+
)
185+
if not response and request.unit_id \
186+
not in self._no_response_devices:
162187
self._no_response_devices.append(request.unit_id)
163188
elif request.unit_id in self._no_response_devices and response:
164189
self._no_response_devices.remove(request.unit_id)
165190
if not response and self.retry_on_empty:
166191
_logger.debug("Retry on empty - {}".format(retries))
192+
retries -= 1
193+
_logger.debug("Changing transaction state from "
194+
"'WAITING_FOR_REPLY' to 'RETRYING'")
195+
self.client.state = ModbusTransactionState.RETRYING
196+
continue
167197
elif not response:
168198
break
169-
if not self.retry_on_invalid:
170-
break
171199
mbap = self.client.framer.decode_data(response)
172-
if (mbap.get('unit') == request.unit_id):
200+
if mbap.get('unit') == request.unit_id:
173201
break
174202
if ('length' in mbap and expected_response_length and
175-
mbap.get('length') == expected_response_length):
203+
mbap.get('length') == expected_response_length and
204+
mbap.get('fcode') == request.function_code):
205+
break
206+
else:
207+
invalid_response = True
208+
if invalid_response and not self.retry_on_invalid:
176209
break
177210
_logger.debug("Retry on invalid - {}".format(retries))
178211
if hasattr(self.client, "state"):
179-
_logger.debug("RESETTING Transaction state to 'IDLE' for retry")
180-
self.client.state = ModbusTransactionState.IDLE
212+
_logger.debug("RESETTING Transaction "
213+
"state to 'RETRY' for retry")
214+
self.client.state = ModbusTransactionState.RETRYING
181215
if self.backoff:
182216
delay = 2 ** (self.retries - retries) * self.backoff
183217
time.sleep(delay)
@@ -206,14 +240,26 @@ def execute(self, request):
206240
"'TRANSACTION_COMPLETE'")
207241
self.client.state = (
208242
ModbusTransactionState.TRANSACTION_COMPLETE)
243+
self.client.close()
209244
return response
210245
except ModbusIOException as ex:
211246
# Handle decode errors in processIncomingPacket method
212247
_logger.exception(ex)
248+
self.client.close()
213249
self.client.state = ModbusTransactionState.TRANSACTION_COMPLETE
214250
return ex
215251

216-
def _transact(self, packet, response_length, full=False, broadcast=False):
252+
def _retry(self, packet, response_length, full=False):
253+
self.client.connect()
254+
in_waiting = self.client._in_waiting()
255+
if in_waiting:
256+
if response_length == in_waiting:
257+
result = self._recv(response_length, full)
258+
return result, None
259+
return self._transact(packet, response_length, full=full)
260+
261+
def _transact(self, packet, response_length,
262+
full=False, broadcast=False):
217263
"""
218264
Does a Write and Read transaction
219265
:param packet: packet to be sent
@@ -229,6 +275,11 @@ def _transact(self, packet, response_length, full=False, broadcast=False):
229275
if _logger.isEnabledFor(logging.DEBUG):
230276
_logger.debug("SEND: " + hexlify_packets(packet))
231277
size = self._send(packet)
278+
if isinstance(size, bytes) and self.client.state == ModbusTransactionState.RETRYING:
279+
_logger.debug("Changing transaction state from "
280+
"'RETRYING' to 'PROCESSING REPLY'")
281+
self.client.state = ModbusTransactionState.PROCESSING_REPLY
282+
return size, None
232283
if broadcast:
233284
if size:
234285
_logger.debug("Changing transaction state from 'SENDING' "
@@ -244,6 +295,7 @@ def _transact(self, packet, response_length, full=False, broadcast=False):
244295
if local_echo_packet != packet:
245296
return b'', "Wrong local echo"
246297
result = self._recv(response_length, full)
298+
# result2 = self._recv(response_length, full)
247299
if _logger.isEnabledFor(logging.DEBUG):
248300
_logger.debug("RECV: " + hexlify_packets(result))
249301

@@ -255,7 +307,7 @@ def _transact(self, packet, response_length, full=False, broadcast=False):
255307
result = b''
256308
return result, last_exception
257309

258-
def _send(self, packet):
310+
def _send(self, packet, retrying=False):
259311
return self.client.framer.sendPacket(packet)
260312

261313
def _recv(self, expected_response_length, full):

pymodbus/utilities.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class ModbusTransactionState(object):
2020
PROCESSING_REPLY = 4
2121
PROCESSING_ERROR = 5
2222
TRANSACTION_COMPLETE = 6
23+
RETRYING = 7
24+
NO_RESPONSE_STATE = 8
2325

2426
@classmethod
2527
def to_string(cls, state):
@@ -30,7 +32,8 @@ def to_string(cls, state):
3032
ModbusTransactionState.WAITING_TURNAROUND_DELAY: "WAITING_TURNAROUND_DELAY",
3133
ModbusTransactionState.PROCESSING_REPLY: "PROCESSING_REPLY",
3234
ModbusTransactionState.PROCESSING_ERROR: "PROCESSING_ERROR",
33-
ModbusTransactionState.TRANSACTION_COMPLETE: "TRANSACTION_COMPLETE"
35+
ModbusTransactionState.TRANSACTION_COMPLETE: "TRANSACTION_COMPLETE",
36+
ModbusTransactionState.RETRYING: "RETRYING TRANSACTION",
3437
}
3538
return states.get(state, None)
3639

0 commit comments

Comments
 (0)