Skip to content

Commit b41d2c4

Browse files
author
sanjay
committed
Merge branch 'pull/480' into dev
2 parents fde1232 + bb41048 commit b41d2c4

File tree

3 files changed

+56
-27
lines changed

3 files changed

+56
-27
lines changed

pymodbus/constants.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ class Defaults(Singleton):
1919
2020
The default modbus tcp over tls server port (802)
2121
22+
.. attribute:: Backoff
23+
24+
The default exponential backoff delay (0.3 seconds)
25+
2226
.. attribute:: Retries
2327
2428
The default number of times a client should retry the given
@@ -28,7 +32,12 @@ class Defaults(Singleton):
2832
2933
A flag indicating if a transaction should be retried in the
3034
case that an empty response is received. This is useful for
31-
slow clients that may need more time to process a requst.
35+
slow clients that may need more time to process a request.
36+
37+
.. attribute:: RetryOnInvalid
38+
39+
A flag indicating if a transaction should be retried in the
40+
case that an invalid response is received.
3241
3342
.. attribute:: Timeout
3443
@@ -104,8 +113,10 @@ class Defaults(Singleton):
104113
'''
105114
Port = 502
106115
TLSPort = 802
116+
Backoff = 0.3
107117
Retries = 3
108118
RetryOnEmpty = False
119+
RetryOnInvalid = False
109120
Timeout = 3
110121
Reconnects = 0
111122
TransactionId = 0

pymodbus/transaction.py

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import struct
77
import socket
8+
import time
89
from threading import RLock
910
from functools import partial
1011

@@ -62,7 +63,9 @@ def __init__(self, client, **kwargs):
6263
"""
6364
self.tid = Defaults.TransactionId
6465
self.client = client
66+
self.backoff = kwargs.get('backoff', Defaults.Backoff) or 0.3
6567
self.retry_on_empty = kwargs.get('retry_on_empty', Defaults.RetryOnEmpty)
68+
self.retry_on_invalid = kwargs.get('retry_on_invalid', Defaults.RetryOnInvalid)
6669
self.retries = kwargs.get('retries', Defaults.Retries) or 1
6770
self._transaction_lock = RLock()
6871
self._no_response_devices = []
@@ -146,34 +149,42 @@ def execute(self, request):
146149
full = True
147150
if not expected_response_length:
148151
expected_response_length = Defaults.ReadSize
149-
response, last_exception = self._transact(
150-
request,
151-
expected_response_length,
152-
full=full,
153-
broadcast=broadcast
154-
)
155-
if not response and (
156-
request.unit_id not in self._no_response_devices):
157-
self._no_response_devices.append(request.unit_id)
158-
elif request.unit_id in self._no_response_devices and response:
159-
self._no_response_devices.remove(request.unit_id)
160-
if not response and self.retry_on_empty and retries:
161-
while retries > 0:
162-
if hasattr(self.client, "state"):
163-
_logger.debug("RESETTING Transaction state to "
164-
"'IDLE' for retry")
165-
self.client.state = ModbusTransactionState.IDLE
166-
_logger.debug("Retry on empty - {}".format(retries))
167-
response, last_exception = self._transact(
168-
request,
169-
expected_response_length
170-
)
171-
if not response:
172-
retries -= 1
173-
continue
174-
# Remove entry
152+
retries += 1
153+
while retries > 0:
154+
response, last_exception = self._transact(
155+
request,
156+
expected_response_length,
157+
full=full,
158+
broadcast=broadcast
159+
)
160+
if not response and (
161+
request.unit_id not in self._no_response_devices):
162+
self._no_response_devices.append(request.unit_id)
163+
elif request.unit_id in self._no_response_devices and response:
175164
self._no_response_devices.remove(request.unit_id)
165+
if not response and self.retry_on_empty:
166+
_logger.debug("Retry on empty - {}".format(retries))
167+
elif not response:
168+
break
169+
if not self.retry_on_invalid:
176170
break
171+
mbap = self.client.framer.decode_data(response)
172+
if (mbap.get('unit') == request.unit_id):
173+
break
174+
if ('length' in mbap and expected_response_length and
175+
mbap.get('length') == expected_response_length):
176+
break
177+
_logger.debug("Retry on invalid - {}".format(retries))
178+
if hasattr(self.client, "state"):
179+
_logger.debug("RESETTING Transaction state to 'IDLE' for retry")
180+
self.client.state = ModbusTransactionState.IDLE
181+
if self.backoff:
182+
delay = 2 ** (self.retries - retries) * self.backoff
183+
time.sleep(delay)
184+
_logger.debug("Sleeping {}".format(delay))
185+
full = False
186+
broadcast = False
187+
retries -= 1
177188
addTransaction = partial(self.addTransaction,
178189
tid=request.transaction_id)
179190
self.client.framer.processIncomingPacket(response,

test/test_transaction.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ def testExecute(self):
123123
response = tm.execute(request)
124124
self.assertIsInstance(response, ModbusIOException)
125125

126+
# retry on invalid response
127+
tm.retry_on_invalid = True
128+
tm._recv = MagicMock(side_effect=iter([b'', b'abcdef', b'deadbe', b'123456']))
129+
# tm._transact.side_effect = [(b'', None), (b'abcdef', None)]
130+
response = tm.execute(request)
131+
self.assertIsInstance(response, ModbusIOException)
132+
126133
# Unable to decode response
127134
tm._recv = MagicMock(side_effect=ModbusIOException())
128135
# tm._transact.side_effect = [(b'abcdef', None)]

0 commit comments

Comments
 (0)