Skip to content

Commit 214c7ed

Browse files
authored
integrate message.encode() into framer.buildPacket. (#2062)
1 parent 16b63b1 commit 214c7ed

18 files changed

+1094
-887
lines changed

pymodbus/framer/ascii_framer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def __init__(self, decoder, client=None):
4141
self._hsize = 0x02
4242
self._start = b":"
4343
self._end = b"\r\n"
44+
self.message_handler = MessageAscii([0], True)
4445

4546
def decode_data(self, data):
4647
"""Decode data."""
@@ -112,7 +113,12 @@ def buildPacket(self, message):
112113
packet.extend(b2a_hex(encoded))
113114
packet.extend(f"{checksum:02x}".encode())
114115
packet.extend(self._end)
115-
return bytes(packet).upper()
116+
packet = bytes(packet).upper()
117+
118+
data = message.function_code.to_bytes(1,'big') + encoded
119+
packet_new = self.message_handler.encode(data, message.slave_id, message.transaction_id)
120+
assert packet == packet_new, "ASCII FRAMER BuildPacket failed!"
121+
return packet
116122

117123

118124
# __END__

pymodbus/framer/binary_framer.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pymodbus.exceptions import ModbusIOException
66
from pymodbus.framer.base import BYTE_ORDER, FRAME_HEADER, ModbusFramer
77
from pymodbus.logging import Log
8-
from pymodbus.utilities import checkCRC, computeCRC
8+
from pymodbus.message.rtu import MessageRTU
99

1010

1111
BINARY_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER
@@ -76,7 +76,7 @@ def check_frame(self) -> bool:
7676
self._header["uid"] = struct.unpack(">B", self._buffer[1:2])[0]
7777
self._header["crc"] = struct.unpack(">H", self._buffer[end - 2 : end])[0]
7878
data = self._buffer[1 : end - 2]
79-
return checkCRC(data, self._header["crc"])
79+
return MessageRTU.check_CRC(data, self._header["crc"])
8080
return False
8181

8282
while len(self._buffer) > 1:
@@ -113,7 +113,7 @@ def buildPacket(self, message):
113113
struct.pack(BINARY_FRAME_HEADER, message.slave_id, message.function_code)
114114
+ data
115115
)
116-
packet += struct.pack(">H", computeCRC(packet))
116+
packet += struct.pack(">H", MessageRTU.compute_CRC(packet))
117117
packet = self._start + packet + self._end
118118
return packet
119119

pymodbus/framer/rtu_framer.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
)
99
from pymodbus.framer.base import BYTE_ORDER, FRAME_HEADER, ModbusFramer
1010
from pymodbus.logging import Log
11-
from pymodbus.utilities import ModbusTransactionState, checkCRC, computeCRC
11+
from pymodbus.message.rtu import MessageRTU
12+
from pymodbus.utilities import ModbusTransactionState
1213

1314

1415
RTU_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER
@@ -62,6 +63,7 @@ def __init__(self, decoder, client=None):
6263
self._end = b"\x0d\x0a"
6364
self._min_frame_size = 4
6465
self.function_codes = decoder.lookup.keys() if decoder else {}
66+
self.message_handler = MessageRTU([0], True)
6567

6668
def decode_data(self, data):
6769
"""Decode data."""
@@ -131,7 +133,7 @@ def check_frame(self):
131133
data = self._buffer[: frame_size - 2]
132134
crc = self._header["crc"]
133135
crc_val = (int(crc[0]) << 8) + int(crc[1])
134-
return checkCRC(data, crc_val)
136+
return MessageRTU.check_CRC(data, crc_val)
135137
except (IndexError, KeyError, struct.error):
136138
return False
137139

@@ -175,7 +177,12 @@ def buildPacket(self, message):
175177
struct.pack(RTU_FRAME_HEADER, message.slave_id, message.function_code)
176178
+ data
177179
)
178-
packet += struct.pack(">H", computeCRC(packet))
180+
packet += struct.pack(">H", MessageRTU.compute_CRC(packet))
181+
182+
data_new = message.function_code.to_bytes(1, 'big') + data
183+
packet_new = self.message_handler.encode(data_new, message.slave_id, message.transaction_id)
184+
assert packet == packet_new, "RTU FRAMER BuildPacket failed!"
185+
179186
# Ensure that transaction is actually the slave id for serial comms
180187
if message.slave_id:
181188
message.transaction_id = message.slave_id

pymodbus/framer/socket_framer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
)
88
from pymodbus.framer.base import SOCKET_FRAME_HEADER, ModbusFramer
99
from pymodbus.logging import Log
10+
from pymodbus.message.socket import MessageSocket
1011

1112

1213
# --------------------------------------------------------------------------- #
@@ -42,6 +43,7 @@ def __init__(self, decoder, client=None):
4243
"""
4344
super().__init__(decoder, client)
4445
self._hsize = 0x07
46+
self.message_handler = MessageSocket([0], True)
4547

4648
def decode_data(self, data):
4749
"""Decode data."""
@@ -126,4 +128,8 @@ def buildPacket(self, message):
126128
message.function_code,
127129
)
128130
packet += data
131+
132+
data_new = message.function_code.to_bytes(1, 'big') + data
133+
packet_new = self.message_handler.encode(data_new, message.slave_id, message.transaction_id)
134+
assert packet == packet_new, "SOCKET FRAMER BuildPacket failed!"
129135
return packet

pymodbus/framer/tls_framer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
)
88
from pymodbus.framer.base import TLS_FRAME_HEADER, ModbusFramer
99
from pymodbus.logging import Log
10+
from pymodbus.message.tls import MessageTLS
1011

1112

1213
# --------------------------------------------------------------------------- #
@@ -34,6 +35,7 @@ def __init__(self, decoder, client=None):
3435
"""
3536
super().__init__(decoder, client)
3637
self._hsize = 0x0
38+
self.message_encoder = MessageTLS([0], True)
3739

3840
def decode_data(self, data):
3941
"""Decode data."""
@@ -80,6 +82,10 @@ def buildPacket(self, message):
8082
data = message.encode()
8183
packet = struct.pack(TLS_FRAME_HEADER, message.function_code)
8284
packet += data
85+
86+
data_new = message.function_code.to_bytes(1,'big') + data
87+
packet_new = self.message_encoder.encode(data_new, message.slave_id, message.transaction_id)
88+
assert packet == packet_new, "TLS FRAMER BuildPacket failed!"
8389
return packet
8490

8591

pymodbus/message/rtu.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,62 @@ class MessageRTU(MessageBase):
4040
neither when receiving nor when sending.
4141
"""
4242

43+
@classmethod
44+
def generate_crc16_table(cls) -> list[int]:
45+
"""Generate a crc16 lookup table.
46+
47+
.. note:: This will only be generated once
48+
"""
49+
result = []
50+
for byte in range(256):
51+
crc = 0x0000
52+
for _ in range(8):
53+
if (byte ^ crc) & 0x0001:
54+
crc = (crc >> 1) ^ 0xA001
55+
else:
56+
crc >>= 1
57+
byte >>= 1
58+
result.append(crc)
59+
return result
60+
crc16_table: list[int] = [0]
61+
4362
def decode(self, _data: bytes) -> tuple[int, int, int, bytes]:
4463
"""Decode message."""
4564
return 0, 0, 0, b''
4665

47-
def encode(self, _data: bytes, _device_id: int, _tid: int) -> bytes:
66+
def encode(self, data: bytes, device_id: int, _tid: int) -> bytes:
4867
"""Decode message."""
49-
return b''
68+
packet = device_id.to_bytes(1,'big') + data
69+
return packet + MessageRTU.compute_CRC(packet).to_bytes(2,'big')
70+
71+
@classmethod
72+
def check_CRC(cls, data: bytes, check: int) -> bool:
73+
"""Check if the data matches the passed in CRC.
74+
75+
:param data: The data to create a crc16 of
76+
:param check: The CRC to validate
77+
:returns: True if matched, False otherwise
78+
"""
79+
return cls.compute_CRC(data) == check
80+
81+
@classmethod
82+
def compute_CRC(cls, data: bytes) -> int:
83+
"""Compute a crc16 on the passed in bytes.
84+
85+
For modbus, this is only used on the binary serial protocols (in this
86+
case RTU).
87+
88+
The difference between modbus's crc16 and a normal crc16
89+
is that modbus starts the crc value out at 0xffff.
90+
91+
:param data: The data to create a crc16 of
92+
:returns: The calculated CRC
93+
"""
94+
crc = 0xFFFF
95+
for data_byte in data:
96+
idx = cls.crc16_table[(crc ^ int(data_byte)) & 0xFF]
97+
crc = ((crc >> 8) & 0xFF) ^ idx
98+
swapped = ((crc << 8) & 0xFF00) | ((crc >> 8) & 0x00FF)
99+
return swapped
100+
101+
MessageRTU.crc16_table = MessageRTU.generate_crc16_table()

pymodbus/utilities.py

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
"pack_bitstring",
1111
"unpack_bitstring",
1212
"default",
13-
"computeCRC",
14-
"checkCRC",
1513
"rtuFrameSize",
1614
]
1715

@@ -152,57 +150,6 @@ def unpack_bitstring(data: bytes) -> list[bool]:
152150
# --------------------------------------------------------------------------- #
153151

154152

155-
def __generate_crc16_table():
156-
"""Generate a crc16 lookup table.
157-
158-
.. note:: This will only be generated once
159-
"""
160-
result = []
161-
for byte in range(256):
162-
crc = 0x0000
163-
for _ in range(8):
164-
if (byte ^ crc) & 0x0001:
165-
crc = (crc >> 1) ^ 0xA001
166-
else:
167-
crc >>= 1
168-
byte >>= 1
169-
result.append(crc)
170-
return result
171-
172-
173-
__crc16_table = __generate_crc16_table()
174-
175-
176-
def computeCRC(data): # pylint: disable=invalid-name
177-
"""Compute a crc16 on the passed in string.
178-
179-
For modbus, this is only used on the binary serial protocols (in this
180-
case RTU).
181-
182-
The difference between modbus's crc16 and a normal crc16
183-
is that modbus starts the crc value out at 0xffff.
184-
185-
:param data: The data to create a crc16 of
186-
:returns: The calculated CRC
187-
"""
188-
crc = 0xFFFF
189-
for data_byte in data:
190-
idx = __crc16_table[(crc ^ int(data_byte)) & 0xFF]
191-
crc = ((crc >> 8) & 0xFF) ^ idx
192-
swapped = ((crc << 8) & 0xFF00) | ((crc >> 8) & 0x00FF)
193-
return swapped
194-
195-
196-
def checkCRC(data, check): # pylint: disable=invalid-name
197-
"""Check if the data matches the passed in CRC.
198-
199-
:param data: The data to create a crc16 of
200-
:param check: The CRC to validate
201-
:returns: True if matched, False otherwise
202-
"""
203-
return computeCRC(data) == check
204-
205-
206153
def rtuFrameSize(data, byte_count_pos): # pylint: disable=invalid-name
207154
"""Calculate the size of the frame based on the byte count.
208155

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,11 @@ source = [
225225
"pymodbus/",
226226
"test/",
227227
]
228-
omit = ["examples/contrib/"]
228+
omit = [
229+
"examples/contrib/",
230+
"test/message/to_do*",
231+
"test/message/generator.py",
232+
]
229233
branch = true
230234

231235
[tool.coverage.report]

test/message/generator.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/usr/bin/env python3
2+
"""Build framer encode responses."""
3+
4+
from pymodbus.factory import ClientDecoder, ServerDecoder
5+
from pymodbus.framer import (
6+
ModbusAsciiFramer,
7+
ModbusRtuFramer,
8+
ModbusSocketFramer,
9+
ModbusTlsFramer,
10+
)
11+
from pymodbus.pdu import ModbusExceptions as merror
12+
from pymodbus.register_read_message import (
13+
ReadHoldingRegistersRequest,
14+
ReadHoldingRegistersResponse,
15+
)
16+
17+
18+
def set_calls():
19+
"""Define calls."""
20+
for framer in (ModbusAsciiFramer, ModbusRtuFramer, ModbusSocketFramer, ModbusTlsFramer):
21+
print(f"framer --> {framer}")
22+
for dev_id in (0, 17, 255):
23+
print(f" dev_id --> {dev_id}")
24+
for tid in (0, 3077):
25+
print(f" tid --> {tid}")
26+
client = framer(ClientDecoder())
27+
request = ReadHoldingRegistersRequest(124, 2, dev_id)
28+
request.transaction_id = tid
29+
result = client.buildPacket(request)
30+
print(f" request --> {result}")
31+
print(f" request --> {result.hex()}")
32+
server = framer(ServerDecoder())
33+
response = ReadHoldingRegistersResponse([141,142])
34+
response.slave_id = dev_id
35+
response.transaction_id = tid
36+
result = server.buildPacket(response)
37+
print(f" response --> {result}")
38+
print(f" response --> {result.hex()}")
39+
exception = request.doException(merror.IllegalAddress)
40+
exception.transaction_id = tid
41+
exception.slave_id = dev_id
42+
result = server.buildPacket(exception)
43+
print(f" exception --> {result}")
44+
print(f" exception --> {result.hex()}")
45+
46+
set_calls()

test/message/test_ascii.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
"""Test transport."""
2-
import struct
3-
42
import pytest
53

64
from pymodbus.message.ascii import MessageAscii
@@ -16,24 +14,9 @@ def prepare_frame():
1614
return MessageAscii([1], False)
1715

1816

19-
def test_check_LRC(self):
20-
"""Test check_LRC."""
21-
data = struct.pack(">HHHH", 0x1234, 0x2345, 0x3456, 0x4567)
22-
assert MessageAscii.check_LRC(data, 0x1C)
23-
24-
def test_check_noLRC(self):
25-
"""Test check_LRC."""
26-
data = struct.pack(">HHHH", 0x1234, 0x2345, 0x3456, 0x4567)
27-
assert not MessageAscii.check_LRC(data, 0x0C)
28-
29-
def test_compute_LRC(self):
30-
"""Test compute_LRC."""
31-
data = struct.pack(">HHHH", 0x1234, 0x2345, 0x3456, 0x4567)
32-
assert MessageAscii.compute_LRC(data) == 0x1c
33-
3417
def test_roundtrip_LRC(self):
3518
"""Test combined compute/check LRC."""
36-
data = struct.pack(">HHHH", 0x1234, 0x2345, 0x3456, 0x4567)
19+
data = b'\x12\x34\x23\x45\x34\x56\x45\x67'
3720
assert MessageAscii.compute_LRC(data) == 0x1c
3821
assert MessageAscii.check_LRC(data, 0x1C)
3922

0 commit comments

Comments
 (0)