Skip to content

Commit

Permalink
RTU decode hunt part. (#2138)
Browse files Browse the repository at this point in the history
  • Loading branch information
janiversen committed Apr 2, 2024
1 parent 24c200c commit c4c14ca
Show file tree
Hide file tree
Showing 15 changed files with 98 additions and 148 deletions.
13 changes: 7 additions & 6 deletions pymodbus/framer/ascii.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
class FramerAscii(FramerBase):
r"""Modbus ASCII Frame Controller.
[ Start ][Address ][ Function ][ Data ][ LRC ][ End ]
1c 2c 2c Nc 1c 2c
[ Start ][ Dev id ][ Function ][ Data ][ LRC ][ End ]
1c 2c 2c N*2c 1c 2c
* data can be 0 - 2x252 chars
* data can be 1 - 2x252 chars
* end is "\\r\\n" (Carriage return line feed), however the line feed
character can be changed via a special command
* start is ":"
Expand All @@ -29,11 +29,12 @@ class FramerAscii(FramerBase):

START = b':'
END = b'\r\n'
MIN_SIZE = 10


def decode(self, data: bytes) -> tuple[int, int, int, bytes]:
"""Decode message."""
if (used_len := len(data)) < 10:
"""Decode ADU."""
if (used_len := len(data)) < self.MIN_SIZE:
Log.debug("Short frame: {} wait for more data", data, ":hex")
return 0, 0, 0, self.EMPTY
if data[0:1] != self.START:
Expand All @@ -54,7 +55,7 @@ def decode(self, data: bytes) -> tuple[int, int, int, bytes]:
return used_len+2, 0, dev_id, msg[1:]

def encode(self, data: bytes, device_id: int, _tid: int) -> bytes:
"""Decode message."""
"""Encode ADU."""
dev_id = device_id.to_bytes(1,'big')
checksum = self.compute_LRC(dev_id + data)
packet = (
Expand Down
21 changes: 9 additions & 12 deletions pymodbus/framer/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,30 @@
"""
from __future__ import annotations

from abc import abstractmethod


class FramerBase:
"""Intern base."""

EMPTY = b''

def __init__(self) -> None:
"""Initialize a message instance."""

"""Initialize a ADU instance."""

@abstractmethod
def decode(self, data: bytes) -> tuple[int, int, int, bytes]:
"""Decode message.
"""Decode ADU.
return:
returns:
used_len (int) or 0 to read more
transaction_id (int) or 0
device_id (int) or 0
modbus request/response (bytes)
"""
raise RuntimeError("NOT IMPLEMENTED!")

@abstractmethod
def encode(self, pdu: bytes, device_id: int, tid: int) -> bytes:
"""Decode message.
def encode(self, pdu: bytes, dev_id: int, tid: int) -> bytes:
"""Encode ADU.
return:
modbus message (bytes)
returns:
modbus ADU (bytes)
"""
raise RuntimeError("NOT IMPLEMENTED!")
19 changes: 9 additions & 10 deletions pymodbus/framer/framer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from enum import Enum

from pymodbus.framer.ascii import FramerAscii
from pymodbus.framer.base import FramerBase
from pymodbus.framer.raw import FramerRaw
from pymodbus.framer.rtu import FramerRTU
from pymodbus.framer.socket import FramerSocket
Expand Down Expand Up @@ -67,13 +66,13 @@ def __init__(self,
super().__init__(params, is_server)
self.device_ids = device_ids
self.broadcast: bool = (0 in device_ids)
self.msg_handle: FramerBase = {
FramerType.RAW: FramerRaw(),
FramerType.ASCII: FramerAscii(),
FramerType.RTU: FramerRTU(),
FramerType.SOCKET: FramerSocket(),
FramerType.TLS: FramerTLS(),
}[framer_type]
self.handle = {
FramerType.RAW: FramerRaw,
FramerType.ASCII: FramerAscii,
FramerType.RTU: FramerRTU,
FramerType.SOCKET: FramerSocket,
FramerType.TLS: FramerTLS,
}[framer_type]()


def validate_device_id(self, dev_id: int) -> bool:
Expand All @@ -86,7 +85,7 @@ def callback_data(self, data: bytes, addr: tuple | None = None) -> int:
tot_len = len(data)
start = 0
while True:
used_len, tid, device_id, msg = self.msg_handle.decode(data[start:])
used_len, tid, device_id, msg = self.handle.decode(data[start:])
if msg:
self.callback_request_response(msg, device_id, tid)
if not used_len:
Expand All @@ -110,5 +109,5 @@ def build_send(self, data: bytes, device_id: int, tid: int, addr: tuple | None =
:param tid: transaction id (0 if not used).
:param addr: optional addr, only used for UDP server.
"""
send_data = self.msg_handle.encode(data, device_id, tid)
send_data = self.handle.encode(data, device_id, tid)
self.send(send_data, addr)
12 changes: 0 additions & 12 deletions pymodbus/framer/old_framer_ascii.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
"""Ascii_framer."""
# pylint: disable=missing-type-doc

from pymodbus.exceptions import ModbusIOException
from pymodbus.framer.old_framer_base import BYTE_ORDER, FRAME_HEADER, ModbusFramer
from pymodbus.logging import Log
Expand Down Expand Up @@ -71,13 +69,3 @@ def frameProcessIncomingPacket(self, single, callback, slave, _tid=None, **kwarg
self._buffer = self._buffer[used_len :]
self._header = {"uid": 0x00}
callback(result) # defer this

def buildPacket(self, message):
"""Create a ready to send modbus packet.
:param message: The request/response to send
:return: The encoded packet
"""
data = message.function_code.to_bytes(1,'big') + message.encode()
packet = self.message_handler.encode(data, message.slave_id, message.transaction_id)
return packet
12 changes: 10 additions & 2 deletions pymodbus/framer/old_framer_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
# pylint: disable=missing-type-doc
from __future__ import annotations

import time
from typing import Any

from pymodbus.factory import ClientDecoder, ServerDecoder
from pymodbus.framer.base import FramerBase
from pymodbus.logging import Log


Expand Down Expand Up @@ -44,6 +46,7 @@ def __init__(
"crc": b"\x00\x00",
}
self._buffer = b""
self.message_handler: FramerBase

def _validate_slave_id(self, slaves: list, single: bool) -> bool:
"""Validate if the received data is valid for the client.
Expand Down Expand Up @@ -76,7 +79,9 @@ def recvPacket(self, size):
:param size: Number of bytes to read
:return:
"""
return self.client.recv(size)
packet = self.client.recv(size)
self.client.last_frame_end = round(time.time(), 6)
return packet

def resetFrame(self):
"""Reset the entire message frame.
Expand Down Expand Up @@ -143,8 +148,11 @@ def frameProcessIncomingPacket(
) -> None:
"""Process new packet pattern."""

def buildPacket(self, message) -> bytes: # type:ignore[empty-body]
def buildPacket(self, message) -> bytes:
"""Create a ready to send modbus packet.
:param message: The populated request/response to send
"""
data = message.function_code.to_bytes(1,'big') + message.encode()
packet = self.message_handler.encode(data, message.slave_id, message.transaction_id)
return packet
17 changes: 2 additions & 15 deletions pymodbus/framer/old_framer_rtu.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ def __init__(self, decoder, client=None):
"""
super().__init__(decoder, client)
self._hsize = 0x01
self._end = b"\x0d\x0a"
self._min_frame_size = 4
self.function_codes = decoder.lookup.keys() if decoder else {}
self.message_handler = FramerRTU()

Expand Down Expand Up @@ -170,12 +168,11 @@ def buildPacket(self, message):
:param message: The populated request/response to send
"""
data = message.function_code.to_bytes(1, 'big') + message.encode()
packet = self.message_handler.encode(data, message.slave_id, message.transaction_id)
packet = super().buildPacket(message)

# Ensure that transaction is actually the slave id for serial comms
if message.slave_id:
message.transaction_id = message.slave_id
message.transaction_id = message.slave_id
return packet

def sendPacket(self, message):
Expand Down Expand Up @@ -227,13 +224,3 @@ def sendPacket(self, message):
size = self.client.send(message)
self.client.last_frame_end = round(time.time(), 6)
return size

def recvPacket(self, size):
"""Receive packet from the bus with specified len.
:param size: Number of bytes to read
:return:
"""
result = self.client.recv(size)
self.client.last_frame_end = round(time.time(), 6)
return result
10 changes: 0 additions & 10 deletions pymodbus/framer/old_framer_socket.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Socket framer."""
# pylint: disable=missing-type-doc
import struct

from pymodbus.exceptions import (
Expand Down Expand Up @@ -93,12 +92,3 @@ def frameProcessIncomingPacket(self, single, callback, slave, tid=None, **kwargs
self.resetFrame()
else:
callback(result) # defer or push to a thread?

def buildPacket(self, message):
"""Create a ready to send modbus packet.
:param message: The populated request/response to send
"""
data = message.function_code.to_bytes(1, 'big') + message.encode()
packet = self.message_handler.encode(data, message.slave_id, message.transaction_id)
return packet
15 changes: 3 additions & 12 deletions pymodbus/framer/old_framer_tls.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""TLS framer."""
# pylint: disable=missing-type-doc
import struct

from pymodbus.exceptions import (
Expand Down Expand Up @@ -56,17 +55,9 @@ def frameProcessIncomingPacket(self, _single, callback, _slave, _tid=None, **kwa
self._header["pid"] = 0

if (result := self.decoder.decode(data)) is None:
self.resetFrame()
raise ModbusIOException("Unable to decode request")
self.populateResult(result)
self._buffer = b""
self._header = {}
self._buffer = self._buffer[used_len:]
self._header = {"tid": 0, "pid": 0, "len": 0, "uid": 0}
callback(result) # defer or push to a thread?

def buildPacket(self, message):
"""Create a ready to send modbus packet.
:param message: The populated request/response to send
"""
data = message.function_code.to_bytes(1,'big') + message.encode()
packet = self.message_handler.encode(data, message.slave_id, message.transaction_id)
return packet
12 changes: 7 additions & 5 deletions pymodbus/framer/raw.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@ class FramerRaw(FramerBase):
r"""Modbus RAW Frame Controller.
[ Device id ][Transaction id ][ Data ]
1c 2c Nc
1b 2b Nb
* data can be 1 - X chars
* data can be 0 - X bytes
This framer is used for non modbus communication and testing purposes.
"""

MIN_SIZE = 3

def decode(self, data: bytes) -> tuple[int, int, int, bytes]:
"""Decode message."""
if len(data) < 3:
"""Decode ADU."""
if len(data) < self.MIN_SIZE:
Log.debug("Short frame: {} wait for more data", data, ":hex")
return 0, 0, 0, self.EMPTY
dev_id = int(data[0])
tid = int(data[1])
return len(data), dev_id, tid, data[2:]

def encode(self, pdu: bytes, device_id: int, tid: int) -> bytes:
"""Decode message."""
"""Encode ADU."""
return device_id.to_bytes(1, 'big') + tid.to_bytes(1, 'big') + pdu

0 comments on commit c4c14ca

Please sign in to comment.