syndesi 0.4.1__py3-none-any.whl → 0.4.4__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.
Files changed (44) hide show
  1. syndesi/__init__.py +16 -1
  2. syndesi/adapters/adapter.py +187 -108
  3. syndesi/adapters/auto.py +26 -15
  4. syndesi/adapters/backend/adapter_backend.py +57 -39
  5. syndesi/adapters/backend/adapter_session.py +30 -32
  6. syndesi/adapters/backend/backend.py +7 -2
  7. syndesi/adapters/backend/backend_status.py +0 -0
  8. syndesi/adapters/backend/backend_tools.py +1 -1
  9. syndesi/adapters/backend/descriptors.py +1 -1
  10. syndesi/adapters/backend/ip_backend.py +45 -42
  11. syndesi/adapters/backend/serialport_backend.py +23 -19
  12. syndesi/adapters/backend/stop_condition_backend.py +54 -30
  13. syndesi/adapters/backend/visa_backend.py +5 -5
  14. syndesi/adapters/ip.py +33 -27
  15. syndesi/adapters/ip_server.py +9 -3
  16. syndesi/adapters/serialport.py +20 -7
  17. syndesi/adapters/stop_condition.py +86 -135
  18. syndesi/adapters/timeout.py +2 -28
  19. syndesi/adapters/visa.py +11 -3
  20. syndesi/cli/backend_status.py +7 -9
  21. syndesi/cli/console.py +0 -53
  22. syndesi/cli/shell.py +3 -16
  23. syndesi/cli/shell_tools.py +0 -5
  24. syndesi/component.py +79 -0
  25. syndesi/protocols/delimited.py +7 -22
  26. syndesi/protocols/modbus.py +9 -8
  27. syndesi/protocols/protocol.py +7 -1
  28. syndesi/protocols/raw.py +4 -4
  29. syndesi/protocols/scpi.py +6 -5
  30. syndesi/scripts/syndesi.py +2 -4
  31. syndesi/tools/backend_api.py +6 -32
  32. syndesi/tools/backend_logger.py +0 -1
  33. syndesi/tools/errors.py +28 -9
  34. syndesi/tools/log.py +0 -87
  35. syndesi/tools/types.py +0 -43
  36. syndesi/version.py +5 -1
  37. syndesi-0.4.4.dist-info/METADATA +96 -0
  38. syndesi-0.4.4.dist-info/RECORD +61 -0
  39. syndesi-0.4.1.dist-info/METADATA +0 -123
  40. syndesi-0.4.1.dist-info/RECORD +0 -59
  41. {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/WHEEL +0 -0
  42. {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/entry_points.txt +0 -0
  43. {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/licenses/LICENSE +0 -0
  44. {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/top_level.txt +0 -0
syndesi/__init__.py CHANGED
@@ -1,5 +1,10 @@
1
+ """
2
+ Syndesi module
3
+ """
4
+
1
5
  from .adapters.ip import IP
2
6
  from .adapters.serialport import SerialPort
7
+ from .adapters.timeout import Timeout
3
8
  from .adapters.visa import Visa
4
9
  from .protocols.delimited import Delimited
5
10
  from .protocols.modbus import Modbus
@@ -7,4 +12,14 @@ from .protocols.raw import Raw
7
12
  from .protocols.scpi import SCPI
8
13
  from .tools.log import log
9
14
 
10
- __all__ = ["IP", "SerialPort", "Visa", "Delimited", "Modbus", "Raw", "SCPI", "log"]
15
+ __all__ = [
16
+ "IP",
17
+ "SerialPort",
18
+ "Visa",
19
+ "Delimited",
20
+ "Modbus",
21
+ "Raw",
22
+ "SCPI",
23
+ "log",
24
+ "Timeout",
25
+ ]
@@ -1,22 +1,24 @@
1
1
  # File : adapters.py
2
2
  # Author : Sébastien Deriaz
3
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
4
 
18
- from enum import Enum
5
+ """
6
+ Adapters provide a common abstraction for the media layers (physical + data link + network)
7
+ The following classes are provided, which all are derived from the main Adapter class
8
+ - IP
9
+ - Serial
10
+ - VISA
11
+
12
+ Note that technically VISA is not part of the media layer, only USB is.
13
+ This is a limitation as it is to this day not possible to communicate "raw"
14
+ with a device through USB yet
15
+
16
+ An adapter is meant to work with bytes objects but it can accept strings.
17
+ Strings will automatically be converted to bytes using utf-8 encoding
18
+ """
19
+
19
20
  import logging
21
+ import os
20
22
  import queue
21
23
  import subprocess
22
24
  import sys
@@ -25,93 +27,125 @@ import time
25
27
  import weakref
26
28
  from abc import ABC, abstractmethod
27
29
  from collections.abc import Callable
30
+ from enum import Enum
28
31
  from multiprocessing.connection import Client, Connection
29
32
  from types import EllipsisType
30
33
  from typing import Any
31
- import os
32
34
 
33
- from .backend.backend_tools import BACKEND_REQUEST_DEFAULT_TIMEOUT
34
- from syndesi.tools.types import NumberLike, is_number
35
+ from ..component import Component
36
+ from ..tools.errors import (
37
+ AdapterDisconnected,
38
+ AdapterFailedToOpen,
39
+ AdapterTimeoutError,
40
+ BackendCommunicationError,
41
+ )
42
+ from ..tools.types import NumberLike, is_number
35
43
 
36
44
  from ..tools.backend_api import (
37
45
  BACKEND_PORT,
46
+ DEFAULT_ADAPTER_OPEN_TIMEOUT,
47
+ EXTRA_BUFFER_RESPONSE_TIME,
38
48
  Action,
39
49
  BackendResponse,
40
50
  Fragment,
41
51
  default_host,
42
52
  raise_if_error,
43
- EXTRA_BUFFER_RESPONSE_TIME
44
53
  )
45
54
  from ..tools.log_settings import LoggerAlias
46
55
  from .backend.adapter_backend import (
47
- AdapterDisconnected,
48
- AdapterResponseTimeout,
56
+ AdapterDisconnectedSignal,
49
57
  AdapterReadPayload,
58
+ AdapterResponseTimeout,
50
59
  AdapterSignal,
51
60
  )
61
+ from .backend.backend_tools import BACKEND_REQUEST_DEFAULT_TIMEOUT
52
62
  from .backend.descriptors import Descriptor
53
- from .stop_condition import StopCondition, Continuation, StopConditionType, Total
63
+ from .stop_condition import Continuation, StopCondition, StopConditionType
54
64
  from .timeout import Timeout, TimeoutAction, any_to_timeout
55
65
 
56
66
  DEFAULT_STOP_CONDITION = Continuation(time=0.1)
57
67
 
58
- DEFAULT_TIMEOUT = Timeout(response=5, action='error')
68
+ DEFAULT_TIMEOUT = Timeout(response=5, action="error")
59
69
 
60
70
  # Maximum time to let the backend start
61
71
  START_TIMEOUT = 2
62
72
  # Time to shutdown the backend
63
73
  SHUTDOWN_DELAY = 2
64
74
 
75
+
65
76
  class SignalQueue(queue.Queue[AdapterSignal]):
77
+ """
78
+ A smart queue to hold adapter signals
79
+ """
66
80
  def __init__(self) -> None:
67
81
  self._read_payload_counter = 0
68
82
  super().__init__(0)
69
83
 
70
84
  def has_read_payload(self) -> bool:
85
+ """
86
+ Return True if the queue contains a read payload
87
+ """
71
88
  return self._read_payload_counter > 0
72
89
 
73
-
74
- def put(self, signal: AdapterSignal, block: bool = True, timeout: float | None = None) -> None:
90
+ def put(self, signal: AdapterSignal) -> None:
91
+ """
92
+ Put a signal in the queue
93
+
94
+ Parameters
95
+ ----------
96
+ signal : AdapterSignal
97
+ """
75
98
  if isinstance(signal, AdapterReadPayload):
76
99
  self._read_payload_counter += 1
77
- return super().put(signal, block, timeout)
78
-
100
+ return super().put(signal)
79
101
 
80
102
  def get(self, block: bool = True, timeout: float | None = None) -> AdapterSignal:
103
+ """
104
+ Get an AdapterSignal from the queue
105
+ """
81
106
  signal = super().get(block, timeout)
82
107
  if isinstance(signal, AdapterReadPayload):
83
108
  self._read_payload_counter -= 1
84
109
  return signal
85
110
 
86
-
87
111
  def is_backend_running(address: str, port: int) -> bool:
88
-
112
+ """
113
+ Return True if the backend is running
114
+ """
89
115
  try:
90
116
  conn = Client((address, port))
91
117
  except ConnectionRefusedError:
92
118
  return False
93
- else:
94
- conn.close()
95
- return True
119
+ conn.close()
120
+ return True
96
121
 
97
122
  def start_backend(port: int | None = None) -> None:
123
+ """
124
+ Start the backend in a separate process
125
+
126
+ A custom port can be specified
127
+
128
+ Parameters
129
+ ----------
130
+ port : int
131
+ """
98
132
  arguments = [
99
- sys.executable,
100
- "-m",
101
- "syndesi.adapters.backend.backend",
102
- "-s",
103
- str(SHUTDOWN_DELAY),
104
- "-q",
105
- "-p",
106
- str(BACKEND_PORT if port is None else port),
107
- ]
108
-
109
- stdin = subprocess.DEVNULL
133
+ sys.executable,
134
+ "-m",
135
+ "syndesi.adapters.backend.backend",
136
+ "-s",
137
+ str(SHUTDOWN_DELAY),
138
+ "-q",
139
+ "-p",
140
+ str(BACKEND_PORT if port is None else port),
141
+ ]
142
+
143
+ stdin = subprocess.DEVNULL
110
144
  stdout = subprocess.DEVNULL
111
145
  stderr = subprocess.DEVNULL
112
146
 
113
147
  if os.name == "posix":
114
- subprocess.Popen(
148
+ subprocess.Popen( #pylint: disable=consider-using-with
115
149
  arguments,
116
150
  stdin=stdin,
117
151
  stdout=stdout,
@@ -122,14 +156,14 @@ def start_backend(port: int | None = None) -> None:
122
156
 
123
157
  else:
124
158
  # Windows: detach from the parent's console so keyboard Ctrl+C won't propagate.
125
- CREATE_NEW_PROCESS_GROUP = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
126
- DETACHED_PROCESS = 0x00000008 # not exposed by subprocess on all Pythons
159
+ create_new_process_group = subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore
160
+ detached_process = 0x00000008 # not exposed by subprocess on all Pythons
127
161
  # Optional: CREATE_NO_WINDOW (no window even for console apps)
128
- CREATE_NO_WINDOW = 0x08000000
162
+ create_no_window = 0x08000000
129
163
 
130
- creationflags = CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS | CREATE_NO_WINDOW
164
+ creationflags = create_new_process_group | detached_process | create_no_window
131
165
 
132
- subprocess.Popen(
166
+ subprocess.Popen( #pylint: disable=consider-using-with
133
167
  arguments,
134
168
  stdin=stdin,
135
169
  stdout=stdout,
@@ -138,13 +172,29 @@ def start_backend(port: int | None = None) -> None:
138
172
  close_fds=True,
139
173
  )
140
174
 
175
+
141
176
  class ReadScope(Enum):
142
- NEXT = 'next'
143
- BUFFERED = 'buffered'
177
+ """
178
+ Read scope
179
+
180
+ NEXT : Only read data after the start of the read() call
181
+ BUFFERED : Return any data that was present before the read() call
182
+ """
183
+ NEXT = "next"
184
+ BUFFERED = "buffered"
185
+
186
+
187
+ class Adapter(Component[bytes]):
188
+ """
189
+ Adapter class
144
190
 
145
- class Adapter(ABC):
191
+ An adapter permits communication with a hardware device.
192
+ The adapter is the user interface of the backend adapter
193
+ """
194
+ #pylint: disable=too-many-instance-attributes
146
195
  def __init__(
147
196
  self,
197
+ *,
148
198
  descriptor: Descriptor,
149
199
  alias: str = "",
150
200
  stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
@@ -169,9 +219,7 @@ class Adapter(ABC):
169
219
  encoding : str
170
220
  Which encoding to use if str has to be encoded into bytes
171
221
  """
172
- self._init_ok = False
173
- super().__init__()
174
- self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
222
+ super().__init__(LoggerAlias.ADAPTER)
175
223
  self.encoding = encoding
176
224
  self._signal_queue: SignalQueue = SignalQueue()
177
225
  self.event_callback: Callable[[AdapterSignal], None] | None = event_callback
@@ -179,17 +227,12 @@ class Adapter(ABC):
179
227
  self._backend_connection_lock = threading.Lock()
180
228
  self._make_backend_request_queue: queue.Queue[BackendResponse] = queue.Queue()
181
229
  self._make_backend_request_flag = threading.Event()
182
- self.opened = False
230
+ self._opened = False
183
231
  self._alias = alias
184
232
 
185
- if backend_address is None:
186
- self._backend_address = default_host
187
- else:
188
- self._backend_address = backend_address
189
- if backend_port is None:
190
- self._backend_port = BACKEND_PORT
191
- else:
192
- self._backend_port = backend_port
233
+ # Use custom backend or the default one
234
+ self._backend_address = default_host if backend_address is None else backend_address
235
+ self._backend_port = BACKEND_PORT if backend_port is None else backend_port
193
236
 
194
237
  # There a two possibilities here
195
238
  # A) The descriptor is fully initialized
@@ -197,9 +240,6 @@ class Adapter(ABC):
197
240
  # B) The descriptor is not fully initialized
198
241
  # -> Wait for the protocol to set defaults and then connect the adapter
199
242
 
200
- assert isinstance(
201
- descriptor, Descriptor
202
- ), "descriptor must be a Descriptor class"
203
243
  self.descriptor = descriptor
204
244
  self.auto_open = auto_open
205
245
 
@@ -215,7 +255,7 @@ class Adapter(ABC):
215
255
  elif isinstance(stop_conditions, list):
216
256
  self._stop_conditions = stop_conditions
217
257
  else:
218
- raise ValueError('Invalid stop_conditions')
258
+ raise ValueError("Invalid stop_conditions")
219
259
 
220
260
  # Set the timeout
221
261
  self.is_default_timeout = False
@@ -239,7 +279,6 @@ class Adapter(ABC):
239
279
  self.connect()
240
280
 
241
281
  weakref.finalize(self, self._cleanup)
242
- self._init_ok = True
243
282
 
244
283
  # We can auto-open only if auto_open is enabled and if
245
284
  # connection with the backend has been made (descriptor initialized)
@@ -247,6 +286,9 @@ class Adapter(ABC):
247
286
  self.open()
248
287
 
249
288
  def connect(self) -> None:
289
+ """
290
+ Connect to the backend
291
+ """
250
292
  if self.backend_connection is not None:
251
293
  # No need to connect, everything has been done already
252
294
  return
@@ -272,7 +314,7 @@ class Adapter(ABC):
272
314
  try:
273
315
  self.backend_connection = Client((default_host, BACKEND_PORT))
274
316
  except ConnectionRefusedError as err:
275
- raise RuntimeError("Failed to connect to backend") from err
317
+ raise BackendCommunicationError("Failed to connect to backend") from err
276
318
  self._read_thread = threading.Thread(
277
319
  target=self.read_thread,
278
320
  args=(self._signal_queue, self._make_backend_request_queue),
@@ -289,7 +331,12 @@ class Adapter(ABC):
289
331
  if self.auto_open:
290
332
  self.open()
291
333
 
292
- def _make_backend_request(self, action: Action, *args: Any) -> BackendResponse:
334
+ def _make_backend_request(
335
+ self,
336
+ action: Action,
337
+ *args: Any,
338
+ timeout: float = BACKEND_REQUEST_DEFAULT_TIMEOUT,
339
+ ) -> BackendResponse:
293
340
  """
294
341
  Send a request to the backend and return the arguments
295
342
  """
@@ -300,11 +347,9 @@ class Adapter(ABC):
300
347
 
301
348
  self._make_backend_request_flag.set()
302
349
  try:
303
- response = self._make_backend_request_queue.get(
304
- timeout=BACKEND_REQUEST_DEFAULT_TIMEOUT
305
- )
350
+ response = self._make_backend_request_queue.get(timeout=timeout)
306
351
  except queue.Empty as err:
307
- raise RuntimeError(
352
+ raise BackendCommunicationError(
308
353
  f"Failed to receive response from backend to {action}"
309
354
  ) from err
310
355
 
@@ -320,24 +365,33 @@ class Adapter(ABC):
320
365
  signal_queue: SignalQueue,
321
366
  request_queue: queue.Queue[BackendResponse],
322
367
  ) -> None:
368
+ """
369
+ Main adapter thread, it constantly listens for data coming from the backend
370
+
371
+ - signal -> put only the signal in the signal queue
372
+ - otherwise -> put the whole request in the request queue
373
+ """
323
374
  while True:
324
375
  try:
325
376
  if self.backend_connection is None:
326
377
  raise RuntimeError("Backend connection wasn't initialized")
327
378
  response: tuple[Any, ...] = self.backend_connection.recv()
328
379
  except (EOFError, TypeError, OSError):
329
- signal_queue.put(AdapterDisconnected())
380
+ signal_queue.put(AdapterDisconnectedSignal())
330
381
  request_queue.put((Action.ERROR_BACKEND_DISCONNECTED,))
331
382
  break
332
383
  else:
333
384
  if not isinstance(response, tuple):
334
- raise RuntimeError(f"Invalid response from backend : {response}")
385
+ raise BackendCommunicationError(
386
+ f"Invalid response from backend : {response}"
387
+ )
335
388
  action = Action(response[0])
336
389
 
337
390
  if action == Action.ADAPTER_SIGNAL:
338
- #if is_event(action):
339
391
  if len(response) <= 1:
340
- raise RuntimeError(f"Invalid event response : {response}")
392
+ raise BackendCommunicationError(
393
+ f"Invalid event response : {response}"
394
+ )
341
395
  signal: AdapterSignal = response[1]
342
396
  if self.event_callback is not None:
343
397
  self.event_callback(signal)
@@ -361,7 +415,8 @@ class Adapter(ABC):
361
415
 
362
416
  def set_default_timeout(self, default_timeout: Timeout | None) -> None:
363
417
  """
364
- Set the default timeout for this adapter. If a previous timeout has been set, it will be fused
418
+ Set the default timeout for this adapter. If a previous
419
+ timeout has been set, it will be fused
365
420
 
366
421
  Parameters
367
422
  ----------
@@ -371,7 +426,9 @@ class Adapter(ABC):
371
426
  self._logger.debug(f"Setting default timeout to {default_timeout}")
372
427
  self._timeout = default_timeout
373
428
 
374
- def set_stop_conditions(self, stop_conditions: StopCondition | None | list[StopCondition]) -> None:
429
+ def set_stop_conditions(
430
+ self, stop_conditions: StopCondition | None | list[StopCondition]
431
+ ) -> None:
375
432
  """
376
433
  Overwrite the stop-condition
377
434
 
@@ -399,7 +456,7 @@ class Adapter(ABC):
399
456
  if self._default_stop_condition:
400
457
  self.set_stop_conditions(stop_condition)
401
458
 
402
- def flushRead(self) -> None:
459
+ def flush_read(self) -> None:
403
460
  """
404
461
  Flush the input buffer
405
462
  """
@@ -411,7 +468,6 @@ class Adapter(ABC):
411
468
  self._signal_queue.get(block=False)
412
469
  except queue.Empty:
413
470
  break
414
-
415
471
 
416
472
  def previous_read_buffer_empty(self) -> bool:
417
473
  """
@@ -427,9 +483,20 @@ class Adapter(ABC):
427
483
  """
428
484
  Start communication with the device
429
485
  """
430
- self._make_backend_request(Action.OPEN, self._stop_conditions)
486
+ self._make_backend_request(
487
+ Action.OPEN,
488
+ self._stop_conditions,
489
+ timeout=BACKEND_REQUEST_DEFAULT_TIMEOUT + DEFAULT_ADAPTER_OPEN_TIMEOUT,
490
+ )
431
491
  self._logger.info("Adapter opened")
432
- self.opened = True
492
+ self._opened = True
493
+
494
+ def try_open(self) -> bool:
495
+ try:
496
+ self.open()
497
+ except AdapterFailedToOpen:
498
+ return False
499
+ return True
433
500
 
434
501
  def close(self, force: bool = False) -> None:
435
502
  """
@@ -445,9 +512,19 @@ class Adapter(ABC):
445
512
  if self.backend_connection is not None:
446
513
  self.backend_connection.close()
447
514
 
448
- self.opened = False
515
+ self._opened = False
449
516
 
450
- def write(self, data: bytes | str) -> None:
517
+ def is_opened(self) -> bool:
518
+ """
519
+ Return True if the adapter is opened and False otherwise
520
+
521
+ Returns
522
+ -------
523
+ opened : bool
524
+ """
525
+ return self._opened
526
+
527
+ def write(self, data: bytes) -> None:
451
528
  """
452
529
  Send data to the device
453
530
 
@@ -464,7 +541,7 @@ class Adapter(ABC):
464
541
  self,
465
542
  timeout: Timeout | EllipsisType | None = ...,
466
543
  stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
467
- scope : str = ReadScope.BUFFERED.value,
544
+ scope: str = ReadScope.BUFFERED.value,
468
545
  ) -> AdapterReadPayload:
469
546
  """
470
547
  Read data from the device
@@ -490,13 +567,13 @@ class Adapter(ABC):
490
567
  read_timeout = self._timeout
491
568
  else:
492
569
  read_timeout = any_to_timeout(timeout)
493
-
570
+
494
571
  if read_timeout is None:
495
572
  raise RuntimeError("Cannot read without setting a timeout")
496
573
 
497
574
  if stop_conditions is not ...:
498
575
  if isinstance(stop_conditions, StopCondition):
499
- stop_conditions = [stop_conditions]
576
+ stop_conditions = [stop_conditions]
500
577
  self._make_backend_request(Action.SET_STOP_CONDITIONs, stop_conditions)
501
578
 
502
579
  # First, we check if data is in the buffer and if the scope if set to BUFFERED
@@ -525,52 +602,54 @@ class Adapter(ABC):
525
602
  # Wait for the response time + a bit more
526
603
  read_stop_timestamp = read_init_time + _response
527
604
 
528
- # else:
529
- # start_read_id = None
530
- # read_init_time = None
531
-
532
-
533
605
  while True:
534
606
  try:
535
607
  if read_stop_timestamp is None:
536
608
  queue_timeout = None
537
609
  else:
538
- queue_timeout = max(0, read_stop_timestamp - time.time() + EXTRA_BUFFER_RESPONSE_TIME)
610
+ queue_timeout = max(
611
+ 0,
612
+ read_stop_timestamp
613
+ - time.time()
614
+ + EXTRA_BUFFER_RESPONSE_TIME,
615
+ )
539
616
 
540
617
  signal = self._signal_queue.get(timeout=queue_timeout)
541
- except queue.Empty:
542
- raise RuntimeError('Failed to receive response from backend')
618
+ except queue.Empty as e:
619
+ raise BackendCommunicationError(
620
+ "Failed to receive response from backend"
621
+ ) from e
543
622
  if isinstance(signal, AdapterReadPayload):
544
623
  output_signal = signal
545
624
  break
546
- elif isinstance(signal, AdapterDisconnected):
547
- raise RuntimeError("Adapter disconnected")
548
- elif isinstance(signal, AdapterResponseTimeout):
625
+ if isinstance(signal, AdapterDisconnectedSignal):
626
+ raise AdapterDisconnected()
627
+ if isinstance(signal, AdapterResponseTimeout):
549
628
  if start_read_id == signal.identifier:
550
629
  output_signal = None
551
630
  break
552
631
  # Otherwise ignore it
553
632
 
554
633
  if output_signal is None:
555
- # TODO : Make read_timeout always Timeout, never None ?
556
634
  match read_timeout.action:
557
635
  case TimeoutAction.RETURN_EMPTY:
558
636
  t = time.time()
559
637
  return AdapterReadPayload(
560
- fragments=[Fragment(b'', t)],
638
+ fragments=[Fragment(b"", t)],
561
639
  stop_timestamp=t,
562
640
  stop_condition_type=StopConditionType.TIMEOUT,
563
641
  previous_read_buffer_used=False,
564
642
  response_timestamp=None,
565
- response_delay=None
643
+ response_delay=None,
566
644
  )
567
645
  case TimeoutAction.ERROR:
568
- raise TimeoutError(
569
- f"No response received from device within {read_timeout.response()} seconds"
646
+ timeout_value = read_timeout.response()
647
+ raise AdapterTimeoutError(
648
+ float("nan") if timeout_value is None else timeout_value
570
649
  )
571
650
  case _:
572
651
  raise NotImplementedError()
573
-
652
+
574
653
  else:
575
654
  return output_signal
576
655
 
@@ -583,7 +662,7 @@ class Adapter(ABC):
583
662
  return signal.data()
584
663
 
585
664
  def _cleanup(self) -> None:
586
- if self._init_ok and self.opened:
665
+ if self._opened:
587
666
  self.close()
588
667
 
589
668
  def query_detailed(
@@ -598,7 +677,7 @@ class Adapter(ABC):
598
677
  - write
599
678
  - read
600
679
  """
601
- self.flushRead()
680
+ self.flush_read()
602
681
  self.write(data)
603
682
  return self.read_detailed(timeout=timeout, stop_conditions=stop_conditions)
604
683
 
syndesi/adapters/auto.py CHANGED
@@ -1,17 +1,19 @@
1
1
  # File : auto.py
2
2
  # Author : Sébastien Deriaz
3
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
4
+
5
+ """
6
+ Automatic adapter function
7
+ This function is used to automatically choose an adapter based on the user's input
8
+ 192.168.1.1 -> IP
9
+ COM4 -> Serial
10
+ /dev/tty* -> Serial
11
+ etc...
12
+ If an adapter class is supplied, it is passed through
13
+
14
+ Additionnaly, it is possible to do COM4:115200 so as to make the life of the user easier
15
+ Same with /dev/ttyACM0:115200
16
+ """
15
17
 
16
18
 
17
19
  from .adapter import Adapter
@@ -27,6 +29,15 @@ from .visa import Visa
27
29
 
28
30
 
29
31
  def auto_adapter(adapter_or_string: Adapter | str) -> Adapter:
32
+ """
33
+ Create an adapter from a string or an adapter
34
+
35
+ - <int>.<int>.<int>.<int>[:<int>] -> IP
36
+ - x.y[:<int>] -> IP
37
+ - COM<int> -> SerialPort
38
+ - /dev/tty[ACM|USB]<int> -> SerialPort
39
+
40
+ """
30
41
  if isinstance(adapter_or_string, Adapter):
31
42
  # Simply return it
32
43
  return adapter_or_string
@@ -39,12 +50,12 @@ def auto_adapter(adapter_or_string: Adapter | str) -> Adapter:
39
50
  port=descriptor.port,
40
51
  transport=descriptor.transport.value,
41
52
  )
42
- elif isinstance(descriptor, SerialPortDescriptor):
53
+ if isinstance(descriptor, SerialPortDescriptor):
43
54
  return SerialPort(port=descriptor.port, baudrate=descriptor.baudrate)
44
- elif isinstance(descriptor, VisaDescriptor):
55
+ if isinstance(descriptor, VisaDescriptor):
45
56
  return Visa(descriptor=descriptor.descriptor)
46
- else:
47
- raise RuntimeError(f"Invalid descriptor : {descriptor}")
57
+
58
+ raise RuntimeError(f"Invalid descriptor : {descriptor}")
48
59
 
49
60
  else:
50
61
  raise ValueError(f"Invalid adapter : {adapter_or_string}")