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.
Files changed (80) hide show
  1. syndesi/__init__.py +3 -0
  2. syndesi/__main__.py +4 -0
  3. syndesi/adapters/__init__.py +3 -3
  4. syndesi/adapters/adapter.py +528 -0
  5. syndesi/adapters/auto.py +49 -0
  6. syndesi/adapters/backend/adapter_backend.py +364 -0
  7. syndesi/adapters/backend/adapter_manager.py +49 -0
  8. syndesi/adapters/backend/adapter_session.py +363 -0
  9. syndesi/adapters/backend/backend.py +369 -0
  10. syndesi/{descriptors/syndesi/__init__.py → adapters/backend/backend_status.py} +0 -0
  11. syndesi/adapters/backend/backend_tools.py +55 -0
  12. syndesi/adapters/backend/descriptors.py +140 -0
  13. syndesi/adapters/backend/ip_backend.py +137 -0
  14. syndesi/adapters/backend/serialport_backend.py +232 -0
  15. syndesi/adapters/backend/stop_condition_backend.py +319 -0
  16. syndesi/adapters/backend/timed_queue.py +39 -0
  17. syndesi/adapters/backend/timeout.py +252 -0
  18. syndesi/adapters/backend/visa_backend.py +179 -0
  19. syndesi/adapters/ip.py +75 -65
  20. syndesi/adapters/ip_server.py +102 -0
  21. syndesi/adapters/serialport.py +74 -0
  22. syndesi/adapters/stop_condition.py +102 -0
  23. syndesi/adapters/timeout.py +99 -0
  24. syndesi/adapters/visa.py +38 -54
  25. {tests → syndesi/cli}/__init__.py +0 -0
  26. syndesi/cli/adapter_shell.py +210 -0
  27. syndesi/cli/backend_console.py +88 -0
  28. syndesi/cli/backend_status.py +212 -0
  29. syndesi/cli/backend_wrapper.py +50 -0
  30. syndesi/cli/shell.py +269 -0
  31. syndesi/protocols/__init__.py +8 -3
  32. syndesi/protocols/delimited.py +145 -45
  33. syndesi/protocols/modbus.py +1561 -0
  34. syndesi/protocols/protocol.py +71 -0
  35. syndesi/protocols/raw.py +45 -60
  36. syndesi/protocols/scpi.py +154 -33
  37. syndesi/scripts/__init__.py +0 -0
  38. syndesi/scripts/syndesi.py +52 -0
  39. syndesi/scripts/syndesi_backend.py +43 -0
  40. syndesi/tools/backend_api.py +190 -0
  41. syndesi/tools/backend_logger.py +53 -0
  42. syndesi/tools/errors.py +18 -0
  43. syndesi/tools/exceptions.py +7 -1
  44. syndesi/tools/internal.py +0 -0
  45. syndesi/tools/log.py +143 -0
  46. syndesi/tools/log_settings.py +16 -0
  47. syndesi/tools/types.py +87 -46
  48. syndesi/version.py +3 -0
  49. {syndesi-0.1.4.dist-info → syndesi-0.3.1.dist-info}/METADATA +24 -22
  50. syndesi-0.3.1.dist-info/RECORD +56 -0
  51. {syndesi-0.1.4.dist-info → syndesi-0.3.1.dist-info}/WHEEL +1 -1
  52. syndesi-0.3.1.dist-info/entry_points.txt +3 -0
  53. {syndesi-0.1.4.dist-info → syndesi-0.3.1.dist-info/licenses}/LICENSE +194 -194
  54. syndesi-0.3.1.dist-info/top_level.txt +1 -0
  55. syndesi/adapters/IP.py +0 -81
  56. syndesi/adapters/VISA.py +0 -50
  57. syndesi/adapters/iadapter.py +0 -73
  58. syndesi/adapters/serial.py +0 -37
  59. syndesi/descriptors/IP.py +0 -9
  60. syndesi/descriptors/Serial.py +0 -10
  61. syndesi/descriptors/VISA.py +0 -31
  62. syndesi/descriptors/__init__.py +0 -1
  63. syndesi/descriptors/descriptor.py +0 -9
  64. syndesi/descriptors/ip.py +0 -9
  65. syndesi/descriptors/syndesi/Syndesi.py +0 -9
  66. syndesi/descriptors/syndesi/_device.py +0 -25
  67. syndesi/descriptors/syndesi/devices.py +0 -10
  68. syndesi/descriptors/syndesi/frame.py +0 -133
  69. syndesi/descriptors/syndesi/network.py +0 -41
  70. syndesi/descriptors/syndesi/payload.py +0 -11
  71. syndesi/descriptors/syndesi/sdid.py +0 -21
  72. syndesi/descriptors/visa.py +0 -31
  73. syndesi/protocols/commands.py +0 -56
  74. syndesi/protocols/iprotocol.py +0 -14
  75. syndesi/protocols/sdp.py +0 -14
  76. syndesi/tools/stop_conditions.py +0 -148
  77. syndesi-0.1.4.data/scripts/syndesi +0 -1
  78. syndesi-0.1.4.dist-info/RECORD +0 -42
  79. syndesi-0.1.4.dist-info/top_level.txt +0 -2
  80. {experiments → syndesi/adapters/backend}/__init__.py +0 -0
syndesi/__init__.py CHANGED
@@ -0,0 +1,3 @@
1
+ from .adapters import IP, SerialPort, Visa
2
+ from .protocols import SCPI, Delimited, Modbus, Raw
3
+ from .tools.log import log
syndesi/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ # from .cli.scripts import main
2
+
3
+ # if __name__ == "__main__":
4
+ # main()
@@ -1,4 +1,4 @@
1
- from .iadapter import IAdapter
2
1
  from .ip import IP
3
- from .serial import Serial
4
- from .visa import VISA
2
+ from .serialport import SerialPort
3
+ from .timeout import Timeout
4
+ from .visa import Visa
@@ -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__()
@@ -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}")