Skip to content

Commit f531e45

Browse files
committed
Merge branch 'dev'
2 parents 56eefe6 + 7bf2065 commit f531e45

File tree

10 files changed

+100
-146
lines changed

10 files changed

+100
-146
lines changed

CHANGELOG.rst

+4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ helps make pymodbus a better product.
77

88
:ref:`Authors`: contains a complete list of volunteers have contributed to each major version.
99

10+
Version 3.8.6
11+
-------------
12+
* Allow id=0 and check if response.id == request.id. (#2572)
13+
1014
Version 3.8.5
1115
-------------
1216
* New simulator is WIP, not to be used. (#2568)

README.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Upgrade examples:
2626
- 3.7.1 -> 3.8.0: Smaller changes to the pymodbus calls might be needed
2727
- 2.5.4 -> 3.0.0: Major changes in the application might be needed
2828

29-
Current release is `3.8.5 <https://github.com/pymodbus-dev/pymodbus/releases/tag/v3.8.5>`_.
29+
Current release is `3.8.6 <https://github.com/pymodbus-dev/pymodbus/releases/tag/v3.8.6>`_.
3030

3131
Bleeding edge (not released) is `dev <https://github.com/pymodbus-dev/pymodbus/tree/dev>`_.
3232

doc/source/_static/examples.tgz

-594 KB
Binary file not shown.

doc/source/_static/examples.zip

0 Bytes
Binary file not shown.

doc/source/client.rst

+4-2
Original file line numberDiff line numberDiff line change
@@ -194,17 +194,19 @@ Client device addressing
194194
------------------------
195195

196196
With **TCP**, **TLS** and **UDP**, the tcp/ip address of the physical device is defined when creating the object.
197-
The logical devices represented by the device is addressed with the :mod:`slave=` parameter.
197+
Logical devices represented by the device is addressed with the :mod:`slave=` parameter.
198198

199199
With **Serial**, the comm port is defined when creating the object.
200200
The physical devices are addressed with the :mod:`slave=` parameter.
201201

202202
:mod:`slave=0` is defined as broadcast in the modbus standard, but pymodbus treats it as a normal device.
203+
please note :mod:`slave=0` can only be used to address devices that truly have id=0 ! Using :mod:`slave=0` to
204+
address a single device with id not 0 is against the protocol.
203205

204206
If an application is expecting multiple responses to a broadcast request, it must call :mod:`client.execute` and deal with the responses.
205207

206208
If no response is expected to a request, the :mod:`no_response_expected=True` argument can be used
207-
in the normal API calls, this will cause the call to return immediately with :mod:`None`.
209+
in the normal API calls, this will cause the call to return immediately with :mod:`ExceptionResponse(0xff)`.
208210

209211

210212
Client response handling

doc/source/roadmap.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ It is the community that decides how pymodbus evolves NOT the maintainers !
1515

1616
The following bullet points are what the maintainers focus on:
1717

18-
- 3.8.6 bug fix release, with:
18+
- 3.8.X bug fix release, with:
1919
- Currently not planned
2020
- 3.9.0, with:
2121
- All of branch wait_next_api

pymodbus/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@
1818
from pymodbus.pdu import ExceptionResponse
1919

2020

21-
__version__ = "3.8.5"
21+
__version__ = "3.8.6"
2222
__version_full__ = f"[pymodbus, version {__version__}]"

pymodbus/transaction/transaction.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def dummy_trace_connect(self, connect: bool) -> None:
7777
"""Do dummy trace."""
7878
_ = connect
7979

80-
def sync_get_response(self) -> ModbusPDU:
80+
def sync_get_response(self, dev_id) -> ModbusPDU:
8181
"""Receive until PDU is correct or timeout."""
8282
databuffer = b''
8383
while True:
@@ -87,6 +87,11 @@ def sync_get_response(self) -> ModbusPDU:
8787
used_len, pdu = self.framer.processIncomingFrame(databuffer)
8888
databuffer = databuffer[used_len:]
8989
if pdu:
90+
if pdu.dev_id != dev_id:
91+
raise ModbusIOException(
92+
f"ERROR: request ask for id={dev_id} but id={pdu.dev_id}, CLOSING CONNECTION."
93+
)
94+
9095
return pdu
9196

9297
def sync_execute(self, no_response_expected: bool, request: ModbusPDU) -> ModbusPDU:
@@ -102,10 +107,10 @@ def sync_execute(self, no_response_expected: bool, request: ModbusPDU) -> Modbus
102107
count_retries = 0
103108
while count_retries <= self.retries:
104109
self.pdu_send(request)
105-
if not request.dev_id or no_response_expected:
110+
if no_response_expected:
106111
return ExceptionResponse(0xff)
107112
try:
108-
return self.sync_get_response()
113+
return self.sync_get_response(request.dev_id)
109114
except asyncio.exceptions.TimeoutError:
110115
count_retries += 1
111116
if self.count_until_disconnect < 0:
@@ -134,13 +139,17 @@ async def execute(self, no_response_expected: bool, request: ModbusPDU) -> Modbu
134139
while count_retries <= self.retries:
135140
self.response_future = asyncio.Future()
136141
self.pdu_send(request)
137-
if not request.dev_id or no_response_expected:
142+
if no_response_expected:
138143
return ExceptionResponse(0xff)
139144
try:
140145
response = await asyncio.wait_for(
141146
self.response_future, timeout=self.comm_params.timeout_connect
142147
)
143148
self.count_until_disconnect= self.max_until_disconnect
149+
if response.dev_id != request.dev_id:
150+
raise ModbusIOException(
151+
f"ERROR: request ask for id={request.dev_id} but id={response.dev_id}, CLOSING CONNECTION."
152+
)
144153
return response
145154
except asyncio.exceptions.TimeoutError:
146155
count_retries += 1

test/not_updated/test_network.py

-134
This file was deleted.

test/transaction/test_transaction.py

+76-3
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ async def test_transaction_execute(self, use_clc, scenario):
151151
transact.trace_pdu = mock.Mock(return_value=request)
152152
transact.trace_packet = mock.Mock(return_value=b'123')
153153
await transact.execute(True, request)
154-
#transact.trace_pdu.assert_called_once_with(True, request)
155-
#transact.trace_packet.assert_called_once_with(True, b'\x00\x01\x00u\x00\x05\xec\x02')
154+
transact.trace_pdu.assert_called_once_with(True, request)
155+
transact.trace_packet.assert_called_once_with(True, b'\x01\x01\x00u\x00\x05\xed\xd3')
156156
elif scenario == 3: # wait receive,timeout, no_responses
157157
transact.comm_params.timeout_connect = 0.1
158158
transact.count_no_responses = 10
@@ -212,7 +212,7 @@ async def test_client_protocol_execute_outside(self, use_clc, no_resp):
212212
transact.transport.write = mock.Mock()
213213
resp = asyncio.create_task(transact.execute(no_resp, request))
214214
await asyncio.sleep(0.2)
215-
data = b"\x00\x00\x12\x34\x00\x06\xff\x01\x01\x02\x00\x04"
215+
data = b"\x00\x00\x12\x34\x00\x06\x01\x01\x01\x02\x00\x04"
216216
transact.data_received(data)
217217
result = await resp
218218
if no_resp:
@@ -417,3 +417,76 @@ def test_sync_client_protocol_execute_no_pdu(self, use_clc):
417417
transact.sync_client.send = mock.Mock()
418418
with pytest.raises(ModbusIOException):
419419
transact.sync_execute(False, request)
420+
421+
def test_transaction_sync_id0(self, use_clc):
422+
"""Test id 0 in sync."""
423+
client = ModbusBaseSyncClient(
424+
FramerType.SOCKET,
425+
5,
426+
use_clc,
427+
None,
428+
None,
429+
None,
430+
)
431+
transact = TransactionManager(
432+
use_clc,
433+
FramerRTU(DecodePDU(False)),
434+
5,
435+
False,
436+
None,
437+
None,
438+
None,
439+
sync_client=client,
440+
)
441+
transact.sync_client.connect = mock.Mock(return_value=True)
442+
transact.sync_client.send = mock.Mock()
443+
request = ReadCoilsRequest(address=117, count=5, dev_id=0)
444+
response = ReadCoilsResponse(bits=[True, False, True, True, False, False, False, False], dev_id=1)
445+
transact.retries = 0
446+
transact.transport = 1
447+
resp_bytes = transact.framer.buildFrame(response)
448+
transact.sync_client.recv = mock.Mock(return_value=resp_bytes)
449+
transact.sync_client.send = mock.Mock()
450+
transact.comm_params.timeout_connect = 0.2
451+
with pytest.raises(ModbusIOException):
452+
transact.sync_execute(False, request)
453+
response = ReadCoilsResponse(bits=[True, False, True, True, False, False, False, False], dev_id=0)
454+
resp_bytes = transact.framer.buildFrame(response)
455+
transact.sync_client.recv = mock.Mock(return_value=resp_bytes)
456+
resp = transact.sync_execute(False, request)
457+
assert not resp.isError()
458+
459+
async def test_transaction_id0(self, use_clc):
460+
"""Test tracers in disconnect."""
461+
transact = TransactionManager(
462+
use_clc,
463+
FramerRTU(DecodePDU(False)),
464+
5,
465+
False,
466+
None,
467+
None,
468+
None,
469+
)
470+
transact.send = mock.Mock()
471+
request = ReadCoilsRequest(address=117, count=5, dev_id=1)
472+
response = ReadCoilsResponse(bits=[True, False, True, True, False], dev_id=0)
473+
transact.retries = 0
474+
transact.connection_made(mock.AsyncMock())
475+
transact.transport.write = mock.Mock()
476+
transact.comm_params.timeout_connect = 0.2
477+
resp = asyncio.create_task(transact.execute(False, request))
478+
await asyncio.sleep(0.1)
479+
transact.response_future.set_result(response)
480+
await asyncio.sleep(0.1)
481+
with pytest.raises(ModbusIOException):
482+
await resp
483+
response = ReadCoilsResponse(bits=[True, False, True, True, False], dev_id=1)
484+
transact.retries = 0
485+
transact.connection_made(mock.AsyncMock())
486+
transact.transport.write = mock.Mock()
487+
transact.comm_params.timeout_connect = 0.2
488+
resp = asyncio.create_task(transact.execute(False, request))
489+
await asyncio.sleep(0.1)
490+
transact.response_future.set_result(response)
491+
await asyncio.sleep(0.1)
492+
assert response == await resp

0 commit comments

Comments
 (0)