Skip to content

Commit a4f5193

Browse files
authored
Framer, final touches. (#2360)
1 parent 774c3a7 commit a4f5193

11 files changed

+88
-138
lines changed

examples/message_parser.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,14 @@ def decode(self, message):
7373
print(f"Decoding Message {value}")
7474
print("=" * 80)
7575
decoders = [
76-
self.framer(ServerDecoder(), [0]),
77-
self.framer(ClientDecoder(), [0]),
76+
self.framer(ServerDecoder(), []),
77+
self.framer(ClientDecoder(), []),
7878
]
7979
for decoder in decoders:
80-
print(f"{decoder.message_handler.decoder.__class__.__name__}")
80+
print(f"{decoder.decoder.__class__.__name__}")
8181
print("-" * 80)
8282
try:
83-
decoder.processIncomingPacket(message, self.report, 0)
83+
decoder.processIncomingPacket(message, self.report)
8484
except Exception: # pylint: disable=broad-except
8585
self.check_errors(decoder, message)
8686

pymodbus/client/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def register(self, custom_response_class: ModbusResponse) -> None:
210210
Use register() to add non-standard responses (like e.g. a login prompt) and
211211
have them interpreted automatically.
212212
"""
213-
self.framer.message_handler.decoder.register(custom_response_class)
213+
self.framer.decoder.register(custom_response_class)
214214

215215
def idle_time(self) -> float:
216216
"""Time before initiating next transaction (call **sync**).

pymodbus/client/modbusclientprotocol.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def __init__(
3535
self.on_connect_callback = on_connect_callback
3636

3737
# Common variables.
38-
self.framer: FramerBase = (FRAMER_NAME_TO_CLASS[framer])(ClientDecoder(), [0])
38+
self.framer: FramerBase = (FRAMER_NAME_TO_CLASS[framer])(ClientDecoder(), [])
3939
self.transaction = ModbusTransactionManager()
4040

4141
def _handle_response(self, reply):
@@ -68,7 +68,7 @@ def callback_data(self, data: bytes, addr: tuple | None = None) -> int:
6868
6969
returns number of bytes consumed
7070
"""
71-
self.framer.processIncomingPacket(data, self._handle_response, 0)
71+
self.framer.processIncomingPacket(data, self._handle_response)
7272
return len(data)
7373

7474
def __str__(self):

pymodbus/framer/base.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ def __init__(
3636
) -> None:
3737
"""Initialize a ADU (framer) instance."""
3838
self.decoder = decoder
39+
if 0 in dev_ids:
40+
dev_ids = []
3941
self.dev_ids = dev_ids
4042
self.incoming_dev_id = 0
4143
self.incoming_tid = 0
4244
self.databuffer = b""
43-
self.message_handler = self
4445

4546
def decode(self, data: bytes) -> tuple[int, bytes]:
4647
"""Decode ADU.
@@ -85,7 +86,7 @@ def buildPacket(self, message: ModbusRequest | ModbusResponse) -> bytes:
8586
packet = self.encode(data, message.slave_id, message.transaction_id)
8687
return packet
8788

88-
def processIncomingPacket(self, data: bytes, callback, slave, tid=None):
89+
def processIncomingPacket(self, data: bytes, callback, tid=None):
8990
"""Process new packet pattern.
9091
9192
This takes in a new request packet, adds it to the current
@@ -99,11 +100,6 @@ def processIncomingPacket(self, data: bytes, callback, slave, tid=None):
99100
"""
100101
Log.debug("Processing: {}", data, ":hex")
101102
self.databuffer += data
102-
if self.databuffer == b'':
103-
return
104-
if not isinstance(slave, (list, tuple)):
105-
slave = [slave]
106-
self.dev_ids = slave
107103
while True:
108104
if self.databuffer == b'':
109105
return
@@ -112,7 +108,7 @@ def processIncomingPacket(self, data: bytes, callback, slave, tid=None):
112108
self.databuffer = self.databuffer[used_len:]
113109
if not data:
114110
return
115-
if slave and 0 not in slave and self.incoming_dev_id not in slave:
111+
if self.dev_ids and self.incoming_dev_id not in self.dev_ids:
116112
Log.debug("Not a valid slave id - {}, ignoring!!", self.incoming_dev_id)
117113
self.databuffer = b''
118114
continue

pymodbus/framer/rtu.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def specific_decode(self, data: bytes, data_len: int) -> tuple[int, bytes]:
101101
return used_len, self.EMPTY
102102
self.incoming_dev_id = int(data[used_len])
103103
func_code = int(data[used_len + 1])
104-
if (self.dev_ids[0] and self.incoming_dev_id not in self.dev_ids) or func_code & 0x7F not in self.decoder.lookup:
104+
if (self.dev_ids and self.incoming_dev_id not in self.dev_ids) or func_code & 0x7F not in self.decoder.lookup:
105105
continue
106106
if data_len - used_len < self.MIN_SIZE:
107107
Log.debug("Garble in front {}, then short frame: {} wait for more data", used_len, data, ":hex")

pymodbus/server/async_io.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,17 @@ def callback_new_connection(self) -> ModbusProtocol:
6464

6565
def callback_connected(self) -> None:
6666
"""Call when connection is succcesfull."""
67+
slaves = self.server.context.slaves()
68+
if self.server.broadcast_enable:
69+
if 0 not in slaves:
70+
slaves.append(0)
71+
if 0 in slaves:
72+
slaves = []
6773
try:
6874
self.running = True
6975
self.framer = self.server.framer(
7076
self.server.decoder,
71-
[0],
77+
slaves,
7278
)
7379

7480
# schedule the connection handler on the event loop
@@ -106,7 +112,6 @@ def callback_disconnected(self, call_exc: Exception | None) -> None:
106112

107113
async def inner_handle(self):
108114
"""Handle handler."""
109-
slaves = self.server.context.slaves()
110115
# this is an asyncio.Queue await, it will never fail
111116
data = await self._recv_()
112117
if isinstance(data, tuple):
@@ -117,15 +122,10 @@ async def inner_handle(self):
117122

118123
# if broadcast is enabled make sure to
119124
# process requests to address 0
120-
if self.server.broadcast_enable:
121-
if 0 not in slaves:
122-
slaves.append(0)
123-
124125
Log.debug("Handling data: {}", data, ":hex")
125126
self.framer.processIncomingPacket(
126127
data=data,
127128
callback=lambda x: self.execute(x, *addr),
128-
slave=slaves,
129129
)
130130

131131
async def handle(self) -> None:

pymodbus/transaction.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,10 @@ def execute(self, request: ModbusRequest): # noqa: C901
187187
request.transaction_id = self.getNextTID()
188188
Log.debug("Running transaction {}", request.transaction_id)
189189
if _buffer := hexlify_packets(
190-
self.client.framer.message_handler.databuffer
190+
self.client.framer.databuffer
191191
):
192192
Log.debug("Clearing current Frame: - {}", _buffer)
193-
self.client.framer.message_handler.databuffer = b''
193+
self.client.framer.databuffer = b''
194194
broadcast = not request.slave_id
195195
expected_response_length = None
196196
if not isinstance(self.client.framer, FramerSocket):
@@ -235,7 +235,6 @@ def execute(self, request: ModbusRequest): # noqa: C901
235235
self.client.framer.processIncomingPacket(
236236
response,
237237
self.addTransaction,
238-
request.slave_id,
239238
tid=request.transaction_id,
240239
)
241240
if not (response := self.getTransaction(request.transaction_id)):
@@ -259,7 +258,7 @@ def execute(self, request: ModbusRequest): # noqa: C901
259258
self.client.state = ModbusTransactionState.TRANSACTION_COMPLETE
260259
return response
261260
except ModbusIOException as exc:
262-
# Handle decode errors in processIncomingPacket method
261+
# Handle decode errors method
263262
Log.error("Modbus IO exception {}", exc)
264263
self.client.state = ModbusTransactionState.TRANSACTION_COMPLETE
265264
self.client.close()
@@ -425,5 +424,5 @@ def _get_expected_response_length(self, data) -> int:
425424
:return: Total frame size
426425
"""
427426
func_code = int(data[1])
428-
pdu_class = self.client.framer.message_handler.decoder.lookupPduClass(func_code)
427+
pdu_class = self.client.framer.decoder.lookupPduClass(func_code)
429428
return pdu_class.calculateRtuFrameSize(data)

test/framers/test_multidrop.py

+20-22
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@
1010
class NotImplementedTestMultidrop:
1111
"""Test that server works on a multidrop line."""
1212

13-
slaves = [2]
14-
1513
good_frame = b"\x02\x03\x00\x01\x00}\xd4\x18"
1614

1715
@pytest.fixture(name="framer")
1816
def fixture_framer(self):
1917
"""Prepare framer."""
20-
return FramerRTU(ServerDecoder(), [0])
18+
return FramerRTU(ServerDecoder(), [2])
2119

2220
@pytest.fixture(name="callback")
2321
def fixture_callback(self):
@@ -27,25 +25,25 @@ def fixture_callback(self):
2725
def test_ok_frame(self, framer, callback):
2826
"""Test ok frame."""
2927
serial_event = self.good_frame
30-
framer.processIncomingPacket(serial_event, callback, self.slaves)
28+
framer.processIncomingPacket(serial_event, callback)
3129
callback.assert_called_once()
3230

3331
def test_ok_2frame(self, framer, callback):
3432
"""Test ok frame."""
3533
serial_event = self.good_frame + self.good_frame
36-
framer.processIncomingPacket(serial_event, callback, self.slaves)
34+
framer.processIncomingPacket(serial_event, callback)
3735
assert callback.call_count == 2
3836

3937
def test_bad_crc(self, framer, callback):
4038
"""Test bad crc."""
4139
serial_event = b"\x02\x03\x00\x01\x00}\xd4\x19" # Manually mangled crc
42-
framer.processIncomingPacket(serial_event, callback, self.slaves)
40+
framer.processIncomingPacket(serial_event, callback)
4341
callback.assert_not_called()
4442

4543
def test_wrong_id(self, framer, callback):
4644
"""Test frame wrong id."""
4745
serial_event = b"\x01\x03\x00\x01\x00}\xd4+" # Frame with good CRC but other id
48-
framer.processIncomingPacket(serial_event, callback, self.slaves)
46+
framer.processIncomingPacket(serial_event, callback)
4947
callback.assert_not_called()
5048

5149
def test_big_split_response_frame_from_other_id(self, framer, callback):
@@ -64,67 +62,67 @@ def test_big_split_response_frame_from_other_id(self, framer, callback):
6462
b"\x00\x00\x00\x00\x00\x00\x00N,",
6563
]
6664
for serial_event in serial_events:
67-
framer.processIncomingPacket(serial_event, callback, self.slaves)
65+
framer.processIncomingPacket(serial_event, callback)
6866
callback.assert_not_called()
6967

7068
def test_split_frame(self, framer, callback):
7169
"""Test split frame."""
7270
serial_events = [self.good_frame[:5], self.good_frame[5:]]
7371
for serial_event in serial_events:
74-
framer.processIncomingPacket(serial_event, callback, self.slaves)
72+
framer.processIncomingPacket(serial_event, callback)
7573
callback.assert_called_once()
7674

7775
def test_complete_frame_trailing_data_without_id(self, framer, callback):
7876
"""Test trailing data."""
7977
garbage = b"\x05\x04\x03" # without id
8078
serial_event = garbage + self.good_frame
81-
framer.processIncomingPacket(serial_event, callback, self.slaves)
79+
framer.processIncomingPacket(serial_event, callback)
8280
callback.assert_called_once()
8381

8482
def test_complete_frame_trailing_data_with_id(self, framer, callback):
8583
"""Test trailing data."""
8684
garbage = b"\x05\x04\x03\x02\x01\x00" # with id
8785
serial_event = garbage + self.good_frame
88-
framer.processIncomingPacket(serial_event, callback, self.slaves)
86+
framer.processIncomingPacket(serial_event, callback)
8987
callback.assert_called_once()
9088

9189
def test_split_frame_trailing_data_with_id(self, framer, callback):
9290
"""Test split frame."""
9391
garbage = b"\x05\x04\x03\x02\x01\x00"
9492
serial_events = [garbage + self.good_frame[:5], self.good_frame[5:]]
9593
for serial_event in serial_events:
96-
framer.processIncomingPacket(serial_event, callback, self.slaves)
94+
framer.processIncomingPacket(serial_event, callback)
9795
callback.assert_called_once()
9896

9997
def test_coincidental_1(self, framer, callback):
10098
"""Test conincidental."""
10199
garbage = b"\x02\x90\x07"
102100
serial_events = [garbage, self.good_frame[:5], self.good_frame[5:]]
103101
for serial_event in serial_events:
104-
framer.processIncomingPacket(serial_event, callback, self.slaves)
102+
framer.processIncomingPacket(serial_event, callback)
105103
callback.assert_called_once()
106104

107105
def test_coincidental_2(self, framer, callback):
108106
"""Test conincidental."""
109107
garbage = b"\x02\x10\x07"
110108
serial_events = [garbage, self.good_frame[:5], self.good_frame[5:]]
111109
for serial_event in serial_events:
112-
framer.processIncomingPacket(serial_event, callback, self.slaves)
110+
framer.processIncomingPacket(serial_event, callback)
113111
callback.assert_called_once()
114112

115113
def test_coincidental_3(self, framer, callback):
116114
"""Test conincidental."""
117115
garbage = b"\x02\x10\x07\x10"
118116
serial_events = [garbage, self.good_frame[:5], self.good_frame[5:]]
119117
for serial_event in serial_events:
120-
framer.processIncomingPacket(serial_event, callback, self.slaves)
118+
framer.processIncomingPacket(serial_event, callback)
121119
callback.assert_called_once()
122120

123121
def test_wrapped_frame(self, framer, callback):
124122
"""Test wrapped frame."""
125123
garbage = b"\x05\x04\x03\x02\x01\x00"
126124
serial_event = garbage + self.good_frame + garbage
127-
framer.processIncomingPacket(serial_event, callback, self.slaves)
125+
framer.processIncomingPacket(serial_event, callback)
128126

129127
# We probably should not respond in this case; in this case we've likely become desynchronized
130128
# i.e. this probably represents a case where a command came for us, but we didn't get
@@ -136,7 +134,7 @@ def test_frame_with_trailing_data(self, framer, callback):
136134
"""Test trailing data."""
137135
garbage = b"\x05\x04\x03\x02\x01\x00"
138136
serial_event = self.good_frame + garbage
139-
framer.processIncomingPacket(serial_event, callback, self.slaves)
137+
framer.processIncomingPacket(serial_event, callback)
140138

141139
# We should not respond in this case for identical reasons as test_wrapped_frame
142140
callback.assert_called_once()
@@ -152,23 +150,23 @@ def test_callback(data):
152150
result = data.function_code.to_bytes(1,'big')+data.encode()
153151

154152
framer_ok = b"\x02\x03\x00\x01\x00\x7d\xd4\x18"
155-
framer.processIncomingPacket(framer_ok, test_callback, self.slaves)
153+
framer.processIncomingPacket(framer_ok, test_callback)
156154
assert framer_ok[1:-2] == result
157155

158156
count = 0
159157
framer_2ok = framer_ok + framer_ok
160-
framer.processIncomingPacket(framer_2ok, test_callback, self.slaves)
158+
framer.processIncomingPacket(framer_2ok, test_callback)
161159
assert count == 2
162160
assert not framer._buffer # pylint: disable=protected-access
163161

164162
framer._buffer = framer_ok[:2] # pylint: disable=protected-access
165-
framer.processIncomingPacket(b'', test_callback, self.slaves)
163+
framer.processIncomingPacket(b'', test_callback)
166164
assert framer_ok[:2] == framer._buffer # pylint: disable=protected-access
167165

168166
framer._buffer = framer_ok[:3] # pylint: disable=protected-access
169-
framer.processIncomingPacket(b'', test_callback, self.slaves)
167+
framer.processIncomingPacket(b'', test_callback)
170168
assert framer_ok[:3] == framer._buffer # pylint: disable=protected-access
171169

172170
framer_ok = b"\xF0\x03\x00\x01\x00}\xd4\x18"
173-
framer.processIncomingPacket(framer_ok, test_callback, self.slaves)
171+
framer.processIncomingPacket(framer_ok, test_callback)
174172
assert framer._buffer == framer_ok[-3:] # pylint: disable=protected-access

test/sub_client/test_client_faulty_response.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,12 @@
1111
class TestFaultyResponses:
1212
"""Test that server works on a multidrop line."""
1313

14-
slaves = [0]
15-
1614
good_frame = b"\x00\x01\x00\x00\x00\x05\x00\x03\x02\x00\x01"
1715

1816
@pytest.fixture(name="framer")
1917
def fixture_framer(self):
2018
"""Prepare framer."""
21-
return FramerSocket(ClientDecoder(), [0])
19+
return FramerSocket(ClientDecoder(), [])
2220

2321
@pytest.fixture(name="callback")
2422
def fixture_callback(self):
@@ -27,21 +25,21 @@ def fixture_callback(self):
2725

2826
def test_ok_frame(self, framer, callback):
2927
"""Test ok frame."""
30-
framer.processIncomingPacket(self.good_frame, callback, self.slaves)
28+
framer.processIncomingPacket(self.good_frame, callback)
3129
callback.assert_called_once()
3230

3331
def test_1917_frame(self, callback):
3432
"""Test invalid frame in issue 1917."""
3533
recv = b"\x01\x86\x02\x00\x01"
3634
framer = FramerRTU(ClientDecoder(), [0])
37-
framer.processIncomingPacket(recv, callback, self.slaves)
35+
framer.processIncomingPacket(recv, callback)
3836
callback.assert_not_called()
3937

4038
def test_faulty_frame1(self, framer, callback):
4139
"""Test ok frame."""
4240
faulty_frame = b"\x00\x04\x00\x00\x00\x05\x00\x03\x0a\x00\x04"
4341
with pytest.raises(ModbusIOException):
44-
framer.processIncomingPacket(faulty_frame, callback, self.slaves)
42+
framer.processIncomingPacket(faulty_frame, callback)
4543
callback.assert_not_called()
46-
framer.processIncomingPacket(self.good_frame, callback, self.slaves)
44+
framer.processIncomingPacket(self.good_frame, callback)
4745
callback.assert_called_once()

test/sub_client/test_client_sync.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ class CustomRequest: # pylint: disable=too-few-public-methods
206206
client = ModbusTcpClient("127.0.0.1")
207207
client.framer = mock.Mock()
208208
client.register(CustomRequest)
209-
client.framer.message_handler.decoder.register.assert_called_once_with(CustomRequest)
209+
client.framer.decoder.register.assert_called_once_with(CustomRequest)
210210

211211
# -----------------------------------------------------------------------#
212212
# Test TLS Client
@@ -296,7 +296,7 @@ class CustomRequest: # pylint: disable=too-few-public-methods
296296
client = ModbusTlsClient("127.0.0.1")
297297
client.framer = mock.Mock()
298298
client.register(CustomRequest)
299-
client.framer.message_handler.decoder.register.assert_called_once_with(CustomRequest)
299+
client.framer.decoder.register.assert_called_once_with(CustomRequest)
300300

301301
# -----------------------------------------------------------------------#
302302
# Test Serial Client

0 commit comments

Comments
 (0)