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

Add trace API for client. #2478

Merged
merged 5 commits into from
Nov 26, 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
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