syndesi 0.4.2__py3-none-any.whl → 0.5.0__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 (57) hide show
  1. syndesi/__init__.py +22 -2
  2. syndesi/adapters/adapter.py +332 -489
  3. syndesi/adapters/adapter_worker.py +820 -0
  4. syndesi/adapters/auto.py +58 -25
  5. syndesi/adapters/descriptors.py +38 -0
  6. syndesi/adapters/ip.py +203 -71
  7. syndesi/adapters/serialport.py +154 -25
  8. syndesi/adapters/stop_conditions.py +354 -0
  9. syndesi/adapters/timeout.py +58 -21
  10. syndesi/adapters/visa.py +236 -11
  11. syndesi/cli/console.py +51 -16
  12. syndesi/cli/shell.py +95 -47
  13. syndesi/cli/terminal_tools.py +8 -8
  14. syndesi/component.py +315 -0
  15. syndesi/protocols/delimited.py +92 -107
  16. syndesi/protocols/modbus.py +2368 -868
  17. syndesi/protocols/protocol.py +186 -33
  18. syndesi/protocols/raw.py +45 -62
  19. syndesi/protocols/scpi.py +65 -102
  20. syndesi/remote/remote.py +188 -0
  21. syndesi/scripts/syndesi.py +12 -2
  22. syndesi/tools/errors.py +49 -31
  23. syndesi/tools/log_settings.py +21 -8
  24. syndesi/tools/{log.py → logmanager.py} +24 -13
  25. syndesi/tools/types.py +9 -7
  26. syndesi/version.py +5 -1
  27. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/METADATA +1 -1
  28. syndesi-0.5.0.dist-info/RECORD +41 -0
  29. syndesi/adapters/backend/__init__.py +0 -0
  30. syndesi/adapters/backend/adapter_backend.py +0 -438
  31. syndesi/adapters/backend/adapter_manager.py +0 -48
  32. syndesi/adapters/backend/adapter_session.py +0 -346
  33. syndesi/adapters/backend/backend.py +0 -438
  34. syndesi/adapters/backend/backend_status.py +0 -0
  35. syndesi/adapters/backend/backend_tools.py +0 -66
  36. syndesi/adapters/backend/descriptors.py +0 -153
  37. syndesi/adapters/backend/ip_backend.py +0 -149
  38. syndesi/adapters/backend/serialport_backend.py +0 -241
  39. syndesi/adapters/backend/stop_condition_backend.py +0 -219
  40. syndesi/adapters/backend/timed_queue.py +0 -39
  41. syndesi/adapters/backend/timeout.py +0 -252
  42. syndesi/adapters/backend/visa_backend.py +0 -197
  43. syndesi/adapters/ip_server.py +0 -102
  44. syndesi/adapters/stop_condition.py +0 -90
  45. syndesi/cli/backend_console.py +0 -96
  46. syndesi/cli/backend_status.py +0 -274
  47. syndesi/cli/backend_wrapper.py +0 -61
  48. syndesi/scripts/syndesi_backend.py +0 -37
  49. syndesi/tools/backend_api.py +0 -175
  50. syndesi/tools/backend_logger.py +0 -64
  51. syndesi/tools/exceptions.py +0 -16
  52. syndesi/tools/internal.py +0 -0
  53. syndesi-0.4.2.dist-info/RECORD +0 -60
  54. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/WHEEL +0 -0
  55. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/entry_points.txt +0 -0
  56. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/licenses/LICENSE +0 -0
  57. {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/top_level.txt +0 -0
@@ -1,252 +0,0 @@
1
- # File : timeout.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
- #
5
- # The timeout backend manages timing when reading from a device, it both
6
- # records how much time each action took and stops communication if it
7
- # exceeds set criteria
8
-
9
- # from enum import Enum
10
- # from typing import Union, Tuple
11
- # from time import time
12
- # import json
13
-
14
- # class TimeoutAction(Enum):
15
- # DISCARD = "discard" # If a timeout is reached, data is discarded
16
- # RETURN = "return" # If a timeout is reached, data is returned (timeout acts as a stop condition)
17
- # STORE = "store" # If a timeout is reached, data is stored and returned on the next read() call
18
- # ERROR = "error" # If a timeout is reached, raise an error
19
-
20
-
21
- # class TimeoutType(Enum):
22
- # RESPONSE = "response"
23
-
24
- # class JsonKey(Enum):
25
- # RESPONSE = "r"
26
- # ACTION = "a"
27
-
28
-
29
- # class TimeoutBackend:
30
- # class _State(Enum):
31
- # WAIT_FOR_RESPONSE = 0
32
- # CONTINUATION = 1
33
-
34
- # def __init__(
35
- # self, response, continuation, total, on_response, on_continuation, on_total
36
- # ) -> None:
37
- # """
38
- # A class to manage timeouts
39
-
40
- # Timeouts are split in three categories :
41
- # - response timeout : the "standard" timeout, (i.e the time it takes for
42
- # a device to start transmitting data)
43
- # - continuation timeout : the time between reception of
44
- # data units (bytes or blocks of data)
45
- # - total timeout : maximum time from start to end of transmission.
46
- # This timeout can stop a communication mid-way. It is used to
47
- # prevent a host from getting stuck reading a constantly streaming device
48
-
49
- # Each timeout is specified in seconds
50
-
51
- # Actions
52
- # - discard : Discard all of data obtained during the read() call if the specified timeout if reached and return empty data
53
- # - return : Return all of the data read up to this point when the specified timeout is reached
54
- # - store : Store all of the data read up to this point in a buffer. Data will be available at the next read() call
55
- # - error : Produce an error
56
-
57
- # Parameters
58
- # ----------
59
- # response : float
60
- # continuation : float
61
- # total : float
62
- # on_response : str
63
- # Action on response timeout (see Actions)
64
- # on_continuation : str
65
- # Action on continuation timeout (see Actions)
66
- # on_total : str
67
- # Action on total timeout (see Actions)
68
- # """
69
- # super().__init__()
70
- # # It is possible to pass a tuple to set response/continuation/total, parse this first if it is the case
71
- # if isinstance(response, (tuple, list)):
72
- # if len(response) >= 3:
73
- # total = response[2]
74
- # if len(response) >= 2:
75
- # continuation = response[1]
76
- # response = response[0]
77
-
78
- # # Timeout values (response, continuation and total)
79
- # self._response = response
80
- # self._continuation = continuation
81
- # self._total = total
82
- # self._on_response = (
83
- # TimeoutAction(on_response) if on_response is not None else None
84
- # )
85
- # self._on_continuation = (
86
- # TimeoutAction(on_continuation) if on_continuation is not None else None
87
- # )
88
- # self._on_total = TimeoutAction(on_total) if on_total is not None else None
89
-
90
- # # State machine flags
91
- # self._state = self._State.WAIT_FOR_RESPONSE
92
- # self._queue_timeout_type = TimeoutType.RESPONSE
93
- # self._last_data_action_origin = TimeoutType.RESPONSE
94
-
95
- # def initiate_read(self, deferred_buffer: bool = False) -> Union[float, None]:
96
- # """
97
- # Initiate a read sequence.
98
-
99
- # The maximum time that should be spent in the next byte read
100
- # is returned
101
-
102
- # Returns
103
- # -------
104
- # stop : bool
105
- # Timeout is reached
106
- # keep : bool
107
- # True if data read up to this point should be kept
108
- # False if data should be discarded
109
- # timeout : float or None
110
- # None is there's no timeout
111
- # """
112
- # self._start_time = time()
113
- # if deferred_buffer:
114
- # self._state = self._State.CONTINUATION
115
- # self._data_action = self._on_continuation
116
- # self._queue_timeout_type = TimeoutType.CONTINUATION
117
- # else:
118
- # self._state = self._State.WAIT_FOR_RESPONSE
119
- # self._data_action = self._on_response
120
- # self._queue_timeout_type = TimeoutType.RESPONSE
121
- # self._last_timestamp = None
122
-
123
- # self.response_time = None
124
- # self.continuation_times = []
125
- # self.total_time = None
126
-
127
- # self._output_timeout = None
128
-
129
- # return self._response
130
-
131
- # def evaluate(self, timestamp: float) -> Tuple[bool, Union[float, None]]:
132
- # stop = False
133
-
134
- # # self._check_uninitialization()
135
-
136
- # self._data_action = None
137
- # self._stop_source_value = (
138
- # "###" # When a timeout occurs, store the value that exceeded its value here
139
- # )
140
- # self._stop_source_limit = "###" # And store the limit value here
141
-
142
- # # First check if the timestamp is None, that would mean the timeout was reached in the queue
143
- # if timestamp is None:
144
- # # Set the data action according to the timeout that was given for the queue before
145
- # match self._queue_timeout_type:
146
- # case TimeoutType.RESPONSE:
147
- # self._data_action = self._on_response
148
- # self._stop_source_limit = self._response
149
- # case TimeoutType.CONTINUATION:
150
- # self._data_action = self._on_continuation
151
- # self._stop_source_limit = self._continuation
152
- # case TimeoutType.TOTAL:
153
- # self._data_action = self._on_total
154
- # self._stop_source_limit = self._total
155
- # self._stop_source_value = None # We do not have the exceed time, but None will be printed as '---'
156
- # self._last_data_action_origin = self._queue_timeout_type
157
- # stop = True
158
-
159
- # else:
160
- # # Check total
161
- # if self._total is not None:
162
- # self.total_time = timestamp - self._start_time
163
- # if self.total_time >= self._total:
164
- # stop = True
165
- # self._data_action = self._on_total
166
- # self._last_data_action_origin = TimeoutType.TOTAL
167
- # self._stop_source_value = self.total_time
168
- # self._stop_source_limit = self._total
169
- # # Check continuation
170
- # elif (
171
- # self._continuation is not None
172
- # and self._state == self._State.CONTINUATION
173
- # and self._last_timestamp is not None
174
- # ):
175
- # continuation_time = timestamp - self._last_timestamp
176
- # self.continuation_times.append(continuation_time)
177
- # if continuation_time >= self._continuation:
178
- # stop = True
179
- # self._data_action = self._on_continuation
180
- # self._last_data_action_origin = TimeoutType.CONTINUATION
181
- # self._stop_source_value = continuation_time
182
- # self._stop_source_limit = self._continuation
183
- # # Check response time
184
- # elif (
185
- # self._response is not None
186
- # and self._state == self._State.WAIT_FOR_RESPONSE
187
- # ):
188
- # self.response_time = timestamp - self._start_time
189
- # if self.response_time >= self._response:
190
- # stop = True
191
- # self._data_action = self._on_response
192
- # self._last_data_action_origin = TimeoutType.RESPONSE
193
- # self._stop_source_value = self.response_time
194
- # self._stop_source_limit = self._response
195
-
196
- # self._output_timeout = None
197
- # # If we continue
198
- # if not stop:
199
- # # Update the state
200
- # if self._state == self._State.WAIT_FOR_RESPONSE:
201
- # self._state = self._State.CONTINUATION
202
- # self._last_timestamp = timestamp
203
- # # No timeouts were reached, return the next one
204
- # # Return the timeout (state is always CONTINUATION at this stage)
205
- # # Take the smallest between continuation and total
206
- # if self._total is not None and self._continuation is not None:
207
- # c = self._continuation
208
- # t = self._start_time + self._total
209
- # if c < t:
210
- # self._output_timeout = c
211
- # self._queue_timeout_type = TimeoutType.CONTINUATION
212
- # else:
213
- # self._output_timeout = t
214
- # self._queue_timeout_type = TimeoutType.TOTAL
215
- # elif self._total is not None:
216
- # self._output_timeout = time() - (self._start_time + self._total)
217
- # self._queue_timeout_type = TimeoutType.TOTAL
218
- # elif self._continuation is not None:
219
- # self._output_timeout = self._continuation
220
- # self._queue_timeout_type = TimeoutType.CONTINUATION
221
-
222
- # return stop, self._output_timeout
223
-
224
- # def dataAction(self):
225
- # """
226
- # Return the data action (discard, return, store or error)
227
- # and the timeout origin (response, continuation or total)
228
-
229
- # Returns
230
- # -------
231
- # data_action : Timeout.DataAction
232
- # origin : Timeout.TimeoutType
233
-
234
- # """
235
- # return self._data_action, self._last_data_action_origin
236
-
237
- # def __str__(self) -> str:
238
- # def _format(value, action):
239
- # if value is None:
240
- # return "None"
241
- # elif value is Ellipsis:
242
- # return "not set"
243
- # else:
244
- # return f'{value*1e3:.3f}ms/{action.value if isinstance(action, Enum) else "not set"}'
245
-
246
- # response = "r:" + _format(self._response, self._on_response)
247
- # continuation = "c:" + _format(self._continuation, self._on_continuation)
248
- # total = "t:" + _format(self._total, self._on_total)
249
- # return f"Timeout({response},{continuation},{total})"
250
-
251
- # def __repr__(self) -> str:
252
- # return self.__str__()
@@ -1,197 +0,0 @@
1
- # File : visa.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
- #
5
- # The VISA backend communicates using pyvisa
6
-
7
- from __future__ import annotations
8
-
9
- import queue
10
- import socket
11
- import threading
12
- import time
13
- from types import ModuleType
14
- from typing import TYPE_CHECKING
15
-
16
- from ...tools.backend_api import AdapterBackendStatus, Fragment
17
- from .adapter_backend import (
18
- AdapterBackend,
19
- AdapterDisconnected,
20
- AdapterSignal,
21
- HasFileno,
22
- )
23
- from .descriptors import VisaDescriptor
24
-
25
- # --- Typing-only imports so mypy knows pyvisa symbols without requiring it at runtime
26
- if TYPE_CHECKING:
27
- import pyvisa # type: ignore
28
- from pyvisa.resources import Resource # type: ignore
29
-
30
- # --- Runtime optional import
31
- try:
32
- import pyvisa as _pyvisa_runtime
33
- except Exception:
34
- _pyvisa_runtime = None
35
-
36
- pyvisa: ModuleType | None = _pyvisa_runtime # type: ignore
37
-
38
-
39
- class VisaBackend(AdapterBackend):
40
- def __init__(self, descriptor: VisaDescriptor):
41
- """
42
- USB VISA stack adapter
43
- """
44
- super().__init__(descriptor=descriptor)
45
- self.descriptor: VisaDescriptor
46
-
47
- if pyvisa is None:
48
- raise ImportError(
49
- "Missing optional dependency 'pyvisa'. Install with:\n"
50
- " python -m pip install pyvisa"
51
- )
52
-
53
- # Safe: guarded above, so mypy knows pyvisa is a module here
54
- self._rm = pyvisa.ResourceManager()
55
- self._inst: Resource | None = None # annotation only; no runtime import needed
56
-
57
- # We need a socket pair because VISA doesn't expose a selectable fileno/socket
58
- # So we create a thread to read data and push that to the socket
59
- self._notify_recv, self._notify_send = socket.socketpair()
60
- self._notify_recv.setblocking(False)
61
- self._notify_send.setblocking(False)
62
-
63
- self._stop_lock = threading.Lock()
64
- self.stop = False
65
-
66
- self._fragment_lock = threading.Lock()
67
- self._fragment = Fragment(b"", None)
68
- self._event_queue: queue.Queue[AdapterSignal] = queue.Queue()
69
-
70
- @classmethod
71
- def list_devices(cls: type[VisaBackend]) -> list[str]:
72
- """
73
- Returns a list of available VISA devices
74
- """
75
- if pyvisa is None:
76
- raise ImportError(
77
- "Missing optional dependency 'pyvisa'. Install with:\n"
78
- " python -m pip install pyvisa"
79
- )
80
-
81
- rm = pyvisa.ResourceManager()
82
- available_resources: list[str] = []
83
- for device in rm.list_resources():
84
- try:
85
- d = rm.open_resource(device)
86
- d.close()
87
- available_resources.append(device)
88
- except pyvisa.VisaIOError:
89
- # Device cannot be opened; skip it
90
- pass
91
-
92
- return available_resources
93
-
94
- def flush_read(self) -> bool:
95
- super().flush_read()
96
- while not self._event_queue.empty():
97
- self._event_queue.get()
98
- return True
99
-
100
- def open(self) -> bool:
101
- output = False
102
- if self._inst is None:
103
- # NOTE: self._rm is always defined in __init__ when pyvisa is present
104
- self._inst = self._rm.open_resource(self.descriptor.descriptor)
105
-
106
- if self._status == AdapterBackendStatus.DISCONNECTED:
107
- # These attributes exist on pyvisa resources
108
- self._inst.write_termination = ""
109
- self._inst.read_termination = None
110
-
111
- self._inst_lock = threading.Lock()
112
- self._status = AdapterBackendStatus.CONNECTED
113
-
114
- if self._thread is None:
115
- self._thread = threading.Thread(
116
- target=self._internal_thread,
117
- args=(self._inst, self._event_queue),
118
- daemon=True,
119
- )
120
- self._thread.start()
121
- output = True
122
-
123
- return output
124
-
125
- def close(self) -> bool:
126
- super().close()
127
- with self._inst_lock:
128
- if self._inst is not None:
129
- self._inst.close()
130
- self._status = AdapterBackendStatus.DISCONNECTED
131
- with self._stop_lock:
132
- self.stop = True
133
- return True
134
-
135
- def write(self, data: bytes) -> bool:
136
- super().write(data)
137
- with self._inst_lock:
138
- if self._inst is not None:
139
- self._inst.write_raw(data)
140
- return True
141
-
142
- def _socket_read(self) -> Fragment:
143
- self._notify_recv.recv(1)
144
- if not self._event_queue.empty():
145
- event = self._event_queue.get()
146
- if isinstance(event, AdapterDisconnected):
147
- return Fragment(b"", None)
148
-
149
- with self._fragment_lock:
150
- output = self._fragment
151
- self._fragment = Fragment(b"", None)
152
- return output
153
-
154
- def _internal_thread(
155
- self,
156
- inst: Resource,
157
- event_queue: queue.Queue[AdapterSignal],
158
- ) -> None:
159
- assert pyvisa is not None # for type-narrowing inside this method
160
- timeout = 2000
161
- while True:
162
- payload = b""
163
- with self._fragment_lock:
164
- self._fragment = Fragment(b"", None)
165
- try:
166
- inst.timeout = timeout
167
- except pyvisa.InvalidSession:
168
- pass
169
- try:
170
- while True:
171
- # Read up to an error
172
- payload += inst.read_bytes(1)
173
- inst.timeout = 0
174
- except pyvisa.VisaIOError:
175
- # Timeout
176
- if payload:
177
- with self._fragment_lock:
178
- if self._fragment.timestamp is None:
179
- self._fragment.timestamp = time.time()
180
- self._fragment.data += payload
181
- # Tell the session that there's data (write to a virtual socket)
182
- self._notify_send.send(b"1")
183
- except (TypeError, pyvisa.InvalidSession, BrokenPipeError):
184
- event_queue.put(AdapterDisconnected())
185
- self._notify_send.send(b"1")
186
- with self._stop_lock:
187
- if self.stop:
188
- break
189
-
190
- def selectable(self) -> HasFileno | None:
191
- return self._notify_recv
192
-
193
- def is_opened(self) -> bool:
194
- if self._inst is None:
195
- return False
196
- else:
197
- return self._status == AdapterBackendStatus.CONNECTED
@@ -1,102 +0,0 @@
1
- # File : ip_server.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
- #
5
- # IP server, WIP
6
-
7
-
8
- # NOTE : The adapter is only meant to work with a single client, thus
9
- # a IPServer class is made to handle the socket and generate IP adapters
10
-
11
- # class IPServer:
12
- # DEFAULT_BUFFER_SIZE = 1024
13
- # class Protocol(Enum):
14
- # TCP = 'TCP'
15
- # UDP = 'UDP'
16
-
17
- # def __init__(self,
18
- # port : int = None,
19
- # transport : str = 'TCP',
20
- # address : str = None,
21
- # max_clients : int = 5,
22
- # stop_condition = None,
23
- # alias : str = '',
24
- # buffer_size : int = DEFAULT_BUFFER_SIZE):
25
-
26
- # """
27
- # IP server adapter
28
-
29
- # Parameters
30
- # ----------
31
- # port : int
32
- # IP port. If None, the default port set with set_default_port will be used
33
- # transport : str
34
- # 'TCP' or 'UDP'
35
- # address : str
36
- # Custom socket ip, None by default
37
- # max_clients : int
38
- # Maximum number of clients, 5 by default
39
- # stop_condition : StopCondition
40
- # Specify a read stop condition (None by default)
41
- # alias : str
42
- # Specify an alias for this adapter, '' by default
43
- # buffer_size : int
44
- # Socket buffer size, may be removed in the future
45
- # """
46
- # self._alias = alias
47
- # self._stop_condition = stop_condition
48
- # self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
49
- # self._transport = self.Protocol(transport)
50
- # if self._transport == self.Protocol.TCP:
51
- # self._logger.info("Setting up TCP IP server adapter")
52
- # self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
53
- # elif self._transport == self.Protocol.UDP:
54
- # self._logger.info("Setting up UDP IP server adapter")
55
- # self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
56
- # else:
57
- # raise ValueError("Invalid protocol")
58
-
59
- # self._address = socket.gethostname() if address is None else address
60
- # self._port = port
61
- # self._max_clients = max_clients
62
- # self._opened = False
63
-
64
-
65
- # def set_default_port(self, port):
66
- # """
67
- # Sets IP port if no port has been set yet.
68
-
69
- # This way, the user can leave the port empty
70
- # and the driver/protocol can specify it later
71
-
72
- # Parameters
73
- # ----------
74
- # port : int
75
- # """
76
- # if self._port is None:
77
- # self._port = port
78
-
79
-
80
- # def open(self):
81
- # if self._port is None:
82
- # raise ValueError(f"Cannot open adapter without specifying a port")
83
- # self._logger.info(f"Listening to incoming connections on {self._address}:{self._port}")
84
- # self._socket.bind((self._address, self._port))
85
- # self._socket.listen(self._max_clients)
86
- # self._opened = True
87
-
88
- # def close(self):
89
- # if hasattr(self, '_socket'):
90
- # self._socket.close()
91
- # self._logger.info("Adapter closed !")
92
- # self._opened = False
93
-
94
- # def get_client(self, stop_condition : StopConditionBackend = None, timeout : Timeout = None) -> IP:
95
- # """
96
- # Wait for a client to connect to the server and return the corresponding adapter
97
- # """
98
- # if not self._opened:
99
- # raise RuntimeError("open() must be called before getting client")
100
- # client_socket, address = self._socket.accept()
101
- # default_timeout = Timeout(response=None, continuation=IP.DEFAULT_CONTINUATION_TIMEOUT, total=None)
102
- # return IP(_socket=client_socket, address=address, stop_condition=stop_condition, timeout=timeout_fuse(timeout, default_timeout))
@@ -1,90 +0,0 @@
1
- # File : stop_condition.py
2
- # Author : Sébastien Deriaz
3
- # License : GPL
4
-
5
- from abc import abstractmethod
6
- from enum import Enum
7
-
8
- class StopConditionType(Enum):
9
- TERMINATION = "termination"
10
- LENGTH = "length"
11
- TIMEOUT = "timeout"
12
-
13
- class StopCondition:
14
- @abstractmethod
15
- def type(self) -> StopConditionType:
16
- pass
17
-
18
- def __init__(self) -> None:
19
- """
20
- A condition to stop reading from a device
21
-
22
- Cannot be used on its own
23
- """
24
-
25
-
26
- class Termination(StopCondition):
27
- def __init__(self, sequence: bytes | str) -> None:
28
- """
29
- Stop reading once the desired sequence is detected
30
-
31
- Parameters
32
- ----------
33
- sequence : bytes
34
- """
35
- self.sequence: bytes
36
- if isinstance(sequence, str):
37
- self.sequence = sequence.encode("utf-8")
38
- elif isinstance(sequence, bytes):
39
- self.sequence = sequence
40
- else:
41
- raise ValueError(f"Invalid termination sequence type : {type(sequence)}")
42
-
43
- def __repr__(self) -> str:
44
- return self.__str__()
45
-
46
- def __str__(self) -> str:
47
- return f"Termination({repr(self.sequence)})"
48
-
49
- def type(self) -> StopConditionType:
50
- return StopConditionType.TERMINATION
51
-
52
-
53
- class Length(StopCondition):
54
- def __init__(self, N: int) -> None:
55
- """
56
- Stop condition when the desired number of bytes is reached or passed
57
-
58
- Parameters
59
- ----------
60
- N : int
61
- Number of bytes
62
- """
63
- self.N = N
64
-
65
- def __repr__(self) -> str:
66
- return self.__str__()
67
-
68
- def __str__(self) -> str:
69
- return f"Length({self.N})"
70
-
71
- def type(self) -> StopConditionType:
72
- return StopConditionType.LENGTH
73
-
74
-
75
- class Continuation(StopCondition):
76
- def __init__(self, time: float) -> None:
77
- super().__init__()
78
- self.continuation = time
79
-
80
- def type(self) -> StopConditionType:
81
- return StopConditionType.TIMEOUT
82
-
83
-
84
- class Total(StopCondition):
85
- def __init__(self, time: float) -> None:
86
- super().__init__()
87
- self.total = time
88
-
89
- def type(self) -> StopConditionType:
90
- return StopConditionType.TIMEOUT