diff --git a/.gitignore b/.gitignore index c276f7914c..c2a1d0c336 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ prof/ /pymodbus.egg-info/ venv downloaded_files/ +pymodbus.log diff --git a/pymodbus/client/base.py b/pymodbus/client/base.py index 51602641b0..62f83ff304 100644 --- a/pymodbus/client/base.py +++ b/pymodbus/client/base.py @@ -23,21 +23,14 @@ 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. + timeout: Timeout for a request, in seconds. + retries: Max number of retries per request. + 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:: @@ -60,30 +53,24 @@ def __init__( # pylint: disable=too-many-arguments 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, + + comm_params: CommParams | None = None, **kwargs: Any, ) -> None: """Initialize a client instance.""" ModbusClientMixin.__init__(self) # type: ignore[arg-type] + if comm_params: + self.comm_params = comm_params + self.comm_params.comm_name="comm" + self.comm_params.source_address=source_address + self.comm_params.reconnect_delay=reconnect_delay + self.comm_params.reconnect_delay_max=reconnect_delay_max + self.comm_params.timeout_connect=timeout + self.comm_params.port=kwargs.get("port", 0) 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 @@ -276,26 +263,21 @@ def __init__( # pylint: disable=too-many-arguments no_resend_on_retry: bool = False, comm_type: CommType | None = None, source_address: tuple[str, int] | None = None, + comm_params: CommParams | 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), - ) + if comm_params: + self.comm_params = comm_params + if comm_type: + self.comm_params.comm_type=comm_type + self.comm_params.comm_name="comm" + self.comm_params.source_address=source_address + self.comm_params.reconnect_delay=reconnect_delay + self.comm_params.reconnect_delay_max=reconnect_delay_max + self.comm_params.timeout_connect=timeout + self.comm_params.port=kwargs.get("port", 0) self.params = self._params() self.params.retries = int(retries) self.params.retry_on_empty = bool(retry_on_empty) diff --git a/pymodbus/client/serial.py b/pymodbus/client/serial.py index 2972bcde0e..8e01484bf6 100644 --- a/pymodbus/client/serial.py +++ b/pymodbus/client/serial.py @@ -9,7 +9,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,15 +32,15 @@ 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: + ----- OLD ------ - :param framer: Framer enum name :param timeout: Timeout for a request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. @@ -73,6 +73,9 @@ def __init__( bytesize: int = 8, parity: str = "N", stopbits: int = 1, + handle_local_echo: bool = False, + + # ----- OLD ------ **kwargs: Any, ) -> None: """Initialize Asyncio Modbus Serial Client.""" @@ -81,15 +84,18 @@ 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, + ) + ModbusBaseClient.__init__( + self, + framer, **kwargs, ) @@ -107,15 +113,16 @@ 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 + ----- OLD ------ + :param timeout: Timeout for a request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. @@ -155,25 +162,29 @@ def __init__( bytesize: int = 8, parity: str = "N", stopbits: int = 1, + handle_local_echo: bool = False, + + # ----- OLD ------ strict: bool = True, **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, + ) + super().__init__( + framer, **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 diff --git a/pymodbus/client/tcp.py b/pymodbus/client/tcp.py index e6ff7bf1f3..452544556d 100644 --- a/pymodbus/client/tcp.py +++ b/pymodbus/client/tcp.py @@ -10,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 class AsyncModbusTcpClient(ModbusBaseClient): @@ -22,12 +22,13 @@ class AsyncModbusTcpClient(ModbusBaseClient): Optional parameters: + :param framer: Framer name, default FramerType.SOCKET + + ----- OLD ------ + :param port: Port used for communication :param source_address: source address of client - Common optional parameters: - - :param framer: Framer enum name :param timeout: Timeout for a request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. @@ -57,20 +58,23 @@ async def run(): def __init__( self, host: str, - port: int = 502, framer: FramerType = FramerType.SOCKET, + # ----- OLD ------ + port: int = 502, source_address: tuple[str, int] | None = None, **kwargs: Any, ) -> None: """Initialize Asyncio 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, + ) if source_address: kwargs["source_address"] = source_address ModbusBaseClient.__init__( self, framer, - host=host, port=port, **kwargs, ) @@ -89,12 +93,13 @@ class ModbusTcpClient(ModbusBaseSyncClient): Optional parameters: + :param framer: Framer name, default FramerType.SOCKET + + ----- OLD ------ + :param port: Port used for communication :param source_address: source address of client - Common optional parameters: - - :param framer: Framer enum name :param timeout: Timeout for a request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. @@ -126,17 +131,19 @@ async def run(): def __init__( self, host: str, - port: int = 502, framer: FramerType = FramerType.SOCKET, + # ----- OLD ------ + port: int = 502, source_address: tuple[str, int] | None = None, **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) super().__init__( framer, - host=host, port=port, **kwargs, ) diff --git a/pymodbus/client/tls.py b/pymodbus/client/tls.py index e906400049..595065560d 100644 --- a/pymodbus/client/tls.py +++ b/pymodbus/client/tls.py @@ -20,9 +20,13 @@ class AsyncModbusTlsClient(AsyncModbusTcpClient): Optional parameters: + :param sslctx: SSLContext to use for TLS + :param framer: Framer name, default FramerType.TLS + + ----- OLD ------ + :param port: Port used for communication :param source_address: Source address of client - :param sslctx: SSLContext to use for TLS :param server_hostname: Bind certificate to host Common optional parameters: @@ -55,20 +59,24 @@ async def run(): def __init__( self, host: str, - port: int = 802, - framer: FramerType = FramerType.TLS, sslctx: ssl.SSLContext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT), + framer: FramerType = FramerType.TLS, + # ----- OLD ------ + port: int = 802, server_hostname: str | None = None, **kwargs: Any, ): """Initialize Asyncio Modbus TLS Client.""" + self.comm_params = CommParams( + comm_type=CommType.TLS, + host=host, + sslctx=sslctx, + ) AsyncModbusTcpClient.__init__( self, - host, + "", port=port, framer=framer, - comm_type=CommType.TLS, - sslctx=sslctx, **kwargs, ) self.server_hostname = server_hostname @@ -104,15 +112,16 @@ class ModbusTlsClient(ModbusTcpClient): Optional parameters: + :param sslctx: SSLContext to use for TLS + :param framer: Framer name, default FramerType.TLS + + ----- OLD ------ + :param port: Port used for communication :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 retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. @@ -142,15 +151,21 @@ async def run(): def __init__( self, host: str, - port: int = 802, - framer: FramerType = FramerType.TLS, sslctx: ssl.SSLContext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT), + framer: FramerType = FramerType.TLS, + # ----- OLD ------ + port: int = 802, server_hostname: str | None = None, **kwargs: Any, ): """Initialize Modbus TLS Client.""" + self.comm_params = CommParams( + comm_type=CommType.TLS, + host=host, + sslctx=sslctx, + ) super().__init__( - host, comm_type=CommType.TLS, port=port, framer=framer, **kwargs + "", port=port, framer=framer, **kwargs ) self.sslctx = sslctx self.server_hostname = server_hostname diff --git a/pymodbus/client/udp.py b/pymodbus/client/udp.py index 4d71e240e4..10addbcf03 100644 --- a/pymodbus/client/udp.py +++ b/pymodbus/client/udp.py @@ -8,7 +8,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 DGRAM_TYPE = socket.SOCK_DGRAM @@ -23,12 +23,13 @@ class AsyncModbusUdpClient(ModbusBaseClient): Optional parameters: + :param framer: Framer name, default FramerType.SOCKET + + ----- OLD ------ + :param port: Port used for communication. :param source_address: source address of client, - Common optional parameters: - - :param framer: Framer enum name :param timeout: Timeout for a request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. @@ -56,17 +57,20 @@ async def run(): def __init__( self, host: str, - port: int = 502, framer: FramerType = FramerType.SOCKET, + # ----- OLD ------ + port: int = 502, source_address: tuple[str, int] | None = None, **kwargs: Any, ) -> None: """Initialize Asyncio Modbus UDP Client.""" + self.comm_params = CommParams( + comm_type=CommType.UDP, + host=host + ) ModbusBaseClient.__init__( self, framer, - comm_type=CommType.UDP, - host=host, port=port, **kwargs, ) @@ -87,12 +91,13 @@ class ModbusUdpClient(ModbusBaseSyncClient): Optional parameters: + :param framer: Framer name, default FramerType.SOCKET + + ----- OLD ------ + :param port: Port used for communication. :param source_address: source address of client, - Common optional parameters: - - :param framer: Framer enum name :param timeout: Timeout for a request, in seconds. :param retries: Max number of retries per request. :param retry_on_empty: Retry on empty response. @@ -124,17 +129,20 @@ async def run(): def __init__( self, host: str, - port: int = 502, framer: FramerType = FramerType.SOCKET, + # ----- OLD ------ + port: int = 502, source_address: tuple[str, int] | None = None, **kwargs: Any, ) -> None: """Initialize Modbus UDP Client.""" + self.comm_params = CommParams( + comm_type=CommType.UDP, + host=host, + ) super().__init__( framer, port=port, - host=host, - comm_type=CommType.UDP, **kwargs, ) self.params.source_address = source_address diff --git a/test/framers/test_old_framers.py b/test/framers/test_old_framers.py index 39bc5035b0..2690fe8693 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 @@ -330,6 +330,7 @@ async def test_send_packet(self, rtu_framer): host="localhost", port=BASE_PORT + 1, CommType=CommType.TCP, + comm_params=CommParams(), ) client.state = ModbusTransactionState.TRANSACTION_COMPLETE client.silent_interval = 1 diff --git a/test/sub_client/test_client.py b/test/sub_client/test_client.py index ec9405009f..d17b3a820b 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 @@ -281,6 +281,7 @@ async def test_client_modbusbaseclient(): host="localhost", port=BASE_PORT + 1, CommType=CommType.TCP, + comm_params=CommParams(), ) client.register(pdu_bit_read.ReadCoilsResponse) assert str(client) @@ -316,6 +317,7 @@ async def test_client_base_async(): host="localhost", port=BASE_PORT + 2, CommType=CommType.TCP, + comm_params=CommParams(), ) as client: str(client) p_connect.return_value = asyncio.Future() @@ -327,7 +329,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 +352,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 +367,8 @@ 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, host="localhost", port=+3, CommType=CommType.TCP, comm_params=CommParams(), + ) transport = mock.MagicMock() base.ctx.connection_made(transport=transport) @@ -411,7 +416,8 @@ 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, host="127.0.0.1", comm_params=CommParams(), +) request = pdu_bit_read.ReadCoilsRequest(1, 1) transport = MockTransport(base, request) base.ctx.connection_made(transport=transport) @@ -422,7 +428,8 @@ 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, host="127.0.0.1", comm_params=CommParams(), +) base.broadcast_enable = True request = pdu_bit_read.ReadCoilsRequest(1, 1) transport = MockTransport(base, request) @@ -432,7 +439,8 @@ 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, host="127.0.0.1", timeout=0.1, comm_params=CommParams(), +) request = pdu_bit_read.ReadCoilsRequest(1, 1) transport = MockTransport(base, request, retries=2) base.ctx.connection_made(transport=transport) @@ -445,7 +453,8 @@ 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, host="127.0.0.1", timeout=0.1, retries=2, comm_params=CommParams(), +) # Avoid creating do_reconnect() task base.ctx.connection_lost = mock.MagicMock() request = pdu_bit_read.ReadCoilsRequest(1, 1) @@ -645,7 +654,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))