Skip to content

Commit 61ea2af

Browse files
authored
Isolate PDU methods (using decoder). (#3061)
1 parent 820edf5 commit 61ea2af

28 files changed

Lines changed: 376 additions & 205 deletions

‎examples/custom_msg.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def __init__(self, values: list[int] | None = None, device_id=1, transaction=0):
5050
super().__init__(dev_id=device_id, transaction_id=transaction)
5151
self.values: list[int] = values or []
5252

53-
def encode(self):
53+
def x_encode(self):
5454
"""Encode response pdu.
5555
5656
:returns: The encoded packet message
@@ -87,7 +87,7 @@ def __init__(self, address=None, device_id=1, transaction=0):
8787
self.address = address
8888
self.count = 2
8989

90-
def encode(self):
90+
def x_encode(self):
9191
"""Encode."""
9292
return struct.pack(">HH", self.address, self.count)
9393

‎pymodbus/framer/base.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def buildFrame(self, message: ModbusPDU) -> bytes:
6161
6262
:param message: The populated request/response to send
6363
"""
64-
data = message.function_code.to_bytes(1, "big") + message.encode()
64+
data = message.function_code.to_bytes(1, "big") + self.decoder.encode(message)
6565
frame = self.encode(data, message.dev_id, message.transaction_id)
6666
return frame
6767

‎pymodbus/pdu/bit_message.py‎

Lines changed: 73 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def x_calculateRtuFrameSize(cls, data: bytes) -> int:
2323
"""Calculate the size of a PDU."""
2424
return 8
2525

26-
def encode(self) -> bytes:
26+
def x_encode(self) -> bytes:
2727
"""Encode a request pdu."""
2828
self.verifyCount(self.MAX_COUNT)
2929
self.verifyAddress()
@@ -43,20 +43,49 @@ async def datastore_update(
4343
)
4444
if isinstance(values, ExcCodes):
4545
return ExceptionResponse(self.function_code, values)
46-
response_class = (
47-
ReadCoilsResponse if self.function_code == 1 else ReadDiscreteInputsResponse
48-
)
49-
return response_class(
46+
return ReadCoilsResponse(
5047
dev_id=self.dev_id,
5148
transaction_id=self.transaction_id,
5249
bits=cast(list[bool], values),
5350
)
5451

5552

56-
class ReadDiscreteInputsRequest(ReadCoilsRequest):
53+
class ReadDiscreteInputsRequest(ModbusPDU):
5754
"""ReadDiscreteInputsRequest."""
5855

5956
function_code = 2
57+
MAX_COUNT = 2000
58+
59+
@classmethod
60+
def x_calculateRtuFrameSize(cls, data: bytes) -> int:
61+
"""Calculate the size of a PDU."""
62+
return 8
63+
64+
def x_encode(self) -> bytes:
65+
"""Encode a request pdu."""
66+
self.verifyCount(self.MAX_COUNT)
67+
self.verifyAddress()
68+
return struct.pack(">HH", self.address, self.count)
69+
70+
def x_decode(self, data: bytes) -> None:
71+
"""Decode a request pdu."""
72+
self.address, self.count = struct.unpack(">HH", data[:4])
73+
self.verifyCount(self.MAX_COUNT)
74+
75+
async def datastore_update(
76+
self, context: ModbusServerContext, device_id: int
77+
) -> ModbusPDU:
78+
"""Run request against a datastore."""
79+
values = await context.async_getValues(
80+
device_id, self.function_code, self.address, self.count
81+
)
82+
if isinstance(values, ExcCodes):
83+
return ExceptionResponse(self.function_code, values)
84+
return ReadDiscreteInputsResponse(
85+
dev_id=self.dev_id,
86+
transaction_id=self.transaction_id,
87+
bits=cast(list[bool], values),
88+
)
6089

6190

6291
class ReadCoilsResponse(ModbusPDU):
@@ -69,7 +98,7 @@ def x_calculateRtuFrameSize(cls, data: bytes) -> int:
6998
"""Calculate the size of a PDU."""
7099
return int(data[2]) + 2 + 3
71100

72-
def encode(self) -> bytes:
101+
def x_encode(self) -> bytes:
73102
"""Encode response pdu."""
74103
result = pack_bitstring(self.bits)
75104
return struct.pack(">B", len(result)) + result
@@ -79,11 +108,25 @@ def x_decode(self, data: bytes) -> None:
79108
self.bits = unpack_bitstring(data[1:])
80109

81110

82-
class ReadDiscreteInputsResponse(ReadCoilsResponse):
111+
class ReadDiscreteInputsResponse(ModbusPDU):
83112
"""ReadDiscreteInputsResponse."""
84113

85114
function_code = 2
86115

116+
@classmethod
117+
def x_calculateRtuFrameSize(cls, data: bytes) -> int:
118+
"""Calculate the size of a PDU."""
119+
return int(data[2]) + 2 + 3
120+
121+
def x_encode(self) -> bytes:
122+
"""Encode response pdu."""
123+
result = pack_bitstring(self.bits)
124+
return struct.pack(">B", len(result)) + result
125+
126+
def x_decode(self, data: bytes) -> None:
127+
"""Decode response pdu."""
128+
self.bits = unpack_bitstring(data[1:])
129+
87130

88131
class WriteSingleCoilResponse(ModbusPDU):
89132
"""WriteSingleCoilResponse."""
@@ -95,7 +138,7 @@ def x_calculateRtuFrameSize(cls, data: bytes) -> int:
95138
"""Calculate the size of a PDU."""
96139
return 8
97140

98-
def encode(self) -> bytes:
141+
def x_encode(self) -> bytes:
99142
"""Encode write coil request."""
100143
self.verifyAddress()
101144
val = ModbusStatus.ON if self.bits[0] else ModbusStatus.OFF
@@ -107,9 +150,27 @@ def x_decode(self, data: bytes) -> None:
107150
self.bits = [bool(value)]
108151

109152

110-
class WriteSingleCoilRequest(WriteSingleCoilResponse):
153+
class WriteSingleCoilRequest(ModbusPDU):
111154
"""WriteSingleCoilRequest."""
112155

156+
function_code = 5
157+
158+
@classmethod
159+
def x_calculateRtuFrameSize(cls, data: bytes) -> int:
160+
"""Calculate the size of a PDU."""
161+
return 8
162+
163+
def x_encode(self) -> bytes:
164+
"""Encode write coil request."""
165+
self.verifyAddress()
166+
val = ModbusStatus.ON if self.bits[0] else ModbusStatus.OFF
167+
return struct.pack(">HH", self.address, val)
168+
169+
def x_decode(self, data: bytes) -> None:
170+
"""Decode a write coil request."""
171+
self.address, value = struct.unpack(">HH", data[:4])
172+
self.bits = [bool(value)]
173+
113174
async def datastore_update(
114175
self, context: ModbusServerContext, device_id: int
115176
) -> ModbusPDU:
@@ -163,7 +224,7 @@ def __init__(
163224
self.byte_count = None
164225
self.data_byte_count = 0
165226

166-
def encode(self) -> bytes:
227+
def x_encode(self) -> bytes:
167228
"""Encode write coils request."""
168229
self.count = len(self.bits)
169230
self.verifyAddress()
@@ -219,7 +280,7 @@ def x_calculateRtuFrameSize(cls, data: bytes) -> int:
219280
"""Calculate the size of a PDU."""
220281
return 8
221282

222-
def encode(self) -> bytes:
283+
def x_encode(self) -> bytes:
223284
"""Encode write coils response."""
224285
return struct.pack(">HH", self.address, self.count)
225286

‎pymodbus/pdu/decoders.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ class DecodePDU:
2020
def __init__(self, is_server: bool) -> None:
2121
"""Initialize function_tables."""
2222
self.pdu_inx = 0 if is_server else 1
23+
self.is_server = is_server
2324

2425
def _lookupPduClass(self, data: bytes) -> type[ModbusPDU] | None:
2526
"""Use `function_code` to determine the class of the PDU."""
26-
if (func_code := int(data[1])) & 0x80:
27+
if (func_code := int(data[1])) > 0x80:
2728
return ExceptionResponse
2829
if not (pdu := self.pdu_table.get(func_code, (None, None))[self.pdu_inx]):
2930
return None
@@ -62,11 +63,13 @@ def register(self, custom_class: type[ModbusPDU]) -> None:
6263

6364
def decode(self, frame: bytes) -> ModbusPDU:
6465
"""Decode a frame."""
66+
if not frame:
67+
raise ModbusIOException("Cannot decode empty frame")
6568
function_code = 0
6669
try:
6770
if (function_code := int(frame[0])) > 0x80:
6871
pdu_exp = ExceptionResponse(function_code & 0x7F)
69-
pdu_exp.x_decode(frame[1:])
72+
pdu_exp.from_frame(False, frame)
7073
return pdu_exp
7174
if not (pdu_class := self._lookupPduClass(b"\x00" + frame)):
7275
raise ModbusIOException(
@@ -91,3 +94,7 @@ def calculateRtuFrameSize(self, data: bytes) -> int:
9194
if not (pdu_class := self._lookupPduClass(data)):
9295
return 0
9396
return pdu_class.x_calculateRtuFrameSize(data)
97+
98+
def encode(self, pdu: ModbusPDU) -> bytes:
99+
"""Calculate the size of a PDU."""
100+
return pdu.x_encode()

‎pymodbus/pdu/device.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def summary(self):
105105
"""
106106
return iter(self.stat_data.values())
107107

108-
def encode(self) -> list[int]:
108+
def x_encode(self) -> list[int]:
109109
"""Return a summary of the modbus plus statistics.
110110
111111
:returns: An iterator over lists of 8-bit integers representing each statistic
@@ -487,7 +487,7 @@ def getEvents(self):
487487
488488
:returns: The encoded events packet
489489
"""
490-
events = [event.encode() for event in self._events]
490+
events = [event.x_encode() for event in self._events]
491491
return b"".join(events)
492492

493493
def clearEvents(self):

‎pymodbus/pdu/diag_message.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __init__(
3737
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
3838
self.message: bytes | int | list | tuple | None = message
3939

40-
def encode(self) -> bytes:
40+
def x_encode(self) -> bytes:
4141
"""Encode a diagnostic response."""
4242
packet = struct.pack(">H", self.sub_function_code)
4343
if self.message is not None:
@@ -446,12 +446,12 @@ async def datastore_update(
446446
message = ModbusPlusOperation.CLEAR_STATISTICS
447447
else:
448448
message = [self.message]
449-
message += _MCB.Plus.encode()
449+
message += _MCB.Plus.x_encode()
450450
return GetClearModbusPlusResponse(
451451
message=message, dev_id=device_id, transaction_id=self.transaction_id
452452
)
453453

454-
def encode(self):
454+
def x_encode(self):
455455
"""Encode a diagnostic response."""
456456
packet = struct.pack(">H", self.sub_function_code)
457457
packet += struct.pack(">H", self.message)

‎pymodbus/pdu/events.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ModbusEvent(ABC):
1616
"""Define modbus events."""
1717

1818
@abstractmethod
19-
def encode(self) -> bytes:
19+
def x_encode(self) -> bytes:
2020
"""Encode the status bits to an event message."""
2121

2222
@abstractmethod
@@ -53,7 +53,7 @@ def __init__(self, overrun=False, listen=False, broadcast=False):
5353
self.listen = listen
5454
self.broadcast = broadcast
5555

56-
def encode(self) -> bytes:
56+
def x_encode(self) -> bytes:
5757
"""Encode the status bits to an event message.
5858
5959
:returns: The encoded event message
@@ -114,7 +114,7 @@ def __init__(
114114
self.write_timeout = write_timeout
115115
self.listen = listen
116116

117-
def encode(self):
117+
def x_encode(self):
118118
"""Encode the status bits to an event message.
119119
120120
:returns: The encoded event message
@@ -156,7 +156,7 @@ class EnteredListenModeEvent(ModbusEvent):
156156
value = 0x04
157157
__encoded = b"\x04"
158158

159-
def encode(self):
159+
def x_encode(self):
160160
"""Encode the status bits to an event message.
161161
162162
:returns: The encoded event message
@@ -193,7 +193,7 @@ class CommunicationRestartEvent(ModbusEvent):
193193
value = 0x00
194194
__encoded = b"\x00"
195195

196-
def encode(self):
196+
def x_encode(self):
197197
"""Encode the status bits to an event message.
198198
199199
:returns: The encoded event message

‎pymodbus/pdu/exceptionresponse.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,21 @@ def __str__(self) -> str:
3636
f"exception_code={self.exception_code})"
3737
)
3838

39-
def encode(self) -> bytes:
39+
def x_encode(self) -> bytes:
4040
"""Encode a modbus exception response."""
4141
return struct.pack(">B", self.exception_code)
4242

4343
def x_decode(self, data: bytes) -> None:
44+
"""Decode data part of the message."""
45+
_ = data
46+
47+
def isError(self) -> bool:
48+
"""Check if the error is a success or failure."""
49+
return True
50+
51+
def from_frame(self, is_request: bool, data: bytes) -> int:
4452
"""Decode a modbus exception response."""
45-
self.exception_code = int(data[0])
53+
_ = is_request
54+
self.function_code = int(data[0])
55+
self.exception_code = int(data[1])
56+
return 5

‎pymodbus/pdu/file_message.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(
5151
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
5252
self.records: list[FileRecord] = records or []
5353

54-
def encode(self) -> bytes:
54+
def x_encode(self) -> bytes:
5555
"""Encode the request packet."""
5656
packet = struct.pack("B", len(self.records) * 7)
5757
for record in self.records:
@@ -111,7 +111,7 @@ def __init__(
111111
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
112112
self.records: list[FileRecord] = records or []
113113

114-
def encode(self) -> bytes:
114+
def x_encode(self) -> bytes:
115115
"""Encode the response."""
116116
total = sum(len(record.record_data) + 1 + 1 for record in self.records)
117117
packet = struct.pack("B", total)
@@ -157,7 +157,7 @@ def __init__(
157157
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
158158
self.records: list[FileRecord] = records or []
159159

160-
def encode(self) -> bytes:
160+
def x_encode(self) -> bytes:
161161
"""Encode the request packet."""
162162
total_length = sum((record.record_length * 2) + 7 for record in self.records)
163163
packet = struct.pack("B", total_length)
@@ -221,7 +221,7 @@ def __init__(
221221
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
222222
self.records: list[FileRecord] = records or []
223223

224-
def encode(self) -> bytes:
224+
def x_encode(self) -> bytes:
225225
"""Encode the response."""
226226
total_length = sum((record.record_length * 2) + 7 for record in self.records)
227227
packet = struct.pack("B", total_length)
@@ -272,7 +272,7 @@ def __init__(
272272
self.address = address
273273
self.verifyAddress()
274274

275-
def encode(self) -> bytes:
275+
def x_encode(self) -> bytes:
276276
"""Encode the request packet."""
277277
return struct.pack(">H", self.address)
278278

@@ -310,7 +310,7 @@ def __init__(
310310
super().__init__(transaction_id=transaction_id, dev_id=dev_id)
311311
self.values = values or []
312312

313-
def encode(self) -> bytes:
313+
def x_encode(self) -> bytes:
314314
"""Encode the response."""
315315
length = len(self.values)
316316
packet = struct.pack(">HH", 2 + length * 2, length)

0 commit comments

Comments
 (0)