Skip to content

Commit 39177d7

Browse files
authored
Reduce transport_serial (#1807)
1 parent f06718d commit 39177d7

File tree

2 files changed

+83
-137
lines changed

2 files changed

+83
-137
lines changed

pymodbus/transport/transport_serial.py

Lines changed: 82 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,62 @@ def __init__(self, loop, protocol, *args, **kwargs):
1818
self.async_loop = loop
1919
self._protocol: asyncio.BaseProtocol = protocol
2020
self.sync_serial = serial.serial_for_url(*args, **kwargs)
21-
self._closing = False
2221
self._write_buffer = []
23-
self.set_write_buffer_limits()
2422
self._has_reader = False
2523
self._has_writer = False
2624
self._poll_wait_time = 0.0005
27-
28-
# Asynchronous I/O requires non-blocking devices
2925
self.sync_serial.timeout = 0
3026
self.sync_serial.write_timeout = 0
31-
loop.call_soon(protocol.connection_made, self)
32-
loop.call_soon(self._ensure_reader)
27+
28+
def setup(self):
29+
"""Prepare to read/write"""
30+
self.async_loop.call_soon(self._protocol.connection_made, self)
31+
if os.name == "nt":
32+
self._has_reader = self.async_loop.call_later(
33+
self._poll_wait_time, self._poll_read
34+
)
35+
else:
36+
self.async_loop.add_reader(self.sync_serial.fileno(), self._read_ready)
37+
self._has_reader = True
38+
39+
def close(self, exc=None):
40+
"""Close the transport gracefully."""
41+
if not self.sync_serial:
42+
return
43+
with contextlib.suppress(Exception):
44+
self.sync_serial.flush()
45+
46+
if self._has_reader:
47+
if os.name == "nt":
48+
self._has_reader.cancel()
49+
else:
50+
self.async_loop.remove_reader(self.sync_serial.fileno())
51+
self._has_reader = False
52+
self.flush()
53+
self.sync_serial.close()
54+
self.sync_serial = None
55+
with contextlib.suppress(Exception):
56+
self._protocol.connection_lost(exc)
57+
58+
def write(self, data):
59+
"""Write some data to the transport."""
60+
self._write_buffer.append(data)
61+
if not self._has_writer:
62+
if os.name == "nt":
63+
self._has_writer = self.async_loop.call_soon(self._poll_write)
64+
else:
65+
self.async_loop.add_writer(self.sync_serial.fileno(), self._write_ready)
66+
self._has_writer = True
67+
68+
def flush(self):
69+
"""Clear output buffer and stops any more data being written"""
70+
if self._has_writer:
71+
if os.name == "nt":
72+
self._has_writer.cancel()
73+
else:
74+
self.async_loop.remove_writer(self.sync_serial.fileno())
75+
self._has_writer = False
76+
self._write_buffer.clear()
3377

3478
# ------------------------------------------------
3579
# Dummy methods needed to please asyncio.Transport.
@@ -75,160 +119,61 @@ def pause_reading(self):
75119
def resume_reading(self):
76120
"""Resume receiver."""
77121

78-
# ------------------------------------------------
79-
80122
def is_closing(self):
81123
"""Return True if the transport is closing or closed."""
82-
return self._closing
124+
return False
83125

84-
def close(self):
85-
"""Close the transport gracefully."""
86-
if self._closing:
87-
return
88-
self._closing = True
89-
self._remove_reader()
90-
self._remove_writer()
91-
self.async_loop.call_soon(self._call_connection_lost, None)
126+
def abort(self):
127+
"""Close the transport immediately."""
128+
self.close()
129+
130+
# ------------------------------------------------
92131

93132
def _read_ready(self):
94133
"""Test if there are data waiting."""
95134
try:
96-
data = self.sync_serial.read(1024)
97-
except serial.SerialException as exc:
98-
self.async_loop.call_soon(self._call_connection_lost, exc)
99-
self.close()
100-
else:
101-
if data:
135+
if data := self.sync_serial.read(1024):
102136
self._protocol.data_received(data)
103-
104-
def write(self, data):
105-
"""Write some data to the transport."""
106-
if self._closing:
107-
return
108-
109-
self._write_buffer.append(data)
110-
self._ensure_writer()
111-
112-
def abort(self):
113-
"""Close the transport immediately."""
114-
self.close()
115-
116-
def flush(self):
117-
"""Clear output buffer and stops any more data being written"""
118-
self._remove_writer()
119-
self._write_buffer.clear()
137+
except serial.SerialException as exc:
138+
self.close(exc=exc)
120139

121140
def _write_ready(self):
122141
"""Asynchronously write buffered data."""
123142
data = b"".join(self._write_buffer)
124-
assert data, "Write buffer should not be empty"
125-
126-
self._write_buffer.clear()
127-
128143
try:
129-
nlen = self.sync_serial.write(data)
144+
if nlen := self.sync_serial.write(data) < len(data):
145+
self._write_buffer = data[nlen:]
146+
return True
147+
self.flush()
130148
except (BlockingIOError, InterruptedError):
131-
self._write_buffer.append(data)
149+
return True
132150
except serial.SerialException as exc:
133-
self.async_loop.call_soon(self._call_connection_lost, exc)
134-
self.abort()
135-
else:
136-
if nlen == len(data):
137-
assert not self.get_write_buffer_size()
138-
self._remove_writer()
139-
if self._closing and not self.get_write_buffer_size():
140-
self.close()
141-
return
142-
143-
assert 0 <= nlen < len(data)
144-
data = data[nlen:]
145-
self._write_buffer.append(data) # Try again later
146-
assert self._has_writer
147-
148-
if os.name == "nt":
149-
150-
def _poll_read(self):
151-
if self._has_reader and not self._closing:
152-
try:
153-
self._has_reader = self.async_loop.call_later(
154-
self._poll_wait_time, self._poll_read
155-
)
156-
if self.sync_serial.in_waiting:
157-
self._read_ready()
158-
except serial.SerialException as exc:
159-
self.async_loop.call_soon(self._call_connection_lost, exc)
160-
self.abort()
161-
162-
def _ensure_reader(self):
163-
if not self._has_reader and not self._closing:
151+
self.close(exc=exc)
152+
return False
153+
154+
def _poll_read(self):
155+
if self._has_reader:
156+
try:
164157
self._has_reader = self.async_loop.call_later(
165158
self._poll_wait_time, self._poll_read
166159
)
160+
if self.sync_serial.in_waiting:
161+
self._read_ready()
162+
except serial.SerialException as exc:
163+
self.close(exc=exc)
167164

168-
def _remove_reader(self):
169-
if self._has_reader:
170-
self._has_reader.cancel()
171-
self._has_reader = False
172-
173-
def _poll_write(self):
174-
if self._has_writer and not self._closing:
175-
self._has_writer = self.async_loop.call_later(
176-
self._poll_wait_time, self._poll_write
177-
)
178-
self._write_ready()
179-
180-
def _ensure_writer(self):
181-
if not self._has_writer and not self._closing:
182-
self._has_writer = self.async_loop.call_soon(self._poll_write)
183-
184-
def _remove_writer(self):
185-
if self._has_writer:
186-
self._has_writer.cancel()
187-
self._has_writer = False
188-
189-
else:
190-
191-
def _ensure_reader(self):
192-
if (not self._has_reader) and (not self._closing):
193-
self.async_loop.add_reader(self.sync_serial.fileno(), self._read_ready)
194-
self._has_reader = True
195-
196-
def _remove_reader(self):
197-
if self._has_reader:
198-
self.async_loop.remove_reader(self.sync_serial.fileno())
199-
self._has_reader = False
200-
201-
def _ensure_writer(self):
202-
if (not self._has_writer) and (not self._closing):
203-
self.async_loop.add_writer(self.sync_serial.fileno(), self._write_ready)
204-
self._has_writer = True
205-
206-
def _remove_writer(self):
207-
if self._has_writer:
208-
self.async_loop.remove_writer(self.sync_serial.fileno())
209-
self._has_writer = False
210-
211-
def _call_connection_lost(self, exc):
212-
"""Close the connection."""
213-
assert self._closing
214-
assert not self._has_writer
215-
assert not self._has_reader
216-
if self.sync_serial:
217-
with contextlib.suppress(Exception):
218-
self.sync_serial.flush()
219-
220-
self.sync_serial.close()
221-
self.sync_serial = None
222-
if self._protocol:
223-
with contextlib.suppress(Exception):
224-
self._protocol.connection_lost(exc)
225-
226-
self._write_buffer.clear()
227-
self._write_buffer.clear()
165+
def _poll_write(self):
166+
if not self._has_writer:
167+
return
168+
if self._write_ready():
169+
self._has_writer = self.async_loop.call_later(
170+
self._poll_wait_time, self._poll_write
171+
)
228172

229173

230174
async def create_serial_connection(loop, protocol_factory, *args, **kwargs):
231175
"""Create a connection to a new serial port instance."""
232176
protocol = protocol_factory()
233177
transport = SerialTransport(loop, protocol, *args, **kwargs)
178+
loop.call_soon(transport.setup)
234179
return transport, protocol

test/sub_transport/test_basic.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ async def test_external_methods(self):
323323
comm.write(b"abcd")
324324
comm.flush()
325325
comm.close()
326+
comm = SerialTransport(mock.MagicMock(), mock.Mock(), "dummy")
326327
comm.abort()
327328
assert await create_serial_connection(
328329
asyncio.get_running_loop(), mock.Mock, url="dummy"

0 commit comments

Comments
 (0)