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,346 +0,0 @@
|
|
|
1
|
-
# File : backendclient.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# The backend client manages the link between clients (frontend) and the adapter (backend)
|
|
6
|
-
# It is instanctiated by the backend and has a thread to manage incoming data from clients
|
|
7
|
-
# as well as read incoming data from the adapter backend
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
import threading
|
|
11
|
-
import time
|
|
12
|
-
from enum import Enum
|
|
13
|
-
from multiprocessing.connection import Pipe, wait
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
from syndesi.adapters.backend.stop_condition_backend import (
|
|
17
|
-
stop_condition_to_backend,
|
|
18
|
-
)
|
|
19
|
-
from syndesi.tools.errors import make_error_description
|
|
20
|
-
from syndesi.tools.types import NumberLike
|
|
21
|
-
|
|
22
|
-
from ...tools.backend_api import Action, frontend_send
|
|
23
|
-
from ...tools.log_settings import LoggerAlias
|
|
24
|
-
from .adapter_backend import (
|
|
25
|
-
AdapterBackend,
|
|
26
|
-
Selectable,
|
|
27
|
-
nmin,
|
|
28
|
-
)
|
|
29
|
-
from .backend_tools import NamedConnection
|
|
30
|
-
from .descriptors import (
|
|
31
|
-
Descriptor,
|
|
32
|
-
IPDescriptor,
|
|
33
|
-
SerialPortDescriptor,
|
|
34
|
-
VisaDescriptor,
|
|
35
|
-
adapter_descriptor_by_string,
|
|
36
|
-
)
|
|
37
|
-
from .ip_backend import IPBackend
|
|
38
|
-
from .serialport_backend import SerialPortBackend
|
|
39
|
-
|
|
40
|
-
# from .stop_condition_backend import stop_condition_from_list
|
|
41
|
-
from .visa_backend import VisaBackend
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class TimeoutEvent(Enum):
|
|
45
|
-
MONITORING = 0
|
|
46
|
-
ADAPTER = 1
|
|
47
|
-
# CONNECTIONS = 2
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def get_adapter(descriptor: Descriptor) -> AdapterBackend:
|
|
51
|
-
# The adapter doesn't exist, create it
|
|
52
|
-
if isinstance(
|
|
53
|
-
descriptor, SerialPortDescriptor
|
|
54
|
-
): # Add mandatory timeout and stop_condition here ?
|
|
55
|
-
return SerialPortBackend(descriptor=descriptor)
|
|
56
|
-
elif isinstance(descriptor, IPDescriptor):
|
|
57
|
-
return IPBackend(descriptor=descriptor)
|
|
58
|
-
elif isinstance(descriptor, VisaDescriptor):
|
|
59
|
-
return VisaBackend(descriptor=descriptor)
|
|
60
|
-
else:
|
|
61
|
-
raise ValueError(f"Unsupported descriptor : {descriptor}")
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class AdapterSession(threading.Thread):
|
|
65
|
-
MONITORING_DELAY = 0.5
|
|
66
|
-
daemon = True
|
|
67
|
-
_shutdown_counter_top: int | None
|
|
68
|
-
_shutdown_counter: int | None
|
|
69
|
-
|
|
70
|
-
def __init__(self, adapter_descriptor: str, shutdown_delay: NumberLike | None):
|
|
71
|
-
super().__init__(daemon=True)
|
|
72
|
-
self._logger = logging.getLogger(LoggerAlias.ADAPTER_BACKEND.value)
|
|
73
|
-
self._logger.setLevel("DEBUG")
|
|
74
|
-
self._role = None
|
|
75
|
-
self._next_monitoring_timestamp = time.time() + self.MONITORING_DELAY
|
|
76
|
-
|
|
77
|
-
# self._stop_flag = False
|
|
78
|
-
self._connections_lock = threading.Lock()
|
|
79
|
-
# self._connection_condition = threading.Condition(self._connections_lock)
|
|
80
|
-
|
|
81
|
-
descriptor = adapter_descriptor_by_string(adapter_descriptor)
|
|
82
|
-
|
|
83
|
-
self._adapter: AdapterBackend = get_adapter(descriptor)
|
|
84
|
-
|
|
85
|
-
self.connections: list[NamedConnection] = []
|
|
86
|
-
# self.connection_names : Dict[NamedConnection] = {}
|
|
87
|
-
|
|
88
|
-
# self._new_connection_r, self._new_connection_w = os.pipe()
|
|
89
|
-
# os.pipe does not work on Windows
|
|
90
|
-
self._new_connection_r, self._new_connection_w = Pipe()
|
|
91
|
-
|
|
92
|
-
self._shutdown_delay = shutdown_delay
|
|
93
|
-
if self._shutdown_delay is not None:
|
|
94
|
-
self._shutdown_counter_top = int(
|
|
95
|
-
round(self._shutdown_delay / self.MONITORING_DELAY)
|
|
96
|
-
)
|
|
97
|
-
self._shutdown_counter = self._shutdown_counter_top
|
|
98
|
-
else:
|
|
99
|
-
self._shutdown_counter_top = None
|
|
100
|
-
self._shutdown_counter = None
|
|
101
|
-
|
|
102
|
-
# self._timeout_events: list[tuple[TimeoutEvent, float]] = []
|
|
103
|
-
|
|
104
|
-
self._read_init_id = 0
|
|
105
|
-
|
|
106
|
-
def add_connection(self, conn: NamedConnection) -> None:
|
|
107
|
-
with self._connections_lock:
|
|
108
|
-
self.connections.append(conn)
|
|
109
|
-
# os.write(self._new_connection_w, b"\x00")
|
|
110
|
-
self._new_connection_w.send(b"\x00")
|
|
111
|
-
self._logger.info(f"New client : {conn.remote()}")
|
|
112
|
-
|
|
113
|
-
def _remove_connection(self, conn: NamedConnection) -> None:
|
|
114
|
-
with self._connections_lock:
|
|
115
|
-
if conn in self.connections:
|
|
116
|
-
conn.conn.close()
|
|
117
|
-
self.connections.remove(conn)
|
|
118
|
-
|
|
119
|
-
def send(self, conn: NamedConnection, action: Action, *args: Any) -> None:
|
|
120
|
-
if not frontend_send(conn.conn, action, *args):
|
|
121
|
-
self._logger.warning(f"Failed to send to {conn.remote()}")
|
|
122
|
-
self._remove_connection(conn)
|
|
123
|
-
|
|
124
|
-
def send_to_all(self, action: Action, *args: Any) -> None:
|
|
125
|
-
for conn in self.connections:
|
|
126
|
-
frontend_send(conn.conn, action, *args)
|
|
127
|
-
|
|
128
|
-
def enumerate_connections(self) -> list[str]:
|
|
129
|
-
return [x.remote_address() for x in self.connections]
|
|
130
|
-
|
|
131
|
-
def is_adapter_opened(self) -> bool:
|
|
132
|
-
return self._adapter.is_opened()
|
|
133
|
-
|
|
134
|
-
def run(self) -> None:
|
|
135
|
-
while True:
|
|
136
|
-
try:
|
|
137
|
-
stop = self.loop()
|
|
138
|
-
if stop:
|
|
139
|
-
break
|
|
140
|
-
except Exception as e:
|
|
141
|
-
error_message = make_error_description(e)
|
|
142
|
-
|
|
143
|
-
self._logger.critical(
|
|
144
|
-
f"Error in {self._adapter.descriptor} session loop : {error_message}"
|
|
145
|
-
)
|
|
146
|
-
try:
|
|
147
|
-
error_message = make_error_description(e)
|
|
148
|
-
|
|
149
|
-
for conn in self.connections:
|
|
150
|
-
frontend_send(conn.conn, Action.ERROR_GENERIC, error_message)
|
|
151
|
-
except Exception:
|
|
152
|
-
break
|
|
153
|
-
self._logger.info(f"Exit {self._adapter.descriptor} session loop")
|
|
154
|
-
|
|
155
|
-
def loop(self) -> bool:
|
|
156
|
-
# This is the main loop of the session
|
|
157
|
-
# It listens for the following events :
|
|
158
|
-
# - New client
|
|
159
|
-
# -> asynchronous from backend
|
|
160
|
-
# - Event on a current client connection
|
|
161
|
-
# -> listen to conn
|
|
162
|
-
# - Adapter event
|
|
163
|
-
# -> listen to socket/fd
|
|
164
|
-
# The wait has a timeout set by the adapter, it corresponds to the current continuation/total timeout
|
|
165
|
-
|
|
166
|
-
# Create a list of what is awaited
|
|
167
|
-
wait_list: list[Selectable] = [conn.conn for conn in self.connections]
|
|
168
|
-
adapter_fd = self._adapter.selectable()
|
|
169
|
-
if adapter_fd is not None and adapter_fd.fileno() >= 0:
|
|
170
|
-
wait_list.append(adapter_fd)
|
|
171
|
-
|
|
172
|
-
wait_list.append(self._new_connection_r)
|
|
173
|
-
|
|
174
|
-
timeout_timestamp = None
|
|
175
|
-
event = None
|
|
176
|
-
|
|
177
|
-
adapter_timestamp = self._adapter.get_next_timeout()
|
|
178
|
-
if adapter_timestamp is not None:
|
|
179
|
-
timeout_timestamp = nmin(timeout_timestamp, adapter_timestamp)
|
|
180
|
-
event = TimeoutEvent.ADAPTER
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
timeout_timestamp is None
|
|
184
|
-
or self._next_monitoring_timestamp < timeout_timestamp
|
|
185
|
-
):
|
|
186
|
-
timeout_timestamp = self._next_monitoring_timestamp
|
|
187
|
-
event = TimeoutEvent.MONITORING
|
|
188
|
-
|
|
189
|
-
if timeout_timestamp is None:
|
|
190
|
-
timeout = None
|
|
191
|
-
else:
|
|
192
|
-
timeout = timeout_timestamp - time.time()
|
|
193
|
-
ready = wait(wait_list, timeout=timeout) # type: ignore
|
|
194
|
-
t = time.time()
|
|
195
|
-
if len(ready) == 0:
|
|
196
|
-
# Timeout event
|
|
197
|
-
if event == TimeoutEvent.MONITORING:
|
|
198
|
-
self._next_monitoring_timestamp = t + self.MONITORING_DELAY
|
|
199
|
-
stop = self._monitor()
|
|
200
|
-
if stop:
|
|
201
|
-
return True
|
|
202
|
-
elif event == TimeoutEvent.ADAPTER:
|
|
203
|
-
signal = self._adapter.on_timeout_event()
|
|
204
|
-
if signal is not None:
|
|
205
|
-
# The signal can be none if it has been disabled in the meantime
|
|
206
|
-
self._logger.debug(f"Adapter signal (timeout) : {signal}")
|
|
207
|
-
self.send_to_all(Action.ADAPTER_SIGNAL, signal)
|
|
208
|
-
|
|
209
|
-
# Main adapter loop
|
|
210
|
-
if self._new_connection_r in ready:
|
|
211
|
-
# New connection event
|
|
212
|
-
self._new_connection_r.recv()
|
|
213
|
-
# Adapter event
|
|
214
|
-
if self._adapter.selectable() in ready:
|
|
215
|
-
for signal in self._adapter.on_socket_ready():
|
|
216
|
-
self._logger.debug(f"Adapter signal (selectable) : {signal}")
|
|
217
|
-
self.send_to_all(Action.ADAPTER_SIGNAL, signal)
|
|
218
|
-
|
|
219
|
-
for conn in self.connections:
|
|
220
|
-
if conn.conn in ready:
|
|
221
|
-
# Manage a command received from the user
|
|
222
|
-
self.manage_conn(conn)
|
|
223
|
-
return False
|
|
224
|
-
|
|
225
|
-
def _monitor(self) -> bool:
|
|
226
|
-
stop = False
|
|
227
|
-
if self._shutdown_counter is not None:
|
|
228
|
-
with self._connections_lock:
|
|
229
|
-
if len(self.connections) == 0:
|
|
230
|
-
if self._shutdown_counter == 0:
|
|
231
|
-
# Shutdown
|
|
232
|
-
self._logger.info(
|
|
233
|
-
f"No clients on adapter {self._adapter.descriptor} for {self._shutdown_delay}s, closing"
|
|
234
|
-
)
|
|
235
|
-
self._adapter.close()
|
|
236
|
-
stop = True
|
|
237
|
-
else:
|
|
238
|
-
self._shutdown_counter -= 1
|
|
239
|
-
else:
|
|
240
|
-
self._shutdown_counter = self._shutdown_counter_top
|
|
241
|
-
|
|
242
|
-
return stop
|
|
243
|
-
|
|
244
|
-
def manage_conn(self, conn: NamedConnection) -> None:
|
|
245
|
-
extra_arguments: tuple[Any, ...]
|
|
246
|
-
remove_after_response = False
|
|
247
|
-
if not conn.conn.poll():
|
|
248
|
-
# No data, connection is closed
|
|
249
|
-
self._logger.warning(f"Client {conn.remote()} closed unexpectedly")
|
|
250
|
-
self._remove_connection(conn)
|
|
251
|
-
return
|
|
252
|
-
try:
|
|
253
|
-
request = conn.conn.recv()
|
|
254
|
-
except (EOFError, ConnectionResetError) as e:
|
|
255
|
-
# Probably a ping or an error
|
|
256
|
-
self._logger.warning(
|
|
257
|
-
f"Failed to read from client {conn.remote()} ({e}), closing connection "
|
|
258
|
-
)
|
|
259
|
-
self._remove_connection(conn)
|
|
260
|
-
else:
|
|
261
|
-
if not (isinstance(request, tuple) and len(request) >= 1):
|
|
262
|
-
response_action = Action.ERROR_INVALID_REQUEST
|
|
263
|
-
extra_arguments = ("",)
|
|
264
|
-
else:
|
|
265
|
-
action: Action
|
|
266
|
-
action = Action(request[0])
|
|
267
|
-
response_action = Action.ERROR_GENERIC
|
|
268
|
-
extra_arguments = ("Unknown error in session",)
|
|
269
|
-
try:
|
|
270
|
-
match action:
|
|
271
|
-
case Action.OPEN:
|
|
272
|
-
self._adapter.set_stop_conditions(
|
|
273
|
-
[stop_condition_to_backend(sc) for sc in request[1]]
|
|
274
|
-
)
|
|
275
|
-
if self._adapter.open():
|
|
276
|
-
# Success !
|
|
277
|
-
response_action = Action.OPEN
|
|
278
|
-
else:
|
|
279
|
-
response_action = Action.ERROR_FAILED_TO_OPEN
|
|
280
|
-
extra_arguments = ("",)
|
|
281
|
-
case Action.WRITE:
|
|
282
|
-
data = request[1]
|
|
283
|
-
if self._adapter.is_opened():
|
|
284
|
-
if self._adapter.write(data):
|
|
285
|
-
# Success
|
|
286
|
-
response_action, extra_arguments = Action.WRITE, ()
|
|
287
|
-
else:
|
|
288
|
-
response_action, extra_arguments = (
|
|
289
|
-
Action.ERROR_ADAPTER_DISCONNECTED,
|
|
290
|
-
("",),
|
|
291
|
-
)
|
|
292
|
-
# TODO : Maybe close here ? not sure
|
|
293
|
-
else:
|
|
294
|
-
response_action, extra_arguments = (
|
|
295
|
-
Action.ERROR_ADAPTER_NOT_OPENED,
|
|
296
|
-
("Open adapter before writing",),
|
|
297
|
-
)
|
|
298
|
-
self._logger.error("Could not write, adapter is closed")
|
|
299
|
-
case Action.PING:
|
|
300
|
-
response_action, extra_arguments = Action.PING, ()
|
|
301
|
-
case Action.SET_STOP_CONDITIONs:
|
|
302
|
-
self._adapter.set_stop_conditions(
|
|
303
|
-
[stop_condition_to_backend(sc) for sc in request[1]]
|
|
304
|
-
)
|
|
305
|
-
response_action, extra_arguments = (
|
|
306
|
-
Action.SET_STOP_CONDITIONs,
|
|
307
|
-
(),
|
|
308
|
-
)
|
|
309
|
-
case Action.FLUSHREAD:
|
|
310
|
-
self._adapter.flush_read()
|
|
311
|
-
response_action, extra_arguments = Action.FLUSHREAD, ()
|
|
312
|
-
case Action.START_READ:
|
|
313
|
-
response_time = float(request[1])
|
|
314
|
-
self._adapter.start_read(response_time, self._read_init_id)
|
|
315
|
-
response_action, extra_arguments = Action.START_READ, (
|
|
316
|
-
self._read_init_id,
|
|
317
|
-
)
|
|
318
|
-
self._read_init_id += 1
|
|
319
|
-
|
|
320
|
-
# case Action.GET_BACKEND_TIME:
|
|
321
|
-
# response_action = Action.GET_BACKEND_TIME
|
|
322
|
-
# extra_arguments = (request_timestamp, )
|
|
323
|
-
case Action.CLOSE:
|
|
324
|
-
force = request[1]
|
|
325
|
-
# Close this connection
|
|
326
|
-
remove_after_response = True
|
|
327
|
-
response_action, extra_arguments = Action.CLOSE, ()
|
|
328
|
-
if force:
|
|
329
|
-
self._adapter.close()
|
|
330
|
-
case _:
|
|
331
|
-
response_action, extra_arguments = (
|
|
332
|
-
Action.ERROR_UNKNOWN_ACTION,
|
|
333
|
-
(f"{action}",),
|
|
334
|
-
)
|
|
335
|
-
except Exception as e:
|
|
336
|
-
error_message = make_error_description(e)
|
|
337
|
-
|
|
338
|
-
response_action, extra_arguments = (
|
|
339
|
-
Action.ERROR_GENERIC,
|
|
340
|
-
(error_message,),
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
frontend_send(conn.conn, response_action, *extra_arguments)
|
|
344
|
-
if remove_after_response:
|
|
345
|
-
self._logger.info(f"Closing client {conn.remote()} connection")
|
|
346
|
-
self._remove_connection(conn)
|