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.
- syndesi/__init__.py +22 -2
- syndesi/adapters/adapter.py +332 -489
- syndesi/adapters/adapter_worker.py +820 -0
- syndesi/adapters/auto.py +58 -25
- syndesi/adapters/descriptors.py +38 -0
- syndesi/adapters/ip.py +203 -71
- syndesi/adapters/serialport.py +154 -25
- syndesi/adapters/stop_conditions.py +354 -0
- syndesi/adapters/timeout.py +58 -21
- syndesi/adapters/visa.py +236 -11
- syndesi/cli/console.py +51 -16
- syndesi/cli/shell.py +95 -47
- syndesi/cli/terminal_tools.py +8 -8
- syndesi/component.py +315 -0
- syndesi/protocols/delimited.py +92 -107
- syndesi/protocols/modbus.py +2368 -868
- syndesi/protocols/protocol.py +186 -33
- syndesi/protocols/raw.py +45 -62
- syndesi/protocols/scpi.py +65 -102
- syndesi/remote/remote.py +188 -0
- syndesi/scripts/syndesi.py +12 -2
- syndesi/tools/errors.py +49 -31
- syndesi/tools/log_settings.py +21 -8
- syndesi/tools/{log.py → logmanager.py} +24 -13
- syndesi/tools/types.py +9 -7
- syndesi/version.py +5 -1
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/METADATA +1 -1
- syndesi-0.5.0.dist-info/RECORD +41 -0
- syndesi/adapters/backend/__init__.py +0 -0
- syndesi/adapters/backend/adapter_backend.py +0 -438
- syndesi/adapters/backend/adapter_manager.py +0 -48
- syndesi/adapters/backend/adapter_session.py +0 -346
- syndesi/adapters/backend/backend.py +0 -438
- syndesi/adapters/backend/backend_status.py +0 -0
- syndesi/adapters/backend/backend_tools.py +0 -66
- syndesi/adapters/backend/descriptors.py +0 -153
- syndesi/adapters/backend/ip_backend.py +0 -149
- syndesi/adapters/backend/serialport_backend.py +0 -241
- syndesi/adapters/backend/stop_condition_backend.py +0 -219
- syndesi/adapters/backend/timed_queue.py +0 -39
- syndesi/adapters/backend/timeout.py +0 -252
- syndesi/adapters/backend/visa_backend.py +0 -197
- syndesi/adapters/ip_server.py +0 -102
- syndesi/adapters/stop_condition.py +0 -90
- syndesi/cli/backend_console.py +0 -96
- syndesi/cli/backend_status.py +0 -274
- syndesi/cli/backend_wrapper.py +0 -61
- syndesi/scripts/syndesi_backend.py +0 -37
- syndesi/tools/backend_api.py +0 -175
- syndesi/tools/backend_logger.py +0 -64
- syndesi/tools/exceptions.py +0 -16
- syndesi/tools/internal.py +0 -0
- syndesi-0.4.2.dist-info/RECORD +0 -60
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/WHEEL +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/entry_points.txt +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# File : ip.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# The IP backend communicates with TCP or UDP capable hosts using the IP layer
|
|
6
|
-
|
|
7
|
-
import socket
|
|
8
|
-
import time
|
|
9
|
-
from typing import cast
|
|
10
|
-
|
|
11
|
-
import _socket
|
|
12
|
-
|
|
13
|
-
from ...tools.backend_api import AdapterBackendStatus, Fragment
|
|
14
|
-
from .adapter_backend import AdapterBackend, HasFileno
|
|
15
|
-
from .descriptors import IPDescriptor
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class IPBackend(AdapterBackend):
|
|
19
|
-
BUFFER_SIZE = 65507
|
|
20
|
-
# _DEFAULT_BUFFER_SIZE = 1024
|
|
21
|
-
# DEFAULT_TIMEOUT = TimeoutBackend(
|
|
22
|
-
# response=5,
|
|
23
|
-
# action="error"
|
|
24
|
-
# )
|
|
25
|
-
DEFAULT_STOP_CONDITION = None
|
|
26
|
-
|
|
27
|
-
def __init__(self, descriptor: IPDescriptor):
|
|
28
|
-
"""
|
|
29
|
-
IP stack adapter
|
|
30
|
-
|
|
31
|
-
Parameters
|
|
32
|
-
----------
|
|
33
|
-
address : str
|
|
34
|
-
IP description
|
|
35
|
-
port : int
|
|
36
|
-
IP port
|
|
37
|
-
transport : str
|
|
38
|
-
'TCP' or 'UDP'
|
|
39
|
-
timeout : Timeout | float
|
|
40
|
-
Specify communication timeout
|
|
41
|
-
stop_condition : StopCondition
|
|
42
|
-
Specify a read stop condition (None by default)
|
|
43
|
-
buffer_size : int
|
|
44
|
-
Socket buffer size, may be removed in the future
|
|
45
|
-
socket : socket.socket
|
|
46
|
-
Specify a custom socket, this is reserved for server application
|
|
47
|
-
"""
|
|
48
|
-
super().__init__(descriptor=descriptor)
|
|
49
|
-
self.descriptor: IPDescriptor
|
|
50
|
-
|
|
51
|
-
self._logger.info(f"Setting up {self.descriptor} adapter ")
|
|
52
|
-
|
|
53
|
-
self._socket: _socket.socket | None = None
|
|
54
|
-
|
|
55
|
-
def selectable(self) -> HasFileno | None:
|
|
56
|
-
return self._socket
|
|
57
|
-
|
|
58
|
-
def open(self) -> bool:
|
|
59
|
-
output = False
|
|
60
|
-
if self._status == AdapterBackendStatus.DISCONNECTED:
|
|
61
|
-
if self.descriptor.port is None: # TODO : Check if this is even possible
|
|
62
|
-
raise ValueError("Cannot open adapter without specifying a port")
|
|
63
|
-
|
|
64
|
-
if self._socket is None:
|
|
65
|
-
if self.descriptor.transport == IPDescriptor.Transport.TCP:
|
|
66
|
-
self._socket = cast(
|
|
67
|
-
_socket.socket,
|
|
68
|
-
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
|
|
69
|
-
)
|
|
70
|
-
elif self.descriptor.transport == IPDescriptor.Transport.UDP:
|
|
71
|
-
self._socket = cast(
|
|
72
|
-
_socket.socket, socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
73
|
-
)
|
|
74
|
-
else:
|
|
75
|
-
raise ValueError(
|
|
76
|
-
f"Invalid transport protocol : {self.descriptor.transport}"
|
|
77
|
-
)
|
|
78
|
-
try:
|
|
79
|
-
self._socket.settimeout(
|
|
80
|
-
0.5
|
|
81
|
-
) # TODO : Configure this cleanly, it has to be less than the receive timeout of the frontend
|
|
82
|
-
self._socket.connect((self.descriptor.address, self.descriptor.port))
|
|
83
|
-
except OSError as e: # TODO : Maybe change the exception ?
|
|
84
|
-
self._logger.error(f"Failed to open adapter {self.descriptor} : {e}")
|
|
85
|
-
else:
|
|
86
|
-
self._status = AdapterBackendStatus.CONNECTED
|
|
87
|
-
self._logger.info(f"IP Adapter {self.descriptor} opened")
|
|
88
|
-
output = True
|
|
89
|
-
else:
|
|
90
|
-
self._logger.info(f"Adapter {self.descriptor} already openend")
|
|
91
|
-
output = True
|
|
92
|
-
|
|
93
|
-
return output
|
|
94
|
-
|
|
95
|
-
def close(self) -> bool:
|
|
96
|
-
super().close()
|
|
97
|
-
|
|
98
|
-
self._status = AdapterBackendStatus.DISCONNECTED
|
|
99
|
-
if self._socket is not None:
|
|
100
|
-
self._socket.close()
|
|
101
|
-
self._socket = None
|
|
102
|
-
self._logger.info(f"Adapter {self.descriptor} closed")
|
|
103
|
-
return True
|
|
104
|
-
else:
|
|
105
|
-
self._logger.error(f"Failed to close adapter {self.descriptor}")
|
|
106
|
-
return False
|
|
107
|
-
|
|
108
|
-
def write(self, data: bytes) -> bool:
|
|
109
|
-
super().write(data)
|
|
110
|
-
|
|
111
|
-
# TODO : Manage adapter reopen
|
|
112
|
-
# if self._status == AdapterBackendStatus.DISCONNECTED:
|
|
113
|
-
# self._logger.info("Adapter is closed, opening...")
|
|
114
|
-
# self.open()
|
|
115
|
-
|
|
116
|
-
# Socket send
|
|
117
|
-
if self._socket is None:
|
|
118
|
-
self._logger.error(f"Cannot write to closed adapter {self.descriptor}")
|
|
119
|
-
return False
|
|
120
|
-
try:
|
|
121
|
-
self._socket.send(data)
|
|
122
|
-
except (BrokenPipeError, OSError) as e:
|
|
123
|
-
# Socket has been disconnected by the remote peer
|
|
124
|
-
self._logger.error(f"Failed to write to adapter {self.descriptor} ({e})")
|
|
125
|
-
self.close()
|
|
126
|
-
return False
|
|
127
|
-
else:
|
|
128
|
-
return True
|
|
129
|
-
|
|
130
|
-
def _socket_read(self) -> Fragment:
|
|
131
|
-
# This function is called only if the socket was ready
|
|
132
|
-
t = time.time()
|
|
133
|
-
if self._socket is None:
|
|
134
|
-
return Fragment(b"", t)
|
|
135
|
-
else:
|
|
136
|
-
try:
|
|
137
|
-
fragment = Fragment(self._socket.recv(self.BUFFER_SIZE), t)
|
|
138
|
-
except (ConnectionRefusedError, OSError):
|
|
139
|
-
fragment = Fragment(b"", t)
|
|
140
|
-
|
|
141
|
-
if fragment.data == b"":
|
|
142
|
-
# Socket disconnected
|
|
143
|
-
self._logger.debug("## Socket disconnected")
|
|
144
|
-
self.close()
|
|
145
|
-
|
|
146
|
-
return fragment
|
|
147
|
-
|
|
148
|
-
def is_opened(self) -> bool:
|
|
149
|
-
return self._status == AdapterBackendStatus.CONNECTED
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
# File : serialport.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# The SerialPort backend communicates with serial devices using the serial package
|
|
6
|
-
|
|
7
|
-
import time
|
|
8
|
-
|
|
9
|
-
import serial
|
|
10
|
-
from serial.serialutil import PortNotOpenError
|
|
11
|
-
|
|
12
|
-
from syndesi.tools.backend_api import AdapterBackendStatus, Fragment
|
|
13
|
-
|
|
14
|
-
from .adapter_backend import AdapterBackend, HasFileno
|
|
15
|
-
from .descriptors import SerialPortDescriptor
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class SerialPortBackend(AdapterBackend):
|
|
19
|
-
def __init__(self, descriptor: SerialPortDescriptor):
|
|
20
|
-
"""
|
|
21
|
-
Serial communication adapter
|
|
22
|
-
|
|
23
|
-
Parameters
|
|
24
|
-
----------
|
|
25
|
-
port : str
|
|
26
|
-
Serial port (COMx or ttyACMx)
|
|
27
|
-
"""
|
|
28
|
-
super().__init__(descriptor=descriptor)
|
|
29
|
-
self.descriptor: SerialPortDescriptor
|
|
30
|
-
self._logger.info(f"Setting up SerialPort adapter {self.descriptor}")
|
|
31
|
-
self._port: serial.Serial | None = None
|
|
32
|
-
self._rts_cts = False
|
|
33
|
-
|
|
34
|
-
self.open()
|
|
35
|
-
|
|
36
|
-
def set_baudrate(self, baudrate: int) -> None:
|
|
37
|
-
"""
|
|
38
|
-
Set baudrate
|
|
39
|
-
|
|
40
|
-
Parameters
|
|
41
|
-
----------
|
|
42
|
-
baudrate : int
|
|
43
|
-
"""
|
|
44
|
-
self.descriptor.baudrate = baudrate
|
|
45
|
-
|
|
46
|
-
if self.is_opened():
|
|
47
|
-
self.close()
|
|
48
|
-
self.open()
|
|
49
|
-
|
|
50
|
-
# TODO : Check if this is still necessary, it's probably more of a frontend thing
|
|
51
|
-
def set_default_baudrate(self, baudrate: int) -> None:
|
|
52
|
-
"""
|
|
53
|
-
Sets the default baudrate
|
|
54
|
-
|
|
55
|
-
Parameters
|
|
56
|
-
----------
|
|
57
|
-
baudrate : int
|
|
58
|
-
"""
|
|
59
|
-
self.descriptor.set_default_baudrate(baudrate)
|
|
60
|
-
|
|
61
|
-
def flush_read(self) -> bool:
|
|
62
|
-
super().flush_read()
|
|
63
|
-
if self._port is None:
|
|
64
|
-
return False
|
|
65
|
-
else:
|
|
66
|
-
self._port.flush()
|
|
67
|
-
return True
|
|
68
|
-
|
|
69
|
-
def set_rtscts(self, enabled: bool) -> None:
|
|
70
|
-
self._rts_cts = enabled
|
|
71
|
-
if self.is_opened():
|
|
72
|
-
self.close()
|
|
73
|
-
self.open()
|
|
74
|
-
|
|
75
|
-
def open(self) -> bool:
|
|
76
|
-
if self.descriptor.baudrate is None:
|
|
77
|
-
raise ValueError("Baudrate must be set, please use set_baudrate")
|
|
78
|
-
|
|
79
|
-
if self._port is None:
|
|
80
|
-
self._port = serial.Serial(
|
|
81
|
-
port=self.descriptor.port,
|
|
82
|
-
baudrate=self.descriptor.baudrate,
|
|
83
|
-
rtscts=self._rts_cts,
|
|
84
|
-
)
|
|
85
|
-
elif not self._port.isOpen(): # type: ignore
|
|
86
|
-
self._port.open()
|
|
87
|
-
|
|
88
|
-
if self._port.isOpen(): # type: ignore
|
|
89
|
-
self._logger.info(f"Adapter {self.descriptor} opened")
|
|
90
|
-
self._status = AdapterBackendStatus.CONNECTED
|
|
91
|
-
return True
|
|
92
|
-
else:
|
|
93
|
-
self._logger.error(f"Failed to open adapter {self.descriptor}")
|
|
94
|
-
self._status = AdapterBackendStatus.DISCONNECTED
|
|
95
|
-
return False
|
|
96
|
-
|
|
97
|
-
def close(self) -> bool:
|
|
98
|
-
super().close()
|
|
99
|
-
if hasattr(self, "_port") and self._port is not None:
|
|
100
|
-
# Close and the read thread will die by itself
|
|
101
|
-
self._port.close()
|
|
102
|
-
self._logger.info("Adapter closed")
|
|
103
|
-
return True
|
|
104
|
-
else:
|
|
105
|
-
return False
|
|
106
|
-
|
|
107
|
-
def write(self, data: bytes) -> bool:
|
|
108
|
-
super().write(data)
|
|
109
|
-
if self._port is None:
|
|
110
|
-
self._logger.error(f"Cannot write to closed adapter {self.descriptor}")
|
|
111
|
-
return False
|
|
112
|
-
else:
|
|
113
|
-
if self._rts_cts: # Experimental
|
|
114
|
-
self._port.setRTS(True) # type: ignore
|
|
115
|
-
# TODO : Implement auto open
|
|
116
|
-
# if self._status == AdapterBackendStatus.DISCONNECTED:
|
|
117
|
-
# self.open()
|
|
118
|
-
# write_start = time.time()
|
|
119
|
-
try:
|
|
120
|
-
self._port.write(data)
|
|
121
|
-
except (OSError, PortNotOpenError):
|
|
122
|
-
return False
|
|
123
|
-
# write_duration = time.time() - write_start
|
|
124
|
-
# self._logger.debug(f"Write [{write_duration * 1e3:.3f}ms]: {repr(data)}")
|
|
125
|
-
|
|
126
|
-
return True
|
|
127
|
-
|
|
128
|
-
def _socket_read(self) -> Fragment:
|
|
129
|
-
t = time.time()
|
|
130
|
-
|
|
131
|
-
if self._port is None:
|
|
132
|
-
self._logger.debug('Port is None -> b""')
|
|
133
|
-
return Fragment(b"", t)
|
|
134
|
-
else:
|
|
135
|
-
try:
|
|
136
|
-
data = self._port.read_all()
|
|
137
|
-
except (OSError, PortNotOpenError):
|
|
138
|
-
self._logger.debug('Port error -> b""')
|
|
139
|
-
return Fragment(b"", t)
|
|
140
|
-
else:
|
|
141
|
-
# This is a test, it seems b"" happens sometimes and disconnects the adapter
|
|
142
|
-
if data is not None and data != b"":
|
|
143
|
-
return Fragment(data, t)
|
|
144
|
-
else:
|
|
145
|
-
return Fragment(b"", t)
|
|
146
|
-
# self._logger.debug('Data is none -> b""')
|
|
147
|
-
# else:
|
|
148
|
-
# self._logger.debug(f'{data=}')
|
|
149
|
-
|
|
150
|
-
# def _start_thread(self):
|
|
151
|
-
# """
|
|
152
|
-
# Start the read thread
|
|
153
|
-
# """
|
|
154
|
-
# #super()._start_thread()
|
|
155
|
-
# if self._thread is None or not self._thread.is_alive():
|
|
156
|
-
# self._thread = Thread(
|
|
157
|
-
# target=self._read_thread,
|
|
158
|
-
# daemon=True,
|
|
159
|
-
# args=(self._port, self._read_queue, self._thread_commands_read),
|
|
160
|
-
# )
|
|
161
|
-
# self._thread.start()
|
|
162
|
-
|
|
163
|
-
# def _read_thread(
|
|
164
|
-
# self,
|
|
165
|
-
# port: serial.Serial,
|
|
166
|
-
# read_queue: TimedQueue,
|
|
167
|
-
# thread_commands: socket.socket,
|
|
168
|
-
# ):
|
|
169
|
-
# # On linux, it is possivle to use the select.select for both serial port and stop socketpair.
|
|
170
|
-
# # On Windows, this is not possible. so the port timeout is used instead.
|
|
171
|
-
# if sys.platform == "win32":
|
|
172
|
-
# port.timeout = 0.1
|
|
173
|
-
# while True:
|
|
174
|
-
# # Check how many bytes are available
|
|
175
|
-
# if sys.platform == "win32":
|
|
176
|
-
# ready, _, _ = select.select([thread_commands], [], [], 0)
|
|
177
|
-
# if thread_commands in ready:
|
|
178
|
-
# # Stop the read thread
|
|
179
|
-
# break
|
|
180
|
-
# else:
|
|
181
|
-
# # Read data from the serialport with a timeout, if the timeout occurs, read again.
|
|
182
|
-
# # This is to avoid having a crazy fast loop
|
|
183
|
-
# fragment = port.read()
|
|
184
|
-
# if len(fragment) > 0:
|
|
185
|
-
# read_queue.put(fragment)
|
|
186
|
-
# else:
|
|
187
|
-
# ready, _, _ = select.select([self._port.fd, thread_commands], [], [])
|
|
188
|
-
# if thread_commands in ready:
|
|
189
|
-
# command = self.ThreadCommands(thread_commands.recv(1))
|
|
190
|
-
# if command == self.ThreadCommands.STOP:
|
|
191
|
-
# # Stop the read thread
|
|
192
|
-
# break
|
|
193
|
-
# elif self._port.fd in ready:
|
|
194
|
-
# try:
|
|
195
|
-
# in_waiting = self._port.in_waiting
|
|
196
|
-
# except OSError:
|
|
197
|
-
# # Input/output error, the port was disconnected
|
|
198
|
-
# read_queue.put(AdapterDisconnected)
|
|
199
|
-
# else:
|
|
200
|
-
# fragment = port.read(in_waiting)
|
|
201
|
-
# if fragment:
|
|
202
|
-
# read_queue.put(fragment)
|
|
203
|
-
|
|
204
|
-
# def read(
|
|
205
|
-
# self, timeout=..., stop_condition=..., full_output: bool = False
|
|
206
|
-
# ) -> bytes:
|
|
207
|
-
# """
|
|
208
|
-
# Read data from the device
|
|
209
|
-
|
|
210
|
-
# Parameters
|
|
211
|
-
# ----------
|
|
212
|
-
# timeout : Timeout or None
|
|
213
|
-
# Set a custom timeout, if None (default), the adapter timeout is used
|
|
214
|
-
# stop_condition : StopCondition or None
|
|
215
|
-
# Set a custom stop condition, if None (Default), the adapater stop condition is used
|
|
216
|
-
# full_output : bool
|
|
217
|
-
# Return a dictionary containing information about the read operation like
|
|
218
|
-
# 'read_duration' : float
|
|
219
|
-
# 'origin' : 'timeout' or 'stop_condition'
|
|
220
|
-
# 'timeout' : Timeout.TimeoutType
|
|
221
|
-
# 'stop_condition' : Length or Termination (StopCondition class)
|
|
222
|
-
# 'previous_read_buffer_used' : bool
|
|
223
|
-
# 'n_fragments' : int
|
|
224
|
-
# """
|
|
225
|
-
# output = super().read(timeout, stop_condition, full_output)
|
|
226
|
-
# if self._rts_cts: # Experimental
|
|
227
|
-
# self._port.setRTS(False)
|
|
228
|
-
# return output
|
|
229
|
-
|
|
230
|
-
def is_opened(self) -> bool:
|
|
231
|
-
if self._port is not None:
|
|
232
|
-
if self._port.isOpen(): # type: ignore
|
|
233
|
-
return True
|
|
234
|
-
|
|
235
|
-
return False
|
|
236
|
-
|
|
237
|
-
def selectable(self) -> HasFileno | None:
|
|
238
|
-
if self._port is not None and self.is_opened():
|
|
239
|
-
return self._port
|
|
240
|
-
else:
|
|
241
|
-
return None
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
# File : stop_condition.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# A stop-condition describes when a communication with a device should
|
|
6
|
-
# be stopped based on the data received (length, contents, termination, etc...)
|
|
7
|
-
# A stop-condition can also format the data if necessary (remove termination for example)
|
|
8
|
-
|
|
9
|
-
import time
|
|
10
|
-
|
|
11
|
-
from syndesi.adapters.stop_condition import (
|
|
12
|
-
Continuation,
|
|
13
|
-
Length,
|
|
14
|
-
StopCondition,
|
|
15
|
-
StopConditionType,
|
|
16
|
-
Termination,
|
|
17
|
-
Total,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
from ...tools.backend_api import Fragment
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def termination_in_data(termination: bytes, data: bytes) -> tuple[int | None, int]:
|
|
24
|
-
"""
|
|
25
|
-
Return the position (if it exists) and length of the termination (or part of it) inside data
|
|
26
|
-
"""
|
|
27
|
-
p = None
|
|
28
|
-
L = len(termination)
|
|
29
|
-
# First check if the full termination is somewhere. If that's the case, data will be split
|
|
30
|
-
try:
|
|
31
|
-
p = data.index(termination)
|
|
32
|
-
# If found, return that
|
|
33
|
-
except ValueError:
|
|
34
|
-
# If not, we'll try to find if part of the sequence is at the end, in that case
|
|
35
|
-
# we'll return the length of the sequence that was found
|
|
36
|
-
L -= 1
|
|
37
|
-
while L > 0:
|
|
38
|
-
if data[-L:] == termination[:L]:
|
|
39
|
-
p = len(data) - L # - 1
|
|
40
|
-
break
|
|
41
|
-
L -= 1
|
|
42
|
-
|
|
43
|
-
return p, L
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class StopConditionBackend:
|
|
47
|
-
def __init__(self) -> None:
|
|
48
|
-
pass
|
|
49
|
-
|
|
50
|
-
def initiate_read(self) -> None:
|
|
51
|
-
raise NotImplementedError()
|
|
52
|
-
|
|
53
|
-
def evaluate(
|
|
54
|
-
self, raw_fragment: Fragment
|
|
55
|
-
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
56
|
-
raise NotImplementedError()
|
|
57
|
-
|
|
58
|
-
def type(self) -> StopConditionType:
|
|
59
|
-
raise NotImplementedError()
|
|
60
|
-
|
|
61
|
-
def flush_read(self) -> None:
|
|
62
|
-
raise NotImplementedError()
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class TerminationBackend(StopConditionBackend):
|
|
66
|
-
def __init__(self, sequence: bytes) -> None:
|
|
67
|
-
super().__init__()
|
|
68
|
-
self._sequence = sequence
|
|
69
|
-
self._sequence_found_length = 0
|
|
70
|
-
|
|
71
|
-
def initiate_read(self) -> None:
|
|
72
|
-
# Termination
|
|
73
|
-
self._sequence_found_length = 0
|
|
74
|
-
|
|
75
|
-
def flush_read(self) -> None:
|
|
76
|
-
self._sequence_found_length = 0
|
|
77
|
-
|
|
78
|
-
def evaluate(
|
|
79
|
-
self, raw_fragment: Fragment
|
|
80
|
-
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
81
|
-
if raw_fragment.data is None:
|
|
82
|
-
raise RuntimeError("Trying to evaluate an invalid fragment")
|
|
83
|
-
|
|
84
|
-
position, length = termination_in_data(
|
|
85
|
-
self._sequence[self._sequence_found_length :], raw_fragment.data
|
|
86
|
-
)
|
|
87
|
-
stop = False
|
|
88
|
-
deferred = Fragment(b"", None)
|
|
89
|
-
|
|
90
|
-
if position is None:
|
|
91
|
-
# Nothing was found, keep everything
|
|
92
|
-
kept = raw_fragment
|
|
93
|
-
else:
|
|
94
|
-
self._sequence_found_length += length
|
|
95
|
-
|
|
96
|
-
if self._sequence_found_length == len(self._sequence):
|
|
97
|
-
# The sequence was found entirely
|
|
98
|
-
deferred = raw_fragment[position + length :]
|
|
99
|
-
self._sequence_found_length = 0
|
|
100
|
-
stop = True
|
|
101
|
-
elif position + length == len(raw_fragment.data):
|
|
102
|
-
# Part of the sequence was found at the end
|
|
103
|
-
# Return what's before the sequence
|
|
104
|
-
deferred = Fragment(b"", None)
|
|
105
|
-
|
|
106
|
-
kept = raw_fragment[:position]
|
|
107
|
-
|
|
108
|
-
return stop, kept, deferred, None
|
|
109
|
-
|
|
110
|
-
def type(self) -> StopConditionType:
|
|
111
|
-
return StopConditionType.TERMINATION
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
class LengthBackend(StopConditionBackend):
|
|
115
|
-
def __init__(self, N: int) -> None:
|
|
116
|
-
super().__init__()
|
|
117
|
-
self._N = N
|
|
118
|
-
self._counter = 0
|
|
119
|
-
|
|
120
|
-
def initiate_read(self) -> None:
|
|
121
|
-
# Length
|
|
122
|
-
self._counter = 0
|
|
123
|
-
|
|
124
|
-
def flush_read(self) -> None:
|
|
125
|
-
self._counter = 0
|
|
126
|
-
|
|
127
|
-
def evaluate(
|
|
128
|
-
self, raw_fragment: Fragment
|
|
129
|
-
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
130
|
-
remaining_bytes = self._N - self._counter
|
|
131
|
-
kept_fragment = raw_fragment[:remaining_bytes]
|
|
132
|
-
deferred_fragment = raw_fragment[remaining_bytes:]
|
|
133
|
-
self._counter += len(kept_fragment.data)
|
|
134
|
-
remaining_bytes = self._N - self._counter
|
|
135
|
-
# TODO : remaining_bytes <= 0 ? Alongside above TODO maybe
|
|
136
|
-
return remaining_bytes == 0, kept_fragment, deferred_fragment, None
|
|
137
|
-
|
|
138
|
-
def type(self) -> StopConditionType:
|
|
139
|
-
return StopConditionType.LENGTH
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
class ContinuationBackend(StopConditionBackend):
|
|
143
|
-
def __init__(self, time: float) -> None:
|
|
144
|
-
super().__init__()
|
|
145
|
-
self._continuation = time
|
|
146
|
-
self._last_fragment: float | None = None
|
|
147
|
-
|
|
148
|
-
def initiate_read(self) -> None:
|
|
149
|
-
self._last_fragment = time.time()
|
|
150
|
-
|
|
151
|
-
def flush_read(self) -> None:
|
|
152
|
-
self._last_fragment = None
|
|
153
|
-
|
|
154
|
-
def evaluate(
|
|
155
|
-
self, raw_fragment: Fragment
|
|
156
|
-
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
157
|
-
deferred = Fragment(b"", None)
|
|
158
|
-
kept = raw_fragment
|
|
159
|
-
|
|
160
|
-
if raw_fragment.timestamp is None:
|
|
161
|
-
raise RuntimeError("Cannot evaluate fragment with no timestamp")
|
|
162
|
-
# last_fragment can be none if no data was ever received
|
|
163
|
-
if self._last_fragment is not None:
|
|
164
|
-
continuation_timestamp = self._last_fragment + self._continuation
|
|
165
|
-
stop = continuation_timestamp <= raw_fragment.timestamp
|
|
166
|
-
next_event_timeout = continuation_timestamp
|
|
167
|
-
else:
|
|
168
|
-
stop = False
|
|
169
|
-
next_event_timeout = None
|
|
170
|
-
|
|
171
|
-
return stop, kept, deferred, next_event_timeout
|
|
172
|
-
|
|
173
|
-
def type(self) -> StopConditionType:
|
|
174
|
-
return StopConditionType.TIMEOUT
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
class TotalBackend(StopConditionBackend):
|
|
178
|
-
def __init__(self, time: float) -> None:
|
|
179
|
-
super().__init__()
|
|
180
|
-
self._total = time
|
|
181
|
-
self._start_time: float | None = None
|
|
182
|
-
|
|
183
|
-
def initiate_read(self) -> None:
|
|
184
|
-
self._start_time = time.time()
|
|
185
|
-
|
|
186
|
-
def flush_read(self) -> None:
|
|
187
|
-
self._start_time = None
|
|
188
|
-
|
|
189
|
-
def evaluate(
|
|
190
|
-
self, raw_fragment: Fragment
|
|
191
|
-
) -> tuple[bool, Fragment, Fragment, float | None]:
|
|
192
|
-
kept = raw_fragment
|
|
193
|
-
deferred = Fragment(b"", None)
|
|
194
|
-
|
|
195
|
-
if raw_fragment.timestamp is None:
|
|
196
|
-
raise RuntimeError("Cannot evaluate fragment with no timestamp")
|
|
197
|
-
|
|
198
|
-
if self._start_time is None:
|
|
199
|
-
raise RuntimeError("Invalid start time")
|
|
200
|
-
total_timestamp = self._start_time + self._total
|
|
201
|
-
stop = total_timestamp <= raw_fragment.timestamp
|
|
202
|
-
|
|
203
|
-
return stop, kept, deferred, total_timestamp
|
|
204
|
-
|
|
205
|
-
def type(self) -> StopConditionType:
|
|
206
|
-
return StopConditionType.TIMEOUT
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def stop_condition_to_backend(stop_condition: StopCondition) -> StopConditionBackend:
|
|
210
|
-
if isinstance(stop_condition, Termination):
|
|
211
|
-
return TerminationBackend(stop_condition.sequence)
|
|
212
|
-
elif isinstance(stop_condition, Length):
|
|
213
|
-
return LengthBackend(stop_condition.N)
|
|
214
|
-
elif isinstance(stop_condition, Continuation):
|
|
215
|
-
return ContinuationBackend(stop_condition.continuation)
|
|
216
|
-
elif isinstance(stop_condition, Total):
|
|
217
|
-
return TotalBackend(stop_condition.total)
|
|
218
|
-
else:
|
|
219
|
-
raise RuntimeError(f"Invalid stop condition : {stop_condition}")
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# File : timed_queue.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# The timed queue is a special queue that stores time information when elements are stored inside
|
|
6
|
-
# This is used to manage timing when reading data from devices
|
|
7
|
-
|
|
8
|
-
# import queue
|
|
9
|
-
# from time import time
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
# class TimedQueue:
|
|
13
|
-
# def __init__(self) -> None:
|
|
14
|
-
# self._queue = queue.Queue()
|
|
15
|
-
|
|
16
|
-
# def put(self, fragment: bytes) -> None:
|
|
17
|
-
# self._queue.put((time(), fragment))
|
|
18
|
-
|
|
19
|
-
# def get(self, timeout: float | None) -> tuple[float, bytes]:
|
|
20
|
-
# """
|
|
21
|
-
# Return an element of the timed queue. Waits at most the amount of time specified by timeout
|
|
22
|
-
|
|
23
|
-
# Parameters
|
|
24
|
-
# ----------
|
|
25
|
-
# timeout : float, None
|
|
26
|
-
# """
|
|
27
|
-
# try:
|
|
28
|
-
# return self._queue.get(block=True, timeout=timeout)
|
|
29
|
-
# except queue.Empty: # No item after timeout
|
|
30
|
-
# return None, None
|
|
31
|
-
|
|
32
|
-
# def is_empty(self):
|
|
33
|
-
# return self._queue.empty()
|
|
34
|
-
|
|
35
|
-
# def clear(self):
|
|
36
|
-
# with self._queue.mutex:
|
|
37
|
-
# self._queue.queue.clear()
|
|
38
|
-
# self._queue.all_tasks_done.notify_all()
|
|
39
|
-
# self._queue.unfinished_tasks = 0
|