syndesi 0.4.1__py3-none-any.whl → 0.4.2__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 (36) hide show
  1. syndesi/adapters/adapter.py +48 -44
  2. syndesi/adapters/backend/adapter_backend.py +54 -36
  3. syndesi/adapters/backend/adapter_session.py +24 -25
  4. syndesi/adapters/backend/backend_status.py +0 -0
  5. syndesi/adapters/backend/backend_tools.py +1 -1
  6. syndesi/adapters/backend/descriptors.py +1 -1
  7. syndesi/adapters/backend/ip_backend.py +1 -1
  8. syndesi/adapters/backend/serialport_backend.py +6 -7
  9. syndesi/adapters/backend/stop_condition_backend.py +47 -26
  10. syndesi/adapters/backend/visa_backend.py +2 -2
  11. syndesi/adapters/ip.py +3 -3
  12. syndesi/adapters/stop_condition.py +10 -83
  13. syndesi/adapters/timeout.py +2 -29
  14. syndesi/cli/backend_status.py +7 -9
  15. syndesi/cli/console.py +1 -54
  16. syndesi/cli/shell.py +1 -14
  17. syndesi/cli/shell_tools.py +0 -5
  18. syndesi/protocols/delimited.py +7 -22
  19. syndesi/protocols/modbus.py +3 -3
  20. syndesi/protocols/raw.py +2 -2
  21. syndesi/protocols/scpi.py +5 -4
  22. syndesi/scripts/syndesi.py +1 -3
  23. syndesi/tools/backend_api.py +4 -37
  24. syndesi/tools/backend_logger.py +0 -1
  25. syndesi/tools/errors.py +4 -5
  26. syndesi/tools/log.py +0 -88
  27. syndesi/tools/types.py +0 -44
  28. syndesi/version.py +1 -1
  29. syndesi-0.4.2.dist-info/METADATA +96 -0
  30. syndesi-0.4.2.dist-info/RECORD +60 -0
  31. syndesi-0.4.1.dist-info/METADATA +0 -123
  32. syndesi-0.4.1.dist-info/RECORD +0 -59
  33. {syndesi-0.4.1.dist-info → syndesi-0.4.2.dist-info}/WHEEL +0 -0
  34. {syndesi-0.4.1.dist-info → syndesi-0.4.2.dist-info}/entry_points.txt +0 -0
  35. {syndesi-0.4.1.dist-info → syndesi-0.4.2.dist-info}/licenses/LICENSE +0 -0
  36. {syndesi-0.4.1.dist-info → syndesi-0.4.2.dist-info}/top_level.txt +0 -0
@@ -333,7 +333,7 @@ class Modbus(Protocol):
333
333
  Return data from PDU
334
334
  """
335
335
  if _pdu is None:
336
- raise RuntimeError("Failed to read modbus data")
336
+ raise RuntimeError("Failed to read modbus data")
337
337
  if self._modbus_type == ModbusType.TCP:
338
338
  # Return raw data
339
339
  # data = _pdu
@@ -398,8 +398,8 @@ class Modbus(Protocol):
398
398
  # timeout=Timeout(continuation=1),
399
399
  stop_conditions=[
400
400
  Length(self._length(n_coil_bytes + 2)),
401
- Continuation(time=1)
402
- ] # TODO : convert to multiple stop conditions here
401
+ Continuation(time=1),
402
+ ], # TODO : convert to multiple stop conditions here
403
403
  )
404
404
  response = self._parse_pdu(pdu)
405
405
  self._raise_if_error(response, exception_codes=EXCEPTIONS)
syndesi/protocols/raw.py CHANGED
@@ -54,7 +54,7 @@ class Raw(Protocol):
54
54
  self._adapter.flushRead()
55
55
  self.write(data)
56
56
  return self.read(timeout=timeout, stop_conditions=stop_conditions)
57
-
57
+
58
58
  def query_detailed(
59
59
  self,
60
60
  data: bytes,
@@ -71,7 +71,7 @@ class Raw(Protocol):
71
71
  stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
72
72
  ) -> bytes:
73
73
  signal = self.read_detailed(timeout=timeout, stop_conditions=stop_conditions)
74
- return signal.data()
74
+ return signal.data()
75
75
 
76
76
  def read_detailed(
77
77
  self,
syndesi/protocols/scpi.py CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  from types import EllipsisType
7
7
 
8
- from syndesi.adapters.backend.adapter_backend import AdapterReadPayload, AdapterSignal
8
+ from syndesi.adapters.backend.adapter_backend import AdapterReadPayload
9
9
 
10
10
  from ..adapters.adapter import Adapter
11
11
  from ..adapters.ip import IP
@@ -121,7 +121,7 @@ class SCPI(Protocol):
121
121
  timeout=timeout,
122
122
  stop_conditions=stop_conditions,
123
123
  )
124
- return signal.data()
124
+ return signal.data()
125
125
 
126
126
  def read_detailed(
127
127
  self,
@@ -129,7 +129,9 @@ class SCPI(Protocol):
129
129
  stop_conditions: StopCondition | EllipsisType | list[StopCondition] = ...,
130
130
  ) -> AdapterReadPayload:
131
131
 
132
- signal = self._adapter.read_detailed(timeout=timeout, stop_conditions=stop_conditions)
132
+ signal = self._adapter.read_detailed(
133
+ timeout=timeout, stop_conditions=stop_conditions
134
+ )
133
135
  return signal
134
136
 
135
137
  def read(
@@ -140,4 +142,3 @@ class SCPI(Protocol):
140
142
  signal = self.read_detailed(timeout=timeout, stop_conditions=stop_conditions)
141
143
  raw_data = signal.data()
142
144
  return self._unformatCommand(self._from_bytes(raw_data))
143
-
@@ -22,9 +22,7 @@ def main() -> None:
22
22
  prog="syndesi", description="Syndesi command line tool", epilog=""
23
23
  )
24
24
 
25
- parser.add_argument(
26
- "--version", action="version", version=f"%(prog)s {__version__}"
27
- )
25
+ parser.add_argument("--version", action="version", version=f"Syndesi {__version__}")
28
26
  parser.add_argument("-v", "--verbose", action="store_true")
29
27
  parser.add_argument(
30
28
  "command",
@@ -26,25 +26,22 @@ default_host = LOCALHOST
26
26
 
27
27
  EXTRA_BUFFER_RESPONSE_TIME = 1
28
28
 
29
+
29
30
  class Action(Enum):
30
31
  # All adapters
31
32
  SELECT_ADAPTER = "select"
32
33
  OPEN = "open" # (descriptor,stop_condition) -> ()
33
- CLOSE = "close" # (descriptor,force) -> ()
34
- #FORCE_CLOSE = "force_close" # (descriptor,) -> ()
34
+ CLOSE = "close" # (descriptor,force) -> ()
35
+ # FORCE_CLOSE = "force_close" # (descriptor,) -> ()
35
36
  WRITE = "write" # (descriptor,data) -> ()
36
37
  READ = "read" # (descriptor,full_output,temporary_timeout,temporary_stop_condition) -> (data,metrics)
37
38
  SET_STOP_CONDITIONs = "set_stop_condition" # (descriptor,stop_condition)
38
39
  FLUSHREAD = "flushread"
39
40
  START_READ = "start_read" # Start a read (descriptor,response_time)
40
41
  RESPONSE_TIMEOUT = "response_timeout"
41
- #GET_BACKEND_TIME = "get_time"
42
42
 
43
43
  # Signal
44
44
  ADAPTER_SIGNAL = "adapter_signal"
45
- #ADAPTER_EVENT_DATA_READY = "event_adapter_data_ready"
46
- #ADAPTER_EVENT_DISCONNECTED = "event_adapter_disconnected"
47
- #ADAPTER_EVENT_READ_INIT = "event_adapter_read_init"
48
45
 
49
46
  # Other
50
47
  SET_ROLE_ADAPTER = (
@@ -72,22 +69,15 @@ class Action(Enum):
72
69
  ERROR_FAILED_TO_OPEN = "error_failed_to_open"
73
70
 
74
71
 
75
- # def is_event(action: Action) -> bool:
76
- # return action.value.startswith("event_")
77
-
78
-
79
72
  def is_action_error(action: Action) -> bool:
80
73
  return action.value.startswith("error_")
81
74
 
82
-
83
75
  class BackendException(Exception):
84
76
  pass
85
77
 
86
-
87
78
  class ValidFragment(Protocol):
88
79
  data: bytes
89
80
 
90
-
91
81
  @dataclass
92
82
  class Fragment:
93
83
  data: bytes
@@ -104,23 +94,6 @@ class Fragment:
104
94
  # raise IndexError('Cannot index invalid fragment')
105
95
  return Fragment(self.data[key], self.timestamp)
106
96
 
107
-
108
- # def get_conn_addresses(conn: Connection) -> tuple[str, str]:
109
- # try:
110
- # fd = conn.fileno()
111
- # except OSError:
112
- # return ("closed", "closed")
113
- # else:
114
- # sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
115
- # try:
116
- # # address, port = sock.getpeername() # (IP, port) tuple
117
- # peer_address = sock.getpeername()
118
- # sock_address = sock.getsockname()
119
- # return sock_address, peer_address
120
- # except Exception:
121
- # return ("error", "closed")
122
-
123
-
124
97
  BackendResponse = tuple[object, ...]
125
98
 
126
99
 
@@ -199,10 +172,4 @@ class AdapterBackendStatus(Enum):
199
172
 
200
173
  class ClientStatus(Enum):
201
174
  DISCONNECTED = 0
202
- CONNECTED = 1
203
-
204
-
205
- # class StatusSnapshot(TypedDict):
206
- # type : Literal['snapshot']
207
- # adapters :
208
- # #clients : List[str]
175
+ CONNECTED = 1
@@ -41,7 +41,6 @@ class BackendLogger(threading.Thread):
41
41
  self.conn_description = conn.local()
42
42
  self._logger.info("Backend connected")
43
43
 
44
-
45
44
  try:
46
45
  backend_request(conn.conn, Action.SET_ROLE_LOGGER)
47
46
  except BackendCommunicationError:
syndesi/tools/errors.py CHANGED
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
 
7
7
  PACKAGE_PATH = Path(__file__).resolve().parent.parent
8
8
 
9
+
9
10
  class SyndesiError(Exception):
10
11
  """Base class for all Syndesi errors"""
11
12
 
@@ -26,7 +27,7 @@ class AdapterError(SyndesiError):
26
27
  """Error inside an adapter frontend"""
27
28
 
28
29
 
29
- def make_error_description(e : Exception) -> str:
30
+ def make_error_description(e: Exception) -> str:
30
31
  tb = e.__traceback__
31
32
  if tb is None:
32
33
  error_message = ""
@@ -44,8 +45,6 @@ def make_error_description(e : Exception) -> str:
44
45
  frame = tb.tb_frame
45
46
  line_no = tb.tb_lineno
46
47
  filename = frame.f_code.co_filename
47
- error_message = (
48
- f"{_type} : {extra_arguments} {filename}:{line_no}"
49
- )
48
+ error_message = f"{_type} : {extra_arguments} {filename}:{line_no}"
50
49
 
51
- return error_message
50
+ return error_message
syndesi/tools/log.py CHANGED
@@ -9,94 +9,6 @@ from typing import TextIO
9
9
  from .backend_logger import BackendLogger
10
10
  from .log_settings import LoggerAlias
11
11
 
12
- # class LogManager:
13
- # _instance = None
14
- # _lock = threading.Lock()
15
- # _initialized : bool = False
16
-
17
-
18
- # DEFAULT_FORMATTER = logging.Formatter(
19
- # "%(asctime)s:%(name)s:%(levelname)s:%(message)s"
20
- # )
21
- # DEFAULT_LOG_LEVEL = logging.ERROR
22
-
23
- # def __new__(cls) -> "LogManager":
24
- # with cls._lock:
25
- # if cls._instance is None:
26
- # cls._instance = super(LogManager, cls).__new__(cls)
27
- # cls._instance._initialized = False
28
- # return cls._instance
29
-
30
- # def __init__(self) -> None:
31
- # if self._initialized:
32
- # return
33
- # self._initialized = True
34
- # self._file_handler : logging.Handler | None = None
35
- # self._all_loggers = False
36
- # self._loggers : list[str] = []
37
- # self._level = self.DEFAULT_LOG_LEVEL
38
- # self._stream_handler : logging.StreamHandler[TextIO] | None = None
39
- # self._backend_logger = BackendLogger()
40
- # self._backend_logger.start()
41
-
42
- # def set_log_level(self, level: str | int) -> None:
43
- # if isinstance(level, str):
44
- # if not hasattr(logging, level.upper()):
45
- # raise ValueError(f"Invalid log level: {level}")
46
- # internal_level = getattr(logging, level.upper())
47
- # elif isinstance(level, int):
48
- # internal_level = level
49
- # else:
50
- # raise ValueError(f"Invalid level : {level}")
51
-
52
- # self._level = internal_level
53
- # self.update_loggers()
54
-
55
- # def set_console_log(self, enabled: bool) -> None:
56
- # if enabled:
57
- # self._stream_handler = logging.StreamHandler()
58
- # self._stream_handler.setFormatter(self.DEFAULT_FORMATTER)
59
- # else:
60
- # self._stream_handler = None
61
-
62
- # def set_log_file(self, file: str | None) -> None:
63
- # if file is not None:
64
- # self._file_handler = logging.FileHandler(file)
65
- # self._file_handler.setFormatter(self.DEFAULT_FORMATTER)
66
- # else:
67
- # if self._file_handler is not None:
68
- # self._file_handler.close()
69
- # self._file_handler = None
70
-
71
- # def set_logger_filter(self, loggers: list[str] | str) -> None:
72
- # self._all_loggers = False
73
- # self._loggers = []
74
- # if isinstance(loggers, list):
75
- # self._loggers = loggers
76
- # elif isinstance(loggers, str):
77
- # if loggers == "all":
78
- # self._all_loggers = True
79
- # else:
80
- # self._loggers = [loggers]
81
- # else:
82
- # raise ValueError("Invalid argument loggers")
83
-
84
- # def update_loggers(self) -> None:
85
- # # 1) Remove everything
86
- # for alias in LoggerAlias:
87
- # logger = logging.getLogger(alias.value)
88
- # logger.handlers.clear()
89
- # # 2) Update
90
- # for alias in LoggerAlias:
91
- # if self._all_loggers or alias.value in self._loggers:
92
- # logger = logging.getLogger(alias.value)
93
- # if self._file_handler is not None:
94
- # logger.addHandler(self._file_handler)
95
- # if self._stream_handler is not None:
96
- # logger.addHandler(self._stream_handler)
97
- # logger.setLevel(self._level)
98
-
99
-
100
12
  class LogManager:
101
13
  _lock = threading.Lock()
102
14
 
syndesi/tools/types.py CHANGED
@@ -2,20 +2,6 @@
2
2
  # Author : Sébastien Deriaz
3
3
  # License : GPL
4
4
 
5
- # try:
6
- # import numpy as np
7
- # HAS_NUMPY = True
8
- # except ImportError:
9
- # HAS_NUMPY = False
10
- # class _Default : __slots__ = ()
11
- # DEFAULT : Final[_Default] = _Default()
12
- # EllipsisType : TypeAlias = _Default
13
- # def is_default(x : Any) -> TypeGuard[EllipsisType]:
14
- # return x is DEFAULT
15
- # DEFAULT : EllipsisType = ...
16
- # EllipsisType : TypeAlias = EllipsisType
17
- # def is_default(x : Any) -> TypeGuard[EllipsisType]:
18
- # return x is DEFAULT
19
5
  from typing import TYPE_CHECKING, Any, TypeGuard
20
6
 
21
7
  try:
@@ -30,36 +16,6 @@ if TYPE_CHECKING:
30
16
  else:
31
17
  NumberLike = int | float | np.number # runtime will resolve string
32
18
 
33
- # def is_byte_instance(X : Any):
34
- # """
35
- # Check if the given X is an instance of bytearray or bytes
36
-
37
- # Parameters
38
- # ----------
39
- # X : any
40
-
41
- # Returns
42
- # -------
43
- # result : bool
44
- # """
45
- # result = isinstance(X, (bytearray, bytes))
46
- # return result
47
-
48
-
49
- # def assert_byte_instance(*args):
50
- # """
51
- # Checks if the given argument(s) is of type bytes
52
- # or bytes. A TypeError is raised if it isn't the case
53
-
54
- # Parameters
55
- # ----------
56
- # args
57
- # """
58
- # for arg in args:
59
- # if not is_byte_instance(arg):
60
- # raise TypeError(f"Variable {arg} should be of type bytes or bytes")
61
-
62
-
63
19
  def is_number(X: Any) -> TypeGuard[NumberLike]:
64
20
  """
65
21
  Check if the given X is an instance of int or float
syndesi/version.py CHANGED
@@ -1,3 +1,3 @@
1
- __version__ = "0.4.1"
1
+ __version__ = "0.4.2"
2
2
  NAME = "Syndesi"
3
3
  AUTHOR = "Sébastien Deriaz"
@@ -0,0 +1,96 @@
1
+ Metadata-Version: 2.4
2
+ Name: syndesi
3
+ Version: 0.4.2
4
+ Summary: Syndesi
5
+ Author-email: Sébastien Deriaz <sebastien.deriaz1@gmail.com>
6
+ License: GPL
7
+ Keywords: python,syndesi,interface,ethernet,serial,visa
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Education
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: Unix
12
+ Classifier: Operating System :: MacOS :: MacOS X
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: prompt_toolkit
18
+ Requires-Dist: pyserial
19
+ Requires-Dist: rich
20
+ Requires-Dist: platformdirs
21
+ Dynamic: license-file
22
+
23
+ # Syndesi Python Implementation
24
+
25
+ Syndesi description is available [here](https://github.com/syndesi-project/Syndesi/README.md)
26
+
27
+ Syndesi is a modular Python framework designed to streamline communication and control of a wide range of electronic instruments and devices. By providing a unified abstraction layer for adapters, protocols, and device drivers, Syndesi enables seamless integration with test equipment such as multimeters, oscilloscopes, power supplies, UART/USB devices, and more. Its flexible architecture supports both high-level and low-level operations, making it ideal for automation, data acquisition, and custom device interfacing in laboratory, industrial, and research environments.
28
+
29
+ ## Installation
30
+
31
+ The syndesi Python package can be installed through pip
32
+
33
+ ``pip install syndesi``
34
+
35
+ The package can also be installed locally by cloning this repository
36
+
37
+ ```bash
38
+ git clone https://github.com/syndesi-project/Syndesi
39
+ cd Syndesi
40
+ pip install .
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ The user can work with any of the three following layers :
46
+
47
+ - Adapters : low-level communication (IP, UART, ...)
48
+ - Protocols : Encapsulated protocols (Delimited, Modbus, ...)
49
+ - Drivers : Device or application specific commands
50
+
51
+ ### Adapters
52
+
53
+ The adapter allows the user to read and write raw data through IP, serial and VISA
54
+
55
+ ```python
56
+ from syndesi import IP
57
+
58
+ my_adapter = IP('192.168.1.12', port=5025)
59
+
60
+ my_adapter.write(b'ping\n')
61
+
62
+ my_adapter.read() # -> b'pong'
63
+ ```
64
+
65
+ ```python
66
+ from syndesi import SerialPort
67
+
68
+ arduino = SerialPort('/dev/ttyUSB0', baudrate=115200) # COMx on Windows
69
+ arduino.query(b'get_temperature\n') # -> 20.5
70
+ ```
71
+
72
+ ### Protocols
73
+
74
+ Protocols encapsulate and format data
75
+
76
+ ```python
77
+ from syndesi import IP, Delimited
78
+
79
+ my_server = Delimited(IP('test.server.local', port=1234))
80
+
81
+ my_server.query('Hello world\n') # -> Hello world (\n is removed by Delimited)
82
+
83
+ ```
84
+
85
+ ### Drivers
86
+
87
+ A driver only requires an adapter, the protocol (if used) is instanciated internally
88
+
89
+ ```python
90
+ from syndesi_drivers.instruments.mutlimeters.siglent.SDM3055 import SDM3055
91
+ from syndesi.adapters import IP
92
+
93
+ mm = SDM3055(IP("192.168.1.123"))
94
+
95
+ voltage = mm.measure_dc_voltage()
96
+ ```
@@ -0,0 +1,60 @@
1
+ syndesi/__init__.py,sha256=FjPBCp-hgUfpzbl9vvNVybvP3Ml8toibnew9kZH2cQw,360
2
+ syndesi/__main__.py,sha256=S9G9k0SHGJJ9qMdF_txtYryiPdEPTI10bgQ-M-rVITs,75
3
+ syndesi/version.py,sha256=N7YMxKDsV6J_qHl8WtOeRrGxjN8_l6QQ1AFeNkv0fRE,68
4
+ syndesi/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ syndesi/adapters/adapter.py,sha256=7f9vjz6zXj7F-TFS9N0sF7voPhNDXzIUmrlnIxfDQHY,21057
6
+ syndesi/adapters/auto.py,sha256=SSNlo_EpjWJIqjpSyYPOYBKowVsd1TlaS8_-nng-Bp4,1592
7
+ syndesi/adapters/ip.py,sha256=IGzp7h_yqyKjrIE9oDppof6cO7JYu3-sxHYT0vdXCyU,3393
8
+ syndesi/adapters/ip_server.py,sha256=8o0cPRxAjnzTvwTCADy6MaNCDc5Uk0NDMXo0sZXoYCY,3773
9
+ syndesi/adapters/serialport.py,sha256=GeMVfwqgpTL0mR0LfI--4M80EwSg4zvjj03FIGh-x9g,2362
10
+ syndesi/adapters/stop_condition.py,sha256=QRxCAj6soDdALWUBbzT1pjNVHnPPEWygKT5BXitgrWU,2164
11
+ syndesi/adapters/timeout.py,sha256=0uLZzLjVXddvq792o9SJ6v3QDQXM649_pISyEZJZ40A,2443
12
+ syndesi/adapters/visa.py,sha256=dbszb9qFaPc3iiFh8KQV2bQK-QrwTcCUA36yPip4dkU,1406
13
+ syndesi/adapters/backend/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ syndesi/adapters/backend/adapter_backend.py,sha256=E69K25fHZtCWy201UQ1mLYQcyl99KJdNAspheY9r800,14321
15
+ syndesi/adapters/backend/adapter_manager.py,sha256=8t_4D3WGtkvdF5RdnFiBJASD3_xny5eBxP8ZQI5BmgM,1938
16
+ syndesi/adapters/backend/adapter_session.py,sha256=Bjm8Pc40lJo4KCiFi4S-HYI3bCpy4BI9h7T0vPmXALs,13999
17
+ syndesi/adapters/backend/backend.py,sha256=yoEYW9wnkii8b6TkiQNz_iRLjB8kdhzZG3Ky1dcycxI,15862
18
+ syndesi/adapters/backend/backend_status.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ syndesi/adapters/backend/backend_tools.py,sha256=cJKAdDh3cVCDGc1bQmKSuaR3kKWXiov8GU32RYCnJcg,1893
20
+ syndesi/adapters/backend/descriptors.py,sha256=xDkdvVx2t0unxvzGIu8zH02q2QSKb5LBrX4MqtEUfxU,4300
21
+ syndesi/adapters/backend/ip_backend.py,sha256=wiETW21eLOi-JlvZf3yw_SdBxCddV7H5ElMmuHYsjfk,5122
22
+ syndesi/adapters/backend/serialport_backend.py,sha256=DRGgAZtKVIpcy6-AF_36Jj_IbxsyokVRo8K2CZNY17k,8479
23
+ syndesi/adapters/backend/stop_condition_backend.py,sha256=nLOoEUEBep5ii8l2fKzVmc-xjLi06Eh9Pxkx9JPup3o,6988
24
+ syndesi/adapters/backend/timed_queue.py,sha256=CwE3PklJqwwwbjyVvdEXIHXAIMwQSGPeBX3O_Kk4wH8,1163
25
+ syndesi/adapters/backend/timeout.py,sha256=thWUajfgvasPLptf0n5TvWLEjp3F2k0s3EAbAICpdvU,10480
26
+ syndesi/adapters/backend/visa_backend.py,sha256=mI6LWurPRye1esiPekoQT_MNpsmEknQ_AF6GueC8910,6352
27
+ syndesi/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ syndesi/cli/backend_console.py,sha256=9INqGf2EtnuCxFYWZbNCJ3exrx6k04uCh6jno8sUQTw,2971
29
+ syndesi/cli/backend_status.py,sha256=12-tIUTXq1fSQ2wphSHXGZ9u0F5UvXpc0FhGaEAjR6A,11653
30
+ syndesi/cli/backend_wrapper.py,sha256=dbBZs0NaWh2OiV3eh_D6_njfkPj_vYGJsY_SSY7QaCI,1900
31
+ syndesi/cli/console.py,sha256=yiJiZH2r5sZzRERVVfMrWI1yNvrkM16pkrqpT2s8LOg,8149
32
+ syndesi/cli/shell.py,sha256=hZDNuO6TrarWhaCbLeyGiUCOhM2RTW9PUhSryCpRYRc,7147
33
+ syndesi/cli/shell_tools.py,sha256=wqa0d5yELV1my_NhcLw00OXmYP8vKlrlkPqjLF2dCNs,3335
34
+ syndesi/cli/terminal.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
+ syndesi/cli/terminal_apps.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ syndesi/cli/terminal_tools.py,sha256=VsqN-i7MM29Vo0gIUqFmgTyvMRBkcNuw6H3M4jJ4b4o,285
37
+ syndesi/protocols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
+ syndesi/protocols/delimited.py,sha256=RIRjDs83cba8SrmlYPVqdiclYU1GLIjPMJXXTUh0c8M,6410
39
+ syndesi/protocols/modbus.py,sha256=BjVJKhLbHVpkwUwX2XvTkHchiwBdRU7nsUOYmBXOxfE,52980
40
+ syndesi/protocols/protocol.py,sha256=r4Hdc5UvR3oYsFW7NYiOYQBG-K0MPEJGHygK3jlp4pI,2263
41
+ syndesi/protocols/raw.py,sha256=CxepjGEYaUQBbZ2L1URdg9LeC7ImyIrD51gqwfQrn-A,2754
42
+ syndesi/protocols/scpi.py,sha256=DE1tWCVQ9rAAAfBv3MrxwSdG0TKJUjGLkOfVDU2zNLw,4798
43
+ syndesi/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
+ syndesi/scripts/syndesi.py,sha256=WYAmDh4WtXGiqyRcNebq8yt6sYnMHBbFMa5BPZC96mI,1371
45
+ syndesi/scripts/syndesi_backend.py,sha256=wrJt-_ciuKumOgbcNTcpNGQj7XN_toJ_Mp_IjdA5TZA,914
46
+ syndesi/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
+ syndesi/tools/backend_api.py,sha256=43T-4t1b2dMh-9Pz4mtYfW4IubRzh_qLFgqun4J8vqg,5325
48
+ syndesi/tools/backend_logger.py,sha256=aS15rWfQe4NWmAIvTp5Tf72MQoyWB4YeHA12lUKsjyE,2299
49
+ syndesi/tools/errors.py,sha256=qr-7pdymqHcjlnx3n_2M6-lZJnXQe35mJzv9WYf4Z-I,1202
50
+ syndesi/tools/exceptions.py,sha256=4zWJFMpNgVHMW91zFX-CafVhBFuP-P8h5tgvHlQMWw4,372
51
+ syndesi/tools/internal.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
+ syndesi/tools/log.py,sha256=Z2uv56bXFNyboyfRBBIcTgJ9XjAr6Xur8He3cFkC2CI,4414
53
+ syndesi/tools/log_settings.py,sha256=CEshxSpIZeVu1BUhy-l1Fel1PY_hxb5ywsYQs9UEUVQ,382
54
+ syndesi/tools/types.py,sha256=fkPMLGTr30NFGNRPqZ3AeTAHMiiFmbFlSLDJEzQo6P4,1403
55
+ syndesi-0.4.2.dist-info/licenses/LICENSE,sha256=7oldAMqsditrYeX4dzMFHpFZ-6AkyGi3bha2zeaJB0A,34492
56
+ syndesi-0.4.2.dist-info/METADATA,sha256=37FRINk27uZcPQxaL0w8yriwFLsW8eDISao92gNnsX0,2827
57
+ syndesi-0.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
58
+ syndesi-0.4.2.dist-info/entry_points.txt,sha256=1JbZYMX6PrakEg-6b_M_f58QJ1mGFJKJd7puoPQPyKM,112
59
+ syndesi-0.4.2.dist-info/top_level.txt,sha256=HrY36JU6hFYp_6qv-GuVBBtHYYemn8qhCrqpvXBd1Lg,8
60
+ syndesi-0.4.2.dist-info/RECORD,,
@@ -1,123 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: syndesi
3
- Version: 0.4.1
4
- Summary: Syndesi
5
- Author-email: Sébastien Deriaz <sebastien.deriaz1@gmail.com>
6
- License: GPL
7
- Keywords: python,syndesi,interface,ethernet,serial,visa
8
- Classifier: Development Status :: 3 - Alpha
9
- Classifier: Intended Audience :: Education
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Operating System :: Unix
12
- Classifier: Operating System :: MacOS :: MacOS X
13
- Classifier: Operating System :: Microsoft :: Windows
14
- Requires-Python: >=3.10
15
- Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Requires-Dist: prompt_toolkit
18
- Requires-Dist: pyserial
19
- Requires-Dist: rich
20
- Requires-Dist: platformdirs
21
- Dynamic: license-file
22
-
23
- # Syndesi Python Implementation
24
-
25
- Syndesi description is available [here](https://github.com/syndesi-project/Syndesi/README.md)
26
-
27
- ## Installation
28
-
29
- The syndesi Python package can be installed through pip
30
-
31
- ``pip install syndesi``
32
-
33
- The package can also be installed locally by cloning this repository
34
-
35
- ```bash
36
- git clone https://github.com/syndesi-project/Syndesi
37
- cd Syndesi/Python
38
- pip install .
39
- ```
40
-
41
- ## Usage
42
-
43
-
44
-
45
- To instantiate a device, one must import the device and a suitable adapter
46
-
47
- ```python
48
- # 1) Import the device
49
- from syndesi.drivers.instruments.mutlimeters.siglent.SDM3055 import SDM3055
50
- # 2) Import the adapter
51
- from syndesi.adapters import IP
52
-
53
- # 3) Instantiate the multimeter using its IP
54
- mm = SDM3055(IP("192.168.1.123"))
55
-
56
- ## 4) Use
57
- voltage = mm.measure_dc_voltage()
58
- ```
59
-
60
- ## Layers
61
-
62
- The first layer is the "Device" base class
63
-
64
- The second layer is made of "Primary drivers". First stage drivers implement mid-level communication protocols like Modbus, SDP, Raw, HTTP, SPCI, etc... Those drivers can be instanciated by the user if he wishes to use a device "as is" (i.e without an application driver)
65
-
66
- Next are device drivers. They provide implementation for device-specific operations
67
-
68
- Last are the application drivers. These are used to provide application-specific operations that mar or may not be tied to a particular device.
69
-
70
- Note that both device drivers and application drivers can be omitted and can also be stacked as all first stage drivers, device drivers and application drivers stem from the same base Class
71
-
72
- ## Usecases
73
-
74
- - Test gear (multimeters, oscilloscopes, power supply, etc...)
75
- - set values (output voltage, settings, etc...)
76
- - get values (measured voltage, trace, screenshot)
77
- - continuously read data (UART multimeter for instance)
78
- - UART devices (Arduinos, etc...)
79
- - Send / receive raw data
80
- - Custom drivers
81
- - Syndesi devices
82
- - Send / receive formatted data
83
- - USB devices
84
- - Send / receive data using the USB protocol
85
-
86
- ## Notes
87
-
88
- 06.09.2023 : bytearray is changed to bytes everywhere
89
-
90
- 23.10.2023 : continuation timeout isn't suitable for TCP, but it can work for UDP as a UDP server can send multiple response packets after a single packet from the client. This can be handled in different ways by firewalls. Thankfull that's none of our business so continuation timeout can be implemented
91
-
92
- 22.11.2023 : The timeout and stop conditions strategy is a bit complicated :
93
-
94
- - What if we receive the message b'ACK\nNCK\n' using a termination stop condition but we receive b'ACK', then a timeout, then b'\nNCK\n' ?
95
- - Should the first part be kept ? should an error be raised at the timeout because nothing was read ?
96
- - Two kinds of timeouts ?
97
- - One where "we read as much as possible during the available time"
98
- - One where "we expect a response within X otherwise it's trash"
99
-
100
- 24.08.2025 : Shell and terminal
101
-
102
- There will be two console tools : shell and terminal
103
-
104
- - Shell : Run commands from drivers, protocols and adapters similar to ipython so "write_coil 0 1", "read_register 12", "read_temperature". I think using the POSIX utility argument syntax is a good idea. Another solution would be to use the Python syntax, so write_register(0, 1)
105
- - Terminal : Send raw data to a driver, a protocol, an adapter, etc... Multiple formats are possible such as text, hex or bytes
106
-
107
- The use could implement both Shell and Terminal in their classes and then call them from syndesi. I'm thinking of 4 ways to get shells or terminals
108
-
109
- - Built-ins "syndesi shell ..." or "syndesi terminal ..."
110
- - relative path "syndesi shell xxx.py" or "syndesi terminal xxx.py"
111
- - plugin, modules behave like built-ins using the plugin/hook system (?) not sure about this one
112
- - dotted syntax, the user can use the dotted syntax to call their modules "syndesi terminal mypkg.myterm:MyTerminal"
113
-
114
- 11.09.2025 : Read and buffers
115
-
116
- When a user calls a read, there's two behaviours :
117
-
118
- 1) Only data that is received after the read() call is be returned (or slightly before with a delay)
119
- 2) The first data available (can be way before the read() call) is returned
120
-
121
- Choice has been made to go with the second solution. This is the expected behaviour and it's easier to simulate the first option by using this architecture than the other way around. To obtain a "read only what's after" we can use the flush method or query with flush enabled
122
-
123
- The consequence is that the adapter has to keep a buffer of data