Skip to content

Commit 1193758

Browse files
ahcm-devjaniversen
andauthored
Resubmit: Don't close/reopen tcp connection on single modbus message timeout (#2350)
Co-authored-by: jan iversen <jancasacondor@gmail.com>
1 parent f7089d5 commit 1193758

File tree

6 files changed

+30
-17
lines changed

6 files changed

+30
-17
lines changed

doc/source/_static/examples.tgz

-4 Bytes
Binary file not shown.

doc/source/_static/examples.zip

-1 Bytes
Binary file not shown.

doc/source/client.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ The line :mod:`result = await client.read_coils(2, 3, slave=1)` is an example of
166166

167167
The last line :mod:`client.close()` closes the connection and render the object inactive.
168168

169+
Retry logic for async clients
170+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
171+
172+
If no response is received to a request (call), it is retried (parameter retries) times, if not successful
173+
an exception response is returned, BUT the connection is not touched.
174+
175+
If 3 consequitve requests (calls) do not receive a response, the connection is terminated.
176+
169177
Development notes
170178
^^^^^^^^^^^^^^^^^
171179

pymodbus/client/base.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from pymodbus.exceptions import ConnectionException, ModbusIOException
1212
from pymodbus.framer import FRAMER_NAME_TO_CLASS, FramerBase, FramerType
1313
from pymodbus.logging import Log
14-
from pymodbus.pdu import DecodePDU, ModbusPDU
14+
from pymodbus.pdu import DecodePDU, ExceptionResponse, ModbusPDU
1515
from pymodbus.transaction import SyncModbusTransactionManager
1616
from pymodbus.transport import CommParams
1717
from pymodbus.utilities import ModbusTransactionState
@@ -50,6 +50,8 @@ def __init__(
5050
self.last_frame_end: float | None = 0
5151
self.silent_interval: float = 0
5252
self._lock = asyncio.Lock()
53+
self.accept_no_response_limit = 3
54+
self.count_no_responses = 0
5355

5456
@property
5557
def connected(self) -> bool:
@@ -115,11 +117,16 @@ async def async_execute(self, request) -> ModbusPDU:
115117
except asyncio.exceptions.TimeoutError:
116118
count += 1
117119
if count > self.retries:
118-
self.ctx.connection_lost(asyncio.TimeoutError("Server not responding"))
119-
raise ModbusIOException(
120-
f"ERROR: No response received after {self.retries} retries"
121-
)
122-
120+
if self.count_no_responses >= self.accept_no_response_limit:
121+
self.ctx.connection_lost(asyncio.TimeoutError("Server not responding"))
122+
raise ModbusIOException(
123+
f"ERROR: No response received of the last {self.accept_no_response_limit} request, CLOSING CONNECTION."
124+
)
125+
self.count_no_responses += 1
126+
Log.error(f"No response received after {self.retries} retries, continue with next request")
127+
return ExceptionResponse(request.function_code)
128+
129+
self.count_no_responses = 0
123130
return resp
124131

125132
def build_response(self, request: ModbusPDU):

pymodbus/pdu/__init__.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22
__all__ = [
33
"DecodePDU",
44
"ExceptionResponse",
5+
"ExceptionResponse",
56
"ModbusExceptions",
67
"ModbusPDU",
78
]
89

910
from pymodbus.pdu.decoders import DecodePDU
10-
from pymodbus.pdu.pdu import (
11-
ExceptionResponse,
12-
ModbusExceptions,
13-
ModbusPDU,
14-
)
11+
from pymodbus.pdu.pdu import ExceptionResponse, ModbusExceptions, ModbusPDU

test/sub_client/test_client.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
from pymodbus.client.mixin import ModbusClientMixin
2121
from pymodbus.datastore import ModbusSlaveContext
2222
from pymodbus.datastore.store import ModbusSequentialDataBlock
23-
from pymodbus.exceptions import ConnectionException, ModbusException, ModbusIOException
24-
from pymodbus.pdu import ModbusPDU
23+
from pymodbus.exceptions import ConnectionException, ModbusException
24+
from pymodbus.pdu import ExceptionResponse, ModbusPDU
2525
from pymodbus.transport import CommParams, CommType
2626

2727

@@ -436,8 +436,9 @@ async def test_client_execute_broadcast():
436436
transport = MockTransport(base, request)
437437
base.ctx.connection_made(transport=transport)
438438

439-
with pytest.raises(ModbusIOException):
440-
assert not await base.async_execute(request)
439+
# with pytest.raises(ModbusIOException):
440+
# assert not await base.async_execute(request)
441+
assert await base.async_execute(request)
441442

442443
async def test_client_protocol_retry():
443444
"""Test the client protocol execute method with retries."""
@@ -477,8 +478,8 @@ async def test_client_protocol_timeout():
477478
transport = MockTransport(base, request, retries=4)
478479
base.ctx.connection_made(transport=transport)
479480

480-
with pytest.raises(ModbusIOException):
481-
await base.async_execute(request)
481+
pdu = await base.async_execute(request)
482+
assert isinstance(pdu, ExceptionResponse)
482483
assert transport.retries == 1
483484

484485

0 commit comments

Comments
 (0)