From 33fac59fe311300dd752fcae0d013bb260a8dc5c Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 20:41:48 +0100 Subject: [PATCH 01/21] Update use pyserial RS485 --- pymodbus/transport/serialtransport.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pymodbus/transport/serialtransport.py b/pymodbus/transport/serialtransport.py index c783187ad..ac278b49c 100644 --- a/pymodbus/transport/serialtransport.py +++ b/pymodbus/transport/serialtransport.py @@ -8,6 +8,7 @@ with contextlib.suppress(ImportError): import serial + import serial.rs485 class SerialTransport(asyncio.Transport): @@ -20,7 +21,7 @@ def __init__(self, loop, protocol, *args, **kwargs) -> None: super().__init__() self.async_loop = loop self.intern_protocol: asyncio.BaseProtocol = protocol - self.sync_serial = serial.serial_for_url(*args, **kwargs) + self.sync_serial = serial.rs485.RS485(*args, **kwargs) self.intern_write_buffer: list[bytes] = [] self.poll_task: asyncio.Task | None = None self._poll_wait_time = 0.0005 From d85f1e934a0a0f134d9fe25567bfaafd94672cb4 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 22:52:39 +0100 Subject: [PATCH 02/21] Correct tests access to async_loop I think this is a fix of the tests? --- test/transport/test_serial.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index 62f54e8b4..690fe81c8 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -55,10 +55,10 @@ async def test_external_methods(self, inx): comm.sync_serial.read = mock.MagicMock(return_value="abcd") comm.sync_serial.write = mock.MagicMock(return_value=4) comm.sync_serial.fileno = mock.MagicMock(return_value=2) - comm.sync_serial.async_loop.add_writer = mock.MagicMock() - comm.sync_serial.async_loop.add_reader = mock.MagicMock() - comm.sync_serial.async_loop.remove_writer = mock.MagicMock() - comm.sync_serial.async_loop.remove_reader = mock.MagicMock() + comm.async_loop.add_writer = mock.MagicMock() + comm.async_loop.add_reader = mock.MagicMock() + comm.async_loop.remove_writer = mock.MagicMock() + comm.async_loop.remove_reader = mock.MagicMock() comm.sync_serial.in_waiting = False methods = [ From 7010a322dac8d7710c244613952865ba1ba4221b Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 22:59:01 +0100 Subject: [PATCH 03/21] Update test_serial.py --- test/transport/test_serial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index 690fe81c8..843dc3460 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -15,7 +15,7 @@ @mock.patch( - "pymodbus.transport.serialtransport.serial.serial_for_url", mock.MagicMock() + "pymodbus.transport.serialtransport.serial.rs485.RS485.__init__", mock.MagicMock() ) class TestTransportSerial: """Test transport serial module.""" From 2e3e3b461de52ef2606f98ad4df5d09fe9878271 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 23:02:54 +0100 Subject: [PATCH 04/21] Update test_serial.py --- test/transport/test_serial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index 843dc3460..eaf6873cf 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -15,7 +15,7 @@ @mock.patch( - "pymodbus.transport.serialtransport.serial.rs485.RS485.__init__", mock.MagicMock() + "pymodbus.transport.serialtransport.serial.rs485.RS485.__init__", None ) class TestTransportSerial: """Test transport serial module.""" From 36a11fbea51d6a71e7e65ee07a8fee168bcc94ff Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 23:14:59 +0100 Subject: [PATCH 05/21] Update test_serial.py --- test/transport/test_serial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index eaf6873cf..12ade7e87 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -15,7 +15,7 @@ @mock.patch( - "pymodbus.transport.serialtransport.serial.rs485.RS485.__init__", None + "pymodbus.transport.serialtransport.serial.rs485.RS485.__init__", mock.MagicMock(return_value=None) ) class TestTransportSerial: """Test transport serial module.""" From eb7bfa06cca32f307cf8ae16001d12323323b592 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 23:22:46 +0100 Subject: [PATCH 06/21] Update serialtransport.py --- pymodbus/transport/serialtransport.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pymodbus/transport/serialtransport.py b/pymodbus/transport/serialtransport.py index ac278b49c..1e901cbb2 100644 --- a/pymodbus/transport/serialtransport.py +++ b/pymodbus/transport/serialtransport.py @@ -21,13 +21,16 @@ def __init__(self, loop, protocol, *args, **kwargs) -> None: super().__init__() self.async_loop = loop self.intern_protocol: asyncio.BaseProtocol = protocol - self.sync_serial = serial.rs485.RS485(*args, **kwargs) + self.sync_serial = self._serial_for_args(*args, **kwargs) self.intern_write_buffer: list[bytes] = [] self.poll_task: asyncio.Task | None = None self._poll_wait_time = 0.0005 self.sync_serial.timeout = 0 self.sync_serial.write_timeout = 0 + def _serial_for_args(self, *args, **kwargs) -> serial.rs485.RS85: + return serial.rs485.RS485(*args, **kwargs) + def setup(self) -> None: """Prepare to read/write.""" if os.name == "nt" or self.force_poll: From 1c385427936e694d425b402d8e61da8676aa3684 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 23:23:22 +0100 Subject: [PATCH 07/21] Update test_serial.py --- test/transport/test_serial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index 12ade7e87..19b498b87 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -15,7 +15,7 @@ @mock.patch( - "pymodbus.transport.serialtransport.serial.rs485.RS485.__init__", mock.MagicMock(return_value=None) + "pymodbus.transport.serialtransport.SerialTransport._serial_for_url", mock.MagicMock() ) class TestTransportSerial: """Test transport serial module.""" From bc598a08d1a08106dc807afe17de35464e71d30e Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 23:23:55 +0100 Subject: [PATCH 08/21] Update test_serial.py --- test/transport/test_serial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index 19b498b87..c8f3968a3 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -15,7 +15,7 @@ @mock.patch( - "pymodbus.transport.serialtransport.SerialTransport._serial_for_url", mock.MagicMock() + "pymodbus.transport.serialtransport.SerialTransport._serial_for_args", mock.MagicMock() ) class TestTransportSerial: """Test transport serial module.""" From 0893839b9432aea3aa09de2fede57d7e74e8335a Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Mon, 27 May 2024 23:35:11 +0100 Subject: [PATCH 09/21] Update serialtransport.py --- pymodbus/transport/serialtransport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymodbus/transport/serialtransport.py b/pymodbus/transport/serialtransport.py index 1e901cbb2..403c998a4 100644 --- a/pymodbus/transport/serialtransport.py +++ b/pymodbus/transport/serialtransport.py @@ -28,7 +28,7 @@ def __init__(self, loop, protocol, *args, **kwargs) -> None: self.sync_serial.timeout = 0 self.sync_serial.write_timeout = 0 - def _serial_for_args(self, *args, **kwargs) -> serial.rs485.RS85: + def _serial_for_args(self, *args, **kwargs) -> serial.rs485.RS485: return serial.rs485.RS485(*args, **kwargs) def setup(self) -> None: From 577848abbe5ac438257153cd80edba2d669ee190 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 13:47:05 +0100 Subject: [PATCH 10/21] Update transport.py --- pymodbus/transport/transport.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pymodbus/transport/transport.py b/pymodbus/transport/transport.py index 265d98e1d..6ada9f30e 100644 --- a/pymodbus/transport/transport.py +++ b/pymodbus/transport/transport.py @@ -52,15 +52,16 @@ import dataclasses import ssl from abc import abstractmethod -from collections.abc import Callable, Coroutine from contextlib import suppress from enum import Enum from functools import partial -from typing import Any +from typing import Any, Callable, Coroutine from pymodbus.logging import Log from pymodbus.transport.serialtransport import create_serial_connection +from serial.rs485 import RS485Settings + NULLMODEM_HOST = "__pymodbus_nullmodem" @@ -98,6 +99,9 @@ class CommParams: parity: str = '' stopbits: int = -1 + # RS485 + rs485_settings: RS485Settings | None = None + @classmethod def generate_ssl( cls, @@ -107,7 +111,7 @@ def generate_ssl( password: str | None = None, sslctx: ssl.SSLContext | None = None, ) -> ssl.SSLContext: - """Generate sslctx from cert/key/password. + """Generate sslctx from cert/key/passwor. MODBUS/TCP Security Protocol Specification demands TLSv2 at least """ @@ -147,7 +151,6 @@ def __init__( self.comm_params = params.copy() self.is_server = is_server self.is_closing = False - self.transport: asyncio.BaseTransport = None # type: ignore[assignment] self.loop: asyncio.AbstractEventLoop = asyncio.get_running_loop() self.recv_buffer: bytes = b"" @@ -188,14 +191,17 @@ def __init__( self.comm_params.comm_type = CommType.TCP parts = host.split(":") host, port = parts[1][2:], int(parts[2]) - self.init_setup_connect_listen(host, port) - def init_setup_connect_listen(self, host: str, port: int) -> None: + rs485_settings = self.comm_params.rs485_settings + self.init_setup_connect_listen(host, port, rs485_settings) + + def init_setup_connect_listen(self, host: str, port: int, rs485_settings: RS485Settings | None) -> None: """Handle connect/listen handler.""" if self.comm_params.comm_type == CommType.SERIAL: self.call_create = partial(create_serial_connection, self.loop, self.handle_new_connection, + rs485_settings, host, baudrate=self.comm_params.baudrate, bytesize=self.comm_params.bytesize, From f7381e327221519c7a97b9124745dee294c18a5c Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 13:48:30 +0100 Subject: [PATCH 11/21] Update transport.py --- pymodbus/transport/transport.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pymodbus/transport/transport.py b/pymodbus/transport/transport.py index 6ada9f30e..96d015d4a 100644 --- a/pymodbus/transport/transport.py +++ b/pymodbus/transport/transport.py @@ -52,10 +52,11 @@ import dataclasses import ssl from abc import abstractmethod +from collections.abc import Callable, Coroutine from contextlib import suppress from enum import Enum from functools import partial -from typing import Any, Callable, Coroutine +from typing import Any from pymodbus.logging import Log from pymodbus.transport.serialtransport import create_serial_connection @@ -111,7 +112,7 @@ def generate_ssl( password: str | None = None, sslctx: ssl.SSLContext | None = None, ) -> ssl.SSLContext: - """Generate sslctx from cert/key/passwor. + """Generate sslctx from cert/key/password. MODBUS/TCP Security Protocol Specification demands TLSv2 at least """ From 0dbf835d5eeaa52ff6a646b2e56cf65220c5732d Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 13:49:06 +0100 Subject: [PATCH 12/21] Update serialtransport.py --- pymodbus/transport/serialtransport.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pymodbus/transport/serialtransport.py b/pymodbus/transport/serialtransport.py index 403c998a4..82fe56236 100644 --- a/pymodbus/transport/serialtransport.py +++ b/pymodbus/transport/serialtransport.py @@ -16,21 +16,22 @@ class SerialTransport(asyncio.Transport): force_poll: bool = False - def __init__(self, loop, protocol, *args, **kwargs) -> None: + def __init__(self, loop, protocol, rs485_settings, *args, **kwargs) -> None: """Initialize.""" super().__init__() self.async_loop = loop self.intern_protocol: asyncio.BaseProtocol = protocol - self.sync_serial = self._serial_for_args(*args, **kwargs) + if rs485_settings != None: + self.sync_serial = serial.rs485.RS485(*args, **kwargs) + self.sync_serial.rs485_mode = rs485_settings + else: + self.sync_serial = serial.serial_for_url(*args, **kwargs) self.intern_write_buffer: list[bytes] = [] self.poll_task: asyncio.Task | None = None self._poll_wait_time = 0.0005 self.sync_serial.timeout = 0 self.sync_serial.write_timeout = 0 - def _serial_for_args(self, *args, **kwargs) -> serial.rs485.RS485: - return serial.rs485.RS485(*args, **kwargs) - def setup(self) -> None: """Prepare to read/write.""" if os.name == "nt" or self.force_poll: @@ -157,10 +158,10 @@ async def polling_task(self): self.intern_read_ready() async def create_serial_connection( - loop, protocol_factory, *args, **kwargs + loop, protocol_factory, rs485_settings, *args, **kwargs ) -> tuple[asyncio.Transport, asyncio.BaseProtocol]: """Create a connection to a new serial port instance.""" protocol = protocol_factory() - transport = SerialTransport(loop, protocol, *args, **kwargs) + transport = SerialTransport(loop, protocol, rs485_settings, *args, **kwargs) loop.call_soon(transport.setup) return transport, protocol From a90488cf59bbb0c2d891ceb5d71f73aa8ef9bfa1 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 13:53:54 +0100 Subject: [PATCH 13/21] Update serialtransport.py --- pymodbus/transport/serialtransport.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pymodbus/transport/serialtransport.py b/pymodbus/transport/serialtransport.py index 82fe56236..0d3a48950 100644 --- a/pymodbus/transport/serialtransport.py +++ b/pymodbus/transport/serialtransport.py @@ -21,17 +21,22 @@ def __init__(self, loop, protocol, rs485_settings, *args, **kwargs) -> None: super().__init__() self.async_loop = loop self.intern_protocol: asyncio.BaseProtocol = protocol - if rs485_settings != None: - self.sync_serial = serial.rs485.RS485(*args, **kwargs) - self.sync_serial.rs485_mode = rs485_settings - else: - self.sync_serial = serial.serial_for_url(*args, **kwargs) + self.sync_serial = self._serial_for_args(rs485_settings, *args, **kwargs) self.intern_write_buffer: list[bytes] = [] self.poll_task: asyncio.Task | None = None self._poll_wait_time = 0.0005 self.sync_serial.timeout = 0 self.sync_serial.write_timeout = 0 + def _serial_for_args(self, rs485_settings, *args, **kwargs) -> serial.Serial: + if rs485_settings != None: + sync_serial = serial.rs485.RS485(*args, **kwargs) + sync_serial.rs485_mode = rs485_settings + else: + sync_serial = serial.serial_for_url(*args, **kwargs) + + return sync_serial + def setup(self) -> None: """Prepare to read/write.""" if os.name == "nt" or self.force_poll: From 61007d4ded4ea3357ff1430bf9ec09aa3e4dc17a Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 13:55:04 +0100 Subject: [PATCH 14/21] Update serialtransport.py --- pymodbus/transport/serialtransport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymodbus/transport/serialtransport.py b/pymodbus/transport/serialtransport.py index 0d3a48950..e4b305d38 100644 --- a/pymodbus/transport/serialtransport.py +++ b/pymodbus/transport/serialtransport.py @@ -29,7 +29,7 @@ def __init__(self, loop, protocol, rs485_settings, *args, **kwargs) -> None: self.sync_serial.write_timeout = 0 def _serial_for_args(self, rs485_settings, *args, **kwargs) -> serial.Serial: - if rs485_settings != None: + if rs485_settings is not None: sync_serial = serial.rs485.RS485(*args, **kwargs) sync_serial.rs485_mode = rs485_settings else: From 1b6cd2193844ed41997a285d2ac740b01ea483a5 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 13:57:10 +0100 Subject: [PATCH 15/21] Update transport.py --- pymodbus/transport/transport.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pymodbus/transport/transport.py b/pymodbus/transport/transport.py index 96d015d4a..e4e8769ba 100644 --- a/pymodbus/transport/transport.py +++ b/pymodbus/transport/transport.py @@ -57,12 +57,11 @@ from enum import Enum from functools import partial from typing import Any +from serial.rs485 import RS485Settings from pymodbus.logging import Log from pymodbus.transport.serialtransport import create_serial_connection -from serial.rs485 import RS485Settings - NULLMODEM_HOST = "__pymodbus_nullmodem" From 0ee5b756d8e28a6172647ad04456063adf53afbd Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 13:57:55 +0100 Subject: [PATCH 16/21] Update test_serial.py --- test/transport/test_serial.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index c8f3968a3..debc4b140 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -73,7 +73,7 @@ async def test_external_methods(self, inx): async def test_create_serial(self): """Test external methods.""" transport, protocol = await create_serial_connection( - asyncio.get_running_loop(), mock.Mock, url="dummy" + asyncio.get_running_loop(), mock.Mock, None, url="dummy" ) assert transport assert protocol @@ -83,7 +83,7 @@ async def test_force_poll(self): """Test external methods.""" SerialTransport.force_poll = True transport, protocol = await create_serial_connection( - asyncio.get_running_loop(), mock.Mock, url="dummy" + asyncio.get_running_loop(), mock.Mock, None, url="dummy" ) await asyncio.sleep(0) assert transport @@ -96,7 +96,7 @@ async def test_write_force_poll(self): """Test write with poll.""" SerialTransport.force_poll = True transport, protocol = await create_serial_connection( - asyncio.get_running_loop(), mock.Mock, url="dummy" + asyncio.get_running_loop(), mock.Mock, None, url="dummy" ) await asyncio.sleep(0) transport.write(b"abcd") From 69622df662cca5d8e03ee90437466c365fe4885a Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 14:00:05 +0100 Subject: [PATCH 17/21] Update serialtransport.py --- pymodbus/transport/serialtransport.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pymodbus/transport/serialtransport.py b/pymodbus/transport/serialtransport.py index e4b305d38..1a6bb367a 100644 --- a/pymodbus/transport/serialtransport.py +++ b/pymodbus/transport/serialtransport.py @@ -29,6 +29,7 @@ def __init__(self, loop, protocol, rs485_settings, *args, **kwargs) -> None: self.sync_serial.write_timeout = 0 def _serial_for_args(self, rs485_settings, *args, **kwargs) -> serial.Serial: + sync_serial: serial.Serial if rs485_settings is not None: sync_serial = serial.rs485.RS485(*args, **kwargs) sync_serial.rs485_mode = rs485_settings From 13868fa2d9ee5029225699d02eb4dba41fb59b3f Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 14:01:46 +0100 Subject: [PATCH 18/21] Update transport.py --- pymodbus/transport/transport.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pymodbus/transport/transport.py b/pymodbus/transport/transport.py index e4e8769ba..9034fbb68 100644 --- a/pymodbus/transport/transport.py +++ b/pymodbus/transport/transport.py @@ -57,6 +57,7 @@ from enum import Enum from functools import partial from typing import Any + from serial.rs485 import RS485Settings from pymodbus.logging import Log From 7209eb02ceec84125040b127d5397b371c96184d Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Fri, 31 May 2024 14:22:59 +0100 Subject: [PATCH 19/21] Update base.py --- pymodbus/client/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pymodbus/client/base.py b/pymodbus/client/base.py index 95dcaf1a4..91a06dc04 100644 --- a/pymodbus/client/base.py +++ b/pymodbus/client/base.py @@ -80,6 +80,7 @@ def __init__( parity=kwargs.get("parity", None), stopbits=kwargs.get("stopbits", None), handle_local_echo=kwargs.get("handle_local_echo", False), + rs485_settings=kwargs.get("rs485_settings", None), ), retries, retry_on_empty, From e6eec1a3fd4ed2606658b5f21c8b349133c2f829 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Thu, 6 Jun 2024 16:24:17 +0000 Subject: [PATCH 20/21] some fixes --- pymodbus/client/serial.py | 6 ++++++ pymodbus/transport/transport.py | 3 +-- test/transport/test_serial.py | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pymodbus/client/serial.py b/pymodbus/client/serial.py index 0fb4c6034..fbc43d1fc 100644 --- a/pymodbus/client/serial.py +++ b/pymodbus/client/serial.py @@ -15,6 +15,7 @@ try: import serial + from serial.rs485 import RS485Settings PYSERIAL_MISSING = False except ImportError: @@ -37,6 +38,7 @@ class AsyncModbusSerialClient(ModbusBaseClient): :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. + :param rs485_settings: Allow configuring the underlying serial port for RS485 mode. Common optional parameters: @@ -73,6 +75,7 @@ def __init__( bytesize: int = 8, parity: str = "N", stopbits: int = 1, + rs485_settings: RS485Settings | None = None, **kwargs: Any, ) -> None: """Initialize Asyncio Modbus Serial Client.""" @@ -90,6 +93,7 @@ def __init__( bytesize=bytesize, parity=parity, stopbits=stopbits, + rs485_settings=rs485_settings, **kwargs, ) @@ -155,6 +159,7 @@ def __init__( bytesize: int = 8, parity: str = "N", stopbits: int = 1, + rs485_settings: RS485Settings | None = None, strict: bool = True, **kwargs: Any, ) -> None: @@ -167,6 +172,7 @@ def __init__( bytesize=bytesize, parity=parity, stopbits=stopbits, + rs485_settings=rs485_settings, **kwargs, ) self.socket: serial.Serial | None = None diff --git a/pymodbus/transport/transport.py b/pymodbus/transport/transport.py index 9034fbb68..0ca0457b2 100644 --- a/pymodbus/transport/transport.py +++ b/pymodbus/transport/transport.py @@ -193,8 +193,7 @@ def __init__( parts = host.split(":") host, port = parts[1][2:], int(parts[2]) - rs485_settings = self.comm_params.rs485_settings - self.init_setup_connect_listen(host, port, rs485_settings) + self.init_setup_connect_listen(host, port, self.comm_params.rs485_settings) def init_setup_connect_listen(self, host: str, port: int, rs485_settings: RS485Settings | None) -> None: """Handle connect/listen handler.""" diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index debc4b140..f1c001e2b 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -15,7 +15,7 @@ @mock.patch( - "pymodbus.transport.serialtransport.SerialTransport._serial_for_args", mock.MagicMock() + "pymodbus.transport.serialtransport.serial.serial_for_url", mock.MagicMock() ) class TestTransportSerial: """Test transport serial module.""" From f2a8215296b795cc193e846c1c5fe94499826f53 Mon Sep 17 00:00:00 2001 From: Sam Duke Date: Thu, 6 Jun 2024 17:32:05 +0000 Subject: [PATCH 21/21] fix test --- test/transport/test_serial.py | 43 ++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/test/transport/test_serial.py b/test/transport/test_serial.py index f1c001e2b..f12b064d5 100644 --- a/test/transport/test_serial.py +++ b/test/transport/test_serial.py @@ -7,6 +7,7 @@ import pytest import serial +from serial.rs485 import RS485Settings from pymodbus.transport.serialtransport import ( SerialTransport, @@ -17,22 +18,25 @@ @mock.patch( "pymodbus.transport.serialtransport.serial.serial_for_url", mock.MagicMock() ) +@mock.patch( + "pymodbus.transport.serialtransport.serial.rs485.RS485", mock.MagicMock() +) class TestTransportSerial: """Test transport serial module.""" async def test_init(self): """Test null modem init.""" - SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") async def test_loop(self): """Test asyncio abstract methods.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") assert comm.loop @pytest.mark.parametrize("inx", range(0, 11)) async def test_abstract_methods(self, inx): """Test asyncio abstract methods.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") methods = [ partial(comm.get_protocol), partial(comm.set_protocol, None), @@ -51,7 +55,7 @@ async def test_abstract_methods(self, inx): @pytest.mark.parametrize("inx", range(0, 4)) async def test_external_methods(self, inx): """Test external methods.""" - comm = SerialTransport(mock.MagicMock(), mock.Mock(), "dummy") + comm = SerialTransport(mock.MagicMock(), mock.Mock(), None, "dummy") comm.sync_serial.read = mock.MagicMock(return_value="abcd") comm.sync_serial.write = mock.MagicMock(return_value=4) comm.sync_serial.fileno = mock.MagicMock(return_value=2) @@ -79,6 +83,19 @@ async def test_create_serial(self): assert protocol transport.close() + @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") + async def test_create_rs485_serial(self): + """Test external methods.""" + settings = RS485Settings(rts_level_for_rx=True, rts_level_for_tx=True, delay_before_rx=0.4) + transport, protocol = await create_serial_connection( + asyncio.get_running_loop(), mock.Mock, settings, url="dummy" + ) + assert transport + assert protocol + assert transport.sync_serial.rs485_mode + assert transport.sync_serial.rs485_mode == settings + transport.close() + async def test_force_poll(self): """Test external methods.""" SerialTransport.force_poll = True @@ -105,14 +122,14 @@ async def test_write_force_poll(self): async def test_close(self): """Test close.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.sync_serial = None comm.close() @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") async def test_polling(self): """Test polling.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.sync_serial = mock.MagicMock() comm.sync_serial.read.side_effect = asyncio.CancelledError("test") with contextlib.suppress(asyncio.CancelledError): @@ -121,7 +138,7 @@ async def test_polling(self): @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") async def test_poll_task(self): """Test polling.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.sync_serial = mock.MagicMock() comm.sync_serial.read.side_effect = serial.SerialException("test") await comm.polling_task() @@ -129,7 +146,7 @@ async def test_poll_task(self): @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") async def test_poll_task2(self): """Test polling.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.sync_serial = mock.MagicMock() comm.sync_serial = mock.MagicMock() comm.sync_serial.write.return_value = 4 @@ -141,7 +158,7 @@ async def test_poll_task2(self): @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") async def test_write_exception(self): """Test write exception.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.sync_serial = mock.MagicMock() comm.sync_serial.write.side_effect = BlockingIOError("test") comm.intern_write_ready() @@ -151,7 +168,7 @@ async def test_write_exception(self): @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") async def test_write_ok(self): """Test write exception.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.sync_serial = mock.MagicMock() comm.sync_serial.write.return_value = 4 comm.intern_write_buffer.append(b"abcd") @@ -160,7 +177,7 @@ async def test_write_ok(self): @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") async def test_write_len(self): """Test write exception.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.sync_serial = mock.MagicMock() comm.sync_serial.write.return_value = 3 comm.async_loop.add_writer = mock.Mock() @@ -170,7 +187,7 @@ async def test_write_len(self): @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") async def test_write_force(self): """Test write exception.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.poll_task = True comm.sync_serial = mock.MagicMock() comm.sync_serial.write.return_value = 3 @@ -180,7 +197,7 @@ async def test_write_force(self): @pytest.mark.skipif(os.name == "nt", reason="Windows not supported") async def test_read_ready(self): """Test polling.""" - comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), "dummy") + comm = SerialTransport(asyncio.get_running_loop(), mock.Mock(), None, "dummy") comm.sync_serial = mock.MagicMock() comm.intern_protocol = mock.Mock() comm.sync_serial.read = mock.Mock()