syndesi 0.4.2__tar.gz → 0.5.0__tar.gz
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-0.4.2/syndesi.egg-info → syndesi-0.5.0}/PKG-INFO +1 -1
- {syndesi-0.4.2 → syndesi-0.5.0}/pyproject.toml +7 -1
- syndesi-0.5.0/syndesi/__init__.py +30 -0
- syndesi-0.5.0/syndesi/adapters/adapter.py +470 -0
- syndesi-0.5.0/syndesi/adapters/adapter_worker.py +820 -0
- syndesi-0.5.0/syndesi/adapters/auto.py +83 -0
- syndesi-0.5.0/syndesi/adapters/descriptors.py +38 -0
- syndesi-0.5.0/syndesi/adapters/ip.py +242 -0
- syndesi-0.5.0/syndesi/adapters/serialport.py +209 -0
- syndesi-0.5.0/syndesi/adapters/stop_conditions.py +354 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/adapters/timeout.py +58 -21
- syndesi-0.5.0/syndesi/adapters/visa.py +269 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/cli/console.py +51 -16
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/cli/shell.py +95 -47
- syndesi-0.5.0/syndesi/cli/terminal_tools.py +14 -0
- syndesi-0.5.0/syndesi/component.py +315 -0
- syndesi-0.5.0/syndesi/protocols/delimited.py +168 -0
- syndesi-0.5.0/syndesi/protocols/modbus.py +3101 -0
- syndesi-0.5.0/syndesi/protocols/protocol.py +233 -0
- syndesi-0.5.0/syndesi/protocols/raw.py +73 -0
- syndesi-0.5.0/syndesi/protocols/scpi.py +107 -0
- syndesi-0.5.0/syndesi/remote/remote.py +188 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/scripts/syndesi.py +12 -2
- syndesi-0.5.0/syndesi/tools/errors.py +68 -0
- syndesi-0.5.0/syndesi/tools/log_settings.py +30 -0
- syndesi-0.4.2/syndesi/tools/log.py → syndesi-0.5.0/syndesi/tools/logmanager.py +24 -13
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/tools/types.py +9 -7
- syndesi-0.5.0/syndesi/version.py +7 -0
- {syndesi-0.4.2 → syndesi-0.5.0/syndesi.egg-info}/PKG-INFO +1 -1
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi.egg-info/SOURCES.txt +6 -25
- syndesi-0.4.2/syndesi/__init__.py +0 -10
- syndesi-0.4.2/syndesi/adapters/adapter.py +0 -627
- syndesi-0.4.2/syndesi/adapters/auto.py +0 -50
- syndesi-0.4.2/syndesi/adapters/backend/adapter_backend.py +0 -438
- syndesi-0.4.2/syndesi/adapters/backend/adapter_manager.py +0 -48
- syndesi-0.4.2/syndesi/adapters/backend/adapter_session.py +0 -346
- syndesi-0.4.2/syndesi/adapters/backend/backend.py +0 -438
- syndesi-0.4.2/syndesi/adapters/backend/backend_status.py +0 -0
- syndesi-0.4.2/syndesi/adapters/backend/backend_tools.py +0 -66
- syndesi-0.4.2/syndesi/adapters/backend/descriptors.py +0 -153
- syndesi-0.4.2/syndesi/adapters/backend/ip_backend.py +0 -149
- syndesi-0.4.2/syndesi/adapters/backend/serialport_backend.py +0 -241
- syndesi-0.4.2/syndesi/adapters/backend/stop_condition_backend.py +0 -219
- syndesi-0.4.2/syndesi/adapters/backend/timed_queue.py +0 -39
- syndesi-0.4.2/syndesi/adapters/backend/timeout.py +0 -252
- syndesi-0.4.2/syndesi/adapters/backend/visa_backend.py +0 -197
- syndesi-0.4.2/syndesi/adapters/ip.py +0 -110
- syndesi-0.4.2/syndesi/adapters/ip_server.py +0 -102
- syndesi-0.4.2/syndesi/adapters/serialport.py +0 -80
- syndesi-0.4.2/syndesi/adapters/stop_condition.py +0 -90
- syndesi-0.4.2/syndesi/adapters/visa.py +0 -44
- syndesi-0.4.2/syndesi/cli/backend_console.py +0 -96
- syndesi-0.4.2/syndesi/cli/backend_status.py +0 -274
- syndesi-0.4.2/syndesi/cli/backend_wrapper.py +0 -61
- syndesi-0.4.2/syndesi/cli/terminal_tools.py +0 -14
- syndesi-0.4.2/syndesi/protocols/delimited.py +0 -183
- syndesi-0.4.2/syndesi/protocols/modbus.py +0 -1601
- syndesi-0.4.2/syndesi/protocols/protocol.py +0 -80
- syndesi-0.4.2/syndesi/protocols/raw.py +0 -90
- syndesi-0.4.2/syndesi/protocols/scpi.py +0 -144
- syndesi-0.4.2/syndesi/scripts/syndesi_backend.py +0 -37
- syndesi-0.4.2/syndesi/tools/__init__.py +0 -0
- syndesi-0.4.2/syndesi/tools/backend_api.py +0 -175
- syndesi-0.4.2/syndesi/tools/backend_logger.py +0 -64
- syndesi-0.4.2/syndesi/tools/errors.py +0 -50
- syndesi-0.4.2/syndesi/tools/exceptions.py +0 -16
- syndesi-0.4.2/syndesi/tools/internal.py +0 -0
- syndesi-0.4.2/syndesi/tools/log_settings.py +0 -17
- syndesi-0.4.2/syndesi/version.py +0 -3
- {syndesi-0.4.2 → syndesi-0.5.0}/LICENSE +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/README.md +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/setup.cfg +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/__main__.py +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/adapters/__init__.py +0 -0
- {syndesi-0.4.2/syndesi/adapters/backend → syndesi-0.5.0/syndesi/cli}/__init__.py +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/cli/shell_tools.py +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/cli/terminal.py +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi/cli/terminal_apps.py +0 -0
- {syndesi-0.4.2/syndesi/cli → syndesi-0.5.0/syndesi/protocols}/__init__.py +0 -0
- {syndesi-0.4.2/syndesi/protocols → syndesi-0.5.0/syndesi/scripts}/__init__.py +0 -0
- {syndesi-0.4.2/syndesi/scripts → syndesi-0.5.0/syndesi/tools}/__init__.py +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi.egg-info/dependency_links.txt +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi.egg-info/entry_points.txt +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi.egg-info/requires.txt +0 -0
- {syndesi-0.4.2 → syndesi-0.5.0}/syndesi.egg-info/top_level.txt +0 -0
|
@@ -50,7 +50,7 @@ ignore = ["E501"]
|
|
|
50
50
|
[tool.ruff.format]
|
|
51
51
|
|
|
52
52
|
[tool.ruff.lint.pydocstyle]
|
|
53
|
-
convention = "numpy"
|
|
53
|
+
convention = "numpy"
|
|
54
54
|
|
|
55
55
|
[tool.isort]
|
|
56
56
|
profile = "black"
|
|
@@ -69,3 +69,9 @@ exclude = ["^tests/fixtures/"]
|
|
|
69
69
|
skips = ["B101"]
|
|
70
70
|
exclude = ["tests"]
|
|
71
71
|
|
|
72
|
+
[tool.pylint]
|
|
73
|
+
# Disable too-many-arguments, this is the case for adapters/protocols/drivers
|
|
74
|
+
# All arguments are necessary and it would not make sense to group them / move them
|
|
75
|
+
# too-many-positional-arguments is kept however because it makes sense
|
|
76
|
+
# Also disable W1203 to allow f-strings in logging lines
|
|
77
|
+
disable = ["R0913", "W1203", "W0511", "R0903"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Syndesi module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .adapters.ip import IP
|
|
6
|
+
from .adapters.serialport import SerialPort
|
|
7
|
+
from .adapters.stop_conditions import Continuation, Length, Termination, Total
|
|
8
|
+
from .adapters.timeout import Timeout
|
|
9
|
+
from .adapters.visa import Visa
|
|
10
|
+
from .protocols.delimited import Delimited
|
|
11
|
+
from .protocols.modbus import Modbus
|
|
12
|
+
from .protocols.raw import Raw
|
|
13
|
+
from .protocols.scpi import SCPI
|
|
14
|
+
from .tools.logmanager import log
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"IP",
|
|
18
|
+
"SerialPort",
|
|
19
|
+
"Visa",
|
|
20
|
+
"Delimited",
|
|
21
|
+
"Modbus",
|
|
22
|
+
"Raw",
|
|
23
|
+
"SCPI",
|
|
24
|
+
"log",
|
|
25
|
+
"Timeout",
|
|
26
|
+
"Continuation",
|
|
27
|
+
"Length",
|
|
28
|
+
"Termination",
|
|
29
|
+
"Total",
|
|
30
|
+
]
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# File : adapter.py
|
|
2
|
+
# Author : Sébastien Deriaz
|
|
3
|
+
# License : GPL
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
Adapters provide a common abstraction for the media layers (physical + data link + network)
|
|
7
|
+
|
|
8
|
+
The user calls methods of the Adapter class synchronously.
|
|
9
|
+
|
|
10
|
+
An adapter is meant to work with bytes objects but it can accept strings.
|
|
11
|
+
Strings will automatically be converted to bytes using utf-8 encoding
|
|
12
|
+
|
|
13
|
+
Each adapter contains a worker thread that monitors the low-level communication layers.
|
|
14
|
+
This approach allows for precise time management (when each fragment is sent/received) and allows
|
|
15
|
+
for asynchronous events (fragment received).
|
|
16
|
+
|
|
17
|
+
Async facade:
|
|
18
|
+
- aopen/awrite/aread/aread_detailed simply await the SAME underlying worker-thread commands
|
|
19
|
+
using asyncio.wrap_future (no extra threads are spawned).
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
# NOTE:
|
|
23
|
+
# This version removes the "worker publishes events into a queue that read_detailed consumes".
|
|
24
|
+
# Instead:
|
|
25
|
+
# - The worker continuously assembles AdapterFrame from fragments (as before).
|
|
26
|
+
# - A read_detailed command registers a "pending read" inside the worker.
|
|
27
|
+
# - When a frame completes, the worker either:
|
|
28
|
+
# * completes the pending read future, OR
|
|
29
|
+
# * buffers the frame for later buffered reads, and optionally calls the callback.
|
|
30
|
+
#
|
|
31
|
+
# This avoids having a sync queue AND an async queue, and makes async wrappers trivial.
|
|
32
|
+
|
|
33
|
+
import asyncio
|
|
34
|
+
import threading
|
|
35
|
+
import weakref
|
|
36
|
+
from abc import abstractmethod
|
|
37
|
+
from collections.abc import Callable
|
|
38
|
+
from enum import Enum
|
|
39
|
+
from types import EllipsisType
|
|
40
|
+
|
|
41
|
+
from syndesi.tools.errors import AdapterError
|
|
42
|
+
|
|
43
|
+
from ..component import AdapterFrame, Component, Descriptor, ReadScope
|
|
44
|
+
from ..tools.log_settings import LoggerAlias
|
|
45
|
+
from ..tools.types import NumberLike, is_number
|
|
46
|
+
from .adapter_worker import (
|
|
47
|
+
AdapterEvent,
|
|
48
|
+
AdapterWorker,
|
|
49
|
+
CloseCommand,
|
|
50
|
+
FlushReadCommand,
|
|
51
|
+
IsOpenCommand,
|
|
52
|
+
OpenCommand,
|
|
53
|
+
ReadCommand,
|
|
54
|
+
SetDescriptorCommand,
|
|
55
|
+
SetEventCallbackCommand,
|
|
56
|
+
SetStopConditionsCommand,
|
|
57
|
+
SetTimeoutCommand,
|
|
58
|
+
StopThreadCommand,
|
|
59
|
+
WriteCommand,
|
|
60
|
+
)
|
|
61
|
+
from .stop_conditions import Fragment, StopCondition
|
|
62
|
+
from .timeout import Timeout, TimeoutAction, any_to_timeout
|
|
63
|
+
|
|
64
|
+
fragments: list[Fragment]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# pylint: disable=too-many-public-methods, too-many-instance-attributes
|
|
68
|
+
class Adapter(Component[bytes], AdapterWorker):
|
|
69
|
+
"""
|
|
70
|
+
Adapter class
|
|
71
|
+
|
|
72
|
+
An adapter manages communication with a hardware device.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
class WorkerTimeout(Enum):
|
|
76
|
+
"""Timeout value for each worker command scenario"""
|
|
77
|
+
|
|
78
|
+
OPEN = 2
|
|
79
|
+
STOP = 1
|
|
80
|
+
IMMEDIATE_COMMAND = 0.2
|
|
81
|
+
CLOSE = 0.5
|
|
82
|
+
WRITE = 0.5
|
|
83
|
+
READ = None
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
*,
|
|
88
|
+
descriptor: Descriptor,
|
|
89
|
+
stop_conditions: StopCondition | EllipsisType | list[StopCondition],
|
|
90
|
+
timeout: Timeout | EllipsisType | NumberLike | None,
|
|
91
|
+
alias: str,
|
|
92
|
+
encoding: str = "utf-8",
|
|
93
|
+
event_callback: Callable[[AdapterEvent], None] | None = None,
|
|
94
|
+
auto_open: bool = True,
|
|
95
|
+
) -> None:
|
|
96
|
+
super().__init__(LoggerAlias.ADAPTER)
|
|
97
|
+
self.encoding = encoding
|
|
98
|
+
self._alias = alias
|
|
99
|
+
|
|
100
|
+
self.descriptor = descriptor
|
|
101
|
+
self.auto_open = auto_open
|
|
102
|
+
|
|
103
|
+
self._initial_event_callback = event_callback
|
|
104
|
+
|
|
105
|
+
# Default stop conditions
|
|
106
|
+
self._initial_stop_conditions: list[StopCondition]
|
|
107
|
+
if stop_conditions is ...:
|
|
108
|
+
self._is_default_stop_condition = True
|
|
109
|
+
self._initial_stop_conditions = self._default_stop_conditions()
|
|
110
|
+
else:
|
|
111
|
+
self._is_default_stop_condition = False
|
|
112
|
+
if isinstance(stop_conditions, StopCondition):
|
|
113
|
+
self._initial_stop_conditions = [stop_conditions]
|
|
114
|
+
elif isinstance(stop_conditions, list):
|
|
115
|
+
self._initial_stop_conditions = stop_conditions
|
|
116
|
+
else:
|
|
117
|
+
raise ValueError("Invalid stop_conditions")
|
|
118
|
+
|
|
119
|
+
# Default timeout
|
|
120
|
+
self.is_default_timeout = timeout is Ellipsis
|
|
121
|
+
|
|
122
|
+
if timeout is Ellipsis:
|
|
123
|
+
self._initial_timeout = self._default_timeout()
|
|
124
|
+
elif isinstance(timeout, Timeout):
|
|
125
|
+
self._initial_timeout = timeout
|
|
126
|
+
elif is_number(timeout):
|
|
127
|
+
self._initial_timeout = Timeout(timeout, action=TimeoutAction.ERROR)
|
|
128
|
+
elif timeout is None:
|
|
129
|
+
self._initial_timeout = Timeout(None)
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(f"Invalid timeout : {timeout}")
|
|
132
|
+
|
|
133
|
+
# Worker thread
|
|
134
|
+
self._worker_thread = threading.Thread(
|
|
135
|
+
target=self._worker_thread_method, daemon=True
|
|
136
|
+
)
|
|
137
|
+
self._worker_thread.start()
|
|
138
|
+
|
|
139
|
+
# Serialize read/write/query ordering for sync callers.
|
|
140
|
+
self._sync_io_lock = threading.Lock()
|
|
141
|
+
# Serialize read/write/query ordering for async callers.
|
|
142
|
+
self._async_io_lock = asyncio.Lock()
|
|
143
|
+
|
|
144
|
+
self._logger.info(f"Setting up {self.descriptor} adapter ")
|
|
145
|
+
self._update_descriptor()
|
|
146
|
+
self.set_stop_conditions(self._initial_stop_conditions)
|
|
147
|
+
self.set_timeout(self._initial_timeout)
|
|
148
|
+
self.set_event_callback(self._initial_event_callback)
|
|
149
|
+
|
|
150
|
+
if self.descriptor.is_initialized() and auto_open:
|
|
151
|
+
self.open()
|
|
152
|
+
|
|
153
|
+
weakref.finalize(self, self._cleanup)
|
|
154
|
+
|
|
155
|
+
# ┌──────────────────────────┐
|
|
156
|
+
# │ Defaults / configuration │
|
|
157
|
+
# └──────────────────────────┘
|
|
158
|
+
|
|
159
|
+
def _stop(self) -> None:
|
|
160
|
+
cmd = StopThreadCommand()
|
|
161
|
+
self._worker_send_command(cmd)
|
|
162
|
+
try:
|
|
163
|
+
cmd.result(self.WorkerTimeout.STOP.value)
|
|
164
|
+
except AdapterError:
|
|
165
|
+
pass
|
|
166
|
+
|
|
167
|
+
def _update_descriptor(self) -> None:
|
|
168
|
+
cmd = SetDescriptorCommand(self.descriptor)
|
|
169
|
+
self._worker_send_command(cmd)
|
|
170
|
+
cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
|
|
171
|
+
|
|
172
|
+
@abstractmethod
|
|
173
|
+
def _default_timeout(self) -> Timeout:
|
|
174
|
+
raise NotImplementedError
|
|
175
|
+
|
|
176
|
+
@abstractmethod
|
|
177
|
+
def _default_stop_conditions(self) -> list[StopCondition]:
|
|
178
|
+
raise NotImplementedError
|
|
179
|
+
|
|
180
|
+
def __str__(self) -> str:
|
|
181
|
+
return str(self.descriptor)
|
|
182
|
+
|
|
183
|
+
def __repr__(self) -> str:
|
|
184
|
+
return self.__str__()
|
|
185
|
+
|
|
186
|
+
def _cleanup(self) -> None:
|
|
187
|
+
# Be defensive: finalizers can run at interpreter shutdown.
|
|
188
|
+
try:
|
|
189
|
+
if self.is_open():
|
|
190
|
+
self.close()
|
|
191
|
+
except AdapterError:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
self._stop()
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
self._command_queue_r.close()
|
|
198
|
+
self._command_queue_w.close()
|
|
199
|
+
except AdapterError:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
# ┌────────────┐
|
|
203
|
+
# │ Public API │
|
|
204
|
+
# └────────────┘
|
|
205
|
+
|
|
206
|
+
def set_timeout(self, timeout: Timeout | None | float) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Set adapter timeout
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
timeout : Timeout, float or None
|
|
213
|
+
"""
|
|
214
|
+
# This is read by the worker when ReadCommand.timeout is ...
|
|
215
|
+
timeout_instance = any_to_timeout(timeout)
|
|
216
|
+
cmd = SetTimeoutCommand(timeout_instance)
|
|
217
|
+
self._worker_send_command(cmd)
|
|
218
|
+
cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
|
|
219
|
+
|
|
220
|
+
def set_default_timeout(self, default_timeout: Timeout | None) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Configure adapter default timeout. Timeout will only be set if none
|
|
223
|
+
has been configured before
|
|
224
|
+
|
|
225
|
+
Parameters
|
|
226
|
+
----------
|
|
227
|
+
default_timeout : Timeout or None
|
|
228
|
+
"""
|
|
229
|
+
if self.is_default_timeout:
|
|
230
|
+
new_timeout = any_to_timeout(default_timeout)
|
|
231
|
+
self._logger.debug(f"Setting default timeout to {new_timeout}")
|
|
232
|
+
self.set_timeout(new_timeout)
|
|
233
|
+
|
|
234
|
+
def set_stop_conditions(
|
|
235
|
+
self, stop_conditions: StopCondition | None | list[StopCondition]
|
|
236
|
+
) -> None:
|
|
237
|
+
"""
|
|
238
|
+
Set adapter stop-conditions
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
stop_conditions : [StopCondition] or None
|
|
243
|
+
"""
|
|
244
|
+
if isinstance(stop_conditions, list):
|
|
245
|
+
lst = stop_conditions
|
|
246
|
+
elif isinstance(stop_conditions, StopCondition):
|
|
247
|
+
lst = [stop_conditions]
|
|
248
|
+
elif stop_conditions is None:
|
|
249
|
+
lst = []
|
|
250
|
+
else:
|
|
251
|
+
raise ValueError("Invalid stop_conditions")
|
|
252
|
+
|
|
253
|
+
cmd = SetStopConditionsCommand(lst)
|
|
254
|
+
self._worker_send_command(cmd)
|
|
255
|
+
cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
|
|
256
|
+
|
|
257
|
+
def set_default_stop_conditions(self, stop_conditions: list[StopCondition]) -> None:
|
|
258
|
+
"""
|
|
259
|
+
Configure adapter default stop-condition. Stop-condition will only be set if none
|
|
260
|
+
has been configured before
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
stop_conditions : [StopCondition]
|
|
265
|
+
"""
|
|
266
|
+
if self._is_default_stop_condition:
|
|
267
|
+
self.set_stop_conditions(stop_conditions)
|
|
268
|
+
|
|
269
|
+
def set_event_callback(
|
|
270
|
+
self, callback: Callable[[AdapterEvent], None] | None
|
|
271
|
+
) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Configure event callback. Event callback is called as such :
|
|
274
|
+
|
|
275
|
+
callback(event : AdapterEvent)
|
|
276
|
+
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
callback : callable
|
|
280
|
+
|
|
281
|
+
"""
|
|
282
|
+
cmd = SetEventCallbackCommand(callback)
|
|
283
|
+
self._worker_send_command(cmd)
|
|
284
|
+
cmd.result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
|
|
285
|
+
|
|
286
|
+
# ==== open ====
|
|
287
|
+
|
|
288
|
+
def _open_future(self) -> OpenCommand:
|
|
289
|
+
cmd = OpenCommand()
|
|
290
|
+
self._worker_send_command(cmd)
|
|
291
|
+
return cmd
|
|
292
|
+
|
|
293
|
+
def open(self) -> None:
|
|
294
|
+
"""
|
|
295
|
+
Open adapter communication with the target (blocking)
|
|
296
|
+
"""
|
|
297
|
+
return self._open_future().result(self.WorkerTimeout.OPEN.value)
|
|
298
|
+
|
|
299
|
+
async def aopen(self) -> None:
|
|
300
|
+
"""
|
|
301
|
+
Open adapter communication with the target (async)
|
|
302
|
+
"""
|
|
303
|
+
await asyncio.wrap_future(self._open_future())
|
|
304
|
+
|
|
305
|
+
# ==== close ====
|
|
306
|
+
|
|
307
|
+
def _close_future(self) -> CloseCommand:
|
|
308
|
+
cmd = CloseCommand()
|
|
309
|
+
self._worker_send_command(cmd)
|
|
310
|
+
return cmd
|
|
311
|
+
|
|
312
|
+
def close(self) -> None:
|
|
313
|
+
"""
|
|
314
|
+
Close adapter communication with the target (blocking)
|
|
315
|
+
"""
|
|
316
|
+
self._close_future().result(self.WorkerTimeout.CLOSE.value)
|
|
317
|
+
|
|
318
|
+
async def aclose(self) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Close adapter communication with the target (async)
|
|
321
|
+
"""
|
|
322
|
+
await asyncio.wrap_future(self._close_future())
|
|
323
|
+
|
|
324
|
+
# ==== read_detailed ====
|
|
325
|
+
|
|
326
|
+
def _read_detailed_future(
|
|
327
|
+
self,
|
|
328
|
+
timeout: Timeout | EllipsisType | None,
|
|
329
|
+
stop_conditions: StopCondition | EllipsisType | list[StopCondition],
|
|
330
|
+
scope: str,
|
|
331
|
+
) -> ReadCommand:
|
|
332
|
+
cmd = ReadCommand(
|
|
333
|
+
timeout=timeout,
|
|
334
|
+
stop_conditions=stop_conditions,
|
|
335
|
+
scope=ReadScope(scope),
|
|
336
|
+
)
|
|
337
|
+
self._worker_send_command(cmd)
|
|
338
|
+
return cmd
|
|
339
|
+
|
|
340
|
+
def read_detailed(
|
|
341
|
+
self,
|
|
342
|
+
timeout: Timeout | EllipsisType | None = ...,
|
|
343
|
+
stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
|
|
344
|
+
scope: str = ReadScope.BUFFERED.value,
|
|
345
|
+
) -> AdapterFrame:
|
|
346
|
+
with self._sync_io_lock:
|
|
347
|
+
return self._read_detailed_future(
|
|
348
|
+
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
349
|
+
).result(self.WorkerTimeout.READ.value)
|
|
350
|
+
|
|
351
|
+
async def aread_detailed(
|
|
352
|
+
self,
|
|
353
|
+
timeout: Timeout | EllipsisType | None = ...,
|
|
354
|
+
stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
|
|
355
|
+
scope: str = ReadScope.BUFFERED.value,
|
|
356
|
+
) -> AdapterFrame:
|
|
357
|
+
async with self._async_io_lock:
|
|
358
|
+
return await asyncio.wrap_future(
|
|
359
|
+
self._read_detailed_future(
|
|
360
|
+
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
361
|
+
)
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# ==== read ====
|
|
365
|
+
|
|
366
|
+
def read(
|
|
367
|
+
self,
|
|
368
|
+
timeout: Timeout | EllipsisType | None = ...,
|
|
369
|
+
stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
|
|
370
|
+
scope: str = ReadScope.BUFFERED.value,
|
|
371
|
+
) -> bytes:
|
|
372
|
+
frame = self.read_detailed(
|
|
373
|
+
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
374
|
+
)
|
|
375
|
+
return frame.get_payload()
|
|
376
|
+
|
|
377
|
+
async def aread(
|
|
378
|
+
self,
|
|
379
|
+
timeout: Timeout | EllipsisType | None = ...,
|
|
380
|
+
stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
|
|
381
|
+
scope: str = ReadScope.BUFFERED.value,
|
|
382
|
+
) -> bytes:
|
|
383
|
+
frame = await self.aread_detailed(
|
|
384
|
+
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
385
|
+
)
|
|
386
|
+
return frame.get_payload()
|
|
387
|
+
|
|
388
|
+
# ==== flush_read ====
|
|
389
|
+
|
|
390
|
+
def _flush_read_future(self) -> FlushReadCommand:
|
|
391
|
+
cmd = FlushReadCommand()
|
|
392
|
+
self._worker_send_command(cmd)
|
|
393
|
+
return cmd
|
|
394
|
+
|
|
395
|
+
async def aflush_read(self) -> None:
|
|
396
|
+
"""
|
|
397
|
+
Clear buffered completed frames and reset current fragment assembly (async)
|
|
398
|
+
"""
|
|
399
|
+
async with self._async_io_lock:
|
|
400
|
+
await asyncio.wrap_future(self._flush_read_future())
|
|
401
|
+
|
|
402
|
+
def flush_read(self) -> None:
|
|
403
|
+
"""
|
|
404
|
+
Clear buffered completed frames and reset current fragment assembly (blocking)
|
|
405
|
+
"""
|
|
406
|
+
with self._sync_io_lock:
|
|
407
|
+
self._flush_read_future().result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
|
|
408
|
+
|
|
409
|
+
# ==== write ====
|
|
410
|
+
|
|
411
|
+
def _write_future(self, data: bytes | str) -> WriteCommand:
|
|
412
|
+
if isinstance(data, str):
|
|
413
|
+
data = data.encode(self.encoding)
|
|
414
|
+
cmd = WriteCommand(data)
|
|
415
|
+
self._worker_send_command(cmd)
|
|
416
|
+
return cmd
|
|
417
|
+
|
|
418
|
+
def write(self, data: bytes | str) -> None:
|
|
419
|
+
with self._sync_io_lock:
|
|
420
|
+
self._write_future(data).result(self.WorkerTimeout.WRITE.value)
|
|
421
|
+
|
|
422
|
+
async def awrite(self, data: bytes | str) -> None:
|
|
423
|
+
async with self._async_io_lock:
|
|
424
|
+
await asyncio.wrap_future(self._write_future(data))
|
|
425
|
+
|
|
426
|
+
# ==== query ====
|
|
427
|
+
|
|
428
|
+
async def aquery_detailed(
|
|
429
|
+
self,
|
|
430
|
+
payload: bytes,
|
|
431
|
+
timeout: Timeout | None | EllipsisType = ...,
|
|
432
|
+
stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
|
|
433
|
+
scope: str = ReadScope.BUFFERED.value,
|
|
434
|
+
) -> AdapterFrame:
|
|
435
|
+
async with self._async_io_lock:
|
|
436
|
+
await self.aflush_read()
|
|
437
|
+
await self.awrite(payload)
|
|
438
|
+
return await self.aread_detailed(
|
|
439
|
+
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
def query_detailed(
|
|
443
|
+
self,
|
|
444
|
+
payload: bytes,
|
|
445
|
+
timeout: Timeout | None | EllipsisType = ...,
|
|
446
|
+
stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
|
|
447
|
+
scope: str = ReadScope.BUFFERED.value,
|
|
448
|
+
) -> AdapterFrame:
|
|
449
|
+
|
|
450
|
+
with self._sync_io_lock:
|
|
451
|
+
self.flush_read()
|
|
452
|
+
self.write(payload)
|
|
453
|
+
return self.read_detailed(
|
|
454
|
+
timeout=timeout, stop_conditions=stop_conditions, scope=scope
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
# ==== Other ====
|
|
458
|
+
|
|
459
|
+
def _is_open_future(self) -> IsOpenCommand:
|
|
460
|
+
cmd = IsOpenCommand()
|
|
461
|
+
self._worker_send_command(cmd)
|
|
462
|
+
return cmd
|
|
463
|
+
|
|
464
|
+
def is_open(self) -> bool:
|
|
465
|
+
"""Check if the adapter is open"""
|
|
466
|
+
return self._is_open_future().result(self.WorkerTimeout.IMMEDIATE_COMMAND.value)
|
|
467
|
+
|
|
468
|
+
async def ais_open(self) -> bool:
|
|
469
|
+
"""Asynchronously check if the adapter is open"""
|
|
470
|
+
return await asyncio.wrap_future(self._is_open_future())
|