From 541bae45d381104ed2782c5f48f0eee36e0dab57 Mon Sep 17 00:00:00 2001 From: jan iversen Date: Tue, 8 Oct 2024 13:10:35 +0200 Subject: [PATCH] Remove old framers (#2358) --- doc/source/library/framer.rst | 16 +- doc/source/library/simulator/config.rst | 8 +- examples/client_async.py | 2 +- examples/client_sync.py | 2 +- examples/message_parser.py | 18 +- pymodbus/client/base.py | 9 +- pymodbus/client/modbusclientprotocol.py | 11 +- pymodbus/framer/__init__.py | 21 +- pymodbus/framer/base.py | 19 +- pymodbus/framer/framer.py | 106 ----- pymodbus/framer/old_framer_ascii.py | 33 -- pymodbus/framer/old_framer_base.py | 56 --- pymodbus/framer/old_framer_rtu.py | 51 --- pymodbus/framer/old_framer_socket.py | 36 -- pymodbus/framer/old_framer_tls.py | 28 -- pymodbus/server/async_io.py | 10 +- pymodbus/transaction.py | 46 +-- test/framers/conftest.py | 24 +- test/framers/generator.py | 14 +- test/framers/test_asyncframer.py | 391 ------------------ test/framers/test_multidrop.py | 4 +- .../sub_client/test_client_faulty_response.py | 6 +- test/sub_client/test_client_sync.py | 18 +- test/sub_current/test_transaction.py | 18 +- test/sub_server/test_server_asyncio.py | 6 +- 25 files changed, 110 insertions(+), 843 deletions(-) delete mode 100644 pymodbus/framer/framer.py delete mode 100644 pymodbus/framer/old_framer_ascii.py delete mode 100644 pymodbus/framer/old_framer_base.py delete mode 100644 pymodbus/framer/old_framer_rtu.py delete mode 100644 pymodbus/framer/old_framer_socket.py delete mode 100644 pymodbus/framer/old_framer_tls.py delete mode 100644 test/framers/test_asyncframer.py diff --git a/doc/source/library/framer.rst b/doc/source/library/framer.rst index 32204cfaf..56dcdb652 100644 --- a/doc/source/library/framer.rst +++ b/doc/source/library/framer.rst @@ -1,34 +1,34 @@ Framer ====== -pymodbus\.framer\.ModbusAsciiFramer module +pymodbus\.framer\.FramerAscii module ------------------------------------------ -.. automodule:: pymodbus.framer.ModbusAsciiFramer +.. automodule:: pymodbus.framer.FramerAscii :members: :undoc-members: :show-inheritance: -pymodbus\.framer\.ModbusRtuFramer module +pymodbus\.framer\.FramerRTU module ---------------------------------------- -.. automodule:: pymodbus.framer.ModbusRtuFramer +.. automodule:: pymodbus.framer.FramerRTU :members: :undoc-members: :show-inheritance: -pymodbus\.framer\.ModbusSocketFramer module +pymodbus\.framer\.FramerSocket module ------------------------------------------- -.. automodule:: pymodbus.framer.ModbusSocketFramer +.. automodule:: pymodbus.framer.FramerSocket :members: :undoc-members: :show-inheritance: -pymodbus\.framer\.ModbusTlsFramer module +pymodbus\.framer\.FramerTLS module ---------------------------------------- -.. automodule:: pymodbus.framer.ModbusTlsFramer +.. automodule:: pymodbus.framer.FramerTLS :members: :undoc-members: :show-inheritance: diff --git a/doc/source/library/simulator/config.rst b/doc/source/library/simulator/config.rst index 9349a313c..ea89c262f 100644 --- a/doc/source/library/simulator/config.rst +++ b/doc/source/library/simulator/config.rst @@ -63,10 +63,10 @@ The entry “comm” allows the following values: The entry “framer” allows the following values: -- “ascii” to use :class:`pymodbus.framer.ModbusAsciiFramer`, -- “rtu” to use :class:`pymodbus.framer.ModbusRtuFramer`, -- “tls” to use :class:`pymodbus.framer.ModbusTlsFramer`, -- “socket” to use :class:`pymodbus.framer.ModbusSocketFramer`. +- “ascii” to use :class:`pymodbus.framer.FramerAscii`, +- “rtu” to use :class:`pymodbus.framer.FramerRTU`, +- “socket” to use :class:`pymodbus.framer.FramerSocket`. +- “tls” to use :class:`pymodbus.framer.FramerTLS`, Optional entry "device_id" will limit server to only accept a single id. If not set, the server will accept all device id. diff --git a/examples/client_async.py b/examples/client_async.py index 281b7792d..c14952dbf 100755 --- a/examples/client_async.py +++ b/examples/client_async.py @@ -81,7 +81,7 @@ def setup_async_client(description=None, cmdline=None): client = modbusClient.AsyncModbusSerialClient( args.port, # Common optional parameters: - # framer=ModbusRtuFramer, + # framer=FramerType.RTU, timeout=args.timeout, # retries=3, # Serial setup parameters diff --git a/examples/client_sync.py b/examples/client_sync.py index 6367306d0..0c3a82627 100755 --- a/examples/client_sync.py +++ b/examples/client_sync.py @@ -85,7 +85,7 @@ def setup_sync_client(description=None, cmdline=None): client = modbusClient.ModbusSerialClient( port=args.port, # serial port # Common optional parameters: - # framer=ModbusRtuFramer, + # framer=FramerType.RTU, timeout=args.timeout, # retries=3, # Serial setup parameters diff --git a/examples/message_parser.py b/examples/message_parser.py index 3b2dfde3e..3074a15d1 100755 --- a/examples/message_parser.py +++ b/examples/message_parser.py @@ -13,10 +13,10 @@ from pymodbus import pymodbus_apply_logging_config from pymodbus.factory import ClientDecoder, ServerDecoder -from pymodbus.transaction import ( - ModbusAsciiFramer, - ModbusRtuFramer, - ModbusSocketFramer, +from pymodbus.framer import ( + FramerAscii, + FramerRTU, + FramerSocket, ) @@ -73,8 +73,8 @@ def decode(self, message): print(f"Decoding Message {value}") print("=" * 80) decoders = [ - self.framer(ServerDecoder(), client=None), - self.framer(ClientDecoder(), client=None), + self.framer(ServerDecoder(), [0]), + self.framer(ClientDecoder(), [0]), ] for decoder in decoders: print(f"{decoder.message_handler.decoder.__class__.__name__}") @@ -143,9 +143,9 @@ def parse_messages(cmdline=None): return framer = { - "ascii": ModbusAsciiFramer, - "rtu": ModbusRtuFramer, - "socket": ModbusSocketFramer, + "ascii": FramerAscii, + "rtu": FramerRTU, + "socket": FramerSocket, }[args.framer] decoder = Decoder(framer) diff --git a/pymodbus/client/base.py b/pymodbus/client/base.py index 1faf709b8..83be824d6 100644 --- a/pymodbus/client/base.py +++ b/pymodbus/client/base.py @@ -5,13 +5,12 @@ import socket from abc import abstractmethod from collections.abc import Awaitable, Callable -from typing import cast from pymodbus.client.mixin import ModbusClientMixin from pymodbus.client.modbusclientprotocol import ModbusClientProtocol from pymodbus.exceptions import ConnectionException, ModbusIOException from pymodbus.factory import ClientDecoder -from pymodbus.framer import FRAMER_NAME_TO_OLD_CLASS, FramerType, ModbusFramer +from pymodbus.framer import FRAMER_NAME_TO_CLASS, FramerBase, FramerType from pymodbus.logging import Log from pymodbus.pdu import ModbusRequest, ModbusResponse from pymodbus.transaction import SyncModbusTransactionManager @@ -77,7 +76,7 @@ def register(self, custom_response_class: ModbusResponse) -> None: Use register() to add non-standard responses (like e.g. a login prompt) and have them interpreted automatically. """ - self.ctx.framer.message_handler.decoder.register(custom_response_class) + self.ctx.framer.decoder.register(custom_response_class) def close(self) -> None: """Close connection.""" @@ -187,9 +186,7 @@ def __init__( self.slaves: list[int] = [] # Common variables. - self.framer: ModbusFramer = FRAMER_NAME_TO_OLD_CLASS.get( - framer, cast(type[ModbusFramer], framer) - )(ClientDecoder(), self) + self.framer: FramerBase = (FRAMER_NAME_TO_CLASS[framer])(ClientDecoder(), [0]) self.transaction = SyncModbusTransactionManager( self, self.retries, diff --git a/pymodbus/client/modbusclientprotocol.py b/pymodbus/client/modbusclientprotocol.py index 0c64bf1c9..12a9c2535 100644 --- a/pymodbus/client/modbusclientprotocol.py +++ b/pymodbus/client/modbusclientprotocol.py @@ -2,10 +2,13 @@ from __future__ import annotations from collections.abc import Callable -from typing import cast from pymodbus.factory import ClientDecoder -from pymodbus.framer import FRAMER_NAME_TO_OLD_CLASS, FramerType, ModbusFramer +from pymodbus.framer import ( + FRAMER_NAME_TO_CLASS, + FramerBase, + FramerType, +) from pymodbus.logging import Log from pymodbus.transaction import ModbusTransactionManager from pymodbus.transport import CommParams, ModbusProtocol @@ -32,9 +35,7 @@ def __init__( self.on_connect_callback = on_connect_callback # Common variables. - self.framer = FRAMER_NAME_TO_OLD_CLASS.get( - framer, cast(type[ModbusFramer], framer) - )(ClientDecoder(), self) + self.framer: FramerBase = (FRAMER_NAME_TO_CLASS[framer])(ClientDecoder(), [0]) self.transaction = ModbusTransactionManager() def _handle_response(self, reply): diff --git a/pymodbus/framer/__init__.py b/pymodbus/framer/__init__.py index 9d17d2842..aa40ccc92 100644 --- a/pymodbus/framer/__init__.py +++ b/pymodbus/framer/__init__.py @@ -1,12 +1,6 @@ """Framer.""" __all__ = [ - "FRAMER_NAME_TO_OLD_CLASS", - "ModbusFramer", - "ModbusAsciiFramer", - "ModbusRtuFramer", - "ModbusSocketFramer", - "ModbusTlsFramer", - "AsyncFramer", + "FramerBase", "FramerType", "FramerAscii", "FramerRTU", @@ -15,23 +9,12 @@ ] from pymodbus.framer.ascii import FramerAscii -from pymodbus.framer.framer import AsyncFramer, FramerType -from pymodbus.framer.old_framer_ascii import ModbusAsciiFramer -from pymodbus.framer.old_framer_base import ModbusFramer -from pymodbus.framer.old_framer_rtu import ModbusRtuFramer -from pymodbus.framer.old_framer_socket import ModbusSocketFramer -from pymodbus.framer.old_framer_tls import ModbusTlsFramer +from pymodbus.framer.base import FramerBase, FramerType from pymodbus.framer.rtu import FramerRTU from pymodbus.framer.socket import FramerSocket from pymodbus.framer.tls import FramerTLS -FRAMER_NAME_TO_OLD_CLASS = { - FramerType.ASCII: ModbusAsciiFramer, - FramerType.RTU: ModbusRtuFramer, - FramerType.SOCKET: ModbusSocketFramer, - FramerType.TLS: ModbusTlsFramer, -} FRAMER_NAME_TO_CLASS = { FramerType.ASCII: FramerAscii, FramerType.RTU: FramerRTU, diff --git a/pymodbus/framer/base.py b/pymodbus/framer/base.py index 889176c17..490559853 100644 --- a/pymodbus/framer/base.py +++ b/pymodbus/framer/base.py @@ -6,7 +6,7 @@ """ from __future__ import annotations -from abc import abstractmethod +from enum import Enum from pymodbus.exceptions import ModbusIOException from pymodbus.factory import ClientDecoder, ServerDecoder @@ -14,6 +14,15 @@ from pymodbus.pdu import ModbusRequest, ModbusResponse +class FramerType(str, Enum): + """Type of Modbus frame.""" + + ASCII = "ascii" + RTU = "rtu" + SOCKET = "socket" + TLS = "tls" + + class FramerBase: """Intern base.""" @@ -31,6 +40,7 @@ def __init__( self.incoming_dev_id = 0 self.incoming_tid = 0 self.databuffer = b"" + self.message_handler = self def decode(self, data: bytes) -> tuple[int, bytes]: """Decode ADU. @@ -48,7 +58,6 @@ def decode(self, data: bytes) -> tuple[int, bytes]: self.incoming_tid = 0 return used_len, res_data - @abstractmethod def specific_decode(self, data: bytes, data_len: int) -> tuple[int, bytes]: """Decode ADU. @@ -56,15 +65,16 @@ def specific_decode(self, data: bytes, data_len: int) -> tuple[int, bytes]: used_len (int) or 0 to read more modbus request/response (bytes) """ + return data_len, data - @abstractmethod - def encode(self, pdu: bytes, dev_id: int, tid: int) -> bytes: + def encode(self, pdu: bytes, _dev_id: int, _tid: int) -> bytes: """Encode ADU. returns: modbus ADU (bytes) """ + return pdu def buildPacket(self, message: ModbusRequest | ModbusResponse) -> bytes: """Create a ready to send modbus packet. @@ -93,6 +103,7 @@ def processIncomingPacket(self, data: bytes, callback, slave, tid=None): return if not isinstance(slave, (list, tuple)): slave = [slave] + self.dev_ids = slave while True: if self.databuffer == b'': return diff --git a/pymodbus/framer/framer.py b/pymodbus/framer/framer.py deleted file mode 100644 index 52eccf1de..000000000 --- a/pymodbus/framer/framer.py +++ /dev/null @@ -1,106 +0,0 @@ -"""Framing layer. - -The framer layer is responsible for isolating/generating the request/request from -the frame (prefix - postfix) - -According to the selected type of modbus frame a prefix/suffix is added/removed - -This layer is also responsible for discarding invalid frames and frames for other slaves. -""" -from __future__ import annotations - -from abc import abstractmethod -from enum import Enum - -from pymodbus.factory import ClientDecoder, ServerDecoder -from pymodbus.framer.ascii import FramerAscii -from pymodbus.framer.rtu import FramerRTU -from pymodbus.framer.socket import FramerSocket -from pymodbus.framer.tls import FramerTLS -from pymodbus.transport.transport import CommParams, ModbusProtocol - - -class FramerType(str, Enum): - """Type of Modbus frame.""" - - ASCII = "ascii" - RTU = "rtu" - SOCKET = "socket" - TLS = "tls" - - -class AsyncFramer(ModbusProtocol): - """Framer layer extending transport layer. - - extends the ModbusProtocol to handle receiving and sending of complete modbus PDU. - - When receiving: - - Secures full valid Modbus PDU is received (across multiple callbacks) - - Validates and removes Modbus prefix/suffix (CRC for serial, MBAP for others) - - Callback with pure request/response - - Skips invalid messagees - - Hunt for valid message (RTU type) - - When sending: - - Add prefix/suffix to request/response (CRC for serial, MBAP for others) - - Call transport to send - - The class is designed to take care of differences between the modbus message types, - and provide a neutral interface with pure requests/responses to/from the upper layers. - """ - - def __init__(self, - framer_type: FramerType, - params: CommParams, - is_server: bool, - decoder: ClientDecoder | ServerDecoder, - device_ids: list[int], - ): - """Initialize a framer instance. - - :param framer_type: Modbus message type - :param params: parameter dataclass - :param is_server: true if object act as a server (listen/connect) - :param device_ids: list of device id to accept, 0 in list means broadcast. - """ - super().__init__(params, is_server) - self.device_ids = device_ids - self.broadcast: bool = (0 in device_ids) - - self.handle = { - FramerType.ASCII: FramerAscii(decoder, device_ids), - FramerType.RTU: FramerRTU(decoder, device_ids), - FramerType.SOCKET: FramerSocket(decoder, device_ids), - FramerType.TLS: FramerTLS(decoder, device_ids), - }[framer_type] - - - def callback_data(self, data: bytes, addr: tuple | None = None) -> int: - """Handle received data.""" - tot_len = 0 - buf_len = len(data) - while True: - used_len, msg = self.handle.decode(data[tot_len:]) - tot_len += used_len - if msg: - if self.broadcast or self.handle.incoming_dev_id in self.device_ids: - self.callback_request_response(msg, self.handle.incoming_dev_id, self.handle.incoming_tid) - if tot_len == buf_len: - return tot_len - else: - return tot_len - - @abstractmethod - def callback_request_response(self, data: bytes, device_id: int, tid: int) -> None: - """Handle received modbus request/response.""" - - def build_send(self, data: bytes, device_id: int, tid: int, addr: tuple | None = None) -> None: - """Send request/response. - - :param data: non-empty bytes object with data to send. - :param device_id: device identifier (slave/unit) - :param tid: transaction id (0 if not used). - :param addr: optional addr, only used for UDP server. - """ - send_data = self.handle.encode(data, device_id, tid) - self.send(send_data, addr) diff --git a/pymodbus/framer/old_framer_ascii.py b/pymodbus/framer/old_framer_ascii.py deleted file mode 100644 index 0ca277e0c..000000000 --- a/pymodbus/framer/old_framer_ascii.py +++ /dev/null @@ -1,33 +0,0 @@ -"""Ascii_framer.""" -from pymodbus.framer.old_framer_base import BYTE_ORDER, FRAME_HEADER, ModbusFramer - -from .ascii import FramerAscii - - -ASCII_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER - - -# --------------------------------------------------------------------------- # -# Modbus ASCII olf framer -# --------------------------------------------------------------------------- # -class ModbusAsciiFramer(ModbusFramer): - r"""Modbus ASCII Frame Controller. - - [ Start ][Address ][ Function ][ Data ][ LRC ][ End ] - 1c 2c 2c Nc 2c 2c - - * data can be 0 - 2x252 chars - * end is "\\r\\n" (Carriage return line feed), however the line feed - character can be changed via a special command - * start is ":" - - This framer is used for serial transmission. Unlike the RTU protocol, - the data in this framer is transferred in plain text ascii. - """ - - def __init__(self, decoder, client=None): - """Initialize a new instance of the framer. - - :param decoder: The decoder implementation to use - """ - super().__init__(decoder, client, FramerAscii) diff --git a/pymodbus/framer/old_framer_base.py b/pymodbus/framer/old_framer_base.py deleted file mode 100644 index c1cc32d0e..000000000 --- a/pymodbus/framer/old_framer_base.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Framer start.""" -from __future__ import annotations - -from typing import TYPE_CHECKING - -from pymodbus.factory import ClientDecoder, ServerDecoder -from pymodbus.framer.base import FramerBase -from pymodbus.pdu import ModbusRequest, ModbusResponse - - -if TYPE_CHECKING: - pass - -# Unit ID, Function Code -BYTE_ORDER = ">" -FRAME_HEADER = "BB" - -# Transaction Id, Protocol ID, Length, Unit ID, Function Code -SOCKET_FRAME_HEADER = BYTE_ORDER + "HHH" + FRAME_HEADER - -# Function Code -TLS_FRAME_HEADER = BYTE_ORDER + "B" - - -class ModbusFramer: - """Base Framer class.""" - - def __init__( - self, - decoder: ClientDecoder | ServerDecoder, - _client, - new_framer, - ) -> None: - """Initialize a new instance of the framer. - - :param decoder: The decoder implementation to use - """ - self.message_handler: FramerBase = new_framer(decoder, [0]) - - @property - def incoming_dev_id(self) -> int: - """Return dev id.""" - return self.message_handler.incoming_dev_id - - @property - def incoming_tid(self) -> int: - """Return tid.""" - return self.message_handler.incoming_tid - - def processIncomingPacket(self, data: bytes, callback, slave, tid=None): - """Process new packet pattern.""" - self.message_handler.processIncomingPacket(data, callback, slave, tid=tid) - - def buildPacket(self, message: ModbusRequest | ModbusResponse) -> bytes: - """Create a ready to send modbus packet.""" - return self.message_handler.buildPacket(message) diff --git a/pymodbus/framer/old_framer_rtu.py b/pymodbus/framer/old_framer_rtu.py deleted file mode 100644 index 5a5efc430..000000000 --- a/pymodbus/framer/old_framer_rtu.py +++ /dev/null @@ -1,51 +0,0 @@ -"""RTU framer.""" - -from pymodbus.framer.old_framer_base import BYTE_ORDER, FRAME_HEADER, ModbusFramer -from pymodbus.framer.rtu import FramerRTU - - -RTU_FRAME_HEADER = BYTE_ORDER + FRAME_HEADER - - -# --------------------------------------------------------------------------- # -# Modbus RTU old Framer -# --------------------------------------------------------------------------- # -class ModbusRtuFramer(ModbusFramer): - """Modbus RTU Frame controller. - - [ Start Wait ] [Address ][ Function Code] [ Data ][ CRC ][ End Wait ] - 3.5 chars 1b 1b Nb 2b 3.5 chars - - Wait refers to the amount of time required to transmit at least x many - characters. In this case it is 3.5 characters. Also, if we receive a - wait of 1.5 characters at any point, we must trigger an error message. - Also, it appears as though this message is little endian. The logic is - simplified as the following:: - - block-on-read: - read until 3.5 delay - check for errors - decode - - The following table is a listing of the baud wait times for the specified - baud rates:: - - ------------------------------------------------------------------ - Baud 1.5c (18 bits) 3.5c (38 bits) - ------------------------------------------------------------------ - 1200 13333.3 us 31666.7 us - 4800 3333.3 us 7916.7 us - 9600 1666.7 us 3958.3 us - 19200 833.3 us 1979.2 us - 38400 416.7 us 989.6 us - ------------------------------------------------------------------ - 1 Byte = start + 8 bits + parity + stop = 11 bits - (1/Baud)(bits) = delay seconds - """ - - def __init__(self, decoder, client=None): - """Initialize a new instance of the framer. - - :param decoder: The decoder factory implementation to use - """ - super().__init__(decoder, client, FramerRTU) diff --git a/pymodbus/framer/old_framer_socket.py b/pymodbus/framer/old_framer_socket.py deleted file mode 100644 index 75a394e7e..000000000 --- a/pymodbus/framer/old_framer_socket.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Socket framer.""" - -from pymodbus.framer.old_framer_base import ModbusFramer -from pymodbus.framer.socket import FramerSocket - - -# --------------------------------------------------------------------------- # -# Modbus TCP old framer -# --------------------------------------------------------------------------- # - - -class ModbusSocketFramer(ModbusFramer): - """Modbus Socket Frame controller. - - Before each modbus TCP message is an MBAP header which is used as a - message frame. It allows us to easily separate messages as follows:: - - [ MBAP Header ] [ Function Code] [ Data ] \ - [ tid ][ pid ][ length ][ uid ] - 2b 2b 2b 1b 1b Nb - - while len(message) > 0: - tid, pid, length`, uid = struct.unpack(">HHHB", message) - request = message[0:7 + length - 1`] - message = [7 + length - 1:] - - * length = uid + function code + data - * The -1 is to account for the uid byte - """ - - def __init__(self, decoder, client=None): - """Initialize a new instance of the framer. - - :param decoder: The decoder factory implementation to use - """ - super().__init__(decoder, client, FramerSocket) diff --git a/pymodbus/framer/old_framer_tls.py b/pymodbus/framer/old_framer_tls.py deleted file mode 100644 index ff13a6c1c..000000000 --- a/pymodbus/framer/old_framer_tls.py +++ /dev/null @@ -1,28 +0,0 @@ -"""TLS framer.""" - -from pymodbus.framer.old_framer_base import ModbusFramer -from pymodbus.framer.tls import FramerTLS - - -# --------------------------------------------------------------------------- # -# Modbus TLS old framer -# --------------------------------------------------------------------------- # - - -class ModbusTlsFramer(ModbusFramer): - """Modbus TLS Frame controller. - - No prefix MBAP header before decrypted PDU is used as a message frame for - Modbus Security Application Protocol. It allows us to easily separate - decrypted messages which is PDU as follows: - - [ Function Code] [ Data ] - 1b Nb - """ - - def __init__(self, decoder, client=None): - """Initialize a new instance of the framer. - - :param decoder: The decoder factory implementation to use - """ - super().__init__(decoder, client, FramerTLS) diff --git a/pymodbus/server/async_io.py b/pymodbus/server/async_io.py index 40aaca12d..378b8cd49 100644 --- a/pymodbus/server/async_io.py +++ b/pymodbus/server/async_io.py @@ -11,7 +11,7 @@ from pymodbus.device import ModbusControlBlock, ModbusDeviceIdentification from pymodbus.exceptions import NoSuchSlaveException from pymodbus.factory import ServerDecoder -from pymodbus.framer import FRAMER_NAME_TO_OLD_CLASS, FramerType, ModbusFramer +from pymodbus.framer import FRAMER_NAME_TO_CLASS, FramerBase, FramerType from pymodbus.logging import Log from pymodbus.pdu import ModbusExceptions as merror from pymodbus.transport import CommParams, CommType, ModbusProtocol @@ -48,7 +48,7 @@ def __init__(self, owner): self.running = False self.receive_queue: asyncio.Queue = asyncio.Queue() self.handler_task = None # coroutine to be run on asyncio loop - self.framer: ModbusFramer + self.framer: FramerBase self.loop = asyncio.get_running_loop() def _log_exception(self): @@ -68,7 +68,7 @@ def callback_connected(self) -> None: self.running = True self.framer = self.server.framer( self.server.decoder, - client=None, + [0], ) # schedule the connection handler on the event loop @@ -271,7 +271,7 @@ def __init__( if isinstance(identity, ModbusDeviceIdentification): self.control.Identity.update(identity) - self.framer = FRAMER_NAME_TO_OLD_CLASS.get(framer, framer) + self.framer = FRAMER_NAME_TO_CLASS[framer] self.serving: asyncio.Future = asyncio.Future() def callback_new_connection(self): @@ -505,7 +505,7 @@ def __init__( If the identity structure is not passed in, the ModbusControlBlock uses its own empty structure. :param context: The ModbusServerContext datastore - :param framer: The framer strategy to use, default ModbusRtuFramer + :param framer: The framer strategy to use, default FramerType.RTU :param identity: An optional identify structure :param port: The serial port to attach to :param stopbits: The number of stop bits to use diff --git a/pymodbus/transaction.py b/pymodbus/transaction.py index 004f42ae6..f4d540e7a 100644 --- a/pymodbus/transaction.py +++ b/pymodbus/transaction.py @@ -4,10 +4,6 @@ __all__ = [ "ModbusTransactionManager", - "ModbusSocketFramer", - "ModbusTlsFramer", - "ModbusRtuFramer", - "ModbusAsciiFramer", "SyncModbusTransactionManager", ] @@ -22,10 +18,10 @@ ModbusIOException, ) from pymodbus.framer import ( - ModbusAsciiFramer, - ModbusRtuFramer, - ModbusSocketFramer, - ModbusTlsFramer, + FramerAscii, + FramerRTU, + FramerSocket, + FramerTLS, ) from pymodbus.logging import Log from pymodbus.pdu import ModbusRequest @@ -146,13 +142,13 @@ def __init__(self, client: ModbusBaseSyncClient, retries): def _set_adu_size(self): """Set adu size.""" # base ADU size of modbus frame in bytes - if isinstance(self.client.framer, ModbusSocketFramer): + if isinstance(self.client.framer, FramerSocket): self.base_adu_size = 7 # tid(2), pid(2), length(2), uid(1) - elif isinstance(self.client.framer, ModbusRtuFramer): + elif isinstance(self.client.framer, FramerRTU): self.base_adu_size = 3 # address(1), CRC(2) - elif isinstance(self.client.framer, ModbusAsciiFramer): + elif isinstance(self.client.framer, FramerAscii): self.base_adu_size = 7 # start(1)+ Address(2), LRC(2) + end(2) - elif isinstance(self.client.framer, ModbusTlsFramer): + elif isinstance(self.client.framer, FramerTLS): self.base_adu_size = 0 # no header and footer else: self.base_adu_size = -1 @@ -165,11 +161,11 @@ def _calculate_response_length(self, expected_pdu_size): def _calculate_exception_length(self): """Return the length of the Modbus Exception Response according to the type of Framer.""" - if isinstance(self.client.framer, (ModbusSocketFramer, ModbusTlsFramer)): + if isinstance(self.client.framer, (FramerSocket, FramerTLS)): return self.base_adu_size + 2 # Fcode(1), ExceptionCode(1) - if isinstance(self.client.framer, ModbusAsciiFramer): + if isinstance(self.client.framer, FramerAscii): return self.base_adu_size + 4 # Fcode(2), ExceptionCode(2) - if isinstance(self.client.framer, ModbusRtuFramer): + if isinstance(self.client.framer, FramerRTU): return self.base_adu_size + 2 # Fcode(1), ExceptionCode(1) return None @@ -197,10 +193,10 @@ def execute(self, request: ModbusRequest): # noqa: C901 self.client.framer.message_handler.databuffer = b'' broadcast = not request.slave_id expected_response_length = None - if not isinstance(self.client.framer, ModbusSocketFramer): + if not isinstance(self.client.framer, FramerSocket): if hasattr(request, "get_response_pdu_size"): response_pdu_size = request.get_response_pdu_size() - if isinstance(self.client.framer, ModbusAsciiFramer): + if isinstance(self.client.framer, FramerAscii): response_pdu_size *= 2 if response_pdu_size: expected_response_length = ( @@ -346,11 +342,11 @@ def _recv(self, expected_response_length, full) -> bytes: # noqa: C901 total = None if not full: exception_length = self._calculate_exception_length() - if isinstance(self.client.framer, ModbusSocketFramer): + if isinstance(self.client.framer, FramerSocket): min_size = 8 - elif isinstance(self.client.framer, ModbusRtuFramer): + elif isinstance(self.client.framer, FramerRTU): min_size = 4 - elif isinstance(self.client.framer, ModbusAsciiFramer): + elif isinstance(self.client.framer, FramerAscii): min_size = 5 else: min_size = expected_response_length @@ -363,21 +359,21 @@ def _recv(self, expected_response_length, full) -> bytes: # noqa: C901 f"({len(read_min)} received)" ) if read_min: - if isinstance(self.client.framer, ModbusSocketFramer): + if isinstance(self.client.framer, FramerSocket): func_code = int(read_min[-1]) - elif isinstance(self.client.framer, ModbusRtuFramer): + elif isinstance(self.client.framer, FramerRTU): func_code = int(read_min[1]) - elif isinstance(self.client.framer, ModbusAsciiFramer): + elif isinstance(self.client.framer, FramerAscii): func_code = int(read_min[3:5], 16) else: func_code = -1 if func_code < 0x80: # Not an error - if isinstance(self.client.framer, ModbusSocketFramer): + if isinstance(self.client.framer, FramerSocket): length = struct.unpack(">H", read_min[4:6])[0] - 1 expected_response_length = 7 + length elif expected_response_length is None and isinstance( - self.client.framer, ModbusRtuFramer + self.client.framer, FramerRTU ): with suppress( IndexError # response length indeterminate with available bytes diff --git a/test/framers/conftest.py b/test/framers/conftest.py index 851c2e69c..fa157404f 100644 --- a/test/framers/conftest.py +++ b/test/framers/conftest.py @@ -1,13 +1,10 @@ """Configure pytest.""" from __future__ import annotations -from unittest import mock - import pytest from pymodbus.factory import ClientDecoder, ServerDecoder -from pymodbus.framer import FRAMER_NAME_TO_CLASS, AsyncFramer, FramerType -from pymodbus.transport import CommParams +from pymodbus.framer import FRAMER_NAME_TO_CLASS, FramerType @pytest.fixture(name="entry") @@ -32,22 +29,3 @@ async def prepare_test_framer(entry, is_server, dev_ids): (ServerDecoder if is_server else ClientDecoder)(), dev_ids, ) - - - - - -@mock.patch.multiple(AsyncFramer, __abstractmethods__=set()) # eliminate abstract methods (callbacks) -@pytest.fixture(name="dummy_async_framer") -async def prepare_test_async_framer(entry, is_server): - """Return framer object.""" - decoder = (ServerDecoder if is_server else ClientDecoder)() - framer = AsyncFramer(entry, CommParams(), is_server, decoder, [0, 1]) # type: ignore[abstract] - framer.send = mock.Mock() # type: ignore[method-assign] - #if entry == FramerType.RTU: - #func_table = decoder.lookup # type: ignore[attr-defined] - #for key, ent in func_table.items(): - # fix_len = getattr(ent, "_rtu_frame_size", 0) - # cnt_pos = getattr(ent, "_rtu_byte_count_pos", 0) - # framer.handle.set_fc_calc(key, fix_len, cnt_pos) - return framer diff --git a/test/framers/generator.py b/test/framers/generator.py index 04344a823..37e4e46c8 100755 --- a/test/framers/generator.py +++ b/test/framers/generator.py @@ -3,10 +3,10 @@ from pymodbus.factory import ClientDecoder, ServerDecoder from pymodbus.framer import ( - ModbusAsciiFramer, - ModbusRtuFramer, - ModbusSocketFramer, - ModbusTlsFramer, + FramerAscii, + FramerRTU, + FramerSocket, + FramerTLS, ) from pymodbus.pdu import ModbusExceptions as merror from pymodbus.pdu.register_read_message import ( @@ -17,19 +17,19 @@ def set_calls(): """Define calls.""" - for framer in (ModbusAsciiFramer, ModbusRtuFramer, ModbusSocketFramer, ModbusTlsFramer): + for framer in (FramerAscii, FramerRTU, FramerSocket, FramerTLS): 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()) + client = framer(ClientDecoder(), [0]) 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()) + server = framer(ServerDecoder(), [0]) response = ReadHoldingRegistersResponse([141,142]) response.slave_id = dev_id response.transaction_id = tid diff --git a/test/framers/test_asyncframer.py b/test/framers/test_asyncframer.py deleted file mode 100644 index b05663e40..000000000 --- a/test/framers/test_asyncframer.py +++ /dev/null @@ -1,391 +0,0 @@ -"""Test framer.""" - -from unittest import mock - -import pytest - -from pymodbus.factory import ClientDecoder -from pymodbus.framer import FramerType -from pymodbus.framer.ascii import FramerAscii -from pymodbus.framer.rtu import FramerRTU -from pymodbus.framer.socket import FramerSocket -from pymodbus.framer.tls import FramerTLS - - -class TestFramer: - """Test module.""" - - @pytest.mark.parametrize(("entry"), list(FramerType)) - async def test_framer_init(self, dummy_async_framer): - """Test framer type.""" - assert dummy_async_framer.handle - - @pytest.mark.parametrize(("data", "res_len", "cx", "rc"), [ - (b'12345', 5, 1, [(5, b'12345')]), # full frame - (b'12345', 0, 0, [(0, b'')]), # not full frame, need more data - (b'12345', 5, 0, [(5, b'')]), # faulty frame, skipped - (b'1234512345', 10, 2, [(5, b'12345'), (5, b'12345')]), # 2 full frames - (b'12345678', 5, 1, [(5, b'12345'), (0, b'')]), # full frame, not full frame - (b'67812345', 8, 1, [(8, b'12345')]), # garble first, full frame next - (b'12345678', 5, 0, [(5, b'')]), # garble first, not full frame - (b'12345678', 8, 0, [(8, b'')]), # garble first, faulty frame - ]) - async def test_framer_callback(self, dummy_async_framer, data, res_len, cx, rc): - """Test framer type.""" - dummy_async_framer.callback_request_response = mock.Mock() - dummy_async_framer.handle.decode = mock.MagicMock(side_effect=iter(rc)) - assert dummy_async_framer.callback_data(data) == res_len - assert dummy_async_framer.callback_request_response.call_count == cx - if cx: - dummy_async_framer.callback_request_response.assert_called_with(b'12345', 0, 0) - else: - dummy_async_framer.callback_request_response.assert_not_called() - - @pytest.mark.parametrize(("data", "res_len", "rc"), [ - (b'12345', 5, [(5, b'12345'), (0, b'')]), # full frame, wrong dev_id - ]) - async def test_framer_callback_wrong_id(self, dummy_async_framer, data, res_len, rc): - """Test framer type.""" - dummy_async_framer.callback_request_response = mock.Mock() - dummy_async_framer.handle.decode = mock.MagicMock(side_effect=iter(rc)) - dummy_async_framer.broadcast = False - assert dummy_async_framer.callback_data(data) == res_len - # dummy_async_framer.callback_request_response.assert_not_called() - - async def test_framer_build_send(self, dummy_async_framer): - """Test framer type.""" - dummy_async_framer.handle.encode = mock.MagicMock(return_value=(b'decode')) - dummy_async_framer.build_send(b'decode', 1, 0) - dummy_async_framer.handle.encode.assert_called_once() - dummy_async_framer.send.assert_called_once() - dummy_async_framer.send.assert_called_with(b'decode', None) - - @pytest.mark.parametrize( - ("data", "res_len", "res_id", "res_tid", "res_data"), [ - (b'\x00\x01', 0, 0, 0, b''), - (b'\x01\x02\x03', 3, 1, 2, b'\x03'), - (b'\x04\x05\x06\x07\x08\x09\x00\x01\x02\x03', 10, 4, 5, b'\x06\x07\x08\x09\x00\x01\x02\x03'), - ]) - async def xtest_framer_decode(self, dummy_async_framer, data, res_id, res_tid, res_len, res_data): - """Test decode method in all types.""" - t_len, t_id, t_tid, t_data = dummy_async_framer.handle.decode(data) - assert res_len == t_len - assert res_id == t_id - assert res_tid == t_tid - assert res_data == t_data - - @pytest.mark.parametrize( - ("data", "dev_id", "tr_id", "res_data"), [ - (b'\x01\x02', 5, 6, b'\x05\x06\x01\x02'), - (b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09', 17, 25, b'\x11\x19\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09'), - ]) - async def xtest_framer_encode(self, dummy_async_framer, data, dev_id, tr_id, res_data): - """Test decode method in all types.""" - t_data = dummy_async_framer.handle.encode(data, dev_id, tr_id) - assert res_data == t_data - - @pytest.mark.parametrize( - ("func", "test_compare", "expect"), - [(FramerAscii.check_LRC, 0x1c, True), - (FramerAscii.check_LRC, 0x0c, False), - (FramerAscii.compute_LRC, None, 0x1c), - (FramerRTU.check_CRC, 0xE2DB, True), - (FramerRTU.check_CRC, 0xDBE2, False), - (FramerRTU.compute_CRC, None, 0xE2DB), - ] - ) - def test_LRC_CRC(self, func, test_compare, expect): - """Test check_LRC.""" - data = b'\x12\x34\x23\x45\x34\x56\x45\x67' - assert expect == func(data, test_compare) if test_compare else func(data) - - def test_roundtrip_LRC(self): - """Test combined compute/check LRC.""" - data = b'\x12\x34\x23\x45\x34\x56\x45\x67' - assert FramerAscii.compute_LRC(data) == 0x1c - assert FramerAscii.check_LRC(data, 0x1C) - - def test_crc16_table(self): - """Test the crc16 table is prefilled.""" - assert len(FramerRTU.crc16_table) == 256 - assert isinstance(FramerRTU.crc16_table[0], int) - assert isinstance(FramerRTU.crc16_table[255], int) - - def test_roundtrip_CRC(self): - """Test combined compute/check CRC.""" - data = b'\x12\x34\x23\x45\x34\x56\x45\x67' - assert FramerRTU.compute_CRC(data) == 0xE2DB - assert FramerRTU.check_CRC(data, 0xE2DB) - - - -class TestFramerType: - """Test classes.""" - - @pytest.mark.parametrize( - ("frame", "frame_expected"), - [ - (FramerAscii, [ - b':0003007C00027F\r\n', - b':000304008D008EDE\r\n', - b':0083027B\r\n', - b':1103007C00026E\r\n', - b':110304008D008ECD\r\n', - b':1183026A\r\n', - b':FF03007C000280\r\n', - b':FF0304008D008EDF\r\n', - b':FF83027C\r\n', - b':0003007C00027F\r\n', - b':000304008D008EDE\r\n', - b':0083027B\r\n', - b':1103007C00026E\r\n', - b':110304008D008ECD\r\n', - b':1183026A\r\n', - b':FF03007C000280\r\n', - b':FF0304008D008EDF\r\n', - b':FF83027C\r\n', - b':0003007C00027F\r\n', - b':000304008D008EDE\r\n', - b':0083027B\r\n', - b':1103007C00026E\r\n', - b':110304008D008ECD\r\n', - b':1183026A\r\n', - b':FF03007C000280\r\n', - b':FF0304008D008EDF\r\n', - b':FF83027C\r\n', - ]), - (FramerRTU, [ - b'\x00\x03\x00\x7c\x00\x02\x04\x02', - b'\x00\x03\x04\x00\x8d\x00\x8e\xfa\xbc', - b'\x00\x83\x02\x91\x31', - b'\x11\x03\x00\x7c\x00\x02\x07\x43', - b'\x11\x03\x04\x00\x8d\x00\x8e\xfb\xbd', - b'\x11\x83\x02\xc1\x34', - b'\xff\x03\x00\x7c\x00\x02\x10\x0d', - b'\xff\x03\x04\x00\x8d\x00\x8e\xf5\xb3', - b'\xff\x83\x02\xa1\x01', - b'\x00\x03\x00\x7c\x00\x02\x04\x02', - b'\x00\x03\x04\x00\x8d\x00\x8e\xfa\xbc', - b'\x00\x83\x02\x91\x31', - b'\x11\x03\x00\x7c\x00\x02\x07\x43', - b'\x11\x03\x04\x00\x8d\x00\x8e\xfb\xbd', - b'\x11\x83\x02\xc1\x34', - b'\xff\x03\x00\x7c\x00\x02\x10\x0d', - b'\xff\x03\x04\x00\x8d\x00\x8e\xf5\xb3', - b'\xff\x83\x02\xa1\x01', - b'\x00\x03\x00\x7c\x00\x02\x04\x02', - b'\x00\x03\x04\x00\x8d\x00\x8e\xfa\xbc', - b'\x00\x83\x02\x91\x31', - b'\x11\x03\x00\x7c\x00\x02\x07\x43', - b'\x11\x03\x04\x00\x8d\x00\x8e\xfb\xbd', - b'\x11\x83\x02\xc1\x34', - b'\xff\x03\x00\x7c\x00\x02\x10\x0d', - b'\xff\x03\x04\x00\x8d\x00\x8e\xf5\xb3', - b'\xff\x83\x02\xa1\x01', - ]), - (FramerSocket, [ - b'\x00\x00\x00\x00\x00\x06\x00\x03\x00\x7c\x00\x02', - b'\x00\x00\x00\x00\x00\x07\x00\x03\x04\x00\x8d\x00\x8e', - b'\x00\x00\x00\x00\x00\x03\x00\x83\x02', - b'\x00\x00\x00\x00\x00\x06\x11\x03\x00\x7c\x00\x02', - b'\x00\x00\x00\x00\x00\x07\x11\x03\x04\x00\x8d\x00\x8e', - b'\x00\x00\x00\x00\x00\x03\x11\x83\x02', - b'\x00\x00\x00\x00\x00\x06\xff\x03\x00\x7c\x00\x02', - b'\x00\x00\x00\x00\x00\x07\xff\x03\x04\x00\x8d\x00\x8e', - b'\x00\x00\x00\x00\x00\x03\xff\x83\x02', - b'\x0c\x05\x00\x00\x00\x06\x00\x03\x00\x7c\x00\x02', - b'\x0c\x05\x00\x00\x00\x07\x00\x03\x04\x00\x8d\x00\x8e', - b'\x0c\x05\x00\x00\x00\x03\x00\x83\x02', - b'\x0c\x05\x00\x00\x00\x06\x11\x03\x00\x7c\x00\x02', - b'\x0c\x05\x00\x00\x00\x07\x11\x03\x04\x00\x8d\x00\x8e', - b'\x0c\x05\x00\x00\x00\x03\x11\x83\x02', - b'\x0c\x05\x00\x00\x00\x06\xff\x03\x00\x7c\x00\x02', - b'\x0c\x05\x00\x00\x00\x07\xff\x03\x04\x00\x8d\x00\x8e', - b'\x0c\x05\x00\x00\x00\x03\xff\x83\x02', - ]), - (FramerTLS, [ - b'\x03\x00\x7c\x00\x02', - b'\x03\x04\x00\x8d\x00\x8e', - b'\x83\x02', - ]), - ] - ) - @pytest.mark.parametrize( - ("inx1", "data"), - [ - (0, b"\x03\x00\x7c\x00\x02",), # Request - (1, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (2, b'\x83\x02',), # Exception - ] - ) - @pytest.mark.parametrize( - ("inx2", "dev_id"), - [ - (0, 0), - (3, 17), - (6, 255), - ] - ) - @pytest.mark.parametrize( - ("inx3", "tr_id"), - [ - (0, 0), - (9, 3077), - ] - ) - def test_encode_type(self, frame, frame_expected, data, dev_id, tr_id, inx1, inx2, inx3): - """Test encode method.""" - if frame == FramerTLS and dev_id + tr_id: - return - frame_obj = frame(ClientDecoder(), [0]) - expected = frame_expected[inx1 + inx2 + inx3] - encoded_data = frame_obj.encode(data, dev_id, tr_id) - assert encoded_data == expected - - @pytest.mark.parametrize( - ("entry", "is_server", "data", "dev_id", "tr_id", "expected"), - [ - (FramerType.ASCII, True, b':0003007C00027F\r\n', 0, 0, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.ASCII, False, b':000304008D008EDE\r\n', 0, 0, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.ASCII, False, b':0083027B\r\n', 0, 0, b'\x83\x02',), # Exception - (FramerType.ASCII, True, b':1103007C00026E\r\n', 17, 17, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.ASCII, False, b':110304008D008ECD\r\n', 17, 17, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.ASCII, False, b':1183026A\r\n', 17, 17, b'\x83\x02',), # Exception - (FramerType.ASCII, True, b':FF03007C000280\r\n', 255, 255, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.ASCII, False, b':FF0304008D008EDF\r\n', 255, 255, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.ASCII, False, b':FF83027C\r\n', 255, 255, b'\x83\x02',), # Exception - (FramerType.RTU, True, b'\x00\x03\x00\x7c\x00\x02\x04\x02', 0, 0, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.RTU, False, b'\x00\x03\x04\x00\x8d\x00\x8e\xfa\xbc', 0, 0, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.RTU, False, b'\x00\x83\x02\x91\x31', 0, 0, b'\x83\x02',), # Exception - (FramerType.RTU, True, b'\x11\x03\x00\x7c\x00\x02\x07\x43', 17, 17, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.RTU, False, b'\x11\x03\x04\x00\x8d\x00\x8e\xfb\xbd', 17, 17, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.RTU, False, b'\x11\x83\x02\xc1\x34', 17, 17, b'\x83\x02',), # Exception - (FramerType.RTU, True, b'\xff\x03\x00|\x00\x02\x10\x0d', 255, 255, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.RTU, False, b'\xff\x03\x04\x00\x8d\x00\x8e\xf5\xb3', 255, 255, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.RTU, False, b'\xff\x83\x02\xa1\x01', 255, 255, b'\x83\x02',), # Exception - (FramerType.SOCKET, True, b'\x00\x00\x00\x00\x00\x06\x00\x03\x00\x7c\x00\x02', 0, 0, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.SOCKET, False, b'\x00\x00\x00\x00\x00\x07\x00\x03\x04\x00\x8d\x00\x8e', 0, 0, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.SOCKET, False, b'\x00\x00\x00\x00\x00\x03\x00\x83\x02', 0, 0, b'\x83\x02',), # Exception - (FramerType.SOCKET, True, b'\x00\x00\x00\x00\x00\x06\x11\x03\x00\x7c\x00\x02', 17, 0, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.SOCKET, False, b'\x00\x00\x00\x00\x00\x07\x11\x03\x04\x00\x8d\x00\x8e', 17, 0, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.SOCKET, False, b'\x00\x00\x00\x00\x00\x03\x11\x83\x02', 17, 0, b'\x83\x02',), # Exception - (FramerType.SOCKET, True, b'\x00\x00\x00\x00\x00\x06\xff\x03\x00\x7c\x00\x02', 255, 0, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.SOCKET, False, b'\x00\x00\x00\x00\x00\x07\xff\x03\x04\x00\x8d\x00\x8e', 255, 0, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.SOCKET, False, b'\x00\x00\x00\x00\x00\x03\xff\x83\x02', 255, 0, b'\x83\x02',), # Exception - (FramerType.SOCKET, True, b'\x0c\x05\x00\x00\x00\x06\x00\x03\x00\x7c\x00\x02', 0, 3077, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.SOCKET, False, b'\x0c\x05\x00\x00\x00\x07\x00\x03\x04\x00\x8d\x00\x8e', 0, 3077, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.SOCKET, False, b'\x0c\x05\x00\x00\x00\x03\x00\x83\x02', 0, 3077, b'\x83\x02',), # Exception - (FramerType.SOCKET, True, b'\x0c\x05\x00\x00\x00\x06\x11\x03\x00\x7c\x00\x02', 17, 3077, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.SOCKET, False, b'\x0c\x05\x00\x00\x00\x07\x11\x03\x04\x00\x8d\x00\x8e', 17, 3077, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.SOCKET, False, b'\x0c\x05\x00\x00\x00\x03\x11\x83\x02', 17, 3077, b'\x83\x02',), # Exception - (FramerType.SOCKET, True, b'\x0c\x05\x00\x00\x00\x06\xff\x03\x00\x7c\x00\x02', 255, 3077, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.SOCKET, False, b'\x0c\x05\x00\x00\x00\x07\xff\x03\x04\x00\x8d\x00\x8e', 255, 3077, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.SOCKET, False, b'\x0c\x05\x00\x00\x00\x03\xff\x83\x02', 255, 3077, b'\x83\x02',), # Exception - (FramerType.TLS, True, b'\x03\x00\x7c\x00\x02', 0, 0, b"\x03\x00\x7c\x00\x02",), # Request - (FramerType.TLS, False, b'\x03\x04\x00\x8d\x00\x8e', 0, 0, b"\x03\x04\x00\x8d\x00\x8e",), # Response - (FramerType.TLS, False, b'\x83\x02', 0, 0, b'\x83\x02',), # Exception - ] - ) - @pytest.mark.parametrize( - ("split"), - [ - "no", - "half", - "single", - ] - ) - async def test_decode_type(self, entry, dummy_async_framer, data, dev_id, tr_id, expected, split): - """Test encode method.""" - if entry == FramerType.TLS and split != "no": - return - if entry == FramerType.RTU: - return - dummy_async_framer.callback_request_response = mock.MagicMock() - if split == "no": - used_len = dummy_async_framer.callback_data(data) - elif split == "half": - split_len = int(len(data) / 2) - assert not dummy_async_framer.callback_data(data[0:split_len]) - dummy_async_framer.callback_request_response.assert_not_called() - used_len = dummy_async_framer.callback_data(data) - else: - last = len(data) - for i in range(0, last -1): - assert not dummy_async_framer.callback_data(data[0:i+1]) - dummy_async_framer.callback_request_response.assert_not_called() - used_len = dummy_async_framer.callback_data(data) - assert used_len == len(data) - dummy_async_framer.callback_request_response.assert_called_with(expected, dev_id, tr_id) - - @pytest.mark.parametrize( - ("entry", "data", "exp"), - [ - (FramerType.ASCII, b':0003007C00017F\r\n', [ # bad crc - (17, b''), - ]), - (FramerType.ASCII, b':0003007C00027F\r\n:0003007C00027F\r\n', [ # double good crc - (17, b'\x03\x00\x7c\x00\x02'), - (17, b'\x03\x00\x7c\x00\x02'), - ]), - (FramerType.ASCII, b':0003007C00017F\r\n:0003007C00027F\r\n', [ # bad crc + good CRC - (34, b'\x03\x00\x7c\x00\x02'), - ]), - (FramerType.ASCII, b'abc:0003007C00027F\r\n', [ # garble in front - (20, b'\x03\x00\x7c\x00\x02'), - ]), - (FramerType.ASCII, b':0003007C00017F\r\nabc', [ # bad crc, garble after - (17, b''), - ]), - (FramerType.ASCII, b':0003007C00017F\r\nabcdefghijkl', [ # bad crc, garble after - (29, b''), - ]), - (FramerType.ASCII, b':0003007C00027F\r\nabc', [ # good crc, garble after - (17, b'\x03\x00\x7c\x00\x02'), - ]), - (FramerType.ASCII, b':0003007C00017F\r\n:0003', [ # bad crc, part second framer - (17, b''), - ]), - (FramerType.SOCKET, b'\x00\x00\x00\x00\x00\x06\x00\x03\x00\x7c\x00\x02\x00\x00\x00\x00\x00\x06\x00\x03\x00\x7c\x00\x02', [ # double good crc - (12, b"\x03\x00\x7c\x00\x02"), - (12, b"\x03\x00\x7c\x00\x02"), - ]), - # (FramerType.RTU, b'\x00\x83\x02\x91\x21', [ # bad crc - # (5, b''), - #]), - #(FramerType.RTU, b'\x00\x83\x02\xf0\x91\x31', [ # dummy char in stream, bad crc - # (5, b''), - #]), - # (FramerType.RTU, b'\x00\x83\x02\x91\x21\x00\x83\x02\x91\x31', [ # bad crc + good CRC - # (10, b'\x83\x02'), - #]), - #(FramerType.RTU, b'\x00\x83\x02\xf0\x91\x31\x00\x83\x02\x91\x31', [ # dummy char in stream, bad crc + good CRC - # (11, b''), - #]), - - # (FramerType.RTU, b'\x00\x83\x02\x91\x31', 0), # garble in front - # (FramerType.ASCII, b'abc:0003007C00027F\r\n', [ # garble in front - # (20, b'\x03\x00\x7c\x00\x02'), - # ]), - - # (FramerType.RTU, b'\x00\x83\x02\x91\x31', 0), # garble after - # (FramerType.ASCII, b':0003007C00017F\r\nabc', [ # bad crc, garble after - # (17, b''), - # ]), - # (FramerType.ASCII, b':0003007C00017F\r\nabcdefghijkl', [ # bad crc, garble after - # (29, b''), - # ]), - # (FramerType.ASCII, b':0003007C00027F\r\nabc', [ # good crc, garble after - # (17, b'\x03\x00\x7c\x00\x02'), - # ]), - # (FramerType.RTU, b'\x00\x83\x02\x91\x31', 0), # part second framer - # (FramerType.ASCII, b':0003007C00017F\r\n:0003', [ # bad crc, part second framer - # (17, b''), - # ]), - ] - ) - async def test_decode_complicated(self, dummy_async_framer, data, exp): - """Test encode method.""" - for ent in exp: - used_len, res_data = dummy_async_framer.handle.decode(data) - assert used_len == ent[0] - assert res_data == ent[1] diff --git a/test/framers/test_multidrop.py b/test/framers/test_multidrop.py index 9461bb2ee..ca4476306 100644 --- a/test/framers/test_multidrop.py +++ b/test/framers/test_multidrop.py @@ -3,7 +3,7 @@ import pytest -from pymodbus.framer import ModbusRtuFramer +from pymodbus.framer import FramerRTU from pymodbus.server.async_io import ServerDecoder @@ -17,7 +17,7 @@ class NotImplementedTestMultidrop: @pytest.fixture(name="framer") def fixture_framer(self): """Prepare framer.""" - return ModbusRtuFramer(ServerDecoder()) + return FramerRTU(ServerDecoder(), [0]) @pytest.fixture(name="callback") def fixture_callback(self): diff --git a/test/sub_client/test_client_faulty_response.py b/test/sub_client/test_client_faulty_response.py index 2cc4731cc..a31235953 100644 --- a/test/sub_client/test_client_faulty_response.py +++ b/test/sub_client/test_client_faulty_response.py @@ -5,7 +5,7 @@ from pymodbus.exceptions import ModbusIOException from pymodbus.factory import ClientDecoder -from pymodbus.framer import ModbusRtuFramer, ModbusSocketFramer +from pymodbus.framer import FramerRTU, FramerSocket class TestFaultyResponses: @@ -18,7 +18,7 @@ class TestFaultyResponses: @pytest.fixture(name="framer") def fixture_framer(self): """Prepare framer.""" - return ModbusSocketFramer(ClientDecoder()) + return FramerSocket(ClientDecoder(), [0]) @pytest.fixture(name="callback") def fixture_callback(self): @@ -33,7 +33,7 @@ def test_ok_frame(self, framer, callback): def test_1917_frame(self, callback): """Test invalid frame in issue 1917.""" recv = b"\x01\x86\x02\x00\x01" - framer = ModbusRtuFramer(ClientDecoder()) + framer = FramerRTU(ClientDecoder(), [0]) framer.processIncomingPacket(recv, callback, self.slaves) callback.assert_not_called() diff --git a/test/sub_client/test_client_sync.py b/test/sub_client/test_client_sync.py index 33dd1d5b0..e2dd62d8a 100755 --- a/test/sub_client/test_client_sync.py +++ b/test/sub_client/test_client_sync.py @@ -14,11 +14,11 @@ ModbusUdpClient, ) from pymodbus.exceptions import ConnectionException -from pymodbus.transaction import ( - ModbusAsciiFramer, - ModbusRtuFramer, - ModbusSocketFramer, - ModbusTlsFramer, +from pymodbus.framer import ( + FramerAscii, + FramerRTU, + FramerSocket, + FramerTLS, ) from test.conftest import mockSocket @@ -217,7 +217,7 @@ def test_syn_tls_client_instantiation(self): # default SSLContext client = ModbusTlsClient("127.0.0.1") assert client - assert isinstance(client.framer, ModbusTlsFramer) + assert isinstance(client.framer, FramerTLS) assert client.comm_params.sslctx @mock.patch("pymodbus.client.tcp.select") @@ -307,15 +307,15 @@ def test_sync_serial_client_instantiation(self): assert client assert isinstance( ModbusSerialClient("/dev/null", framer=FramerType.ASCII).framer, - ModbusAsciiFramer, + FramerAscii, ) assert isinstance( ModbusSerialClient("/dev/null", framer=FramerType.RTU).framer, - ModbusRtuFramer, + FramerRTU, ) assert isinstance( ModbusSerialClient("/dev/null", framer=FramerType.SOCKET).framer, - ModbusSocketFramer, + FramerSocket, ) def test_sync_serial_rtu_client_timeouts(self): diff --git a/test/sub_current/test_transaction.py b/test/sub_current/test_transaction.py index 152071d9e..a951db679 100755 --- a/test/sub_current/test_transaction.py +++ b/test/sub_current/test_transaction.py @@ -5,12 +5,14 @@ ModbusIOException, ) from pymodbus.factory import ServerDecoder +from pymodbus.framer import ( + FramerAscii, + FramerRTU, + FramerSocket, + FramerTLS, +) from pymodbus.pdu import ModbusRequest from pymodbus.transaction import ( - ModbusAsciiFramer, - ModbusRtuFramer, - ModbusSocketFramer, - ModbusTlsFramer, ModbusTransactionManager, SyncModbusTransactionManager, ) @@ -38,10 +40,10 @@ def setup_method(self): """Set up the test environment.""" self.client = None self.decoder = ServerDecoder() - self._tcp = ModbusSocketFramer(decoder=self.decoder, client=None) - self._tls = ModbusTlsFramer(decoder=self.decoder, client=None) - self._rtu = ModbusRtuFramer(decoder=self.decoder, client=None) - self._ascii = ModbusAsciiFramer(decoder=self.decoder, client=None) + self._tcp = FramerSocket(self.decoder, [0]) + self._tls = FramerTLS(self.decoder, [0]) + self._rtu = FramerRTU(self.decoder, [0]) + self._ascii = FramerAscii(self.decoder, [0]) self._manager = SyncModbusTransactionManager(self.client, 3) # ----------------------------------------------------------------------- # diff --git a/test/sub_server/test_server_asyncio.py b/test/sub_server/test_server_asyncio.py index e3de50956..fb9d8dbfc 100755 --- a/test/sub_server/test_server_asyncio.py +++ b/test/sub_server/test_server_asyncio.py @@ -215,7 +215,7 @@ async def test_async_tcp_server_receive_data(self): BasicClient.data = b"\x01\x00\x00\x00\x00\x06\x01\x03\x00\x00\x00\x19" await self.start_server() with mock.patch( - "pymodbus.transaction.ModbusSocketFramer.processIncomingPacket", + "pymodbus.framer.FramerSocket.processIncomingPacket", new_callable=mock.Mock, ) as process: await self.connect_server() @@ -345,7 +345,7 @@ async def test_async_udp_server_exception(self): BasicClient.done = asyncio.Future() await self.start_server(do_udp=True) with mock.patch( - "pymodbus.transaction.ModbusSocketFramer.processIncomingPacket", + "pymodbus.framer.FramerSocket.processIncomingPacket", new_callable=lambda: mock.Mock(side_effect=Exception), ): # get the random server port pylint: disable=protected-access @@ -361,7 +361,7 @@ async def test_async_tcp_server_exception(self): BasicClient.data = b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" await self.start_server() with mock.patch( - "pymodbus.transaction.ModbusSocketFramer.processIncomingPacket", + "pymodbus.framer.FramerSocket.processIncomingPacket", new_callable=lambda: mock.Mock(side_effect=Exception), ): await self.connect_server()