Skip to content

Commit

Permalink
integrate message.encode() into framer.buildPacket. (#2062)
Browse files Browse the repository at this point in the history
  • Loading branch information
janiversen authored Mar 5, 2024
1 parent 16b63b1 commit 214c7ed
Show file tree
Hide file tree
Showing 18 changed files with 1,094 additions and 887 deletions.
8 changes: 7 additions & 1 deletion pymodbus/framer/ascii_framer.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(self, decoder, client=None):
self._hsize = 0x02
self._start = b":"
self._end = b"\r\n"
self.message_handler = MessageAscii([0], True)

def decode_data(self, data):
"""Decode data."""
Expand Down Expand Up @@ -112,7 +113,12 @@ def buildPacket(self, message):
packet.extend(b2a_hex(encoded))
packet.extend(f"{checksum:02x}".encode())
packet.extend(self._end)
return bytes(packet).upper()
packet = bytes(packet).upper()

data = message.function_code.to_bytes(1,'big') + encoded
packet_new = self.message_handler.encode(data, message.slave_id, message.transaction_id)
assert packet == packet_new, "ASCII FRAMER BuildPacket failed!"
return packet


# __END__
6 changes: 3 additions & 3 deletions pymodbus/framer/binary_framer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from pymodbus.exceptions import ModbusIOException
from pymodbus.framer.base import BYTE_ORDER, FRAME_HEADER, ModbusFramer
from pymodbus.logging import Log
from pymodbus.utilities import checkCRC, computeCRC
from pymodbus.message.rtu import MessageRTU


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

while len(self._buffer) > 1:
Expand Down Expand Up @@ -113,7 +113,7 @@ def buildPacket(self, message):
struct.pack(BINARY_FRAME_HEADER, message.slave_id, message.function_code)
+ data
)
packet += struct.pack(">H", computeCRC(packet))
packet += struct.pack(">H", MessageRTU.compute_CRC(packet))
packet = self._start + packet + self._end
return packet

Expand Down
13 changes: 10 additions & 3 deletions pymodbus/framer/rtu_framer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
)
from pymodbus.framer.base import BYTE_ORDER, FRAME_HEADER, ModbusFramer
from pymodbus.logging import Log
from pymodbus.utilities import ModbusTransactionState, checkCRC, computeCRC
from pymodbus.message.rtu import MessageRTU
from pymodbus.utilities import ModbusTransactionState


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

def decode_data(self, data):
"""Decode data."""
Expand Down Expand Up @@ -131,7 +133,7 @@ def check_frame(self):
data = self._buffer[: frame_size - 2]
crc = self._header["crc"]
crc_val = (int(crc[0]) << 8) + int(crc[1])
return checkCRC(data, crc_val)
return MessageRTU.check_CRC(data, crc_val)
except (IndexError, KeyError, struct.error):
return False

Expand Down Expand Up @@ -175,7 +177,12 @@ def buildPacket(self, message):
struct.pack(RTU_FRAME_HEADER, message.slave_id, message.function_code)
+ data
)
packet += struct.pack(">H", computeCRC(packet))
packet += struct.pack(">H", MessageRTU.compute_CRC(packet))

data_new = message.function_code.to_bytes(1, 'big') + data
packet_new = self.message_handler.encode(data_new, message.slave_id, message.transaction_id)
assert packet == packet_new, "RTU FRAMER BuildPacket failed!"

# Ensure that transaction is actually the slave id for serial comms
if message.slave_id:
message.transaction_id = message.slave_id
Expand Down
6 changes: 6 additions & 0 deletions pymodbus/framer/socket_framer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
)
from pymodbus.framer.base import SOCKET_FRAME_HEADER, ModbusFramer
from pymodbus.logging import Log
from pymodbus.message.socket import MessageSocket


# --------------------------------------------------------------------------- #
Expand Down Expand Up @@ -42,6 +43,7 @@ def __init__(self, decoder, client=None):
"""
super().__init__(decoder, client)
self._hsize = 0x07
self.message_handler = MessageSocket([0], True)

def decode_data(self, data):
"""Decode data."""
Expand Down Expand Up @@ -126,4 +128,8 @@ def buildPacket(self, message):
message.function_code,
)
packet += data

data_new = message.function_code.to_bytes(1, 'big') + data
packet_new = self.message_handler.encode(data_new, message.slave_id, message.transaction_id)
assert packet == packet_new, "SOCKET FRAMER BuildPacket failed!"
return packet
6 changes: 6 additions & 0 deletions pymodbus/framer/tls_framer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
)
from pymodbus.framer.base import TLS_FRAME_HEADER, ModbusFramer
from pymodbus.logging import Log
from pymodbus.message.tls import MessageTLS


# --------------------------------------------------------------------------- #
Expand Down Expand Up @@ -34,6 +35,7 @@ def __init__(self, decoder, client=None):
"""
super().__init__(decoder, client)
self._hsize = 0x0
self.message_encoder = MessageTLS([0], True)

def decode_data(self, data):
"""Decode data."""
Expand Down Expand Up @@ -80,6 +82,10 @@ def buildPacket(self, message):
data = message.encode()
packet = struct.pack(TLS_FRAME_HEADER, message.function_code)
packet += data

data_new = message.function_code.to_bytes(1,'big') + data
packet_new = self.message_encoder.encode(data_new, message.slave_id, message.transaction_id)
assert packet == packet_new, "TLS FRAMER BuildPacket failed!"
return packet


Expand Down
56 changes: 54 additions & 2 deletions pymodbus/message/rtu.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,62 @@ class MessageRTU(MessageBase):
neither when receiving nor when sending.
"""

@classmethod
def generate_crc16_table(cls) -> list[int]:
"""Generate a crc16 lookup table.
.. note:: This will only be generated once
"""
result = []
for byte in range(256):
crc = 0x0000
for _ in range(8):
if (byte ^ crc) & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
byte >>= 1
result.append(crc)
return result
crc16_table: list[int] = [0]

def decode(self, _data: bytes) -> tuple[int, int, int, bytes]:
"""Decode message."""
return 0, 0, 0, b''

def encode(self, _data: bytes, _device_id: int, _tid: int) -> bytes:
def encode(self, data: bytes, device_id: int, _tid: int) -> bytes:
"""Decode message."""
return b''
packet = device_id.to_bytes(1,'big') + data
return packet + MessageRTU.compute_CRC(packet).to_bytes(2,'big')

@classmethod
def check_CRC(cls, data: bytes, check: int) -> bool:
"""Check if the data matches the passed in CRC.
:param data: The data to create a crc16 of
:param check: The CRC to validate
:returns: True if matched, False otherwise
"""
return cls.compute_CRC(data) == check

@classmethod
def compute_CRC(cls, data: bytes) -> int:
"""Compute a crc16 on the passed in bytes.
For modbus, this is only used on the binary serial protocols (in this
case RTU).
The difference between modbus's crc16 and a normal crc16
is that modbus starts the crc value out at 0xffff.
:param data: The data to create a crc16 of
:returns: The calculated CRC
"""
crc = 0xFFFF
for data_byte in data:
idx = cls.crc16_table[(crc ^ int(data_byte)) & 0xFF]
crc = ((crc >> 8) & 0xFF) ^ idx
swapped = ((crc << 8) & 0xFF00) | ((crc >> 8) & 0x00FF)
return swapped

MessageRTU.crc16_table = MessageRTU.generate_crc16_table()
53 changes: 0 additions & 53 deletions pymodbus/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
"pack_bitstring",
"unpack_bitstring",
"default",
"computeCRC",
"checkCRC",
"rtuFrameSize",
]

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


def __generate_crc16_table():
"""Generate a crc16 lookup table.
.. note:: This will only be generated once
"""
result = []
for byte in range(256):
crc = 0x0000
for _ in range(8):
if (byte ^ crc) & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
byte >>= 1
result.append(crc)
return result


__crc16_table = __generate_crc16_table()


def computeCRC(data): # pylint: disable=invalid-name
"""Compute a crc16 on the passed in string.
For modbus, this is only used on the binary serial protocols (in this
case RTU).
The difference between modbus's crc16 and a normal crc16
is that modbus starts the crc value out at 0xffff.
:param data: The data to create a crc16 of
:returns: The calculated CRC
"""
crc = 0xFFFF
for data_byte in data:
idx = __crc16_table[(crc ^ int(data_byte)) & 0xFF]
crc = ((crc >> 8) & 0xFF) ^ idx
swapped = ((crc << 8) & 0xFF00) | ((crc >> 8) & 0x00FF)
return swapped


def checkCRC(data, check): # pylint: disable=invalid-name
"""Check if the data matches the passed in CRC.
:param data: The data to create a crc16 of
:param check: The CRC to validate
:returns: True if matched, False otherwise
"""
return computeCRC(data) == check


def rtuFrameSize(data, byte_count_pos): # pylint: disable=invalid-name
"""Calculate the size of the frame based on the byte count.
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ source = [
"pymodbus/",
"test/",
]
omit = ["examples/contrib/"]
omit = [
"examples/contrib/",
"test/message/to_do*",
"test/message/generator.py",
]
branch = true

[tool.coverage.report]
Expand Down
46 changes: 46 additions & 0 deletions test/message/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""Build framer encode responses."""

from pymodbus.factory import ClientDecoder, ServerDecoder
from pymodbus.framer import (
ModbusAsciiFramer,
ModbusRtuFramer,
ModbusSocketFramer,
ModbusTlsFramer,
)
from pymodbus.pdu import ModbusExceptions as merror
from pymodbus.register_read_message import (
ReadHoldingRegistersRequest,
ReadHoldingRegistersResponse,
)


def set_calls():
"""Define calls."""
for framer in (ModbusAsciiFramer, ModbusRtuFramer, ModbusSocketFramer, ModbusTlsFramer):
print(f"framer --> {framer}")
for dev_id in (0, 17, 255):
print(f" dev_id --> {dev_id}")
for tid in (0, 3077):
print(f" tid --> {tid}")
client = framer(ClientDecoder())
request = ReadHoldingRegistersRequest(124, 2, dev_id)
request.transaction_id = tid
result = client.buildPacket(request)
print(f" request --> {result}")
print(f" request --> {result.hex()}")
server = framer(ServerDecoder())
response = ReadHoldingRegistersResponse([141,142])
response.slave_id = dev_id
response.transaction_id = tid
result = server.buildPacket(response)
print(f" response --> {result}")
print(f" response --> {result.hex()}")
exception = request.doException(merror.IllegalAddress)
exception.transaction_id = tid
exception.slave_id = dev_id
result = server.buildPacket(exception)
print(f" exception --> {result}")
print(f" exception --> {result.hex()}")

set_calls()
19 changes: 1 addition & 18 deletions test/message/test_ascii.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""Test transport."""
import struct

import pytest

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


def test_check_LRC(self):
"""Test check_LRC."""
data = struct.pack(">HHHH", 0x1234, 0x2345, 0x3456, 0x4567)
assert MessageAscii.check_LRC(data, 0x1C)

def test_check_noLRC(self):
"""Test check_LRC."""
data = struct.pack(">HHHH", 0x1234, 0x2345, 0x3456, 0x4567)
assert not MessageAscii.check_LRC(data, 0x0C)

def test_compute_LRC(self):
"""Test compute_LRC."""
data = struct.pack(">HHHH", 0x1234, 0x2345, 0x3456, 0x4567)
assert MessageAscii.compute_LRC(data) == 0x1c

def test_roundtrip_LRC(self):
"""Test combined compute/check LRC."""
data = struct.pack(">HHHH", 0x1234, 0x2345, 0x3456, 0x4567)
data = b'\x12\x34\x23\x45\x34\x56\x45\x67'
assert MessageAscii.compute_LRC(data) == 0x1c
assert MessageAscii.check_LRC(data, 0x1C)

Expand Down
Loading

0 comments on commit 214c7ed

Please sign in to comment.