Skip to content

Commit

Permalink
Add trace API for client. (#2478)
Browse files Browse the repository at this point in the history
  • Loading branch information
janiversen authored Nov 26, 2024
1 parent b2d9799 commit 68653be
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 171 deletions.
3 changes: 2 additions & 1 deletion API_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ API changes 3.8.0
- Remove skip_encode parameter.
- rename ModbusExceptions enums to legal constants.
- enforce client keyword only parameters (positional not allowed).

- added trace_packet/pdu/connect to client
- removed on_connect_callback from client

API changes 3.7.0
-----------------
Expand Down
22 changes: 16 additions & 6 deletions pymodbus/client/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from collections.abc import Awaitable, Callable

from pymodbus.client.mixin import ModbusClientMixin
from pymodbus.client.modbusclientprotocol import ModbusClientProtocol
from pymodbus.exceptions import ConnectionException
from pymodbus.framer import FRAMER_NAME_TO_CLASS, FramerBase, FramerType
from pymodbus.logging import Log
Expand All @@ -25,20 +24,25 @@ def __init__(
self,
framer: FramerType,
retries: int,
on_connect_callback: Callable[[bool], None] | None,
comm_params: CommParams,
trace_packet: Callable[[bool, bytes | None], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU | None], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None,
) -> None:
"""Initialize a client instance.
:meta private:
"""
ModbusClientMixin.__init__(self) # type: ignore[arg-type]
self.comm_params = comm_params
self.ctx = ModbusClientProtocol(
self.ctx = TransactionManager(
comm_params,
(FRAMER_NAME_TO_CLASS[framer])(DecodePDU(False)),
self.comm_params,
retries,
on_connect_callback,
False,
trace_packet=trace_packet,
trace_pdu=trace_pdu,
trace_connect=trace_connect,
)
self.state = ModbusTransactionState.IDLE

Expand Down Expand Up @@ -115,6 +119,9 @@ def __init__(
framer: FramerType,
retries: int,
comm_params: CommParams,
trace_packet: Callable[[bool, bytes | None], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU | None], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None,
) -> None:
"""Initialize a client instance.
Expand All @@ -132,7 +139,10 @@ def __init__(
self.framer,
retries,
False,
self,
trace_packet=trace_packet,
trace_pdu=trace_pdu,
trace_connect=trace_connect,
sync_client=self,
)
self.reconnect_delay_current = self.comm_params.reconnect_delay or 0
self.use_udp = False
Expand Down
15 changes: 4 additions & 11 deletions pymodbus/client/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

import struct
from abc import abstractmethod
from enum import Enum
from typing import Generic, TypeVar

Expand Down Expand Up @@ -48,17 +49,9 @@ class ModbusClientMixin(Generic[T]): # pylint: disable=too-many-public-methods
def __init__(self):
"""Initialize."""

def execute(self, _no_response_expected: bool, _request: ModbusPDU,) -> T:
"""Execute request (code ???).
:raises ModbusException:
Call with custom function codes.
.. tip::
Response is not interpreted.
"""
raise NotImplementedError("execute of ModbusClientMixin needs to be overridden")
@abstractmethod
def execute(self, no_response_expected: bool, request: ModbusPDU,) -> T:
"""Execute request."""

def read_coils(self, address: int, *, count: int = 1, slave: int = 1, no_response_expected: bool = False) -> T:
"""Read coils (code 0x01).
Expand Down
49 changes: 0 additions & 49 deletions pymodbus/client/modbusclientprotocol.py

This file was deleted.

28 changes: 21 additions & 7 deletions pymodbus/client/serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pymodbus.exceptions import ConnectionException
from pymodbus.framer import FramerType
from pymodbus.logging import Log
from pymodbus.pdu import ModbusPDU
from pymodbus.transport import CommParams, CommType
from pymodbus.utilities import ModbusTransactionState

Expand Down Expand Up @@ -39,7 +40,12 @@ class AsyncModbusSerialClient(ModbusBaseClient):
:param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting.
:param timeout: Timeout for connecting and receiving data, in seconds.
:param retries: Max number of retries per request.
:param on_connect_callback: Function that will be called just before a connection attempt.
:param trace_packet: Called with bytestream received/to be sent
:param trace_pdu: Called with PDU received/to be sent
:param trace_connect: Called when connected/disconnected
.. tip::
The trace methods allow to modify the datastream/pdu !
.. tip::
**reconnect_delay** doubles automatically with each unsuccessful connect, from
Expand Down Expand Up @@ -75,7 +81,9 @@ def __init__( # pylint: disable=too-many-arguments
reconnect_delay_max: float = 300,
timeout: float = 3,
retries: int = 3,
on_connect_callback: Callable[[bool], None] | None = None,
trace_packet: Callable[[bool, bytes | None], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU | None], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None,
) -> None:
"""Initialize Asyncio Modbus Serial Client."""
if "serial" not in sys.modules: # pragma: no cover
Expand All @@ -102,7 +110,6 @@ def __init__( # pylint: disable=too-many-arguments
self,
framer,
retries,
on_connect_callback,
self.comm_params,
)

Expand All @@ -127,11 +134,12 @@ class ModbusSerialClient(ModbusBaseSyncClient):
:param reconnect_delay_max: Not used in the sync client
:param timeout: Timeout for connecting and receiving data, in seconds.
:param retries: Max number of retries per request.
:param trace_packet: Called with bytestream received/to be sent
:param trace_pdu: Called with PDU received/to be sent
:param trace_connect: Called when connected/disconnected
Note that unlike the async client, the sync client does not perform
retries. If the connection has closed, the client will attempt to reconnect
once before executing each read/write request, and will raise a
ConnectionException if this fails.
.. tip::
The trace methods allow to modify the datastream/pdu !
Example::
Expand Down Expand Up @@ -166,6 +174,9 @@ def __init__( # pylint: disable=too-many-arguments
reconnect_delay_max: float = 300,
timeout: float = 3,
retries: int = 3,
trace_packet: Callable[[bool, bytes | None], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU | None], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None,
) -> None:
"""Initialize Modbus Serial Client."""
if "serial" not in sys.modules: # pragma: no cover
Expand All @@ -192,6 +203,9 @@ def __init__( # pylint: disable=too-many-arguments
framer,
retries,
self.comm_params,
trace_connect=trace_connect,
trace_packet=trace_packet,
trace_pdu=trace_pdu,
)
self.socket: serial.Serial | None = None
self.last_frame_end = None
Expand Down
26 changes: 18 additions & 8 deletions pymodbus/client/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pymodbus.exceptions import ConnectionException
from pymodbus.framer import FramerType
from pymodbus.logging import Log
from pymodbus.pdu import ModbusPDU
from pymodbus.transport import CommParams, CommType


Expand All @@ -31,7 +32,12 @@ class AsyncModbusTcpClient(ModbusBaseClient):
:param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting.
:param timeout: Timeout for connecting and receiving data, in seconds.
:param retries: Max number of retries per request.
:param on_connect_callback: Function that will be called just before a connection attempt.
:param trace_packet: Called with bytestream received/to be sent
:param trace_pdu: Called with PDU received/to be sent
:param trace_connect: Called when connected/disconnected
.. tip::
The trace methods allow to modify the datastream/pdu !
.. tip::
**reconnect_delay** doubles automatically with each unsuccessful connect, from
Expand Down Expand Up @@ -64,7 +70,9 @@ def __init__( # pylint: disable=too-many-arguments
reconnect_delay_max: float = 300,
timeout: float = 3,
retries: int = 3,
on_connect_callback: Callable[[bool], None] | None = None,
trace_packet: Callable[[bool, bytes | None], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU | None], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None,
) -> None:
"""Initialize Asyncio Modbus TCP Client."""
if not hasattr(self,"comm_params"):
Expand All @@ -84,7 +92,6 @@ def __init__( # pylint: disable=too-many-arguments
self,
framer,
retries,
on_connect_callback,
self.comm_params,
)

Expand All @@ -106,12 +113,12 @@ class ModbusTcpClient(ModbusBaseSyncClient):
:param reconnect_delay_max: Not used in the sync client
:param timeout: Timeout for connecting and receiving data, in seconds.
:param retries: Max number of retries per request.
:param trace_packet: Called with bytestream received/to be sent
:param trace_pdu: Called with PDU received/to be sent
:param trace_connect: Called when connected/disconnected
.. tip::
Unlike the async client, the sync client does not perform
retries. If the connection has closed, the client will attempt to reconnect
once before executing each read/write request, and will raise a
ConnectionException if this fails.
The trace methods allow to modify the datastream/pdu !
Example::
Expand All @@ -129,7 +136,7 @@ async def run():

socket: socket.socket | None

def __init__(
def __init__( # pylint: disable=too-many-arguments
self,
host: str,
*,
Expand All @@ -141,6 +148,9 @@ def __init__(
reconnect_delay_max: float = 300,
timeout: float = 3,
retries: int = 3,
trace_packet: Callable[[bool, bytes | None], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU | None], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None,
) -> None:
"""Initialize Modbus TCP Client."""
if not hasattr(self,"comm_params"):
Expand Down
24 changes: 17 additions & 7 deletions pymodbus/client/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from pymodbus.client.tcp import AsyncModbusTcpClient, ModbusTcpClient
from pymodbus.framer import FramerType
from pymodbus.logging import Log
from pymodbus.pdu import ModbusPDU
from pymodbus.transport import CommParams, CommType


Expand All @@ -29,7 +30,12 @@ class AsyncModbusTlsClient(AsyncModbusTcpClient):
:param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting.
:param timeout: Timeout for connecting and receiving data, in seconds.
:param retries: Max number of retries per request.
:param on_connect_callback: Function that will be called just before a connection attempt.
:param trace_packet: Called with bytestream received/to be sent
:param trace_pdu: Called with PDU received/to be sent
:param trace_connect: Called when connected/disconnected
.. tip::
The trace methods allow to modify the datastream/pdu !
.. tip::
**reconnect_delay** doubles automatically with each unsuccessful connect, from
Expand Down Expand Up @@ -63,7 +69,9 @@ def __init__( # pylint: disable=too-many-arguments
reconnect_delay_max: float = 300,
timeout: float = 3,
retries: int = 3,
on_connect_callback: Callable[[bool], None] | None = None,
trace_packet: Callable[[bool, bytes | None], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU | None], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None,
):
"""Initialize Asyncio Modbus TLS Client."""
if framer not in [FramerType.TLS]:
Expand All @@ -84,7 +92,6 @@ def __init__( # pylint: disable=too-many-arguments
"",
framer=framer,
retries=retries,
on_connect_callback=on_connect_callback,
)

@classmethod
Expand Down Expand Up @@ -127,12 +134,12 @@ class ModbusTlsClient(ModbusTcpClient):
:param reconnect_delay_max: Not used in the sync client
:param timeout: Timeout for connecting and receiving data, in seconds.
:param retries: Max number of retries per request.
:param trace_packet: Called with bytestream received/to be sent
:param trace_pdu: Called with PDU received/to be sent
:param trace_connect: Called when connected/disconnected
.. tip::
Unlike the async client, the sync client does not perform
retries. If the connection has closed, the client will attempt to reconnect
once before executing each read/write request, and will raise a
ConnectionException if this fails.
The trace methods allow to modify the datastream/pdu !
Example::
Expand Down Expand Up @@ -161,6 +168,9 @@ def __init__( # pylint: disable=too-many-arguments
reconnect_delay_max: float = 300,
timeout: float = 3,
retries: int = 3,
trace_packet: Callable[[bool, bytes | None], bytes] | None = None,
trace_pdu: Callable[[bool, ModbusPDU | None], ModbusPDU] | None = None,
trace_connect: Callable[[bool], None] | None = None,
):
"""Initialize Modbus TLS Client."""
if framer not in [FramerType.TLS]:
Expand Down
Loading

0 comments on commit 68653be

Please sign in to comment.