We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Invalid `stopbits=0` for serial connection
In the following [docstring](https://github.com/pymodbus-dev/pymodbus/blob/8d3e36fe78ed5ec8bcf94b024df1125ee2b1f015/pymodbus/client/serial.py#L121) valid values for stop bits is 0-2:
```
:param stopbits: Number of stop bits 0-2.
```
Is this a typo? I don't think you can have a serial connection without stop bits. Also I don't think the underlying serial library [`pyserial`](https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.STOPBITS_ONE) supports no stop bits !
```
Stop bits

serial.STOPBITS_ONE
serial.STOPBITS_ONE_POINT_FIVE
serial.STOPBITS_TWO
```
I tried setting `stopbits=0` and got the following **expected** error:
```
ValueError: Not a valid stop bit size: 0
```

I propose if this needs to be fixed, instead of changing `0-2` to `1-2`, it is better to have explicit valid values like `1, 1.5, 2`.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pymodbus/client/serial.py`
Content:
```
1 """Modbus client async serial communication."""
2 from __future__ import annotations
3 
4 import asyncio
5 import time
6 from functools import partial
7 from typing import Any
8 
9 from pymodbus.client.base import ModbusBaseClient
10 from pymodbus.exceptions import ConnectionException
11 from pymodbus.framer import Framer
12 from pymodbus.logging import Log
13 from pymodbus.transport import CommType
14 from pymodbus.utilities import ModbusTransactionState
15 
16 
17 try:
18     import serial
19 
20     PYSERIAL_MISSING = False
21 except ImportError:
22     PYSERIAL_MISSING = True
23 
24 
25 class AsyncModbusSerialClient(ModbusBaseClient, asyncio.Protocol):
26     """**AsyncModbusSerialClient**.
27 
28     Fixed parameters:
29 
30     :param port: Serial port used for communication.
31 
32     Optional parameters:
33 
34     :param baudrate: Bits per second.
35     :param bytesize: Number of bits per byte 7-8.
36     :param parity: 'E'ven, 'O'dd or 'N'one
37     :param stopbits: Number of stop bits 0-2.
38     :param handle_local_echo: Discard local echo from dongle.
39 
40     Common optional parameters:
41 
42     :param framer: Framer enum name
43     :param timeout: Timeout for a request, in seconds.
44     :param retries: Max number of retries per request.
45     :param retry_on_empty: Retry on empty response.
46     :param close_comm_on_error: Close connection on error.
47     :param strict: Strict timing, 1.5 character between requests.
48     :param broadcast_enable: True to treat id 0 as broadcast address.
49     :param reconnect_delay: Minimum delay in milliseconds before reconnecting.
50     :param reconnect_delay_max: Maximum delay in milliseconds before reconnecting.
51     :param on_reconnect_callback: Function that will be called just before a reconnection attempt.
52     :param no_resend_on_retry: Do not resend request when retrying due to missing response.
53     :param kwargs: Experimental parameters.
54 
55     Example::
56 
57         from pymodbus.client import AsyncModbusSerialClient
58 
59         async def run():
60             client = AsyncModbusSerialClient("dev/serial0")
61 
62             await client.connect()
63             ...
64             client.close()
65 
66     Please refer to :ref:`Pymodbus internals` for advanced usage.
67     """
68 
69     def __init__(
70         self,
71         port: str,
72         framer: Framer = Framer.RTU,
73         baudrate: int = 19200,
74         bytesize: int = 8,
75         parity: str = "N",
76         stopbits: int = 1,
77         **kwargs: Any,
78     ) -> None:
79         """Initialize Asyncio Modbus Serial Client."""
80         if PYSERIAL_MISSING:
81             raise RuntimeError(
82                 "Serial client requires pyserial "
83                 'Please install with "pip install pyserial" and try again.'
84             )
85         asyncio.Protocol.__init__(self)
86         ModbusBaseClient.__init__(
87             self,
88             framer,
89             CommType=CommType.SERIAL,
90             host=port,
91             baudrate=baudrate,
92             bytesize=bytesize,
93             parity=parity,
94             stopbits=stopbits,
95             **kwargs,
96         )
97 
98     async def connect(self) -> bool:
99         """Connect Async client."""
100         self.reset_delay()
101         Log.debug("Connecting to {}.", self.comm_params.host)
102         return await self.transport_connect()
103 
104     def close(self, reconnect: bool = False) -> None:
105         """Close connection."""
106         super().close(reconnect=reconnect)
107 
108 
109 class ModbusSerialClient(ModbusBaseClient):
110     """**ModbusSerialClient**.
111 
112     Fixed parameters:
113 
114     :param port: Serial port used for communication.
115 
116     Optional parameters:
117 
118     :param baudrate: Bits per second.
119     :param bytesize: Number of bits per byte 7-8.
120     :param parity: 'E'ven, 'O'dd or 'N'one
121     :param stopbits: Number of stop bits 0-2.
122     :param handle_local_echo: Discard local echo from dongle.
123 
124     Common optional parameters:
125 
126     :param framer: Framer enum name
127     :param timeout: Timeout for a request, in seconds.
128     :param retries: Max number of retries per request.
129     :param retry_on_empty: Retry on empty response.
130     :param close_comm_on_error: Close connection on error.
131     :param strict: Strict timing, 1.5 character between requests.
132     :param broadcast_enable: True to treat id 0 as broadcast address.
133     :param reconnect_delay: Minimum delay in milliseconds before reconnecting.
134     :param reconnect_delay_max: Maximum delay in milliseconds before reconnecting.
135     :param on_reconnect_callback: Function that will be called just before a reconnection attempt.
136     :param no_resend_on_retry: Do not resend request when retrying due to missing response.
137     :param kwargs: Experimental parameters.
138 
139     Example::
140 
141         from pymodbus.client import ModbusSerialClient
142 
143         def run():
144             client = ModbusSerialClient("dev/serial0")
145 
146             client.connect()
147             ...
148             client.close()
149 
150     Please refer to :ref:`Pymodbus internals` for advanced usage.
151 
152     Remark: There are no automatic reconnect as with AsyncModbusSerialClient
153     """
154 
155     state = ModbusTransactionState.IDLE
156     inter_char_timeout: float = 0
157     silent_interval: float = 0
158 
159     def __init__(
160         self,
161         port: str,
162         framer: Framer = Framer.RTU,
163         baudrate: int = 19200,
164         bytesize: int = 8,
165         parity: str = "N",
166         stopbits: int = 1,
167         **kwargs: Any,
168     ) -> None:
169         """Initialize Modbus Serial Client."""
170         self.transport = None
171         kwargs["use_sync"] = True
172         ModbusBaseClient.__init__(
173             self,
174             framer,
175             CommType=CommType.SERIAL,
176             host=port,
177             baudrate=baudrate,
178             bytesize=bytesize,
179             parity=parity,
180             stopbits=stopbits,
181             **kwargs,
182         )
183         self.socket = None
184 
185         self.last_frame_end = None
186 
187         self._t0 = float(1 + bytesize + stopbits) / self.comm_params.baudrate
188 
189         # Check every 4 bytes / 2 registers if the reading is ready
190         self._recv_interval = self._t0 * 4
191         # Set a minimum of 1ms for high baudrates
192         self._recv_interval = max(self._recv_interval, 0.001)
193 
194         if self.comm_params.baudrate > 19200:
195             self.silent_interval = 1.75 / 1000  # ms
196         else:
197             self.inter_char_timeout = 1.5 * self._t0
198             self.silent_interval = 3.5 * self._t0
199         self.silent_interval = round(self.silent_interval, 6)
200 
201     @property
202     def connected(self):
203         """Connect internal."""
204         return self.connect()
205 
206     def connect(self):  # pylint: disable=invalid-overridden-method
207         """Connect to the modbus serial server."""
208         if self.socket:
209             return True
210         try:
211             self.socket = serial.serial_for_url(
212                 self.comm_params.host,
213                 timeout=self.comm_params.timeout_connect,
214                 bytesize=self.comm_params.bytesize,
215                 stopbits=self.comm_params.stopbits,
216                 baudrate=self.comm_params.baudrate,
217                 parity=self.comm_params.parity,
218             )
219             if self.params.strict:
220                 self.socket.interCharTimeout = self.inter_char_timeout
221             self.last_frame_end = None
222         except serial.SerialException as msg:
223             Log.error("{}", msg)
224             self.close()
225         return self.socket is not None
226 
227     def close(self):  # pylint: disable=arguments-differ
228         """Close the underlying socket connection."""
229         if self.socket:
230             self.socket.close()
231         self.socket = None
232 
233     def _in_waiting(self):
234         """Return _in_waiting."""
235         in_waiting = "in_waiting" if hasattr(self.socket, "in_waiting") else "inWaiting"
236 
237         if in_waiting == "in_waiting":
238             waitingbytes = getattr(self.socket, in_waiting)
239         else:
240             waitingbytes = getattr(self.socket, in_waiting)()
241         return waitingbytes
242 
243     def send(self, request):
244         """Send data on the underlying socket.
245 
246         If receive buffer still holds some data then flush it.
247 
248         Sleep if last send finished less than 3.5 character times ago.
249         """
250         super().send(request)
251         if not self.socket:
252             raise ConnectionException(str(self))
253         if request:
254             try:
255                 if waitingbytes := self._in_waiting():
256                     result = self.socket.read(waitingbytes)
257                     if self.state == ModbusTransactionState.RETRYING:
258                         Log.debug(
259                             "Sending available data in recv buffer {}", result, ":hex"
260                         )
261                         return result
262                     Log.warning("Cleanup recv buffer before send: {}", result, ":hex")
263             except NotImplementedError:
264                 pass
265             if self.state != ModbusTransactionState.SENDING:
266                 Log.debug('New Transaction state "SENDING"')
267                 self.state = ModbusTransactionState.SENDING
268             size = self.socket.write(request)
269             return size
270         return 0
271 
272     def _wait_for_data(self):
273         """Wait for data."""
274         size = 0
275         more_data = False
276         if (
277             self.comm_params.timeout_connect is not None
278             and self.comm_params.timeout_connect
279         ):
280             condition = partial(
281                 lambda start, timeout: (time.time() - start) <= timeout,
282                 timeout=self.comm_params.timeout_connect,
283             )
284         else:
285             condition = partial(lambda dummy1, dummy2: True, dummy2=None)
286         start = time.time()
287         while condition(start):
288             available = self._in_waiting()
289             if (more_data and not available) or (more_data and available == size):
290                 break
291             if available and available != size:
292                 more_data = True
293                 size = available
294             time.sleep(self._recv_interval)
295         return size
296 
297     def recv(self, size):
298         """Read data from the underlying descriptor."""
299         super().recv(size)
300         if not self.socket:
301             raise ConnectionException(
302                 self.__str__()  # pylint: disable=unnecessary-dunder-call
303             )
304         if size is None:
305             size = self._wait_for_data()
306         if size > self._in_waiting():
307             self._wait_for_data()
308         result = self.socket.read(size)
309         return result
310 
311     def is_socket_open(self):
312         """Check if socket is open."""
313         if self.socket:
314             if hasattr(self.socket, "is_open"):
315                 return self.socket.is_open
316             return self.socket.isOpen()
317         return False
318 
319     def __str__(self):
320         """Build a string representation of the connection."""
321         return f"ModbusSerialClient({self.framer} baud[{self.comm_params.baudrate}])"
322 
323     def __repr__(self):
324         """Return string representation."""
325         return (
326             f"<{self.__class__.__name__} at {hex(id(self))} socket={self.socket}, "
327             f"framer={self.framer}, timeout={self.comm_params.timeout_connect}>"
328         )
329
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/pymodbus/client/serial.py b/pymodbus/client/serial.py
--- a/pymodbus/client/serial.py
+++ b/pymodbus/client/serial.py
@@ -34,7 +34,7 @@
     :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 stopbits: Number of stop bits 1, 1.5, 2.
     :param handle_local_echo: Discard local echo from dongle.
 
     Common optional parameters:

{"golden_diff": "diff --git a/pymodbus/client/serial.py b/pymodbus/client/serial.py\n--- a/pymodbus/client/serial.py\n+++ b/pymodbus/client/serial.py\n@@ -34,7 +34,7 @@\n     :param baudrate: Bits per second.\n     :param bytesize: Number of bits per byte 7-8.\n     :param parity: 'E'ven, 'O'dd or 'N'one\n-    :param stopbits: Number of stop bits 0-2.\n+    :param stopbits: Number of stop bits 1, 1.5, 2.\n     :param handle_local_echo: Discard local echo from dongle.\n \n     Common optional parameters:\n", "issue": "Invalid `stopbits=0` for serial connection\nIn the following [docstring](https://github.com/pymodbus-dev/pymodbus/blob/8d3e36fe78ed5ec8bcf94b024df1125ee2b1f015/pymodbus/client/serial.py#L121) valid values for stop bits is 0-2:\r\n```\r\n:param stopbits: Number of stop bits 0-2.\r\n```\r\nIs this a typo? I don't think you can have a serial connection without stop bits. Also I don't think the underlying serial library [`pyserial`](https://pyserial.readthedocs.io/en/latest/pyserial_api.html#serial.STOPBITS_ONE) supports no stop bits !\r\n```\r\nStop bits\r\n\r\nserial.STOPBITS_ONE\r\nserial.STOPBITS_ONE_POINT_FIVE\r\nserial.STOPBITS_TWO\r\n```\r\nI tried setting `stopbits=0` and got the following **expected** error:\r\n```\r\nValueError: Not a valid stop bit size: 0\r\n```\r\n\r\nI propose if this needs to be fixed, instead of changing `0-2` to `1-2`, it is better to have explicit valid values like `1, 1.5, 2`.\n", "before_files": [{"content": "\"\"\"Modbus client async serial communication.\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport time\nfrom functools import partial\nfrom typing import Any\n\nfrom pymodbus.client.base import ModbusBaseClient\nfrom pymodbus.exceptions import ConnectionException\nfrom pymodbus.framer import Framer\nfrom pymodbus.logging import Log\nfrom pymodbus.transport import CommType\nfrom pymodbus.utilities import ModbusTransactionState\n\n\ntry:\n    import serial\n\n    PYSERIAL_MISSING = False\nexcept ImportError:\n    PYSERIAL_MISSING = True\n\n\nclass AsyncModbusSerialClient(ModbusBaseClient, asyncio.Protocol):\n    \"\"\"**AsyncModbusSerialClient**.\n\n    Fixed parameters:\n\n    :param port: Serial port used for communication.\n\n    Optional parameters:\n\n    :param baudrate: Bits per second.\n    :param bytesize: Number of bits per byte 7-8.\n    :param parity: 'E'ven, 'O'dd or 'N'one\n    :param stopbits: Number of stop bits 0-2.\n    :param handle_local_echo: Discard local echo from dongle.\n\n    Common optional parameters:\n\n    :param framer: Framer enum name\n    :param timeout: Timeout for a request, in seconds.\n    :param retries: Max number of retries per request.\n    :param retry_on_empty: Retry on empty response.\n    :param close_comm_on_error: Close connection on error.\n    :param strict: Strict timing, 1.5 character between requests.\n    :param broadcast_enable: True to treat id 0 as broadcast address.\n    :param reconnect_delay: Minimum delay in milliseconds before reconnecting.\n    :param reconnect_delay_max: Maximum delay in milliseconds before reconnecting.\n    :param on_reconnect_callback: Function that will be called just before a reconnection attempt.\n    :param no_resend_on_retry: Do not resend request when retrying due to missing response.\n    :param kwargs: Experimental parameters.\n\n    Example::\n\n        from pymodbus.client import AsyncModbusSerialClient\n\n        async def run():\n            client = AsyncModbusSerialClient(\"dev/serial0\")\n\n            await client.connect()\n            ...\n            client.close()\n\n    Please refer to :ref:`Pymodbus internals` for advanced usage.\n    \"\"\"\n\n    def __init__(\n        self,\n        port: str,\n        framer: Framer = Framer.RTU,\n        baudrate: int = 19200,\n        bytesize: int = 8,\n        parity: str = \"N\",\n        stopbits: int = 1,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"Initialize Asyncio Modbus Serial Client.\"\"\"\n        if PYSERIAL_MISSING:\n            raise RuntimeError(\n                \"Serial client requires pyserial \"\n                'Please install with \"pip install pyserial\" and try again.'\n            )\n        asyncio.Protocol.__init__(self)\n        ModbusBaseClient.__init__(\n            self,\n            framer,\n            CommType=CommType.SERIAL,\n            host=port,\n            baudrate=baudrate,\n            bytesize=bytesize,\n            parity=parity,\n            stopbits=stopbits,\n            **kwargs,\n        )\n\n    async def connect(self) -> bool:\n        \"\"\"Connect Async client.\"\"\"\n        self.reset_delay()\n        Log.debug(\"Connecting to {}.\", self.comm_params.host)\n        return await self.transport_connect()\n\n    def close(self, reconnect: bool = False) -> None:\n        \"\"\"Close connection.\"\"\"\n        super().close(reconnect=reconnect)\n\n\nclass ModbusSerialClient(ModbusBaseClient):\n    \"\"\"**ModbusSerialClient**.\n\n    Fixed parameters:\n\n    :param port: Serial port used for communication.\n\n    Optional parameters:\n\n    :param baudrate: Bits per second.\n    :param bytesize: Number of bits per byte 7-8.\n    :param parity: 'E'ven, 'O'dd or 'N'one\n    :param stopbits: Number of stop bits 0-2.\n    :param handle_local_echo: Discard local echo from dongle.\n\n    Common optional parameters:\n\n    :param framer: Framer enum name\n    :param timeout: Timeout for a request, in seconds.\n    :param retries: Max number of retries per request.\n    :param retry_on_empty: Retry on empty response.\n    :param close_comm_on_error: Close connection on error.\n    :param strict: Strict timing, 1.5 character between requests.\n    :param broadcast_enable: True to treat id 0 as broadcast address.\n    :param reconnect_delay: Minimum delay in milliseconds before reconnecting.\n    :param reconnect_delay_max: Maximum delay in milliseconds before reconnecting.\n    :param on_reconnect_callback: Function that will be called just before a reconnection attempt.\n    :param no_resend_on_retry: Do not resend request when retrying due to missing response.\n    :param kwargs: Experimental parameters.\n\n    Example::\n\n        from pymodbus.client import ModbusSerialClient\n\n        def run():\n            client = ModbusSerialClient(\"dev/serial0\")\n\n            client.connect()\n            ...\n            client.close()\n\n    Please refer to :ref:`Pymodbus internals` for advanced usage.\n\n    Remark: There are no automatic reconnect as with AsyncModbusSerialClient\n    \"\"\"\n\n    state = ModbusTransactionState.IDLE\n    inter_char_timeout: float = 0\n    silent_interval: float = 0\n\n    def __init__(\n        self,\n        port: str,\n        framer: Framer = Framer.RTU,\n        baudrate: int = 19200,\n        bytesize: int = 8,\n        parity: str = \"N\",\n        stopbits: int = 1,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"Initialize Modbus Serial Client.\"\"\"\n        self.transport = None\n        kwargs[\"use_sync\"] = True\n        ModbusBaseClient.__init__(\n            self,\n            framer,\n            CommType=CommType.SERIAL,\n            host=port,\n            baudrate=baudrate,\n            bytesize=bytesize,\n            parity=parity,\n            stopbits=stopbits,\n            **kwargs,\n        )\n        self.socket = None\n\n        self.last_frame_end = None\n\n        self._t0 = float(1 + bytesize + stopbits) / self.comm_params.baudrate\n\n        # Check every 4 bytes / 2 registers if the reading is ready\n        self._recv_interval = self._t0 * 4\n        # Set a minimum of 1ms for high baudrates\n        self._recv_interval = max(self._recv_interval, 0.001)\n\n        if self.comm_params.baudrate > 19200:\n            self.silent_interval = 1.75 / 1000  # ms\n        else:\n            self.inter_char_timeout = 1.5 * self._t0\n            self.silent_interval = 3.5 * self._t0\n        self.silent_interval = round(self.silent_interval, 6)\n\n    @property\n    def connected(self):\n        \"\"\"Connect internal.\"\"\"\n        return self.connect()\n\n    def connect(self):  # pylint: disable=invalid-overridden-method\n        \"\"\"Connect to the modbus serial server.\"\"\"\n        if self.socket:\n            return True\n        try:\n            self.socket = serial.serial_for_url(\n                self.comm_params.host,\n                timeout=self.comm_params.timeout_connect,\n                bytesize=self.comm_params.bytesize,\n                stopbits=self.comm_params.stopbits,\n                baudrate=self.comm_params.baudrate,\n                parity=self.comm_params.parity,\n            )\n            if self.params.strict:\n                self.socket.interCharTimeout = self.inter_char_timeout\n            self.last_frame_end = None\n        except serial.SerialException as msg:\n            Log.error(\"{}\", msg)\n            self.close()\n        return self.socket is not None\n\n    def close(self):  # pylint: disable=arguments-differ\n        \"\"\"Close the underlying socket connection.\"\"\"\n        if self.socket:\n            self.socket.close()\n        self.socket = None\n\n    def _in_waiting(self):\n        \"\"\"Return _in_waiting.\"\"\"\n        in_waiting = \"in_waiting\" if hasattr(self.socket, \"in_waiting\") else \"inWaiting\"\n\n        if in_waiting == \"in_waiting\":\n            waitingbytes = getattr(self.socket, in_waiting)\n        else:\n            waitingbytes = getattr(self.socket, in_waiting)()\n        return waitingbytes\n\n    def send(self, request):\n        \"\"\"Send data on the underlying socket.\n\n        If receive buffer still holds some data then flush it.\n\n        Sleep if last send finished less than 3.5 character times ago.\n        \"\"\"\n        super().send(request)\n        if not self.socket:\n            raise ConnectionException(str(self))\n        if request:\n            try:\n                if waitingbytes := self._in_waiting():\n                    result = self.socket.read(waitingbytes)\n                    if self.state == ModbusTransactionState.RETRYING:\n                        Log.debug(\n                            \"Sending available data in recv buffer {}\", result, \":hex\"\n                        )\n                        return result\n                    Log.warning(\"Cleanup recv buffer before send: {}\", result, \":hex\")\n            except NotImplementedError:\n                pass\n            if self.state != ModbusTransactionState.SENDING:\n                Log.debug('New Transaction state \"SENDING\"')\n                self.state = ModbusTransactionState.SENDING\n            size = self.socket.write(request)\n            return size\n        return 0\n\n    def _wait_for_data(self):\n        \"\"\"Wait for data.\"\"\"\n        size = 0\n        more_data = False\n        if (\n            self.comm_params.timeout_connect is not None\n            and self.comm_params.timeout_connect\n        ):\n            condition = partial(\n                lambda start, timeout: (time.time() - start) <= timeout,\n                timeout=self.comm_params.timeout_connect,\n            )\n        else:\n            condition = partial(lambda dummy1, dummy2: True, dummy2=None)\n        start = time.time()\n        while condition(start):\n            available = self._in_waiting()\n            if (more_data and not available) or (more_data and available == size):\n                break\n            if available and available != size:\n                more_data = True\n                size = available\n            time.sleep(self._recv_interval)\n        return size\n\n    def recv(self, size):\n        \"\"\"Read data from the underlying descriptor.\"\"\"\n        super().recv(size)\n        if not self.socket:\n            raise ConnectionException(\n                self.__str__()  # pylint: disable=unnecessary-dunder-call\n            )\n        if size is None:\n            size = self._wait_for_data()\n        if size > self._in_waiting():\n            self._wait_for_data()\n        result = self.socket.read(size)\n        return result\n\n    def is_socket_open(self):\n        \"\"\"Check if socket is open.\"\"\"\n        if self.socket:\n            if hasattr(self.socket, \"is_open\"):\n                return self.socket.is_open\n            return self.socket.isOpen()\n        return False\n\n    def __str__(self):\n        \"\"\"Build a string representation of the connection.\"\"\"\n        return f\"ModbusSerialClient({self.framer} baud[{self.comm_params.baudrate}])\"\n\n    def __repr__(self):\n        \"\"\"Return string representation.\"\"\"\n        return (\n            f\"<{self.__class__.__name__} at {hex(id(self))} socket={self.socket}, \"\n            f\"framer={self.framer}, timeout={self.comm_params.timeout_connect}>\"\n        )\n", "path": "pymodbus/client/serial.py"}], "after_files": [{"content": "\"\"\"Modbus client async serial communication.\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport time\nfrom functools import partial\nfrom typing import Any\n\nfrom pymodbus.client.base import ModbusBaseClient\nfrom pymodbus.exceptions import ConnectionException\nfrom pymodbus.framer import Framer\nfrom pymodbus.logging import Log\nfrom pymodbus.transport import CommType\nfrom pymodbus.utilities import ModbusTransactionState\n\n\ntry:\n    import serial\n\n    PYSERIAL_MISSING = False\nexcept ImportError:\n    PYSERIAL_MISSING = True\n\n\nclass AsyncModbusSerialClient(ModbusBaseClient, asyncio.Protocol):\n    \"\"\"**AsyncModbusSerialClient**.\n\n    Fixed parameters:\n\n    :param port: Serial port used for communication.\n\n    Optional parameters:\n\n    :param baudrate: Bits per second.\n    :param bytesize: Number of bits per byte 7-8.\n    :param parity: 'E'ven, 'O'dd or 'N'one\n    :param stopbits: Number of stop bits 1, 1.5, 2.\n    :param handle_local_echo: Discard local echo from dongle.\n\n    Common optional parameters:\n\n    :param framer: Framer enum name\n    :param timeout: Timeout for a request, in seconds.\n    :param retries: Max number of retries per request.\n    :param retry_on_empty: Retry on empty response.\n    :param close_comm_on_error: Close connection on error.\n    :param strict: Strict timing, 1.5 character between requests.\n    :param broadcast_enable: True to treat id 0 as broadcast address.\n    :param reconnect_delay: Minimum delay in milliseconds before reconnecting.\n    :param reconnect_delay_max: Maximum delay in milliseconds before reconnecting.\n    :param on_reconnect_callback: Function that will be called just before a reconnection attempt.\n    :param no_resend_on_retry: Do not resend request when retrying due to missing response.\n    :param kwargs: Experimental parameters.\n\n    Example::\n\n        from pymodbus.client import AsyncModbusSerialClient\n\n        async def run():\n            client = AsyncModbusSerialClient(\"dev/serial0\")\n\n            await client.connect()\n            ...\n            client.close()\n\n    Please refer to :ref:`Pymodbus internals` for advanced usage.\n    \"\"\"\n\n    def __init__(\n        self,\n        port: str,\n        framer: Framer = Framer.RTU,\n        baudrate: int = 19200,\n        bytesize: int = 8,\n        parity: str = \"N\",\n        stopbits: int = 1,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"Initialize Asyncio Modbus Serial Client.\"\"\"\n        if PYSERIAL_MISSING:\n            raise RuntimeError(\n                \"Serial client requires pyserial \"\n                'Please install with \"pip install pyserial\" and try again.'\n            )\n        asyncio.Protocol.__init__(self)\n        ModbusBaseClient.__init__(\n            self,\n            framer,\n            CommType=CommType.SERIAL,\n            host=port,\n            baudrate=baudrate,\n            bytesize=bytesize,\n            parity=parity,\n            stopbits=stopbits,\n            **kwargs,\n        )\n\n    async def connect(self) -> bool:\n        \"\"\"Connect Async client.\"\"\"\n        self.reset_delay()\n        Log.debug(\"Connecting to {}.\", self.comm_params.host)\n        return await self.transport_connect()\n\n    def close(self, reconnect: bool = False) -> None:\n        \"\"\"Close connection.\"\"\"\n        super().close(reconnect=reconnect)\n\n\nclass ModbusSerialClient(ModbusBaseClient):\n    \"\"\"**ModbusSerialClient**.\n\n    Fixed parameters:\n\n    :param port: Serial port used for communication.\n\n    Optional parameters:\n\n    :param baudrate: Bits per second.\n    :param bytesize: Number of bits per byte 7-8.\n    :param parity: 'E'ven, 'O'dd or 'N'one\n    :param stopbits: Number of stop bits 0-2.\n    :param handle_local_echo: Discard local echo from dongle.\n\n    Common optional parameters:\n\n    :param framer: Framer enum name\n    :param timeout: Timeout for a request, in seconds.\n    :param retries: Max number of retries per request.\n    :param retry_on_empty: Retry on empty response.\n    :param close_comm_on_error: Close connection on error.\n    :param strict: Strict timing, 1.5 character between requests.\n    :param broadcast_enable: True to treat id 0 as broadcast address.\n    :param reconnect_delay: Minimum delay in milliseconds before reconnecting.\n    :param reconnect_delay_max: Maximum delay in milliseconds before reconnecting.\n    :param on_reconnect_callback: Function that will be called just before a reconnection attempt.\n    :param no_resend_on_retry: Do not resend request when retrying due to missing response.\n    :param kwargs: Experimental parameters.\n\n    Example::\n\n        from pymodbus.client import ModbusSerialClient\n\n        def run():\n            client = ModbusSerialClient(\"dev/serial0\")\n\n            client.connect()\n            ...\n            client.close()\n\n    Please refer to :ref:`Pymodbus internals` for advanced usage.\n\n    Remark: There are no automatic reconnect as with AsyncModbusSerialClient\n    \"\"\"\n\n    state = ModbusTransactionState.IDLE\n    inter_char_timeout: float = 0\n    silent_interval: float = 0\n\n    def __init__(\n        self,\n        port: str,\n        framer: Framer = Framer.RTU,\n        baudrate: int = 19200,\n        bytesize: int = 8,\n        parity: str = \"N\",\n        stopbits: int = 1,\n        **kwargs: Any,\n    ) -> None:\n        \"\"\"Initialize Modbus Serial Client.\"\"\"\n        self.transport = None\n        kwargs[\"use_sync\"] = True\n        ModbusBaseClient.__init__(\n            self,\n            framer,\n            CommType=CommType.SERIAL,\n            host=port,\n            baudrate=baudrate,\n            bytesize=bytesize,\n            parity=parity,\n            stopbits=stopbits,\n            **kwargs,\n        )\n        self.socket = None\n\n        self.last_frame_end = None\n\n        self._t0 = float(1 + bytesize + stopbits) / self.comm_params.baudrate\n\n        # Check every 4 bytes / 2 registers if the reading is ready\n        self._recv_interval = self._t0 * 4\n        # Set a minimum of 1ms for high baudrates\n        self._recv_interval = max(self._recv_interval, 0.001)\n\n        if self.comm_params.baudrate > 19200:\n            self.silent_interval = 1.75 / 1000  # ms\n        else:\n            self.inter_char_timeout = 1.5 * self._t0\n            self.silent_interval = 3.5 * self._t0\n        self.silent_interval = round(self.silent_interval, 6)\n\n    @property\n    def connected(self):\n        \"\"\"Connect internal.\"\"\"\n        return self.connect()\n\n    def connect(self):  # pylint: disable=invalid-overridden-method\n        \"\"\"Connect to the modbus serial server.\"\"\"\n        if self.socket:\n            return True\n        try:\n            self.socket = serial.serial_for_url(\n                self.comm_params.host,\n                timeout=self.comm_params.timeout_connect,\n                bytesize=self.comm_params.bytesize,\n                stopbits=self.comm_params.stopbits,\n                baudrate=self.comm_params.baudrate,\n                parity=self.comm_params.parity,\n            )\n            if self.params.strict:\n                self.socket.interCharTimeout = self.inter_char_timeout\n            self.last_frame_end = None\n        except serial.SerialException as msg:\n            Log.error(\"{}\", msg)\n            self.close()\n        return self.socket is not None\n\n    def close(self):  # pylint: disable=arguments-differ\n        \"\"\"Close the underlying socket connection.\"\"\"\n        if self.socket:\n            self.socket.close()\n        self.socket = None\n\n    def _in_waiting(self):\n        \"\"\"Return _in_waiting.\"\"\"\n        in_waiting = \"in_waiting\" if hasattr(self.socket, \"in_waiting\") else \"inWaiting\"\n\n        if in_waiting == \"in_waiting\":\n            waitingbytes = getattr(self.socket, in_waiting)\n        else:\n            waitingbytes = getattr(self.socket, in_waiting)()\n        return waitingbytes\n\n    def send(self, request):\n        \"\"\"Send data on the underlying socket.\n\n        If receive buffer still holds some data then flush it.\n\n        Sleep if last send finished less than 3.5 character times ago.\n        \"\"\"\n        super().send(request)\n        if not self.socket:\n            raise ConnectionException(str(self))\n        if request:\n            try:\n                if waitingbytes := self._in_waiting():\n                    result = self.socket.read(waitingbytes)\n                    if self.state == ModbusTransactionState.RETRYING:\n                        Log.debug(\n                            \"Sending available data in recv buffer {}\", result, \":hex\"\n                        )\n                        return result\n                    Log.warning(\"Cleanup recv buffer before send: {}\", result, \":hex\")\n            except NotImplementedError:\n                pass\n            if self.state != ModbusTransactionState.SENDING:\n                Log.debug('New Transaction state \"SENDING\"')\n                self.state = ModbusTransactionState.SENDING\n            size = self.socket.write(request)\n            return size\n        return 0\n\n    def _wait_for_data(self):\n        \"\"\"Wait for data.\"\"\"\n        size = 0\n        more_data = False\n        if (\n            self.comm_params.timeout_connect is not None\n            and self.comm_params.timeout_connect\n        ):\n            condition = partial(\n                lambda start, timeout: (time.time() - start) <= timeout,\n                timeout=self.comm_params.timeout_connect,\n            )\n        else:\n            condition = partial(lambda dummy1, dummy2: True, dummy2=None)\n        start = time.time()\n        while condition(start):\n            available = self._in_waiting()\n            if (more_data and not available) or (more_data and available == size):\n                break\n            if available and available != size:\n                more_data = True\n                size = available\n            time.sleep(self._recv_interval)\n        return size\n\n    def recv(self, size):\n        \"\"\"Read data from the underlying descriptor.\"\"\"\n        super().recv(size)\n        if not self.socket:\n            raise ConnectionException(\n                self.__str__()  # pylint: disable=unnecessary-dunder-call\n            )\n        if size is None:\n            size = self._wait_for_data()\n        if size > self._in_waiting():\n            self._wait_for_data()\n        result = self.socket.read(size)\n        return result\n\n    def is_socket_open(self):\n        \"\"\"Check if socket is open.\"\"\"\n        if self.socket:\n            if hasattr(self.socket, \"is_open\"):\n                return self.socket.is_open\n            return self.socket.isOpen()\n        return False\n\n    def __str__(self):\n        \"\"\"Build a string representation of the connection.\"\"\"\n        return f\"ModbusSerialClient({self.framer} baud[{self.comm_params.baudrate}])\"\n\n    def __repr__(self):\n        \"\"\"Return string representation.\"\"\"\n        return (\n            f\"<{self.__class__.__name__} at {hex(id(self))} socket={self.socket}, \"\n            f\"framer={self.framer}, timeout={self.comm_params.timeout_connect}>\"\n        )\n", "path": "pymodbus/client/serial.py"}]}