Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integrate message.encode() into framer.buildPacket. #2062

Merged
merged 4 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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