syndesi 0.5.0__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- syndesi/adapters/adapter.py +32 -24
- syndesi/adapters/adapter_worker.py +28 -10
- syndesi/adapters/ip.py +7 -13
- syndesi/adapters/serialport.py +70 -12
- syndesi/adapters/stop_conditions.py +9 -16
- syndesi/adapters/timeout.py +1 -3
- syndesi/adapters/tracehub.py +229 -0
- syndesi/adapters/visa.py +96 -75
- syndesi/cli/shell.py +6 -5
- syndesi/component.py +2 -2
- syndesi/protocols/delimited.py +16 -28
- syndesi/protocols/modbus.py +0 -3
- syndesi/protocols/protocol.py +19 -5
- syndesi/protocols/raw.py +13 -8
- syndesi/scripts/syndesi_trace.py +711 -0
- syndesi/version.py +1 -1
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/METADATA +3 -3
- syndesi-0.5.1.dist-info/RECORD +43 -0
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/WHEEL +1 -1
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/entry_points.txt +1 -1
- syndesi-0.5.0.dist-info/RECORD +0 -41
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/top_level.txt +0 -0
syndesi/adapters/adapter.py
CHANGED
|
@@ -93,11 +93,12 @@ class Adapter(Component[bytes], AdapterWorker):
|
|
|
93
93
|
event_callback: Callable[[AdapterEvent], None] | None = None,
|
|
94
94
|
auto_open: bool = True,
|
|
95
95
|
) -> None:
|
|
96
|
-
|
|
97
|
-
self
|
|
96
|
+
Component.__init__(self, LoggerAlias.ADAPTER)
|
|
97
|
+
AdapterWorker.__init__(self, encoding)
|
|
98
|
+
|
|
98
99
|
self._alias = alias
|
|
99
100
|
|
|
100
|
-
self.
|
|
101
|
+
self._descriptor = descriptor
|
|
101
102
|
self.auto_open = auto_open
|
|
102
103
|
|
|
103
104
|
self._initial_event_callback = event_callback
|
|
@@ -141,22 +142,33 @@ class Adapter(Component[bytes], AdapterWorker):
|
|
|
141
142
|
# Serialize read/write/query ordering for async callers.
|
|
142
143
|
self._async_io_lock = asyncio.Lock()
|
|
143
144
|
|
|
144
|
-
self._logger.info(f"Setting up {self.
|
|
145
|
+
self._logger.info(f"Setting up {self._descriptor} adapter ")
|
|
145
146
|
self._update_descriptor()
|
|
146
147
|
self.set_stop_conditions(self._initial_stop_conditions)
|
|
147
148
|
self.set_timeout(self._initial_timeout)
|
|
148
149
|
self.set_event_callback(self._initial_event_callback)
|
|
149
150
|
|
|
150
|
-
if self.
|
|
151
|
+
if self._descriptor.is_initialized() and auto_open:
|
|
151
152
|
self.open()
|
|
152
153
|
|
|
153
154
|
weakref.finalize(self, self._cleanup)
|
|
154
155
|
|
|
156
|
+
def get_descriptor(self) -> Descriptor:
|
|
157
|
+
"""
|
|
158
|
+
Return the adapter's descriptor
|
|
159
|
+
|
|
160
|
+
Returns
|
|
161
|
+
-------
|
|
162
|
+
descriptor : Descriptor
|
|
163
|
+
"""
|
|
164
|
+
return self._descriptor
|
|
165
|
+
|
|
155
166
|
# ┌──────────────────────────┐
|
|
156
167
|
# │ Defaults / configuration │
|
|
157
168
|
# └──────────────────────────┘
|
|
158
169
|
|
|
159
170
|
def _stop(self) -> None:
|
|
171
|
+
super()._stop()
|
|
160
172
|
cmd = StopThreadCommand()
|
|
161
173
|
self._worker_send_command(cmd)
|
|
162
174
|
try:
|
|
@@ -165,7 +177,7 @@ class Adapter(Component[bytes], AdapterWorker):
|
|
|
165
177
|
pass
|
|
166
178
|
|
|
167
179
|
def _update_descriptor(self) -> None:
|
|
168
|
-
cmd = SetDescriptorCommand(self.
|
|
180
|
+
cmd = SetDescriptorCommand(self._descriptor)
|
|
169
181
|
self._worker_send_command(cmd)
|
|
170
182
|
cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
|
|
171
183
|
|
|
@@ -178,27 +190,19 @@ class Adapter(Component[bytes], AdapterWorker):
|
|
|
178
190
|
raise NotImplementedError
|
|
179
191
|
|
|
180
192
|
def __str__(self) -> str:
|
|
181
|
-
return str(self.
|
|
193
|
+
return str(self._descriptor)
|
|
182
194
|
|
|
183
195
|
def __repr__(self) -> str:
|
|
184
196
|
return self.__str__()
|
|
185
197
|
|
|
186
198
|
def _cleanup(self) -> None:
|
|
187
|
-
# Be defensive: finalizers can run at interpreter shutdown.
|
|
188
199
|
try:
|
|
189
200
|
if self.is_open():
|
|
190
201
|
self.close()
|
|
191
202
|
except AdapterError:
|
|
192
203
|
pass
|
|
193
|
-
|
|
194
204
|
self._stop()
|
|
195
205
|
|
|
196
|
-
try:
|
|
197
|
-
self._command_queue_r.close()
|
|
198
|
-
self._command_queue_w.close()
|
|
199
|
-
except AdapterError:
|
|
200
|
-
pass
|
|
201
|
-
|
|
202
206
|
# ┌────────────┐
|
|
203
207
|
# │ Public API │
|
|
204
208
|
# └────────────┘
|
|
@@ -344,9 +348,10 @@ class Adapter(Component[bytes], AdapterWorker):
|
|
|
344
348
|
scope: str = ReadScope.BUFFERED.value,
|
|
345
349
|
) -> AdapterFrame:
|
|
346
350
|
with self._sync_io_lock:
|
|
347
|
-
|
|
351
|
+
result = self._read_detailed_future(
|
|
348
352
|
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
349
353
|
).result(self.WorkerTimeout.READ.value)
|
|
354
|
+
return result
|
|
350
355
|
|
|
351
356
|
async def aread_detailed(
|
|
352
357
|
self,
|
|
@@ -433,10 +438,12 @@ class Adapter(Component[bytes], AdapterWorker):
|
|
|
433
438
|
scope: str = ReadScope.BUFFERED.value,
|
|
434
439
|
) -> AdapterFrame:
|
|
435
440
|
async with self._async_io_lock:
|
|
436
|
-
await self.
|
|
437
|
-
await self.
|
|
438
|
-
return await
|
|
439
|
-
|
|
441
|
+
await asyncio.wrap_future(self._flush_read_future())
|
|
442
|
+
await asyncio.wrap_future(self._write_future(payload))
|
|
443
|
+
return await asyncio.wrap_future(
|
|
444
|
+
self._read_detailed_future(
|
|
445
|
+
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
446
|
+
)
|
|
440
447
|
)
|
|
441
448
|
|
|
442
449
|
def query_detailed(
|
|
@@ -448,11 +455,12 @@ class Adapter(Component[bytes], AdapterWorker):
|
|
|
448
455
|
) -> AdapterFrame:
|
|
449
456
|
|
|
450
457
|
with self._sync_io_lock:
|
|
451
|
-
self.
|
|
452
|
-
self.
|
|
453
|
-
|
|
458
|
+
self._flush_read_future().result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
|
|
459
|
+
self._write_future(payload).result(self.WorkerTimeout.WRITE.value)
|
|
460
|
+
output = self._read_detailed_future(
|
|
454
461
|
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
455
|
-
)
|
|
462
|
+
).result(self.WorkerTimeout.READ.value)
|
|
463
|
+
return output
|
|
456
464
|
|
|
457
465
|
# ==== Other ====
|
|
458
466
|
|
|
@@ -38,6 +38,7 @@ from .stop_conditions import (
|
|
|
38
38
|
Total,
|
|
39
39
|
)
|
|
40
40
|
from .timeout import Timeout, TimeoutAction, any_to_timeout
|
|
41
|
+
from .tracehub import tracehub
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
def nmin(a: float | None, b: float | None) -> float | None:
|
|
@@ -71,22 +72,18 @@ class HasFileno(Protocol):
|
|
|
71
72
|
# │ Adapter events │
|
|
72
73
|
# └────────────────┘
|
|
73
74
|
|
|
74
|
-
|
|
75
75
|
class AdapterEvent(Event):
|
|
76
76
|
"""Adapter event"""
|
|
77
77
|
|
|
78
|
-
|
|
79
78
|
class AdapterDisconnectedEvent(AdapterEvent):
|
|
80
79
|
"""Adapter disconnected event"""
|
|
81
80
|
|
|
82
|
-
|
|
83
81
|
@dataclass
|
|
84
82
|
class AdapterFrameEvent(AdapterEvent):
|
|
85
83
|
"""Adapter frame event, emitted when new data is available"""
|
|
86
84
|
|
|
87
85
|
frame: AdapterFrame
|
|
88
86
|
|
|
89
|
-
|
|
90
87
|
@dataclass
|
|
91
88
|
class FirstFragmentEvent(AdapterEvent):
|
|
92
89
|
"""Adapter first fragment event"""
|
|
@@ -238,7 +235,8 @@ class AdapterWorker:
|
|
|
238
235
|
_FRAME_BUFFER_MAX = 256
|
|
239
236
|
_COMMAND_READY = b"\x00"
|
|
240
237
|
|
|
241
|
-
def __init__(self) -> None:
|
|
238
|
+
def __init__(self, encoding : str) -> None:
|
|
239
|
+
self.encoding = encoding
|
|
242
240
|
# Command queue (worker input)
|
|
243
241
|
self._command_queue_r, self._command_queue_w = socket.socketpair()
|
|
244
242
|
self._command_queue_r.setblocking(False)
|
|
@@ -270,12 +268,16 @@ class AdapterWorker:
|
|
|
270
268
|
self._first_fragment_timestamp: float | None = None
|
|
271
269
|
self._last_fragment_timestamp: float | None = None
|
|
272
270
|
self._last_write_timestamp: float | None = None
|
|
273
|
-
self._timeout_origin: StopConditionType
|
|
271
|
+
self._timeout_origin: StopConditionType = StopConditionType.TIMEOUT
|
|
274
272
|
self._next_stop_condition_timeout_timestamp: float | None = None
|
|
275
273
|
self._read_start_timestamp: float | None = None
|
|
276
274
|
|
|
277
275
|
self._event_callback: Callable[[AdapterEvent], None] | None = None
|
|
278
276
|
|
|
277
|
+
def _stop(self) -> None:
|
|
278
|
+
self._command_queue_r.close()
|
|
279
|
+
self._command_queue_w.close()
|
|
280
|
+
|
|
279
281
|
# ┌─────────────────┐
|
|
280
282
|
# │ Worker plumbing │
|
|
281
283
|
# └─────────────────┘
|
|
@@ -323,12 +325,18 @@ class AdapterWorker:
|
|
|
323
325
|
self._worker_open()
|
|
324
326
|
if not self._opened:
|
|
325
327
|
raise AdapterWriteError("Adapter not opened")
|
|
328
|
+
if self._worker_descriptor is not None:
|
|
329
|
+
tracehub.emit_write(str(self._worker_descriptor), data)
|
|
326
330
|
|
|
327
331
|
@abstractmethod
|
|
328
|
-
def _worker_open(self) -> None:
|
|
332
|
+
def _worker_open(self) -> None:
|
|
333
|
+
if self._worker_descriptor is not None:
|
|
334
|
+
tracehub.emit_open(str(self._worker_descriptor))
|
|
329
335
|
|
|
330
336
|
@abstractmethod
|
|
331
|
-
def _worker_close(self) -> None:
|
|
337
|
+
def _worker_close(self) -> None:
|
|
338
|
+
if self._worker_descriptor is not None:
|
|
339
|
+
tracehub.emit_close(str(self._worker_descriptor))
|
|
332
340
|
|
|
333
341
|
# ┌──────────────────────────┐
|
|
334
342
|
# │ Worker: command handling │
|
|
@@ -469,6 +477,9 @@ class AdapterWorker:
|
|
|
469
477
|
- always emit callback event (if configured)
|
|
470
478
|
"""
|
|
471
479
|
self._worker_emit_event(AdapterFrameEvent(frame))
|
|
480
|
+
if self._worker_descriptor is not None:
|
|
481
|
+
payload = frame.get_payload()
|
|
482
|
+
tracehub.emit_read(str(self._worker_descriptor), payload, frame.stop_condition_type)
|
|
472
483
|
|
|
473
484
|
pr = self._pending_read
|
|
474
485
|
if pr is not None:
|
|
@@ -516,7 +527,7 @@ class AdapterWorker:
|
|
|
516
527
|
AdapterFrame(
|
|
517
528
|
fragments=[Fragment(b"", time.time())],
|
|
518
529
|
stop_timestamp=None,
|
|
519
|
-
stop_condition_type=
|
|
530
|
+
stop_condition_type=StopConditionType.TIMEOUT,
|
|
520
531
|
previous_read_buffer_used=False,
|
|
521
532
|
response_delay=None,
|
|
522
533
|
)
|
|
@@ -640,7 +651,13 @@ class AdapterWorker:
|
|
|
640
651
|
|
|
641
652
|
self.fragments.append(kept)
|
|
642
653
|
|
|
654
|
+
# If there's no stop, break here
|
|
643
655
|
if stop_condition_type is None:
|
|
656
|
+
# Only upload emit a fragment event if there's no frame
|
|
657
|
+
if self._worker_descriptor is not None:
|
|
658
|
+
tracehub.emit_fragment(str(self._worker_descriptor), kept.data)
|
|
659
|
+
|
|
660
|
+
|
|
644
661
|
break
|
|
645
662
|
|
|
646
663
|
# frame complete
|
|
@@ -708,7 +725,7 @@ class AdapterWorker:
|
|
|
708
725
|
self._last_write_timestamp = None
|
|
709
726
|
self.fragments = []
|
|
710
727
|
self._next_stop_condition_timeout_timestamp = None
|
|
711
|
-
self._timeout_origin =
|
|
728
|
+
self._timeout_origin = StopConditionType.TIMEOUT
|
|
712
729
|
|
|
713
730
|
def _worker_next_timeout_timestamp(self) -> float | None:
|
|
714
731
|
stop_conditions = self._stop_conditions
|
|
@@ -803,6 +820,7 @@ class AdapterWorker:
|
|
|
803
820
|
|
|
804
821
|
# Timeout wakeup: decide what timed out
|
|
805
822
|
# 1) pending read response timeout (before qualifying first fragment)
|
|
823
|
+
print('Pending read')
|
|
806
824
|
if (
|
|
807
825
|
self._pending_read is not None
|
|
808
826
|
and not self._pending_read.first_fragment_seen
|
syndesi/adapters/ip.py
CHANGED
|
@@ -10,9 +10,6 @@ from collections.abc import Callable
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from enum import StrEnum
|
|
12
12
|
from types import EllipsisType
|
|
13
|
-
from typing import cast
|
|
14
|
-
|
|
15
|
-
import _socket
|
|
16
13
|
|
|
17
14
|
from syndesi.adapters.adapter_worker import AdapterEvent, HasFileno
|
|
18
15
|
from syndesi.adapters.stop_conditions import Continuation, StopCondition
|
|
@@ -142,7 +139,8 @@ class IP(Adapter):
|
|
|
142
139
|
port=port,
|
|
143
140
|
transport=IPDescriptor.Transport(transport.upper()),
|
|
144
141
|
)
|
|
145
|
-
self._socket: _socket.socket | None = None
|
|
142
|
+
#self._socket: _socket.socket | None = None
|
|
143
|
+
self._socket : socket.socket | None = None
|
|
146
144
|
|
|
147
145
|
super().__init__(
|
|
148
146
|
descriptor=descriptor,
|
|
@@ -194,21 +192,16 @@ class IP(Adapter):
|
|
|
194
192
|
)
|
|
195
193
|
|
|
196
194
|
def _worker_open(self) -> None:
|
|
195
|
+
super()._worker_open()
|
|
197
196
|
self._worker_check_descriptor()
|
|
198
197
|
|
|
199
198
|
# Create the socket instance
|
|
200
199
|
if self._worker_descriptor.transport == IPDescriptor.Transport.TCP:
|
|
201
|
-
self._socket =
|
|
202
|
-
_socket.socket,
|
|
203
|
-
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
|
|
204
|
-
)
|
|
200
|
+
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
205
201
|
elif self._worker_descriptor.transport == IPDescriptor.Transport.UDP:
|
|
206
|
-
self._socket =
|
|
207
|
-
_socket.socket, socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
208
|
-
)
|
|
202
|
+
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
209
203
|
else:
|
|
210
204
|
raise AdapterOpenError("Invalid transport protocol")
|
|
211
|
-
|
|
212
205
|
try:
|
|
213
206
|
self._socket.settimeout(self.WorkerTimeout.OPEN.value)
|
|
214
207
|
self._socket.connect(
|
|
@@ -224,9 +217,10 @@ class IP(Adapter):
|
|
|
224
217
|
self._logger.info(f"IP Adapter {self._worker_descriptor} opened")
|
|
225
218
|
|
|
226
219
|
def _worker_close(self) -> None:
|
|
220
|
+
super()._worker_close()
|
|
227
221
|
if self._socket is not None:
|
|
228
222
|
try:
|
|
229
|
-
self._socket.shutdown(
|
|
223
|
+
self._socket.shutdown(socket.SHUT_RDWR)
|
|
230
224
|
self._socket.close()
|
|
231
225
|
except OSError:
|
|
232
226
|
pass
|
syndesi/adapters/serialport.py
CHANGED
|
@@ -8,8 +8,10 @@ the OS layers (COMx, /dev/ttyUSBx or /dev/ttyACMx)
|
|
|
8
8
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import threading
|
|
11
12
|
from collections.abc import Callable
|
|
12
13
|
from dataclasses import dataclass
|
|
14
|
+
from enum import StrEnum
|
|
13
15
|
from types import EllipsisType
|
|
14
16
|
|
|
15
17
|
import serial
|
|
@@ -25,6 +27,18 @@ from .stop_conditions import Continuation, Fragment, StopCondition
|
|
|
25
27
|
from .timeout import Timeout
|
|
26
28
|
|
|
27
29
|
|
|
30
|
+
class Parity(StrEnum):
|
|
31
|
+
"""
|
|
32
|
+
SerialPort parity setting, copied from pyserial
|
|
33
|
+
"""
|
|
34
|
+
NONE = "N"
|
|
35
|
+
EVEN = "E"
|
|
36
|
+
ODD = "O"
|
|
37
|
+
MARK = "M"
|
|
38
|
+
SPACE = "S"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# pylint: disable=too-many-instance-attributes
|
|
28
42
|
@dataclass
|
|
29
43
|
class SerialPortDescriptor(Descriptor):
|
|
30
44
|
"""
|
|
@@ -34,6 +48,12 @@ class SerialPortDescriptor(Descriptor):
|
|
|
34
48
|
DETECTION_PATTERN = r"(COM\d+|/dev[/\w\d]+):\d+"
|
|
35
49
|
port: str
|
|
36
50
|
baudrate: int | None = None
|
|
51
|
+
bytesize: int = 8
|
|
52
|
+
stopbits: int = 1
|
|
53
|
+
parity: str = Parity.NONE.value
|
|
54
|
+
rts_cts: bool = False
|
|
55
|
+
dsr_dtr: bool = False
|
|
56
|
+
xon_xoff: bool = False
|
|
37
57
|
|
|
38
58
|
@staticmethod
|
|
39
59
|
def from_string(string: str) -> "SerialPortDescriptor":
|
|
@@ -50,7 +70,7 @@ class SerialPortDescriptor(Descriptor):
|
|
|
50
70
|
----------
|
|
51
71
|
baudrate : int
|
|
52
72
|
"""
|
|
53
|
-
if self.baudrate is
|
|
73
|
+
if self.baudrate is None:
|
|
54
74
|
self.baudrate = baudrate
|
|
55
75
|
return True
|
|
56
76
|
|
|
@@ -75,6 +95,9 @@ class SerialPort(Adapter):
|
|
|
75
95
|
Baudrate
|
|
76
96
|
"""
|
|
77
97
|
|
|
98
|
+
_open_ports: set[str] = set()
|
|
99
|
+
_open_ports_lock = threading.Lock()
|
|
100
|
+
|
|
78
101
|
def __init__(
|
|
79
102
|
self,
|
|
80
103
|
port: str,
|
|
@@ -83,14 +106,29 @@ class SerialPort(Adapter):
|
|
|
83
106
|
timeout: Timeout | NumberLike | None | EllipsisType = ...,
|
|
84
107
|
stop_conditions: StopCondition | list[StopCondition] | EllipsisType = ...,
|
|
85
108
|
alias: str = "",
|
|
86
|
-
|
|
109
|
+
bytesize: int = 8,
|
|
110
|
+
stopbits: int = 1,
|
|
111
|
+
parity: str = Parity.NONE.value,
|
|
112
|
+
rts_cts: bool = False,
|
|
113
|
+
xon_xoff: bool = False,
|
|
114
|
+
dsr_dtr: bool = False,
|
|
87
115
|
event_callback: Callable[[AdapterEvent], None] | None = None,
|
|
88
116
|
auto_open: bool = True,
|
|
89
117
|
) -> None:
|
|
90
118
|
"""
|
|
91
119
|
Instanciate new SerialPort adapter
|
|
92
120
|
"""
|
|
93
|
-
|
|
121
|
+
self._port: serial.Serial | None = None
|
|
122
|
+
descriptor = SerialPortDescriptor(
|
|
123
|
+
port=port,
|
|
124
|
+
baudrate=baudrate,
|
|
125
|
+
bytesize=bytesize,
|
|
126
|
+
stopbits=stopbits,
|
|
127
|
+
parity=parity,
|
|
128
|
+
rts_cts=rts_cts,
|
|
129
|
+
dsr_dtr=dsr_dtr,
|
|
130
|
+
xon_xoff=xon_xoff,
|
|
131
|
+
)
|
|
94
132
|
super().__init__(
|
|
95
133
|
descriptor=descriptor,
|
|
96
134
|
timeout=timeout,
|
|
@@ -107,11 +145,11 @@ class SerialPort(Adapter):
|
|
|
107
145
|
timeout={timeout} and stop_conditions={self._stop_conditions}"
|
|
108
146
|
)
|
|
109
147
|
|
|
110
|
-
self.
|
|
111
|
-
|
|
112
|
-
self.
|
|
113
|
-
|
|
114
|
-
self.
|
|
148
|
+
# self._bytesize = bytesize
|
|
149
|
+
# self._stopbits = stopbits
|
|
150
|
+
# self._parity = Parity(parity)
|
|
151
|
+
# self._xonxoff = xon_xoff
|
|
152
|
+
# self._dsrdtr = dsr_dtr
|
|
115
153
|
|
|
116
154
|
def _default_timeout(self) -> Timeout:
|
|
117
155
|
return Timeout(response=2, action="error")
|
|
@@ -120,6 +158,7 @@ class SerialPort(Adapter):
|
|
|
120
158
|
return [Continuation(0.1)]
|
|
121
159
|
|
|
122
160
|
def _worker_open(self) -> None:
|
|
161
|
+
super()._worker_open()
|
|
123
162
|
self._worker_check_descriptor()
|
|
124
163
|
|
|
125
164
|
if self._worker_descriptor.baudrate is None:
|
|
@@ -130,13 +169,26 @@ class SerialPort(Adapter):
|
|
|
130
169
|
if self._port is not None:
|
|
131
170
|
self.close()
|
|
132
171
|
|
|
172
|
+
port_name = self._worker_descriptor.port
|
|
173
|
+
with self._open_ports_lock:
|
|
174
|
+
if port_name in self._open_ports:
|
|
175
|
+
raise AdapterOpenError(f"Port '{port_name}' is already in use")
|
|
176
|
+
self._open_ports.add(port_name)
|
|
177
|
+
|
|
133
178
|
try:
|
|
134
179
|
self._port = serial.Serial(
|
|
135
180
|
port=self._worker_descriptor.port,
|
|
136
181
|
baudrate=self._worker_descriptor.baudrate,
|
|
137
|
-
rtscts=self.
|
|
182
|
+
rtscts=self._worker_descriptor.rts_cts,
|
|
183
|
+
bytesize=self._worker_descriptor.bytesize,
|
|
184
|
+
parity=self._worker_descriptor.parity,
|
|
185
|
+
stopbits=self._worker_descriptor.stopbits,
|
|
186
|
+
xonxoff=self._worker_descriptor.xon_xoff,
|
|
187
|
+
dsrdtr=self._worker_descriptor.dsr_dtr
|
|
138
188
|
)
|
|
139
189
|
except serial.SerialException as e:
|
|
190
|
+
with self._open_ports_lock:
|
|
191
|
+
self._open_ports.discard(port_name)
|
|
140
192
|
if "No such file" in str(e):
|
|
141
193
|
raise AdapterOpenError(
|
|
142
194
|
f"Port '{self._worker_descriptor.port}' was not found"
|
|
@@ -146,13 +198,19 @@ class SerialPort(Adapter):
|
|
|
146
198
|
if self._port.isOpen(): # type: ignore
|
|
147
199
|
self._logger.info(f"Adapter {self._worker_descriptor} opened")
|
|
148
200
|
else:
|
|
201
|
+
with self._open_ports_lock:
|
|
202
|
+
self._open_ports.discard(port_name)
|
|
149
203
|
self._logger.error(f"Failed to open adapter {self._worker_descriptor}")
|
|
150
204
|
raise AdapterOpenError("Unknown error")
|
|
151
205
|
|
|
152
206
|
def _worker_close(self) -> None:
|
|
207
|
+
super()._worker_close()
|
|
153
208
|
if self._port is not None:
|
|
154
209
|
self._port.close()
|
|
155
210
|
self._logger.info(f"Adapter {self._worker_descriptor} closed")
|
|
211
|
+
self._port = None
|
|
212
|
+
with self._open_ports_lock:
|
|
213
|
+
self._open_ports.discard(self._worker_descriptor.port)
|
|
156
214
|
|
|
157
215
|
async def aflush_read(self) -> None:
|
|
158
216
|
await super().aflush_read()
|
|
@@ -173,7 +231,8 @@ class SerialPort(Adapter):
|
|
|
173
231
|
self.open()
|
|
174
232
|
|
|
175
233
|
def _worker_write(self, data: bytes) -> None:
|
|
176
|
-
|
|
234
|
+
super()._worker_write(data)
|
|
235
|
+
if self._worker_descriptor.rts_cts: # Experimental
|
|
177
236
|
self._port.setRTS(True) # type: ignore
|
|
178
237
|
if self._port is not None:
|
|
179
238
|
try:
|
|
@@ -188,10 +247,9 @@ class SerialPort(Adapter):
|
|
|
188
247
|
try:
|
|
189
248
|
data = self._port.read_all()
|
|
190
249
|
except (OSError, PortNotOpenError):
|
|
191
|
-
self._logger.debug('Port error -> b""')
|
|
192
250
|
data = None
|
|
193
251
|
|
|
194
|
-
if data is None or data
|
|
252
|
+
if data is None or data == b"":
|
|
195
253
|
raise AdapterReadError(
|
|
196
254
|
f"Error while reading from {self._worker_descriptor}"
|
|
197
255
|
)
|
|
@@ -39,12 +39,12 @@ class StopConditionType(Enum):
|
|
|
39
39
|
"""
|
|
40
40
|
Stop-condition type
|
|
41
41
|
"""
|
|
42
|
-
|
|
43
42
|
TERMINATION = "termination"
|
|
44
43
|
LENGTH = "length"
|
|
45
44
|
CONTINUATION = "continuation"
|
|
46
45
|
TOTAL = "total"
|
|
47
46
|
FRAGMENT = "fragment"
|
|
47
|
+
TIMEOUT = "timeout"
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
class StopCondition:
|
|
@@ -98,21 +98,13 @@ class Termination(StopCondition):
|
|
|
98
98
|
self._sequence = sequence.encode("utf-8")
|
|
99
99
|
else:
|
|
100
100
|
self._sequence = sequence
|
|
101
|
-
self._sequence_found_length = 0
|
|
102
|
-
|
|
103
|
-
# TYPE = StopConditionType.TERMINATION
|
|
104
101
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# self.sequence = sequence.encode("utf-8")
|
|
112
|
-
# elif isinstance(sequence, bytes):
|
|
113
|
-
# self.sequence = sequence
|
|
114
|
-
# else:
|
|
115
|
-
# raise ValueError(f"Invalid termination sequence type : {type(sequence)}")
|
|
102
|
+
if self._sequence == b"":
|
|
103
|
+
raise ValueError(
|
|
104
|
+
"Empty termination isn't allowed. If you wish "
|
|
105
|
+
"to stop on any received data, use Datagram stop-conditions instead"
|
|
106
|
+
)
|
|
107
|
+
self._sequence_found_length = 0
|
|
116
108
|
|
|
117
109
|
def __str__(self) -> str:
|
|
118
110
|
return f"Termination({repr(self._sequence)})"
|
|
@@ -202,7 +194,6 @@ class Length(StopCondition):
|
|
|
202
194
|
deferred_fragment = raw_fragment[remaining_bytes:]
|
|
203
195
|
self._counter += len(kept_fragment.data)
|
|
204
196
|
remaining_bytes = self._n - self._counter
|
|
205
|
-
# TODO : remaining_bytes <= 0 ? Alongside above TODO maybe
|
|
206
197
|
return remaining_bytes == 0, kept_fragment, deferred_fragment, None
|
|
207
198
|
|
|
208
199
|
|
|
@@ -250,6 +241,8 @@ class Continuation(StopCondition):
|
|
|
250
241
|
stop = False
|
|
251
242
|
next_event_timeout = None
|
|
252
243
|
|
|
244
|
+
self._last_fragment = raw_fragment.timestamp
|
|
245
|
+
|
|
253
246
|
return stop, kept, deferred, next_event_timeout
|
|
254
247
|
|
|
255
248
|
def type(self) -> StopConditionType:
|
syndesi/adapters/timeout.py
CHANGED
|
@@ -17,12 +17,10 @@ class TimeoutAction(Enum):
|
|
|
17
17
|
"""
|
|
18
18
|
Action on timeout expiration
|
|
19
19
|
"""
|
|
20
|
-
|
|
21
20
|
ERROR = "error"
|
|
22
21
|
RETURN_EMPTY = "return_empty"
|
|
23
22
|
RETURN_NONE = "return_none"
|
|
24
23
|
|
|
25
|
-
|
|
26
24
|
class Timeout:
|
|
27
25
|
"""
|
|
28
26
|
This class holds timeout information
|
|
@@ -60,7 +58,7 @@ class Timeout:
|
|
|
60
58
|
self._response: EllipsisType | NumberLike | None = response
|
|
61
59
|
|
|
62
60
|
def __str__(self) -> str:
|
|
63
|
-
if self._response is
|
|
61
|
+
if self._response is ... or self._response is None:
|
|
64
62
|
r = "..."
|
|
65
63
|
else:
|
|
66
64
|
r = f"{self._response:.3f}"
|