syndesi 0.1.4__py3-none-any.whl → 0.3.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/__init__.py +3 -0
- syndesi/__main__.py +4 -0
- syndesi/adapters/__init__.py +3 -3
- syndesi/adapters/adapter.py +528 -0
- syndesi/adapters/auto.py +49 -0
- syndesi/adapters/backend/adapter_backend.py +364 -0
- syndesi/adapters/backend/adapter_manager.py +49 -0
- syndesi/adapters/backend/adapter_session.py +363 -0
- syndesi/adapters/backend/backend.py +369 -0
- syndesi/{descriptors/syndesi/__init__.py → adapters/backend/backend_status.py} +0 -0
- syndesi/adapters/backend/backend_tools.py +55 -0
- syndesi/adapters/backend/descriptors.py +140 -0
- syndesi/adapters/backend/ip_backend.py +137 -0
- syndesi/adapters/backend/serialport_backend.py +232 -0
- syndesi/adapters/backend/stop_condition_backend.py +319 -0
- syndesi/adapters/backend/timed_queue.py +39 -0
- syndesi/adapters/backend/timeout.py +252 -0
- syndesi/adapters/backend/visa_backend.py +179 -0
- syndesi/adapters/ip.py +75 -65
- syndesi/adapters/ip_server.py +102 -0
- syndesi/adapters/serialport.py +74 -0
- syndesi/adapters/stop_condition.py +102 -0
- syndesi/adapters/timeout.py +99 -0
- syndesi/adapters/visa.py +38 -54
- {tests → syndesi/cli}/__init__.py +0 -0
- syndesi/cli/adapter_shell.py +210 -0
- syndesi/cli/backend_console.py +88 -0
- syndesi/cli/backend_status.py +212 -0
- syndesi/cli/backend_wrapper.py +50 -0
- syndesi/cli/shell.py +269 -0
- syndesi/protocols/__init__.py +8 -3
- syndesi/protocols/delimited.py +145 -45
- syndesi/protocols/modbus.py +1561 -0
- syndesi/protocols/protocol.py +71 -0
- syndesi/protocols/raw.py +45 -60
- syndesi/protocols/scpi.py +154 -33
- syndesi/scripts/__init__.py +0 -0
- syndesi/scripts/syndesi.py +52 -0
- syndesi/scripts/syndesi_backend.py +43 -0
- syndesi/tools/backend_api.py +190 -0
- syndesi/tools/backend_logger.py +53 -0
- syndesi/tools/errors.py +18 -0
- syndesi/tools/exceptions.py +7 -1
- syndesi/tools/internal.py +0 -0
- syndesi/tools/log.py +143 -0
- syndesi/tools/log_settings.py +16 -0
- syndesi/tools/types.py +87 -46
- syndesi/version.py +3 -0
- {syndesi-0.1.4.dist-info → syndesi-0.3.1.dist-info}/METADATA +24 -22
- syndesi-0.3.1.dist-info/RECORD +56 -0
- {syndesi-0.1.4.dist-info → syndesi-0.3.1.dist-info}/WHEEL +1 -1
- syndesi-0.3.1.dist-info/entry_points.txt +3 -0
- {syndesi-0.1.4.dist-info → syndesi-0.3.1.dist-info/licenses}/LICENSE +194 -194
- syndesi-0.3.1.dist-info/top_level.txt +1 -0
- syndesi/adapters/IP.py +0 -81
- syndesi/adapters/VISA.py +0 -50
- syndesi/adapters/iadapter.py +0 -73
- syndesi/adapters/serial.py +0 -37
- syndesi/descriptors/IP.py +0 -9
- syndesi/descriptors/Serial.py +0 -10
- syndesi/descriptors/VISA.py +0 -31
- syndesi/descriptors/__init__.py +0 -1
- syndesi/descriptors/descriptor.py +0 -9
- syndesi/descriptors/ip.py +0 -9
- syndesi/descriptors/syndesi/Syndesi.py +0 -9
- syndesi/descriptors/syndesi/_device.py +0 -25
- syndesi/descriptors/syndesi/devices.py +0 -10
- syndesi/descriptors/syndesi/frame.py +0 -133
- syndesi/descriptors/syndesi/network.py +0 -41
- syndesi/descriptors/syndesi/payload.py +0 -11
- syndesi/descriptors/syndesi/sdid.py +0 -21
- syndesi/descriptors/visa.py +0 -31
- syndesi/protocols/commands.py +0 -56
- syndesi/protocols/iprotocol.py +0 -14
- syndesi/protocols/sdp.py +0 -14
- syndesi/tools/stop_conditions.py +0 -148
- syndesi-0.1.4.data/scripts/syndesi +0 -1
- syndesi-0.1.4.dist-info/RECORD +0 -42
- syndesi-0.1.4.dist-info/top_level.txt +0 -2
- {experiments → syndesi/adapters/backend}/__init__.py +0 -0
syndesi/__init__.py
CHANGED
syndesi/__main__.py
ADDED
syndesi/adapters/__init__.py
CHANGED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
# File : adapters.py
|
|
2
|
+
# Author : Sébastien Deriaz
|
|
3
|
+
# License : GPL
|
|
4
|
+
#
|
|
5
|
+
# Adapters provide a common abstraction for the media layers (physical + data link + network)
|
|
6
|
+
# The following classes are provided, which all are derived from the main Adapter class
|
|
7
|
+
# - IP
|
|
8
|
+
# - Serial
|
|
9
|
+
# - VISA
|
|
10
|
+
#
|
|
11
|
+
# Note that technically VISA is not part of the media layer, only USB is.
|
|
12
|
+
# This is a limitation as it is to this day not possible to communicate "raw"
|
|
13
|
+
# with a device through USB yet
|
|
14
|
+
#
|
|
15
|
+
# An adapter is meant to work with bytes objects but it can accept strings.
|
|
16
|
+
# Strings will automatically be converted to bytes using utf-8 encoding
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import queue
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
import threading
|
|
23
|
+
import time
|
|
24
|
+
from typing import Any, Callable, Literal, Optional, Tuple, Union, overload
|
|
25
|
+
import uuid
|
|
26
|
+
import weakref
|
|
27
|
+
from abc import ABC, abstractmethod
|
|
28
|
+
from multiprocessing.connection import Client
|
|
29
|
+
|
|
30
|
+
from syndesi.tools.types import DEFAULT, DefaultType, NumberLike, is_number
|
|
31
|
+
|
|
32
|
+
from ..tools.backend_api import BACKEND_PORT, Action, BackendResponse, _default_host, is_event
|
|
33
|
+
from ..tools.log_settings import LoggerAlias
|
|
34
|
+
from .backend.adapter_backend import AdapterDisconnected, AdapterReadInit, AdapterReadPayload, AdapterSignal
|
|
35
|
+
from .stop_condition import StopCondition, TimeoutStopCondition
|
|
36
|
+
from .timeout import Timeout, TimeoutAction, any_to_timeout
|
|
37
|
+
|
|
38
|
+
DEFAULT_STOP_CONDITION = TimeoutStopCondition(continuation=0.1)
|
|
39
|
+
|
|
40
|
+
DEFAULT_TIMEOUT = 5
|
|
41
|
+
|
|
42
|
+
SHUTDOWN_DELAY = 2
|
|
43
|
+
|
|
44
|
+
# Maximum time to let the backend start
|
|
45
|
+
START_TIMEOUT = 2
|
|
46
|
+
|
|
47
|
+
#from enum import Enum, auto
|
|
48
|
+
|
|
49
|
+
from ..tools.backend_api import raise_if_error
|
|
50
|
+
from .backend.descriptors import Descriptor
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# class CallbackEvent(Enum):
|
|
54
|
+
# DATA_READY = auto()
|
|
55
|
+
# ADAPTER_DISCONNECTED = auto()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def is_backend_running(address : Optional[str] = None, port : Optional[int] = None) -> bool:
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
conn = Client((
|
|
62
|
+
_default_host if address is None else address,
|
|
63
|
+
BACKEND_PORT if port is None else port
|
|
64
|
+
))
|
|
65
|
+
except ConnectionRefusedError:
|
|
66
|
+
return False
|
|
67
|
+
else:
|
|
68
|
+
conn.close()
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def start_backend(port : Optional[int] = None) -> None:
|
|
73
|
+
subprocess.Popen(
|
|
74
|
+
[
|
|
75
|
+
sys.executable,
|
|
76
|
+
"-m",
|
|
77
|
+
"syndesi.adapters.backend.backend",
|
|
78
|
+
"-s",
|
|
79
|
+
str(SHUTDOWN_DELAY),
|
|
80
|
+
"-q",
|
|
81
|
+
"-p",
|
|
82
|
+
str(BACKEND_PORT if port is None else port)
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Adapter(ABC):
|
|
88
|
+
ADDITIONNAL_RESPONSE_DELAY = 1
|
|
89
|
+
BACKEND_REQUEST_DEFAULT_TIMEOUT = 1
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
descriptor: Descriptor,
|
|
94
|
+
alias: str = "",
|
|
95
|
+
stop_condition: Optional[Union[StopCondition, DefaultType]] = DEFAULT,
|
|
96
|
+
timeout: Optional[Union[Timeout, DefaultType, NumberLike]] = DEFAULT,
|
|
97
|
+
encoding: str = "utf-8",
|
|
98
|
+
event_callback : Optional[Callable[[AdapterSignal], None]] = None,
|
|
99
|
+
auto_open : bool = True,
|
|
100
|
+
backend_address : Optional[str] = None,
|
|
101
|
+
backend_port : Optional[int] = None
|
|
102
|
+
) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Adapter instance
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
alias : str
|
|
109
|
+
The alias is used to identify the class in the logs
|
|
110
|
+
timeout : float or Timeout instance
|
|
111
|
+
Default timeout is
|
|
112
|
+
stop_condition : StopCondition or None
|
|
113
|
+
Default to None
|
|
114
|
+
encoding : str
|
|
115
|
+
Which encoding to use if str has to be encoded into bytes
|
|
116
|
+
"""
|
|
117
|
+
self._init_ok = False
|
|
118
|
+
super().__init__()
|
|
119
|
+
self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
|
|
120
|
+
self.encoding = encoding
|
|
121
|
+
self._event_queue : queue.Queue[BackendResponse] = queue.Queue()
|
|
122
|
+
self.event_callback : Optional[Callable[[AdapterSignal], None]] = event_callback
|
|
123
|
+
self._backend_connection_lock = threading.Lock()
|
|
124
|
+
self._make_backend_request_queue : queue.Queue[BackendResponse] = queue.Queue()
|
|
125
|
+
self._make_backend_request_flag = threading.Event()
|
|
126
|
+
self.opened = False
|
|
127
|
+
|
|
128
|
+
if is_backend_running(backend_address, backend_port):
|
|
129
|
+
self._logger.info("Backend already running")
|
|
130
|
+
elif backend_address is not None:
|
|
131
|
+
raise RuntimeError(f"Cannot connect to backend {backend_address}")
|
|
132
|
+
else:
|
|
133
|
+
self._logger.info("Starting backend...")
|
|
134
|
+
start_backend(backend_port)
|
|
135
|
+
start = time.time()
|
|
136
|
+
while time.time() < (start + START_TIMEOUT):
|
|
137
|
+
if is_backend_running():
|
|
138
|
+
self._logger.info("Backend started")
|
|
139
|
+
break
|
|
140
|
+
time.sleep(0.1)
|
|
141
|
+
else:
|
|
142
|
+
# Backend could not start
|
|
143
|
+
self._logger.error("Could not start backend")
|
|
144
|
+
|
|
145
|
+
assert isinstance(
|
|
146
|
+
descriptor, Descriptor
|
|
147
|
+
), "descriptor must be a Descriptor class"
|
|
148
|
+
self.descriptor = descriptor
|
|
149
|
+
|
|
150
|
+
# Open the connection with the backend
|
|
151
|
+
try:
|
|
152
|
+
self.backend_connection = Client((_default_host, BACKEND_PORT))
|
|
153
|
+
except ConnectionRefusedError:
|
|
154
|
+
raise RuntimeError("Failed to connect to backend")
|
|
155
|
+
self._read_thread = threading.Thread(
|
|
156
|
+
target=self.read_thread,
|
|
157
|
+
args=(self._event_queue, self._make_backend_request_queue),
|
|
158
|
+
daemon=True,
|
|
159
|
+
)
|
|
160
|
+
self._read_thread.start()
|
|
161
|
+
|
|
162
|
+
# Identify ourselves
|
|
163
|
+
self._make_backend_request(Action.SET_ROLE_ADAPTER)
|
|
164
|
+
|
|
165
|
+
# Set the adapter
|
|
166
|
+
self._make_backend_request(Action.SELECT_ADAPTER, str(self.descriptor))
|
|
167
|
+
|
|
168
|
+
self._alias = alias
|
|
169
|
+
|
|
170
|
+
# Set the timeout
|
|
171
|
+
self.is_default_timeout = False
|
|
172
|
+
self._timeout : Optional[Timeout]
|
|
173
|
+
if timeout is Ellipsis:
|
|
174
|
+
# Not set
|
|
175
|
+
self.is_default_timeout = True
|
|
176
|
+
elif isinstance(timeout, Timeout):
|
|
177
|
+
self._timeout = timeout
|
|
178
|
+
elif is_number(timeout):
|
|
179
|
+
self._timeout = Timeout(timeout, action=TimeoutAction.ERROR)
|
|
180
|
+
elif timeout is None:
|
|
181
|
+
self._timeout = timeout
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Set the stop-condition
|
|
185
|
+
self._stop_condition : Optional[StopCondition]
|
|
186
|
+
if stop_condition is DEFAULT:
|
|
187
|
+
self._default_stop_condition = True
|
|
188
|
+
self._stop_condition = DEFAULT_STOP_CONDITION
|
|
189
|
+
else:
|
|
190
|
+
self._default_stop_condition = False
|
|
191
|
+
self._stop_condition = stop_condition
|
|
192
|
+
|
|
193
|
+
# Buffer for data that has been pulled from the queue but
|
|
194
|
+
# not used because of termination or length stop condition
|
|
195
|
+
self._previous_buffer = b""
|
|
196
|
+
|
|
197
|
+
weakref.finalize(self, self._cleanup)
|
|
198
|
+
self._init_ok = True
|
|
199
|
+
self.auto_open = auto_open
|
|
200
|
+
if auto_open:
|
|
201
|
+
self.open()
|
|
202
|
+
|
|
203
|
+
def _make_backend_request(self, action: Action, *args : Any) -> BackendResponse:
|
|
204
|
+
"""
|
|
205
|
+
Send a request to the backend and return the arguments
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
with self._backend_connection_lock:
|
|
209
|
+
self.backend_connection.send((action.value, *args))
|
|
210
|
+
|
|
211
|
+
self._make_backend_request_flag.set()
|
|
212
|
+
try:
|
|
213
|
+
response = self._make_backend_request_queue.get(
|
|
214
|
+
timeout=self.BACKEND_REQUEST_DEFAULT_TIMEOUT
|
|
215
|
+
)
|
|
216
|
+
except queue.Empty:
|
|
217
|
+
raise RuntimeError(f"Failed to receive response from backend to {action}")
|
|
218
|
+
|
|
219
|
+
assert (
|
|
220
|
+
isinstance(response, tuple) and len(response) > 0
|
|
221
|
+
), f"Invalid response received from backend : {response}"
|
|
222
|
+
raise_if_error(response)
|
|
223
|
+
|
|
224
|
+
return response[1:]
|
|
225
|
+
|
|
226
|
+
def read_thread(self, event_queue: queue.Queue[BackendResponse], request_queue: queue.Queue[BackendResponse]) -> None:
|
|
227
|
+
while True:
|
|
228
|
+
try:
|
|
229
|
+
response : Tuple[Any,...] = self.backend_connection.recv()
|
|
230
|
+
except (EOFError, TypeError, OSError):
|
|
231
|
+
event_queue.put((Action.ERROR_BACKEND_DISCONNECTED,))
|
|
232
|
+
request_queue.put((Action.ERROR_BACKEND_DISCONNECTED,))
|
|
233
|
+
else:
|
|
234
|
+
if not isinstance(response, tuple):
|
|
235
|
+
raise RuntimeError(f"Invalid response from backend : {response}")
|
|
236
|
+
action = Action(response[0])
|
|
237
|
+
|
|
238
|
+
if is_event(action):
|
|
239
|
+
if self.event_callback is not None:
|
|
240
|
+
signal : AdapterSignal = response[1]
|
|
241
|
+
self.event_callback(signal)
|
|
242
|
+
event_queue.put(response)
|
|
243
|
+
else:
|
|
244
|
+
request_queue.put(response)
|
|
245
|
+
|
|
246
|
+
@abstractmethod
|
|
247
|
+
def _default_timeout(self) -> Timeout:
|
|
248
|
+
pass
|
|
249
|
+
|
|
250
|
+
def set_timeout(self, timeout: Optional[Timeout]) -> None:
|
|
251
|
+
"""
|
|
252
|
+
Overwrite timeout
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
timeout : Timeout
|
|
257
|
+
"""
|
|
258
|
+
self._timeout = timeout
|
|
259
|
+
|
|
260
|
+
def set_default_timeout(self, default_timeout: Optional[Timeout]) -> None:
|
|
261
|
+
"""
|
|
262
|
+
Set the default timeout for this adapter. If a previous timeout has been set, it will be fused
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
default_timeout : Timeout or tuple or float
|
|
267
|
+
"""
|
|
268
|
+
if self.is_default_timeout:
|
|
269
|
+
self._logger.debug(f"Setting default timeout to {default_timeout}")
|
|
270
|
+
self._timeout = default_timeout
|
|
271
|
+
|
|
272
|
+
def set_stop_condition(self, stop_condition: Optional[StopCondition]) -> None:
|
|
273
|
+
"""
|
|
274
|
+
Overwrite the stop-condition
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
stop_condition : StopCondition
|
|
279
|
+
"""
|
|
280
|
+
self._stop_condition = stop_condition
|
|
281
|
+
#payload : Optional[str]
|
|
282
|
+
if self._stop_condition is None:
|
|
283
|
+
payload = None
|
|
284
|
+
else:
|
|
285
|
+
payload = self._stop_condition.compose_json()
|
|
286
|
+
self._make_backend_request(
|
|
287
|
+
Action.SET_STOP_CONDITION, payload
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def set_default_stop_condition(self, stop_condition : StopCondition) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Set the default stop condition for this adapter.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
stop_condition : StopCondition
|
|
297
|
+
"""
|
|
298
|
+
if self._default_stop_condition:
|
|
299
|
+
self.set_stop_condition(stop_condition)
|
|
300
|
+
|
|
301
|
+
def flushRead(self) -> None:
|
|
302
|
+
"""
|
|
303
|
+
Flush the input buffer
|
|
304
|
+
"""
|
|
305
|
+
self._make_backend_request(
|
|
306
|
+
Action.FLUSHREAD,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def previous_read_buffer_empty(self) -> bool:
|
|
310
|
+
"""
|
|
311
|
+
Check whether the previous read buffer is empty
|
|
312
|
+
|
|
313
|
+
Returns
|
|
314
|
+
-------
|
|
315
|
+
empty : bool
|
|
316
|
+
"""
|
|
317
|
+
return self._previous_buffer == b""
|
|
318
|
+
|
|
319
|
+
def open(self) -> None:
|
|
320
|
+
"""
|
|
321
|
+
Start communication with the device
|
|
322
|
+
"""
|
|
323
|
+
if self._stop_condition is None:
|
|
324
|
+
payload = None
|
|
325
|
+
else:
|
|
326
|
+
payload = self._stop_condition.compose_json()
|
|
327
|
+
self._make_backend_request(
|
|
328
|
+
Action.OPEN,
|
|
329
|
+
payload
|
|
330
|
+
)
|
|
331
|
+
self._logger.info("Adapter opened")
|
|
332
|
+
self.opened = True
|
|
333
|
+
|
|
334
|
+
def close(self, force: bool = False) -> None:
|
|
335
|
+
"""
|
|
336
|
+
Stop communication with the device
|
|
337
|
+
"""
|
|
338
|
+
self._logger.debug("Closing adapter frontend")
|
|
339
|
+
self._make_backend_request(Action.CLOSE)
|
|
340
|
+
if force:
|
|
341
|
+
self._logger.debug("Force closing adapter backend")
|
|
342
|
+
self._make_backend_request(Action.FORCE_CLOSE)
|
|
343
|
+
|
|
344
|
+
with self._backend_connection_lock:
|
|
345
|
+
self.backend_connection.close()
|
|
346
|
+
|
|
347
|
+
self.opened = False
|
|
348
|
+
|
|
349
|
+
def write(self, data: bytes | str) -> None:
|
|
350
|
+
"""
|
|
351
|
+
Send data to the device
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
data : bytes or str
|
|
356
|
+
"""
|
|
357
|
+
|
|
358
|
+
if isinstance(data, str):
|
|
359
|
+
data = data.encode(self.encoding)
|
|
360
|
+
self._make_backend_request(Action.WRITE, data)
|
|
361
|
+
|
|
362
|
+
@overload
|
|
363
|
+
def read( #type: ignore (default arguments mess everything up)
|
|
364
|
+
self,
|
|
365
|
+
timeout: Union[Timeout, DefaultType, None] = DEFAULT,
|
|
366
|
+
stop_condition: Union[StopCondition, DefaultType, None] = DEFAULT,
|
|
367
|
+
full_output: Literal[True] = True,
|
|
368
|
+
) -> Tuple[bytes, AdapterSignal]: ...
|
|
369
|
+
|
|
370
|
+
@overload
|
|
371
|
+
def read(
|
|
372
|
+
self,
|
|
373
|
+
timeout: Union[Timeout, DefaultType, None] = DEFAULT,
|
|
374
|
+
stop_condition: Union[StopCondition, DefaultType, None] = DEFAULT,
|
|
375
|
+
full_output: Literal[False] = False,
|
|
376
|
+
) -> bytes: ...
|
|
377
|
+
|
|
378
|
+
def read(
|
|
379
|
+
self,
|
|
380
|
+
timeout: Union[None, Timeout, DefaultType] = DEFAULT,
|
|
381
|
+
stop_condition: Optional[Union[StopCondition, DefaultType]] = DEFAULT,
|
|
382
|
+
full_output: bool = False,
|
|
383
|
+
) -> Union[bytes, Tuple[bytes, AdapterSignal]]:
|
|
384
|
+
"""
|
|
385
|
+
Read data from the device
|
|
386
|
+
|
|
387
|
+
Parameters
|
|
388
|
+
----------
|
|
389
|
+
timeout : tuple, Timeout
|
|
390
|
+
Temporary timeout
|
|
391
|
+
stop_condition : StopCondition
|
|
392
|
+
Temporary stop condition
|
|
393
|
+
full_output : bool
|
|
394
|
+
If True, return read information as well as data
|
|
395
|
+
Returns
|
|
396
|
+
-------
|
|
397
|
+
data : bytes
|
|
398
|
+
metrics : dict
|
|
399
|
+
Only if full_output is True
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
if timeout is DEFAULT:
|
|
403
|
+
read_timeout = self._timeout
|
|
404
|
+
else:
|
|
405
|
+
read_timeout = any_to_timeout(timeout)
|
|
406
|
+
|
|
407
|
+
output = None
|
|
408
|
+
|
|
409
|
+
response_received = False
|
|
410
|
+
|
|
411
|
+
if read_timeout is None:
|
|
412
|
+
queue_timeout_timestamp = None
|
|
413
|
+
elif read_timeout.response is None:
|
|
414
|
+
queue_timeout_timestamp = None
|
|
415
|
+
else:
|
|
416
|
+
if read_timeout.response is DEFAULT:
|
|
417
|
+
raise RuntimeError('Timeout needs to be initialized')
|
|
418
|
+
|
|
419
|
+
queue_timeout_timestamp = (
|
|
420
|
+
time.time() + read_timeout.response + self.ADDITIONNAL_RESPONSE_DELAY
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
start_read_uuid = uuid.uuid1()
|
|
424
|
+
# Send a read signal to the backend
|
|
425
|
+
self._make_backend_request(
|
|
426
|
+
Action.START_READ,
|
|
427
|
+
read_timeout.response if read_timeout is not None else None,
|
|
428
|
+
start_read_uuid
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
while True:
|
|
432
|
+
if queue_timeout_timestamp is None or response_received:
|
|
433
|
+
queue_timeout = None
|
|
434
|
+
else:
|
|
435
|
+
queue_timeout = queue_timeout_timestamp - time.time()
|
|
436
|
+
if queue_timeout < 0:
|
|
437
|
+
queue_timeout = 0
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
response = self._event_queue.get(block=True, timeout=queue_timeout)
|
|
441
|
+
signal = response[1]
|
|
442
|
+
except queue.Empty:
|
|
443
|
+
raise RuntimeError(
|
|
444
|
+
"Failed to receive response confirmation from backend"
|
|
445
|
+
)
|
|
446
|
+
else:
|
|
447
|
+
if isinstance(signal, AdapterReadPayload):
|
|
448
|
+
if response_received:
|
|
449
|
+
output = signal.data()
|
|
450
|
+
break
|
|
451
|
+
elif isinstance(signal, AdapterReadInit):
|
|
452
|
+
#signal: AdapterReadInit = response[1]
|
|
453
|
+
# Check if it's the right read_init with the uuid, otherwise ignore it
|
|
454
|
+
if signal.uuid == start_read_uuid:
|
|
455
|
+
if signal.received_response_in_time:
|
|
456
|
+
response_received = True
|
|
457
|
+
else:
|
|
458
|
+
if self._timeout is None:
|
|
459
|
+
raise RuntimeError('Failed to receive data in time but timeout is None')
|
|
460
|
+
else:
|
|
461
|
+
if self._timeout.action == TimeoutAction.RETURN:
|
|
462
|
+
output = b""
|
|
463
|
+
break
|
|
464
|
+
elif self._timeout.action == TimeoutAction.ERROR:
|
|
465
|
+
raise TimeoutError(
|
|
466
|
+
f"No response received from device within {self._timeout.response} seconds"
|
|
467
|
+
)
|
|
468
|
+
elif isinstance(signal, AdapterDisconnected):
|
|
469
|
+
raise RuntimeError("Adapter disconnected")
|
|
470
|
+
|
|
471
|
+
if full_output:
|
|
472
|
+
return output, signal
|
|
473
|
+
else:
|
|
474
|
+
return output
|
|
475
|
+
|
|
476
|
+
def _cleanup(self) -> None:
|
|
477
|
+
if self._init_ok and self.opened:
|
|
478
|
+
self.close()
|
|
479
|
+
|
|
480
|
+
@overload
|
|
481
|
+
def query(
|
|
482
|
+
self,
|
|
483
|
+
data: Union[bytes, str],
|
|
484
|
+
timeout: Optional[Union[Timeout, DefaultType]] = DEFAULT,
|
|
485
|
+
stop_condition: Optional[Union[StopCondition, DefaultType]] = DEFAULT,
|
|
486
|
+
full_output: Literal[True] = True,
|
|
487
|
+
) -> Tuple[bytes, AdapterSignal]: ...
|
|
488
|
+
@overload
|
|
489
|
+
def query(
|
|
490
|
+
self,
|
|
491
|
+
data: Union[bytes, str],
|
|
492
|
+
timeout: Optional[Union[Timeout, DefaultType]] = DEFAULT,
|
|
493
|
+
stop_condition: Optional[Union[StopCondition, DefaultType]] = DEFAULT,
|
|
494
|
+
full_output: Literal[False] = False,
|
|
495
|
+
) -> bytes: ...
|
|
496
|
+
|
|
497
|
+
def query(
|
|
498
|
+
self,
|
|
499
|
+
data: Union[bytes, str],
|
|
500
|
+
timeout: Optional[Union[Timeout, DefaultType]] = DEFAULT,
|
|
501
|
+
stop_condition: Optional[Union[StopCondition, DefaultType]] = DEFAULT,
|
|
502
|
+
full_output: bool = False,
|
|
503
|
+
) -> Union[bytes, Tuple[bytes, AdapterSignal]]:
|
|
504
|
+
"""
|
|
505
|
+
Shortcut function that combines
|
|
506
|
+
- flush_read
|
|
507
|
+
- write
|
|
508
|
+
- read
|
|
509
|
+
"""
|
|
510
|
+
self.flushRead()
|
|
511
|
+
self.write(data)
|
|
512
|
+
output, signal = self.read(
|
|
513
|
+
timeout=timeout, stop_condition=stop_condition, full_output=True
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
if full_output:
|
|
517
|
+
return output, signal
|
|
518
|
+
else:
|
|
519
|
+
return output
|
|
520
|
+
|
|
521
|
+
def set_event_callback(self, callback : Callable[[AdapterSignal], None]) -> None:
|
|
522
|
+
self.event_callback = callback
|
|
523
|
+
|
|
524
|
+
def __str__(self) -> str:
|
|
525
|
+
return str(self.descriptor)
|
|
526
|
+
|
|
527
|
+
def __repr__(self) -> str:
|
|
528
|
+
return self.__str__()
|
syndesi/adapters/auto.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# File : auto.py
|
|
2
|
+
# Author : Sébastien Deriaz
|
|
3
|
+
# License : GPL
|
|
4
|
+
#
|
|
5
|
+
# Automatic adapter function
|
|
6
|
+
# This function is used to automatically choose an adapter based on the user's input
|
|
7
|
+
# 192.168.1.1 -> IP
|
|
8
|
+
# COM4 -> Serial
|
|
9
|
+
# /dev/tty* -> Serial
|
|
10
|
+
# etc...
|
|
11
|
+
# If an adapter class is supplied, it is passed through
|
|
12
|
+
#
|
|
13
|
+
# Additionnaly, it is possible to do COM4:115200 so as to make the life of the user easier
|
|
14
|
+
# Same with /dev/ttyACM0:115200
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from .ip import IP
|
|
18
|
+
from .serialport import SerialPort
|
|
19
|
+
from .visa import Visa
|
|
20
|
+
from .adapter import Adapter
|
|
21
|
+
from .backend.descriptors import (
|
|
22
|
+
IPDescriptor,
|
|
23
|
+
SerialPortDescriptor,
|
|
24
|
+
VisaDescriptor,
|
|
25
|
+
adapter_descriptor_by_string,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def auto_adapter(adapter_or_string: Adapter | str) -> Adapter:
|
|
29
|
+
if isinstance(adapter_or_string, Adapter):
|
|
30
|
+
# Simply return it
|
|
31
|
+
return adapter_or_string
|
|
32
|
+
|
|
33
|
+
elif isinstance(adapter_or_string, str):
|
|
34
|
+
descriptor = adapter_descriptor_by_string(adapter_or_string)
|
|
35
|
+
if isinstance(descriptor, IPDescriptor):
|
|
36
|
+
return IP(
|
|
37
|
+
address=descriptor.address,
|
|
38
|
+
port=descriptor.port,
|
|
39
|
+
transport=descriptor.transport,
|
|
40
|
+
)
|
|
41
|
+
elif isinstance(descriptor, SerialPortDescriptor):
|
|
42
|
+
return SerialPort(port=descriptor.port, baudrate=descriptor.baudrate)
|
|
43
|
+
elif isinstance(descriptor, VisaDescriptor):
|
|
44
|
+
return Visa(descriptor=descriptor.descriptor)
|
|
45
|
+
else:
|
|
46
|
+
raise RuntimeError(f"Invalid descriptor : {descriptor}")
|
|
47
|
+
|
|
48
|
+
else:
|
|
49
|
+
raise ValueError(f"Invalid adapter : {adapter_or_string}")
|