diff --git a/.gitignore b/.gitignore index c276f7914..c2a1d0c33 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ prof/ /pymodbus.egg-info/ venv downloaded_files/ +pymodbus.log diff --git a/examples/client_async.py b/examples/client_async.py index 081770e87..16ed44c8d 100755 --- a/examples/client_async.py +++ b/examples/client_async.py @@ -94,7 +94,6 @@ def setup_async_client(description=None, cmdline=None): # parity="N", # stopbits=1, # handle_local_echo=False, - # strict=True, ) elif args.comm == "tls": client = modbusClient.AsyncModbusTlsClient( @@ -111,7 +110,6 @@ def setup_async_client(description=None, cmdline=None): keyfile=helper.get_certificate("key"), # password="none", ), - server_hostname="localhost", ) else: raise RuntimeError(f"Unknown commtype {args.comm}") diff --git a/examples/client_custom_msg.py b/examples/client_custom_msg.py index 8fb6cb3a5..b125d02e6 100755 --- a/examples/client_custom_msg.py +++ b/examples/client_custom_msg.py @@ -118,7 +118,7 @@ def __init__(self, address, count=None, slave=0, transaction=0, protocol=0, skip async def main(host="localhost", port=5020): """Run versions of read coil.""" - async with ModbusClient(host=host, port=port, framer_name=FramerType.SOCKET) as client: + async with ModbusClient(host=host, port=port, framer=FramerType.SOCKET) as client: await client.connect() # create a response object to control it works diff --git a/examples/client_sync.py b/examples/client_sync.py index 58bf19966..15f5b0ae1 100755 --- a/examples/client_sync.py +++ b/examples/client_sync.py @@ -68,7 +68,6 @@ def setup_sync_client(description=None, cmdline=None): timeout=args.timeout, # retries=3, # retry_on_empty=False,y - # strict=True, # TCP setup parameters # source_address=("localhost", 0), ) @@ -81,7 +80,6 @@ def setup_sync_client(description=None, cmdline=None): timeout=args.timeout, # retries=3, # retry_on_empty=False, - # strict=True, # UDP setup parameters # source_address=None, ) @@ -93,14 +91,12 @@ def setup_sync_client(description=None, cmdline=None): timeout=args.timeout, # retries=3, # retry_on_empty=False, - # strict=True, # Serial setup parameters baudrate=args.baudrate, # bytesize=8, # parity="N", # stopbits=1, # handle_local_echo=False, - # strict=True, ) elif args.comm == "tls": client = modbusClient.ModbusTlsClient( @@ -117,7 +113,6 @@ def setup_sync_client(description=None, cmdline=None): keyfile=helper.get_certificate("key"), # password=None, ), - server_hostname="localhost", ) return client diff --git a/examples/server_async.py b/examples/server_async.py index a4f71cb71..6835426d5 100755 --- a/examples/server_async.py +++ b/examples/server_async.py @@ -201,7 +201,6 @@ async def run_async_server(args): # handle_local_echo=False, # Handle local echo of the USB-to-RS485 adaptor # ignore_missing_slaves=True, # ignore request to a missing slave # broadcast_enable=False, # treat slave_id 0 as broadcast address, - # strict=True, # use strict timing, t1.5 for Modbus RTU ) elif args.comm == "tls": address = (args.host if args.host else "", args.port if args.port else None) diff --git a/examples/server_hook.py b/examples/server_hook.py index 89a8baecd..a50035efb 100755 --- a/examples/server_hook.py +++ b/examples/server_hook.py @@ -17,11 +17,7 @@ class Manipulator: - """A Class to run the server. - - Using a class allows the easy use of global variables, but - are not strictly needed - """ + """A Class to run the server.""" message_count: int = 1 server: ModbusTcpServer = None diff --git a/examples/server_sync.py b/examples/server_sync.py index d60fd9d05..66e2d7f4a 100755 --- a/examples/server_sync.py +++ b/examples/server_sync.py @@ -110,7 +110,6 @@ def run_sync_server(args): # handle_local_echo=False, # Handle local echo of the USB-to-RS485 adaptor # ignore_missing_slaves=True, # ignore request to a missing slave # broadcast_enable=False, # treat slave_id 0 as broadcast address, - # strict=True, # use strict timing, t1.5 for Modbus RTU ) elif args.comm == "tls": address = ("", args.port) if args.port else None diff --git a/examples/simple_async_client.py b/examples/simple_async_client.py index 500112799..2b08686a0 100755 --- a/examples/simple_async_client.py +++ b/examples/simple_async_client.py @@ -53,7 +53,6 @@ async def run_async_simple_client(comm, host, port, framer=FramerType.SOCKET): # timeout=10, # retries=3, # retry_on_empty=False, - # strict=True, baudrate=9600, bytesize=8, parity="N", @@ -72,7 +71,6 @@ async def run_async_simple_client(comm, host, port, framer=FramerType.SOCKET): certfile="../examples/certificates/pymodbus.crt", keyfile="../examples/certificates/pymodbus.key", # password="none", - server_hostname="localhost", ) else: print(f"Unknown client {comm} selected") diff --git a/examples/simple_sync_client.py b/examples/simple_sync_client.py index 2cd363d8b..d2dfd3a57 100755 --- a/examples/simple_sync_client.py +++ b/examples/simple_sync_client.py @@ -55,7 +55,6 @@ def run_sync_simple_client(comm, host, port, framer=FramerType.SOCKET): # timeout=10, # retries=3, # retry_on_empty=False, - # strict=True, baudrate=9600, bytesize=8, parity="N", @@ -74,7 +73,6 @@ def run_sync_simple_client(comm, host, port, framer=FramerType.SOCKET): certfile="../examples/certificates/pymodbus.crt", keyfile="../examples/certificates/pymodbus.key", # password=None, - server_hostname="localhost", ) else: print(f"Unknown client {comm} selected") diff --git a/pymodbus/client/base.py b/pymodbus/client/base.py index 51602641b..91412901b 100644 --- a/pymodbus/client/base.py +++ b/pymodbus/client/base.py @@ -5,8 +5,7 @@ import socket from abc import abstractmethod from collections.abc import Awaitable, Callable -from dataclasses import dataclass -from typing import Any, cast +from typing import cast from pymodbus.client.mixin import ModbusClientMixin from pymodbus.client.modbusclientprotocol import ModbusClientProtocol @@ -16,79 +15,39 @@ from pymodbus.logging import Log from pymodbus.pdu import ModbusRequest, ModbusResponse from pymodbus.transaction import SyncModbusTransactionManager -from pymodbus.transport import CommParams, CommType +from pymodbus.transport import CommParams from pymodbus.utilities import ModbusTransactionState class ModbusBaseClient(ModbusClientMixin[Awaitable[ModbusResponse]]): """**ModbusBaseClient**. - Fixed parameters: - - :param framer: Framer enum name - - Optional parameters: - - :param timeout: Timeout for a request, in seconds. - :param retries: Max number of retries per request. - :param retry_on_empty: Retry on empty response. - :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_connect_callback: Will be called when connected/disconnected (bool parameter) - :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param comm_type: Type of communication (set by interface class) - :param kwargs: Experimental parameters. - - .. tip:: - **reconnect_delay** doubles automatically with each unsuccessful connect, from - **reconnect_delay** to **reconnect_delay_max**. - Set `reconnect_delay=0` to avoid automatic reconnection. - :mod:`ModbusBaseClient` is normally not referenced outside :mod:`pymodbus`. - - **Application methods, common to all clients**: """ - def __init__( # pylint: disable=too-many-arguments + def __init__( self, framer: FramerType, - timeout: float = 3, retries: int = 3, + comm_params: CommParams | None = None, + retry_on_empty: bool = False, broadcast_enable: bool = False, - reconnect_delay: float = 0.1, - reconnect_delay_max: float = 300, - on_connect_callback: Callable[[bool], None] | None = None, no_resend_on_retry: bool = False, - comm_type: CommType | None = None, - source_address: tuple[str, int] | None = None, - **kwargs: Any, + on_connect_callback: Callable[[bool], None] | None = None, ) -> None: """Initialize a client instance.""" ModbusClientMixin.__init__(self) # type: ignore[arg-type] + if comm_params: + self.comm_params = comm_params + self.retries = retries + self.retry_on_empty = retry_on_empty self.ctx = ModbusClientProtocol( framer, - CommParams( - comm_type=comm_type, - comm_name="comm", - source_address=source_address, - reconnect_delay=reconnect_delay, - reconnect_delay_max=reconnect_delay_max, - timeout_connect=timeout, - host=kwargs.get("host", None), - port=kwargs.get("port", 0), - sslctx=kwargs.get("sslctx", None), - baudrate=kwargs.get("baudrate", None), - bytesize=kwargs.get("bytesize", None), - parity=kwargs.get("parity", None), - stopbits=kwargs.get("stopbits", None), - handle_local_echo=kwargs.get("handle_local_echo", False), - ), + self.comm_params, on_connect_callback, ) self.no_resend_on_retry = no_resend_on_retry self.broadcast_enable = broadcast_enable - self.retries = retries # Common variables. self.use_udp = False @@ -97,9 +56,6 @@ def __init__( # pylint: disable=too-many-arguments self.silent_interval: float = 0 self._lock = asyncio.Lock() - # ----------------------------------------------------------------------- # - # Client external interface - # ----------------------------------------------------------------------- # @property def connected(self) -> bool: """Return state of connection.""" @@ -115,7 +71,6 @@ async def connect(self) -> bool: ) return await self.ctx.connect() - def register(self, custom_response_class: ModbusResponse) -> None: """Register a custom response class with the decoder (call **sync**). @@ -155,9 +110,6 @@ def execute(self, request: ModbusRequest): raise ConnectionException(f"Not connected[{self!s}]") return self.async_execute(request) - # ----------------------------------------------------------------------- # - # Merged client methods - # ----------------------------------------------------------------------- # async def async_execute(self, request) -> ModbusResponse: """Execute requests asynchronously.""" request.transaction_id = self.ctx.transaction.getNextTID() @@ -199,9 +151,6 @@ def build_response(self, request: ModbusRequest): self.ctx.transaction.addTransaction(request) return my_future - # ----------------------------------------------------------------------- # - # The magic methods - # ----------------------------------------------------------------------- # async def __aenter__(self): """Implement the client with enter block. @@ -228,79 +177,25 @@ def __str__(self): class ModbusBaseSyncClient(ModbusClientMixin[ModbusResponse]): """**ModbusBaseClient**. - Fixed parameters: - - :param framer: Framer enum name - - Optional parameters: - - :param timeout: Timeout for a request, in seconds. - :param retries: Max number of retries per request. - :param retry_on_empty: Retry on empty response. - :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param source_address: source address of client - :param kwargs: Experimental parameters. - - .. tip:: - **reconnect_delay** doubles automatically with each unsuccessful connect, from - **reconnect_delay** to **reconnect_delay_max**. - Set `reconnect_delay=0` to avoid automatic reconnection. - :mod:`ModbusBaseClient` is normally not referenced outside :mod:`pymodbus`. - - **Application methods, common to all clients**: """ - @dataclass - class _params: - """Parameter class.""" - - retries: int | None = None - retry_on_empty: bool | None = None - broadcast_enable: bool | None = None - reconnect_delay: int | None = None - source_address: tuple[str, int] | None = None - - def __init__( # pylint: disable=too-many-arguments + def __init__( self, framer: FramerType, - timeout: float = 3, retries: int = 3, + comm_params: CommParams | None = None, retry_on_empty: bool = False, broadcast_enable: bool = False, - reconnect_delay: float = 0.1, - reconnect_delay_max: float = 300.0, no_resend_on_retry: bool = False, - comm_type: CommType | None = None, - source_address: tuple[str, int] | None = None, - **kwargs: Any, ) -> None: """Initialize a client instance.""" ModbusClientMixin.__init__(self) # type: ignore[arg-type] - self.comm_params = CommParams( - comm_type=comm_type, - comm_name="comm", - source_address=source_address, - reconnect_delay=reconnect_delay, - reconnect_delay_max=reconnect_delay_max, - timeout_connect=timeout, - host=kwargs.get("host", None), - port=kwargs.get("port", 0), - sslctx=kwargs.get("sslctx", None), - baudrate=kwargs.get("baudrate", None), - bytesize=kwargs.get("bytesize", None), - parity=kwargs.get("parity", None), - stopbits=kwargs.get("stopbits", None), - handle_local_echo=kwargs.get("handle_local_echo", False), - ) - self.params = self._params() - self.params.retries = int(retries) - self.params.retry_on_empty = bool(retry_on_empty) - self.params.broadcast_enable = bool(broadcast_enable) - self.retry_on_empty: int = 0 + if comm_params: + self.comm_params = comm_params + self.retries = retries + self.broadcast_enable = bool(broadcast_enable) + self.retry_on_empty = retry_on_empty self.no_resend_on_retry = no_resend_on_retry self.slaves: list[int] = [] @@ -310,12 +205,10 @@ def __init__( # pylint: disable=too-many-arguments )(ClientDecoder(), self) self.transaction = SyncModbusTransactionManager( self, - kwargs.get("backoff", 0.3), retry_on_empty, - kwargs.get("retry_on_invalid", False), - retries, + self.retries, ) - self.reconnect_delay_current = self.params.reconnect_delay or 0 + self.reconnect_delay_current = self.comm_params.reconnect_delay or 0 self.use_udp = False self.state = ModbusTransactionState.IDLE self.last_frame_end: float | None = 0 diff --git a/pymodbus/client/serial.py b/pymodbus/client/serial.py index 2972bcde0..089d3a017 100644 --- a/pymodbus/client/serial.py +++ b/pymodbus/client/serial.py @@ -2,6 +2,7 @@ from __future__ import annotations import time +from collections.abc import Callable from functools import partial from typing import TYPE_CHECKING, Any @@ -9,7 +10,7 @@ from pymodbus.exceptions import ConnectionException from pymodbus.framer import FramerType from pymodbus.logging import Log -from pymodbus.transport import CommType +from pymodbus.transport import CommParams, CommType from pymodbus.utilities import ModbusTransactionState @@ -32,24 +33,26 @@ class AsyncModbusSerialClient(ModbusBaseClient): Optional parameters: + :param framer: Framer name, default FramerType.RTU :param baudrate: Bits per second. :param bytesize: Number of bits per byte 7-8. :param parity: 'E'ven, 'O'dd or 'N'one :param stopbits: Number of stop bits 1, 1.5, 2. :param handle_local_echo: Discard local echo from dongle. - - Common optional parameters: - - :param framer: Framer enum name - :param timeout: Timeout for a request, in seconds. + :param name: Set communication name, used in logging + :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. + :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. + :param timeout: Timeout for a connection request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_reconnect_callback: Function that will be called just before a reconnection attempt. :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param kwargs: Experimental parameters. + :param on_reconnect_callback: Function that will be called just before a reconnection attempt. + + .. tip:: + **reconnect_delay** doubles automatically with each unsuccessful connect, from + **reconnect_delay** to **reconnect_delay_max**. + Set `reconnect_delay=0` to avoid automatic reconnection. Example:: @@ -65,7 +68,7 @@ async def run(): Please refer to :ref:`Pymodbus internals` for advanced usage. """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, port: str, framer: FramerType = FramerType.RTU, @@ -73,6 +76,18 @@ def __init__( bytesize: int = 8, parity: str = "N", stopbits: int = 1, + handle_local_echo: bool = False, + name: str = "comm", + reconnect_delay: float = 0.1, + reconnect_delay_max: float = 300, + timeout: float = 3, + retries: int = 3, + retry_on_empty: bool = False, + broadcast_enable: bool = False, + no_resend_on_retry: bool = False, + on_connect_callback: Callable[[bool], None] | None = None, + + # ----- OLD ------ **kwargs: Any, ) -> None: """Initialize Asyncio Modbus Serial Client.""" @@ -81,15 +96,27 @@ def __init__( "Serial client requires pyserial " 'Please install with "pip install pyserial" and try again.' ) - ModbusBaseClient.__init__( - self, - framer, + self.comm_params = CommParams( comm_type=CommType.SERIAL, host=port, baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits, + handle_local_echo=handle_local_echo, + comm_name=name, + reconnect_delay=reconnect_delay, + reconnect_delay_max=reconnect_delay_max, + timeout_connect=timeout, + ) + ModbusBaseClient.__init__( + self, + framer, + retries=retries, + retry_on_empty=retry_on_empty, + broadcast_enable=broadcast_enable, + no_resend_on_retry=no_resend_on_retry, + on_connect_callback=on_connect_callback, **kwargs, ) @@ -107,25 +134,25 @@ class ModbusSerialClient(ModbusBaseSyncClient): Optional parameters: + :param framer: Framer name, default FramerType.RTU :param baudrate: Bits per second. :param bytesize: Number of bits per byte 7-8. :param parity: 'E'ven, 'O'dd or 'N'one :param stopbits: Number of stop bits 0-2. :param handle_local_echo: Discard local echo from dongle. - - Common optional parameters: - - :param framer: Framer enum name - :param timeout: Timeout for a request, in seconds. + :param name: Set communication name, used in logging + :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. + :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. + :param timeout: Timeout for a connection request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. - :param strict: Strict timing, 1.5 character between requests. :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_reconnect_callback: Function that will be called just before a reconnection attempt. :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param kwargs: Experimental parameters. + + .. tip:: + **reconnect_delay** doubles automatically with each unsuccessful connect, from + **reconnect_delay** to **reconnect_delay_max**. + Set `reconnect_delay=0` to avoid automatic reconnection. Example:: @@ -147,7 +174,7 @@ def run(): inter_byte_timeout: float = 0 silent_interval: float = 0 - def __init__( + def __init__( # pylint: disable=too-many-arguments self, port: str, framer: FramerType = FramerType.RTU, @@ -155,25 +182,43 @@ def __init__( bytesize: int = 8, parity: str = "N", stopbits: int = 1, - strict: bool = True, + handle_local_echo: bool = False, + name: str = "comm", + reconnect_delay: float = 0.1, + reconnect_delay_max: float = 300, + timeout: float = 3, + retries: int = 3, + retry_on_empty: bool = False, + broadcast_enable: bool = False, + no_resend_on_retry: bool = False, + + # ----- OLD ------ **kwargs: Any, ) -> None: """Initialize Modbus Serial Client.""" - super().__init__( - framer, + self.comm_params = CommParams( comm_type=CommType.SERIAL, host=port, baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits, + handle_local_echo=handle_local_echo, + comm_name=name, + reconnect_delay=reconnect_delay, + reconnect_delay_max=reconnect_delay_max, + timeout_connect=timeout, + ) + super().__init__( + framer, + retries=retries, + retry_on_empty=retry_on_empty, + broadcast_enable=broadcast_enable, + no_resend_on_retry=no_resend_on_retry, **kwargs, ) self.socket: serial.Serial | None = None - self.strict = bool(strict) - self.last_frame_end = None - self._t0 = float(1 + bytesize + stopbits) / baudrate # Check every 4 bytes / 2 registers if the reading is ready @@ -207,8 +252,7 @@ def connect(self) -> bool: parity=self.comm_params.parity, exclusive=True, ) - if self.strict: - self.socket.inter_byte_timeout = self.inter_byte_timeout + self.socket.inter_byte_timeout = self.inter_byte_timeout self.last_frame_end = None # except serial.SerialException as msg: # pyserial raises undocumented exceptions like termios diff --git a/pymodbus/client/tcp.py b/pymodbus/client/tcp.py index e6ff7bf1f..b1ed98572 100644 --- a/pymodbus/client/tcp.py +++ b/pymodbus/client/tcp.py @@ -4,13 +4,14 @@ import select import socket import time +from collections.abc import Callable from typing import Any from pymodbus.client.base import ModbusBaseClient, ModbusBaseSyncClient from pymodbus.exceptions import ConnectionException from pymodbus.framer import FramerType from pymodbus.logging import Log -from pymodbus.transport import CommType +from pymodbus.transport import CommParams, CommType class AsyncModbusTcpClient(ModbusBaseClient): @@ -22,21 +23,23 @@ class AsyncModbusTcpClient(ModbusBaseClient): Optional parameters: + :param framer: Framer name, default FramerType.SOCKET :param port: Port used for communication + :param name: Set communication name, used in logging :param source_address: source address of client - - Common optional parameters: - - :param framer: Framer enum name - :param timeout: Timeout for a request, in seconds. + :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. + :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. + :param timeout: Timeout for a connection request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_reconnect_callback: Function that will be called just before a reconnection attempt. :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param kwargs: Experimental parameters. + :param on_reconnect_callback: Function that will be called just before a reconnection attempt. + + .. tip:: + **reconnect_delay** doubles automatically with each unsuccessful connect, from + **reconnect_delay** to **reconnect_delay_max**. + Set `reconnect_delay=0` to avoid automatic reconnection. Example:: @@ -54,24 +57,45 @@ async def run(): socket: socket.socket | None - def __init__( + def __init__( # pylint: disable=too-many-arguments self, host: str, - port: int = 502, framer: FramerType = FramerType.SOCKET, + port: int = 502, + name: str = "comm", source_address: tuple[str, int] | None = None, + reconnect_delay: float = 0.1, + reconnect_delay_max: float = 300, + timeout: float = 3, + retries: int = 3, + retry_on_empty: bool = False, + broadcast_enable: bool = False, + no_resend_on_retry: bool = False, + on_connect_callback: Callable[[bool], None] | None = None, + + # ----- OLD ------ **kwargs: Any, ) -> None: """Initialize Asyncio Modbus TCP Client.""" - if "comm_type" not in kwargs: - kwargs["comm_type"] = CommType.TCP - if source_address: - kwargs["source_address"] = source_address + if not hasattr(self,"comm_params"): + self.comm_params = CommParams( + comm_type=CommType.TCP, + host=host, + port=port, + comm_name=name, + source_address=source_address, + reconnect_delay=reconnect_delay, + reconnect_delay_max=reconnect_delay_max, + timeout_connect=timeout, + ) ModbusBaseClient.__init__( self, framer, - host=host, - port=port, + retries=retries, + retry_on_empty=retry_on_empty, + broadcast_enable=broadcast_enable, + no_resend_on_retry=no_resend_on_retry, + on_connect_callback=on_connect_callback, **kwargs, ) @@ -89,21 +113,22 @@ class ModbusTcpClient(ModbusBaseSyncClient): Optional parameters: + :param framer: Framer name, default FramerType.SOCKET :param port: Port used for communication + :param name: Set communication name, used in logging :param source_address: source address of client - - Common optional parameters: - - :param framer: Framer enum name - :param timeout: Timeout for a request, in seconds. + :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. + :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. + :param timeout: Timeout for a connection request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_reconnect_callback: Function that will be called just before a reconnection attempt. :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param kwargs: Experimental parameters. + + .. tip:: + **reconnect_delay** doubles automatically with each unsuccessful connect, from + **reconnect_delay** to **reconnect_delay_max**. + Set `reconnect_delay=0` to avoid automatic reconnection. Example:: @@ -123,24 +148,44 @@ async def run(): socket: socket.socket | None - def __init__( + def __init__( # pylint: disable=too-many-arguments self, host: str, - port: int = 502, framer: FramerType = FramerType.SOCKET, + port: int = 502, + name: str = "comm", source_address: tuple[str, int] | None = None, + reconnect_delay: float = 0.1, + reconnect_delay_max: float = 300, + timeout: float = 3, + retries: int = 3, + retry_on_empty: bool = False, + broadcast_enable: bool = False, + no_resend_on_retry: bool = False, + + # ----- OLD ------ **kwargs: Any, ) -> None: """Initialize Modbus TCP Client.""" - if "comm_type" not in kwargs: - kwargs["comm_type"] = CommType.TCP + if not hasattr(self,"comm_params"): + self.comm_params = CommParams( + comm_type=CommType.TCP, + host=host, + port=port, + comm_name=name, + source_address=source_address, + reconnect_delay=reconnect_delay, + reconnect_delay_max=reconnect_delay_max, + timeout_connect=timeout, + ) super().__init__( framer, - host=host, - port=port, + retries=retries, + retry_on_empty=retry_on_empty, + broadcast_enable=broadcast_enable, + no_resend_on_retry=no_resend_on_retry, **kwargs, ) - self.params.source_address = source_address self.socket = None @property @@ -156,7 +201,7 @@ def connect(self): self.socket = socket.create_connection( (self.comm_params.host, self.comm_params.port), timeout=self.comm_params.timeout_connect, - source_address=self.params.source_address, + source_address=self.comm_params.source_address, ) Log.debug( "Connection to Modbus server established. Socket {}", diff --git a/pymodbus/client/tls.py b/pymodbus/client/tls.py index e90640004..22eb1899b 100644 --- a/pymodbus/client/tls.py +++ b/pymodbus/client/tls.py @@ -3,6 +3,7 @@ import socket import ssl +from collections.abc import Callable from typing import Any from pymodbus.client.tcp import AsyncModbusTcpClient, ModbusTcpClient @@ -20,23 +21,24 @@ class AsyncModbusTlsClient(AsyncModbusTcpClient): Optional parameters: + :param sslctx: SSLContext to use for TLS + :param framer: Framer name, default FramerType.TLS :param port: Port used for communication + :param name: Set communication name, used in logging :param source_address: Source address of client - :param sslctx: SSLContext to use for TLS - :param server_hostname: Bind certificate to host - - Common optional parameters: - - :param framer: Framer enum name - :param timeout: Timeout for a request, in seconds. + :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. + :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. + :param timeout: Timeout for a connection request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_reconnect_callback: Function that will be called just before a reconnection attempt. :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param kwargs: Experimental parameters. + :param on_reconnect_callback: Function that will be called just before a reconnection attempt. + + .. tip:: + **reconnect_delay** doubles automatically with each unsuccessful connect, from + **reconnect_delay** to **reconnect_delay_max**. + Set `reconnect_delay=0` to avoid automatic reconnection. Example:: @@ -52,26 +54,49 @@ async def run(): Please refer to :ref:`Pymodbus internals` for advanced usage. """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, host: str, - port: int = 802, - framer: FramerType = FramerType.TLS, sslctx: ssl.SSLContext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT), - server_hostname: str | None = None, + framer: FramerType = FramerType.TLS, + port: int = 802, + name: str = "comm", + source_address: tuple[str, int] | None = None, + reconnect_delay: float = 0.1, + reconnect_delay_max: float = 300, + timeout: float = 3, + retries: int = 3, + retry_on_empty: bool = False, + broadcast_enable: bool = False, + no_resend_on_retry: bool = False, + on_connect_callback: Callable[[bool], None] | None = None, + + # ----- OLD ------ **kwargs: Any, ): """Initialize Asyncio Modbus TLS Client.""" + self.comm_params = CommParams( + comm_type=CommType.TLS, + host=host, + sslctx=sslctx, + port=port, + comm_name=name, + source_address=source_address, + reconnect_delay=reconnect_delay, + reconnect_delay_max=reconnect_delay_max, + timeout_connect=timeout, + ) AsyncModbusTcpClient.__init__( self, - host, - port=port, + "", framer=framer, - comm_type=CommType.TLS, - sslctx=sslctx, + retries=retries, + retry_on_empty=retry_on_empty, + broadcast_enable=broadcast_enable, + no_resend_on_retry=no_resend_on_retry, + on_connect_callback=on_connect_callback, **kwargs, ) - self.server_hostname = server_hostname @classmethod def generate_ssl( @@ -104,24 +129,23 @@ class ModbusTlsClient(ModbusTcpClient): Optional parameters: + :param sslctx: SSLContext to use for TLS + :param framer: Framer name, default FramerType.TLS :param port: Port used for communication + :param name: Set communication name, used in logging :param source_address: Source address of client - :param sslctx: SSLContext to use for TLS - :param server_hostname: Bind certificate to host - :param kwargs: Experimental parameters - - Common optional parameters: - - :param framer: Framer enum name - :param timeout: Timeout for a request, in seconds. + :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. + :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. + :param timeout: Timeout for a connection request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_reconnect_callback: Function that will be called just before a reconnection attempt. :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param kwargs: Experimental parameters. + + .. tip:: + **reconnect_delay** doubles automatically with each unsuccessful connect, from + **reconnect_delay** to **reconnect_delay_max**. + Set `reconnect_delay=0` to avoid automatic reconnection. Example:: @@ -139,22 +163,46 @@ async def run(): Remark: There are no automatic reconnect as with AsyncModbusTlsClient """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, host: str, - port: int = 802, - framer: FramerType = FramerType.TLS, sslctx: ssl.SSLContext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT), - server_hostname: str | None = None, + framer: FramerType = FramerType.TLS, + port: int = 802, + name: str = "comm", + source_address: tuple[str, int] | None = None, + reconnect_delay: float = 0.1, + reconnect_delay_max: float = 300, + timeout: float = 3, + retries: int = 3, + retry_on_empty: bool = False, + broadcast_enable: bool = False, + no_resend_on_retry: bool = False, + + # ----- OLD ------ **kwargs: Any, ): """Initialize Modbus TLS Client.""" + self.comm_params = CommParams( + comm_type=CommType.TLS, + host=host, + sslctx=sslctx, + port=port, + comm_name=name, + source_address=source_address, + reconnect_delay=reconnect_delay, + reconnect_delay_max=reconnect_delay_max, + timeout_connect=timeout, + ) super().__init__( - host, comm_type=CommType.TLS, port=port, framer=framer, **kwargs + "", + framer=framer, + retries=retries, + retry_on_empty=retry_on_empty, + broadcast_enable=broadcast_enable, + no_resend_on_retry=no_resend_on_retry, + **kwargs ) - self.sslctx = sslctx - self.server_hostname = server_hostname - @classmethod def generate_ssl( @@ -188,11 +236,9 @@ def connect(self): return True try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if self.params.source_address: - sock.bind(self.params.source_address) - self.socket = self.sslctx.wrap_socket( - sock, server_side=False, server_hostname=self.server_hostname - ) + if self.comm_params.source_address: + sock.bind(self.comm_params.source_address) + self.socket = self.comm_params.sslctx.wrap_socket(sock, server_side=False) # type: ignore[union-attr] self.socket.settimeout(self.comm_params.timeout_connect) self.socket.connect((self.comm_params.host, self.comm_params.port)) except OSError as msg: @@ -209,6 +255,6 @@ def __repr__(self): """Return string representation.""" return ( f"<{self.__class__.__name__} at {hex(id(self))} socket={self.socket}, " - f"ipaddr={self.comm_params.host}, port={self.comm_params.port}, sslctx={self.sslctx}, " + f"ipaddr={self.comm_params.host}, port={self.comm_params.port}, sslctx={self.comm_params.sslctx}, " f"timeout={self.comm_params.timeout_connect}>" ) diff --git a/pymodbus/client/udp.py b/pymodbus/client/udp.py index 4d71e240e..6038e3faf 100644 --- a/pymodbus/client/udp.py +++ b/pymodbus/client/udp.py @@ -2,13 +2,14 @@ from __future__ import annotations import socket +from collections.abc import Callable from typing import Any from pymodbus.client.base import ModbusBaseClient, ModbusBaseSyncClient from pymodbus.exceptions import ConnectionException from pymodbus.framer import FramerType from pymodbus.logging import Log -from pymodbus.transport import CommType +from pymodbus.transport import CommParams, CommType DGRAM_TYPE = socket.SOCK_DGRAM @@ -23,21 +24,23 @@ class AsyncModbusUdpClient(ModbusBaseClient): Optional parameters: + :param framer: Framer name, default FramerType.SOCKET :param port: Port used for communication. + :param name: Set communication name, used in logging :param source_address: source address of client, - - Common optional parameters: - - :param framer: Framer enum name - :param timeout: Timeout for a request, in seconds. + :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. + :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. + :param timeout: Timeout for a connection request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_reconnect_callback: Function that will be called just before a reconnection attempt. :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param kwargs: Experimental parameters. + :param on_reconnect_callback: Function that will be called just before a reconnection attempt. + + .. tip:: + **reconnect_delay** doubles automatically with each unsuccessful connect, from + **reconnect_delay** to **reconnect_delay_max**. + Set `reconnect_delay=0` to avoid automatic reconnection. Example:: @@ -53,21 +56,44 @@ async def run(): Please refer to :ref:`Pymodbus internals` for advanced usage. """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, host: str, - port: int = 502, framer: FramerType = FramerType.SOCKET, + port: int = 502, + name: str = "comm", source_address: tuple[str, int] | None = None, + reconnect_delay: float = 0.1, + reconnect_delay_max: float = 300, + timeout: float = 3, + retries: int = 3, + retry_on_empty: bool = False, + broadcast_enable: bool = False, + no_resend_on_retry: bool = False, + on_connect_callback: Callable[[bool], None] | None = None, + + # ----- OLD ------ **kwargs: Any, ) -> None: """Initialize Asyncio Modbus UDP Client.""" - ModbusBaseClient.__init__( - self, - framer, + self.comm_params = CommParams( comm_type=CommType.UDP, host=host, port=port, + comm_name=name, + source_address=source_address, + reconnect_delay=reconnect_delay, + reconnect_delay_max=reconnect_delay_max, + timeout_connect=timeout, + ) + ModbusBaseClient.__init__( + self, + framer, + retries=retries, + retry_on_empty=retry_on_empty, + broadcast_enable=broadcast_enable, + no_resend_on_retry=no_resend_on_retry, + on_connect_callback=on_connect_callback, **kwargs, ) self.source_address = source_address @@ -87,21 +113,22 @@ class ModbusUdpClient(ModbusBaseSyncClient): Optional parameters: + :param framer: Framer name, default FramerType.SOCKET :param port: Port used for communication. + :param name: Set communication name, used in logging :param source_address: source address of client, - - Common optional parameters: - - :param framer: Framer enum name - :param timeout: Timeout for a request, in seconds. + :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. + :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. + :param timeout: Timeout for a connection request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. :param broadcast_enable: True to treat id 0 as broadcast address. - :param reconnect_delay: Minimum delay in seconds.milliseconds before reconnecting. - :param reconnect_delay_max: Maximum delay in seconds.milliseconds before reconnecting. - :param on_reconnect_callback: Function that will be called just before a reconnection attempt. :param no_resend_on_retry: Do not resend request when retrying due to missing response. - :param kwargs: Experimental parameters. + + .. tip:: + **reconnect_delay** doubles automatically with each unsuccessful connect, from + **reconnect_delay** to **reconnect_delay_max**. + Set `reconnect_delay=0` to avoid automatic reconnection. Example:: @@ -121,24 +148,43 @@ async def run(): socket: socket.socket | None - def __init__( + def __init__( # pylint: disable=too-many-arguments self, host: str, - port: int = 502, framer: FramerType = FramerType.SOCKET, + port: int = 502, + name: str = "comm", source_address: tuple[str, int] | None = None, + reconnect_delay: float = 0.1, + reconnect_delay_max: float = 300, + timeout: float = 3, + retries: int = 3, + retry_on_empty: bool = False, + broadcast_enable: bool = False, + no_resend_on_retry: bool = False, + + # ----- OLD ------ **kwargs: Any, ) -> None: """Initialize Modbus UDP Client.""" + self.comm_params = CommParams( + comm_type=CommType.UDP, + host=host, + port=port, + comm_name=name, + source_address=source_address, + reconnect_delay=reconnect_delay, + reconnect_delay_max=reconnect_delay_max, + timeout_connect=timeout, + ) super().__init__( framer, - port=port, - host=host, - comm_type=CommType.UDP, + retries=retries, + retry_on_empty=retry_on_empty, + broadcast_enable=broadcast_enable, + no_resend_on_retry=no_resend_on_retry, **kwargs, ) - self.params.source_address = source_address - self.socket = None @property diff --git a/pymodbus/transaction.py b/pymodbus/transaction.py index fd3f6e329..b64c17932 100644 --- a/pymodbus/transaction.py +++ b/pymodbus/transaction.py @@ -12,7 +12,6 @@ ] import struct -import time from contextlib import suppress from threading import RLock from typing import TYPE_CHECKING @@ -134,19 +133,16 @@ class SyncModbusTransactionManager(ModbusTransactionManager): Results are keyed based on the supplied transaction id. """ - def __init__(self, client: ModbusBaseSyncClient, backoff, retry_on_empty, retry_on_invalid, retries): + def __init__(self, client: ModbusBaseSyncClient, retry_on_empty, retries): """Initialize an instance of the ModbusTransactionManager. :param client: The client socket wrapper - :param backoff: 0.3 :param retry_on_empty: Should the client retry on empty :param retries: The number of retries to allow """ super().__init__() self.client: ModbusBaseSyncClient = client - self.backoff = backoff self.retry_on_empty = retry_on_empty - self.retry_on_invalid = retry_on_invalid self.retries = retries self._transaction_lock = RLock() self._no_response_devices: list[int] = [] @@ -225,7 +221,7 @@ def execute(self, request: ModbusRequest): # noqa: C901 Log.debug("Clearing current Frame: - {}", _buffer) self.client.framer.resetFrame() if broadcast := ( - self.client.params.broadcast_enable and not request.slave_id + self.client.broadcast_enable and not request.slave_id ): self._transact(request, None, broadcast=True) response = b"Broadcast write sent - no response expected" @@ -286,15 +282,6 @@ def execute(self, request: ModbusRequest): # noqa: C901 else: # No response received and retries not enabled break - elif self.retry_on_invalid: - response, last_exception = self._retry_transaction( - retries, - "invalid", - request, - expected_response_length, - full=full, - ) - retries -= 1 else: break # full = False @@ -338,10 +325,6 @@ def _retry_transaction(self, retries, reason, packet, response_length, full=Fals Log.debug("Retry on {} response - {}", reason, retries) Log.debug('Changing transaction state from "WAITING_FOR_REPLY" to "RETRYING"') self.client.state = ModbusTransactionState.RETRYING - if self.backoff: - delay = 2 ** (self.retries - retries) * self.backoff - time.sleep(delay) - Log.debug("Sleeping {}", delay) self.client.connect() if hasattr(self.client, "_in_waiting"): if ( diff --git a/test/framers/test_old_framers.py b/test/framers/test_old_framers.py index 39bc5035b..5bb2c0ca1 100644 --- a/test/framers/test_old_framers.py +++ b/test/framers/test_old_framers.py @@ -14,7 +14,7 @@ ModbusTlsFramer, ) from pymodbus.pdu.bit_read_message import ReadCoilsRequest -from pymodbus.transport import CommType +from pymodbus.transport import CommParams, CommType from pymodbus.utilities import ModbusTransactionState @@ -327,9 +327,11 @@ async def test_send_packet(self, rtu_framer): message = TEST_MESSAGE client = ModbusBaseClient( FramerType.ASCII, - host="localhost", - port=BASE_PORT + 1, - CommType=CommType.TCP, + comm_params=CommParams( + comm_type=CommType.TCP, + host="localhost", + port=BASE_PORT + 1, + ), ) client.state = ModbusTransactionState.TRANSACTION_COMPLETE client.silent_interval = 1 diff --git a/test/framers/test_tbc_transaction.py b/test/framers/test_tbc_transaction.py index cd78d1e75..90f93c8d5 100755 --- a/test/framers/test_tbc_transaction.py +++ b/test/framers/test_tbc_transaction.py @@ -1,5 +1,4 @@ """Test transaction.""" -from itertools import count from unittest import mock from pymodbus.exceptions import ( @@ -42,7 +41,7 @@ def setup_method(self): 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._manager = SyncModbusTransactionManager(self.client, 0.3, False, False, 3) + self._manager = SyncModbusTransactionManager(self.client, False, 3) # ----------------------------------------------------------------------- # # Modbus transaction manager @@ -89,11 +88,8 @@ def test_calculate_exception_length(self): == exception_length ) - @mock.patch("pymodbus.transaction.time") - def test_execute(self, mock_time): + def test_execute(self): """Test execute.""" - mock_time.time.side_effect = count() - client = mock.MagicMock() client.framer = self._ascii client.framer._buffer = b"deadbeef" # pylint: disable=protected-access @@ -113,7 +109,7 @@ def test_execute(self, mock_time): request.get_response_pdu_size.return_value = 10 request.slave_id = 1 request.function_code = 222 - trans = SyncModbusTransactionManager(client, 0.3, False, False, 3) + trans = SyncModbusTransactionManager(client, False, 3) trans._recv = mock.MagicMock( # pylint: disable=protected-access return_value=b"abcdef" ) @@ -148,12 +144,10 @@ def test_execute(self, mock_time): ) client.comm_params.handle_local_echo = True trans.retry_on_empty = False - trans.retry_on_invalid = False assert trans.execute(request).message == "[Input/Output] Wrong local echo" client.comm_params.handle_local_echo = False # retry on invalid response - trans.retry_on_invalid = True trans._recv = mock.MagicMock( # pylint: disable=protected-access side_effect=iter([b"", b"abcdef", b"deadbe", b"123456"]) ) diff --git a/test/sub_client/test_client.py b/test/sub_client/test_client.py index ec9405009..2356c5634 100755 --- a/test/sub_client/test_client.py +++ b/test/sub_client/test_client.py @@ -22,7 +22,7 @@ from pymodbus.datastore.store import ModbusSequentialDataBlock from pymodbus.exceptions import ConnectionException, ModbusException, ModbusIOException from pymodbus.pdu import ModbusRequest -from pymodbus.transport import CommType +from pymodbus.transport import CommParams, CommType BASE_PORT = 6500 @@ -123,7 +123,6 @@ def fake_execute(_self, request): "timeout": 3 + 2, "retries": 3 + 2, "retry_on_empty": True, - "strict": False, "broadcast_enable": not False, "reconnect_delay": 117, "reconnect_delay_max": 250, @@ -132,7 +131,6 @@ def fake_execute(_self, request): "timeout": 3, "retries": 3, "retry_on_empty": False, - "strict": True, "broadcast_enable": False, "reconnect_delay": 100, "reconnect_delay_max": 1000 * 60 * 5, @@ -149,7 +147,6 @@ def fake_execute(_self, request): "handle_local_echo": True, }, "defaults": { - "host": None, "port": "/dev/tty", "framer": FramerType.RTU, "baudrate": 19200, @@ -167,7 +164,6 @@ def fake_execute(_self, request): "source_address": ("195.6.7.8", 1025), }, "defaults": { - "host": "192.168.1.2", "port": 502, "framer": FramerType.SOCKET, "source_address": None, @@ -180,19 +176,12 @@ def fake_execute(_self, request): "framer": FramerType.ASCII, "source_address": ("195.6.7.8", 1025), "sslctx": None, - "certfile": None, - "keyfile": None, - "password": None, }, "defaults": { - "host": "192.168.1.2", "port": 802, "framer": FramerType.TLS, "source_address": None, "sslctx": None, - "certfile": None, - "keyfile": None, - "password": None, }, }, "udp": { @@ -203,7 +192,6 @@ def fake_execute(_self, request): "source_address": ("195.6.7.8", 1025), }, "defaults": { - "host": "192.168.1.2", "port": 502, "framer": FramerType.SOCKET, "source_address": None, @@ -236,15 +224,12 @@ async def test_client_instanciate( cur_args = arg_list[type_args] if test_default: client = clientclass(cur_args["pos_arg"]) - to_test = dict(arg_list["fix"]["defaults"], **cur_args["defaults"]) else: client = clientclass( cur_args["pos_arg"], **arg_list["fix"]["opt_args"], **cur_args["opt_args"], ) - to_test = dict(arg_list["fix"]["opt_args"], **cur_args["opt_args"]) - to_test["host"] = cur_args["defaults"]["host"] # Test information methods client.last_frame_end = 2 @@ -278,9 +263,11 @@ async def test_client_modbusbaseclient(): """Test modbus base client class.""" client = ModbusBaseClient( FramerType.ASCII, - host="localhost", - port=BASE_PORT + 1, - CommType=CommType.TCP, + comm_params=CommParams( + host="localhost", + port=BASE_PORT + 1, + comm_type=CommType.TCP, + ), ) client.register(pdu_bit_read.ReadCoilsResponse) assert str(client) @@ -313,9 +300,11 @@ async def test_client_base_async(): p_close.return_value.set_result(True) async with ModbusBaseClient( FramerType.ASCII, - host="localhost", - port=BASE_PORT + 2, - CommType=CommType.TCP, + comm_params=CommParams( + host="localhost", + port=BASE_PORT + 2, + comm_type=CommType.TCP, + ), ) as client: str(client) p_connect.return_value = asyncio.Future() @@ -327,7 +316,8 @@ async def test_client_base_async(): @pytest.mark.skip() async def test_client_protocol_receiver(): """Test the client protocol data received.""" - base = ModbusBaseClient(FramerType.SOCKET) + base = ModbusBaseClient(FramerType.SOCKET, comm_params=CommParams(), +) transport = mock.MagicMock() base.ctx.connection_made(transport) assert base.transport == transport @@ -349,7 +339,8 @@ async def test_client_protocol_receiver(): @pytest.mark.skip() async def test_client_protocol_response(): """Test the udp client protocol builds responses.""" - base = ModbusBaseClient(FramerType.SOCKET) + base = ModbusBaseClient(FramerType.SOCKET, comm_params=CommParams(), +) response = base.build_response(0x00) # pylint: disable=protected-access excp = response.exception() assert isinstance(excp, ConnectionException) @@ -363,7 +354,12 @@ async def test_client_protocol_response(): async def test_client_protocol_handler(): """Test the client protocol handles responses.""" base = ModbusBaseClient( - FramerType.ASCII, host="localhost", port=+3, CommType=CommType.TCP + FramerType.ASCII, + comm_params=CommParams( + host="localhost", + port=BASE_PORT + 3, + comm_type=CommType.TCP, + ), ) transport = mock.MagicMock() base.ctx.connection_made(transport=transport) @@ -411,7 +407,13 @@ def close(self): async def test_client_protocol_execute(): """Test the client protocol execute method.""" - base = ModbusBaseClient(FramerType.SOCKET, host="127.0.0.1") + base = ModbusBaseClient( + FramerType.SOCKET, + comm_params=CommParams( + host="127.0.0.1", + timeout_connect=3, + ), + ) request = pdu_bit_read.ReadCoilsRequest(1, 1) transport = MockTransport(base, request) base.ctx.connection_made(transport=transport) @@ -422,7 +424,12 @@ async def test_client_protocol_execute(): async def test_client_execute_broadcast(): """Test the client protocol execute method.""" - base = ModbusBaseClient(FramerType.SOCKET, host="127.0.0.1") + base = ModbusBaseClient( + FramerType.SOCKET, + comm_params=CommParams( + host="127.0.0.1", + ), + ) base.broadcast_enable = True request = pdu_bit_read.ReadCoilsRequest(1, 1) transport = MockTransport(base, request) @@ -432,7 +439,13 @@ async def test_client_execute_broadcast(): async def test_client_protocol_retry(): """Test the client protocol execute method with retries.""" - base = ModbusBaseClient(FramerType.SOCKET, host="127.0.0.1", timeout=0.1) + base = ModbusBaseClient( + FramerType.SOCKET, + comm_params=CommParams( + host="127.0.0.1", + timeout_connect=0.1, + ), + ) request = pdu_bit_read.ReadCoilsRequest(1, 1) transport = MockTransport(base, request, retries=2) base.ctx.connection_made(transport=transport) @@ -445,7 +458,14 @@ async def test_client_protocol_retry(): async def test_client_protocol_timeout(): """Test the client protocol execute method with timeout.""" - base = ModbusBaseClient(FramerType.SOCKET, host="127.0.0.1", timeout=0.1, retries=2) + base = ModbusBaseClient( + FramerType.SOCKET, + retries=2, + comm_params=CommParams( + host="127.0.0.1", + timeout_connect=0.1, + ), + ) # Avoid creating do_reconnect() task base.ctx.connection_lost = mock.MagicMock() request = pdu_bit_read.ReadCoilsRequest(1, 1) @@ -645,7 +665,8 @@ def test_client_mixin_convert_fail(): async def test_client_build_response(): """Test fail of build_response.""" - client = ModbusBaseClient(FramerType.RTU) + client = ModbusBaseClient(FramerType.RTU, comm_params=CommParams(), +) with pytest.raises(ConnectionException): await client.build_response(ModbusRequest(0, 0, 0, False)) diff --git a/test/sub_client/test_client_sync.py b/test/sub_client/test_client_sync.py index 08635de89..90f5ee862 100755 --- a/test/sub_client/test_client_sync.py +++ b/test/sub_client/test_client_sync.py @@ -218,7 +218,7 @@ def test_syn_tls_client_instantiation(self): client = ModbusTlsClient("127.0.0.1") assert client assert isinstance(client.framer, ModbusTlsFramer) - assert client.sslctx + assert client.comm_params.sslctx @mock.patch("pymodbus.client.tcp.select") def test_basic_syn_tls_client(self, mock_select): @@ -280,7 +280,7 @@ def test_tls_client_repr(self): client = ModbusTlsClient("127.0.0.1") rep = ( f"<{client.__class__.__name__} at {hex(id(client))} socket={client.socket}, " - f"ipaddr={client.comm_params.host}, port={client.comm_params.port}, sslctx={client.sslctx}, " + f"ipaddr={client.comm_params.host}, port={client.comm_params.port}, sslctx={client.comm_params.sslctx}, " f"timeout={client.comm_params.timeout_connect}>" ) assert repr(client) == rep @@ -347,7 +347,7 @@ def test_basic_sync_serial_client(self, mock_serial): client.close() # rtu connect/disconnect - rtu_client = ModbusSerialClient("/dev/null", framer=FramerType.RTU, strict=True) + rtu_client = ModbusSerialClient("/dev/null", framer=FramerType.RTU) assert rtu_client.connect() assert rtu_client.socket.inter_byte_timeout == rtu_client.inter_byte_timeout rtu_client.close() diff --git a/test/test_transaction.py b/test/test_transaction.py index 3d2311241..404e87fa6 100755 --- a/test/test_transaction.py +++ b/test/test_transaction.py @@ -1,5 +1,4 @@ """Test transaction.""" -from itertools import count from unittest import mock from pymodbus.exceptions import ( @@ -43,7 +42,7 @@ def setup_method(self): 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._manager = SyncModbusTransactionManager(self.client, 0.3, False, False, 3) + self._manager = SyncModbusTransactionManager(self.client, False, 3) # ----------------------------------------------------------------------- # # Modbus transaction manager @@ -90,13 +89,10 @@ def test_calculate_exception_length(self): == exception_length ) - @mock.patch("pymodbus.transaction.time") @mock.patch.object(SyncModbusTransactionManager, "_recv") @mock.patch.object(ModbusTransactionManager, "getTransaction") - def test_execute(self, mock_get_transaction, mock_recv, mock_time): + def test_execute(self, mock_get_transaction, mock_recv): """Test execute.""" - mock_time.time.side_effect = count() - client = mock.MagicMock() client.framer = self._ascii client.framer._buffer = b"deadbeef" # pylint: disable=protected-access @@ -116,7 +112,7 @@ def test_execute(self, mock_get_transaction, mock_recv, mock_time): request.get_response_pdu_size.return_value = 10 request.slave_id = 1 request.function_code = 222 - trans = SyncModbusTransactionManager(client, 0.3, False, False, 3) + trans = SyncModbusTransactionManager(client, False, 3) mock_recv.reset_mock( return_value=b"abcdef" ) @@ -149,12 +145,10 @@ def test_execute(self, mock_get_transaction, mock_recv, mock_time): ) client.comm_params.handle_local_echo = True trans.retry_on_empty = False - trans.retry_on_invalid = False assert trans.execute(request).message == "[Input/Output] Wrong local echo" client.comm_params.handle_local_echo = False # retry on invalid response - trans.retry_on_invalid = True mock_recv.reset_mock( side_effect=iter([b"", b"abcdef", b"deadbe", b"123456"]) )