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,438 +0,0 @@
|
|
|
1
|
-
# File : backend.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# The backend is responsible for managing incoming client connections (frontend)
|
|
6
|
-
# and creating backend client threads if necessary
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import argparse
|
|
10
|
-
import logging
|
|
11
|
-
import os
|
|
12
|
-
import select
|
|
13
|
-
import threading
|
|
14
|
-
import time
|
|
15
|
-
from collections.abc import Callable
|
|
16
|
-
from multiprocessing.connection import Client, Listener
|
|
17
|
-
from types import EllipsisType
|
|
18
|
-
from typing import Any, TypeGuard, cast
|
|
19
|
-
|
|
20
|
-
from syndesi.adapters.backend.adapter_backend import Selectable
|
|
21
|
-
from syndesi.adapters.backend.backend_tools import NamedConnection
|
|
22
|
-
from syndesi.tools.types import NumberLike
|
|
23
|
-
|
|
24
|
-
from ...tools.backend_api import (
|
|
25
|
-
BACKEND_PORT,
|
|
26
|
-
LOCALHOST,
|
|
27
|
-
Action,
|
|
28
|
-
BackendResponse,
|
|
29
|
-
frontend_send,
|
|
30
|
-
)
|
|
31
|
-
from ...tools.log_settings import LoggerAlias
|
|
32
|
-
from .adapter_session import AdapterSession
|
|
33
|
-
|
|
34
|
-
# There is a single backend, each connection to the backend creates a thread
|
|
35
|
-
# We need something to manage all of these threads so that two can access the same ressource
|
|
36
|
-
|
|
37
|
-
DEFAULT_BACKEND_SHUTDOWN_DELAY = 2
|
|
38
|
-
DEFAULT_SESSION_SHUTDOWN_DELAY = 2
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class LogRelayHandler(logging.Handler):
|
|
42
|
-
def __init__(self, history_size: int = 100):
|
|
43
|
-
super().__init__()
|
|
44
|
-
self.connections: set[NamedConnection] = (
|
|
45
|
-
set()
|
|
46
|
-
) # Set of active logger connections
|
|
47
|
-
self.connections_lock = threading.Lock()
|
|
48
|
-
self.log_history: list[logging.LogRecord] = []
|
|
49
|
-
self.log_history_lock = threading.Lock()
|
|
50
|
-
self.history_size = history_size
|
|
51
|
-
|
|
52
|
-
def add_connection(
|
|
53
|
-
self, conn: NamedConnection, delete_callback: Callable[[NamedConnection], None]
|
|
54
|
-
) -> None:
|
|
55
|
-
# Send log history to the new connection
|
|
56
|
-
with self.log_history_lock:
|
|
57
|
-
for record in self.log_history:
|
|
58
|
-
try:
|
|
59
|
-
conn.conn.send(record)
|
|
60
|
-
except (BrokenPipeError, OSError):
|
|
61
|
-
delete_callback(conn)
|
|
62
|
-
return
|
|
63
|
-
# Add to active connections
|
|
64
|
-
with self.connections_lock:
|
|
65
|
-
self.connections.add(conn)
|
|
66
|
-
|
|
67
|
-
def remove_connection(self, conn: NamedConnection) -> None:
|
|
68
|
-
with self.connections_lock:
|
|
69
|
-
self.connections = {c for c in self.connections if c != conn}
|
|
70
|
-
|
|
71
|
-
def emit(self, record: logging.LogRecord) -> None:
|
|
72
|
-
# Add to history
|
|
73
|
-
with self.log_history_lock:
|
|
74
|
-
self.log_history.append(record)
|
|
75
|
-
if len(self.log_history) > self.history_size:
|
|
76
|
-
self.log_history.pop(0)
|
|
77
|
-
# Broadcast to all connections
|
|
78
|
-
to_remove: list[NamedConnection] = []
|
|
79
|
-
with self.connections_lock:
|
|
80
|
-
for conn in list(self.connections):
|
|
81
|
-
try:
|
|
82
|
-
conn.conn.send(record)
|
|
83
|
-
except (BrokenPipeError, OSError):
|
|
84
|
-
to_remove.append(conn)
|
|
85
|
-
|
|
86
|
-
for conn in to_remove:
|
|
87
|
-
self.remove_connection(conn)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def is_request(x: object) -> TypeGuard[tuple[str, object]]:
|
|
91
|
-
if not isinstance(x, tuple) or not x:
|
|
92
|
-
return False
|
|
93
|
-
if not isinstance(x[0], str):
|
|
94
|
-
return False
|
|
95
|
-
|
|
96
|
-
return True
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class Backend:
|
|
100
|
-
MONITORING_DELAY = 0.5
|
|
101
|
-
_session_shutdown_delay: NumberLike | None
|
|
102
|
-
_backend_shutdown_delay: NumberLike | None
|
|
103
|
-
_backend_shutdown_timestamp: NumberLike | None
|
|
104
|
-
shutdown_timer: threading.Timer | None
|
|
105
|
-
|
|
106
|
-
def __init__(
|
|
107
|
-
self,
|
|
108
|
-
host: str,
|
|
109
|
-
port: int,
|
|
110
|
-
backend_shutdown_delay: None | NumberLike | EllipsisType = ...,
|
|
111
|
-
session_shutdown_delay: None | NumberLike | EllipsisType = ...,
|
|
112
|
-
):
|
|
113
|
-
|
|
114
|
-
if backend_shutdown_delay is ...:
|
|
115
|
-
self._backend_shutdown_delay = DEFAULT_BACKEND_SHUTDOWN_DELAY
|
|
116
|
-
else:
|
|
117
|
-
self._backend_shutdown_delay = backend_shutdown_delay
|
|
118
|
-
|
|
119
|
-
if session_shutdown_delay is ...:
|
|
120
|
-
self._session_shutdown_delay = DEFAULT_SESSION_SHUTDOWN_DELAY
|
|
121
|
-
else:
|
|
122
|
-
self._session_shutdown_delay = session_shutdown_delay
|
|
123
|
-
|
|
124
|
-
if self._backend_shutdown_delay is None:
|
|
125
|
-
self._backend_shutdown_timestamp = None
|
|
126
|
-
else:
|
|
127
|
-
self._backend_shutdown_timestamp = (
|
|
128
|
-
time.time() + self._backend_shutdown_delay
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
self.host = host
|
|
132
|
-
self.port = port
|
|
133
|
-
|
|
134
|
-
# self.adapters_lock = threading.Lock()
|
|
135
|
-
self.listener: Listener = Listener((self.host, self.port), backlog=10)
|
|
136
|
-
self.adapter_sessions: dict[str, AdapterSession] = {}
|
|
137
|
-
self.shutdown_timer = None
|
|
138
|
-
|
|
139
|
-
# Monitoring connections
|
|
140
|
-
self._monitoring_connections: list[NamedConnection] = []
|
|
141
|
-
|
|
142
|
-
# Logger connections
|
|
143
|
-
# self._logger_connections: list[NamedConnection] = []
|
|
144
|
-
|
|
145
|
-
# Configure loggers
|
|
146
|
-
self._log_handler = LogRelayHandler(history_size=100)
|
|
147
|
-
|
|
148
|
-
self._adapter_session_logger = logging.getLogger(
|
|
149
|
-
LoggerAlias.ADAPTER_BACKEND.value
|
|
150
|
-
)
|
|
151
|
-
self._adapter_session_logger.addHandler(self._log_handler)
|
|
152
|
-
self._adapter_session_logger.setLevel(logging.DEBUG)
|
|
153
|
-
self._logger = logging.getLogger(LoggerAlias.BACKEND.value)
|
|
154
|
-
self._logger.setLevel(logging.DEBUG)
|
|
155
|
-
self._logger.addHandler(self._log_handler)
|
|
156
|
-
|
|
157
|
-
self._logger.info(f"Init backend on {self.host}:{self.port}")
|
|
158
|
-
|
|
159
|
-
self.running = True
|
|
160
|
-
|
|
161
|
-
def _remove_logger_connection(self, conn: NamedConnection) -> None:
|
|
162
|
-
self._log_handler.remove_connection(conn)
|
|
163
|
-
self._update_monitoring(monitoring_sessions=True)
|
|
164
|
-
|
|
165
|
-
def _remove_monitoring_connection(self, conn: NamedConnection) -> None:
|
|
166
|
-
try:
|
|
167
|
-
self._monitoring_connections.remove(conn)
|
|
168
|
-
except ValueError:
|
|
169
|
-
pass
|
|
170
|
-
self._update_monitoring(monitoring_sessions=True)
|
|
171
|
-
|
|
172
|
-
def _remove_session(self, descriptor: str) -> None:
|
|
173
|
-
self._logger.info(f"Remove adapter session {descriptor}")
|
|
174
|
-
# with self.adapters_lock:
|
|
175
|
-
self.adapter_sessions.pop(descriptor, None)
|
|
176
|
-
self._update_monitoring(adapter_sessions=True)
|
|
177
|
-
|
|
178
|
-
def manage_monitoring_clients(self, conn: NamedConnection) -> None:
|
|
179
|
-
try:
|
|
180
|
-
raw: object = conn.conn.recv()
|
|
181
|
-
except (EOFError, ConnectionResetError):
|
|
182
|
-
# Monitor disconnected
|
|
183
|
-
# Remove from the list of monitoring connections
|
|
184
|
-
self._monitoring_connections.remove(conn)
|
|
185
|
-
conn.conn.close()
|
|
186
|
-
else:
|
|
187
|
-
response: BackendResponse
|
|
188
|
-
response = (Action.ERROR_GENERIC, "Unknown error")
|
|
189
|
-
if not is_request(raw):
|
|
190
|
-
response = (
|
|
191
|
-
Action.ERROR_INVALID_REQUEST,
|
|
192
|
-
"Invalid backend debugger request",
|
|
193
|
-
)
|
|
194
|
-
else:
|
|
195
|
-
action: Action = Action(raw[0])
|
|
196
|
-
|
|
197
|
-
if action == Action.BACKEND_STATS:
|
|
198
|
-
response = (Action.BACKEND_STATS, os.getpid())
|
|
199
|
-
elif action == Action.SET_LOG_LEVEL:
|
|
200
|
-
response = (Action.SET_LOG_LEVEL,)
|
|
201
|
-
level: int = cast(int, raw[1])
|
|
202
|
-
self._logger.setLevel(level)
|
|
203
|
-
self._adapter_session_logger.setLevel(level)
|
|
204
|
-
else:
|
|
205
|
-
response = (Action.ERROR_UNKNOWN_ACTION, f"{action}")
|
|
206
|
-
|
|
207
|
-
if not frontend_send(conn.conn, *response):
|
|
208
|
-
self._monitoring_connections.remove(conn)
|
|
209
|
-
|
|
210
|
-
def _broadcast_to_monitoring_clients(self, action: Action, *args: Any) -> None:
|
|
211
|
-
for conn in self._monitoring_connections:
|
|
212
|
-
if not frontend_send(conn.conn, action, *args):
|
|
213
|
-
self._remove_monitoring_connection(conn)
|
|
214
|
-
|
|
215
|
-
def _update_monitoring(
|
|
216
|
-
self,
|
|
217
|
-
adapter_sessions: bool = False,
|
|
218
|
-
monitoring_sessions: bool = False,
|
|
219
|
-
stats: bool = False,
|
|
220
|
-
) -> None:
|
|
221
|
-
if adapter_sessions:
|
|
222
|
-
snapshot: dict[str, tuple[bool, list[str]]] = {}
|
|
223
|
-
for adapter_descriptor, thread in self.adapter_sessions.items():
|
|
224
|
-
adapter_clients = thread.enumerate_connections()
|
|
225
|
-
status = thread.is_adapter_opened()
|
|
226
|
-
snapshot[adapter_descriptor] = status, adapter_clients
|
|
227
|
-
self._broadcast_to_monitoring_clients(
|
|
228
|
-
Action.ENUMERATE_ADAPTER_CONNECTIONS, snapshot
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
if monitoring_sessions:
|
|
232
|
-
self._broadcast_to_monitoring_clients(
|
|
233
|
-
Action.ENUMERATE_MONITORING_CONNECTIONS,
|
|
234
|
-
[(x.remote(), "logging") for x in self._log_handler.connections]
|
|
235
|
-
+ [(x.remote(), "monitoring") for x in self._monitoring_connections],
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
if stats:
|
|
239
|
-
self._broadcast_to_monitoring_clients(Action.BACKEND_STATS, os.getpid())
|
|
240
|
-
|
|
241
|
-
def _monitoring(self, ready_monitoring_clients: list[NamedConnection]) -> None:
|
|
242
|
-
t = time.time()
|
|
243
|
-
|
|
244
|
-
for conn in ready_monitoring_clients:
|
|
245
|
-
self.manage_monitoring_clients(conn)
|
|
246
|
-
|
|
247
|
-
if self._backend_shutdown_delay is not None:
|
|
248
|
-
active_threads = self.active_threads()
|
|
249
|
-
|
|
250
|
-
if active_threads == 0:
|
|
251
|
-
if self._backend_shutdown_timestamp is not None:
|
|
252
|
-
if time.time() >= self._backend_shutdown_timestamp:
|
|
253
|
-
self.stop()
|
|
254
|
-
else:
|
|
255
|
-
self._backend_shutdown_timestamp = t + self._backend_shutdown_delay
|
|
256
|
-
|
|
257
|
-
def active_threads(self) -> int:
|
|
258
|
-
# Check all threads, if one is active, return True
|
|
259
|
-
# Remove all of the dead ones
|
|
260
|
-
# Make as many passes as necessary
|
|
261
|
-
# with self.adapters_lock:
|
|
262
|
-
while True:
|
|
263
|
-
i = 0
|
|
264
|
-
for k, t in self.adapter_sessions.items():
|
|
265
|
-
if t.is_alive():
|
|
266
|
-
i += 1
|
|
267
|
-
else:
|
|
268
|
-
self._remove_session(k)
|
|
269
|
-
# Break because the dict changed size
|
|
270
|
-
break
|
|
271
|
-
else:
|
|
272
|
-
# Get out of the loop
|
|
273
|
-
break
|
|
274
|
-
|
|
275
|
-
return i
|
|
276
|
-
|
|
277
|
-
def _manage_new_adapter_client(self, client: NamedConnection) -> None:
|
|
278
|
-
# Wait for adapter
|
|
279
|
-
# ready = wait([client.conn], timeout=0.1)
|
|
280
|
-
# selectors to work on Unix and Windows
|
|
281
|
-
ready, _, _ = select.select([client.conn], [], [], 0.1)
|
|
282
|
-
if len(ready) == 0:
|
|
283
|
-
client.conn.close()
|
|
284
|
-
return
|
|
285
|
-
|
|
286
|
-
try:
|
|
287
|
-
adapter_request = client.conn.recv()
|
|
288
|
-
except EOFError:
|
|
289
|
-
return
|
|
290
|
-
action = Action(adapter_request[0])
|
|
291
|
-
if action == Action.SELECT_ADAPTER:
|
|
292
|
-
adapter_descriptor = adapter_request[1]
|
|
293
|
-
# If the session exists but it is dead, delete it
|
|
294
|
-
if (
|
|
295
|
-
adapter_descriptor in self.adapter_sessions
|
|
296
|
-
and not self.adapter_sessions[adapter_descriptor].is_alive()
|
|
297
|
-
):
|
|
298
|
-
self._remove_session(adapter_descriptor)
|
|
299
|
-
|
|
300
|
-
if adapter_descriptor not in self.adapter_sessions:
|
|
301
|
-
# Create the adapter backend thread
|
|
302
|
-
self._logger.info(f"Creating adapter session for {adapter_descriptor}")
|
|
303
|
-
thread = AdapterSession(
|
|
304
|
-
adapter_descriptor, shutdown_delay=self._session_shutdown_delay
|
|
305
|
-
) # TODO : Put another delay here ?
|
|
306
|
-
# with self.adapters_lock:
|
|
307
|
-
thread.start()
|
|
308
|
-
self.adapter_sessions[adapter_descriptor] = thread
|
|
309
|
-
|
|
310
|
-
self.adapter_sessions[adapter_descriptor].add_connection(client)
|
|
311
|
-
frontend_send(client.conn, action)
|
|
312
|
-
else:
|
|
313
|
-
client.conn.close()
|
|
314
|
-
|
|
315
|
-
self._update_monitoring(adapter_sessions=True)
|
|
316
|
-
|
|
317
|
-
def _new_monitoring_client(self, client: NamedConnection) -> None:
|
|
318
|
-
# with self._monitoring_connections_lock:
|
|
319
|
-
self._monitoring_connections.append(client)
|
|
320
|
-
self._update_monitoring(monitoring_sessions=True, stats=True)
|
|
321
|
-
|
|
322
|
-
def _new_logger_client(self, client: NamedConnection) -> None:
|
|
323
|
-
# Add connection to the single log handler
|
|
324
|
-
self._log_handler.add_connection(client, self._remove_logger_connection)
|
|
325
|
-
# self._logger_connections.append(client)
|
|
326
|
-
self._update_monitoring(monitoring_sessions=True)
|
|
327
|
-
|
|
328
|
-
def _new_adapter_client(self, client: NamedConnection) -> None:
|
|
329
|
-
try:
|
|
330
|
-
role_request = client.conn.recv()
|
|
331
|
-
except EOFError:
|
|
332
|
-
return
|
|
333
|
-
|
|
334
|
-
action = Action(role_request[0])
|
|
335
|
-
frontend_send(client.conn, action)
|
|
336
|
-
|
|
337
|
-
# Send confirmation directly
|
|
338
|
-
if action == Action.SET_ROLE_ADAPTER:
|
|
339
|
-
self._manage_new_adapter_client(client)
|
|
340
|
-
elif action == Action.SET_ROLE_MONITORING:
|
|
341
|
-
self._new_monitoring_client(client)
|
|
342
|
-
elif action == Action.SET_ROLE_LOGGER:
|
|
343
|
-
self._new_logger_client(client)
|
|
344
|
-
elif action == Action.PING:
|
|
345
|
-
frontend_send(client.conn, Action.PING)
|
|
346
|
-
elif action == Action.STOP:
|
|
347
|
-
self._logger.info("Stop request from client")
|
|
348
|
-
client.conn.close()
|
|
349
|
-
self.stop()
|
|
350
|
-
else:
|
|
351
|
-
frontend_send(client.conn, Action.ERROR_INVALID_ROLE)
|
|
352
|
-
client.conn.close()
|
|
353
|
-
|
|
354
|
-
def start(self) -> None:
|
|
355
|
-
while self.running:
|
|
356
|
-
# Use selectors to work on both Linux and Windows
|
|
357
|
-
|
|
358
|
-
selectables: list[Selectable] = [
|
|
359
|
-
x.conn for x in self._monitoring_connections
|
|
360
|
-
]
|
|
361
|
-
selectables.append(self.listener._listener._socket) # type: ignore
|
|
362
|
-
|
|
363
|
-
ready, _, _ = select.select(selectables, [], [], self.MONITORING_DELAY)
|
|
364
|
-
|
|
365
|
-
# ready : List[Selectable] = wait( # type: ignore
|
|
366
|
-
# [self.listener._listener._socket] + [x.conn for x in self._monitoring_connections], # type: ignore
|
|
367
|
-
# timeout=self.MONITORING_DELAY,
|
|
368
|
-
# )
|
|
369
|
-
|
|
370
|
-
if self.listener._listener._socket in ready: # type: ignore
|
|
371
|
-
conn = self.listener.accept()
|
|
372
|
-
|
|
373
|
-
self._new_adapter_client(NamedConnection(conn))
|
|
374
|
-
ready.remove(self.listener._listener._socket) # type: ignore
|
|
375
|
-
|
|
376
|
-
self._monitoring(
|
|
377
|
-
[c for c in self._monitoring_connections if c.conn in ready]
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
self.listener.close()
|
|
381
|
-
self._logger.info("Backend stopped")
|
|
382
|
-
|
|
383
|
-
def _delayed_stop(self) -> None:
|
|
384
|
-
if self._backend_shutdown_delay is not None:
|
|
385
|
-
self.shutdown_timer: threading.Timer | None = threading.Timer(
|
|
386
|
-
float(self._backend_shutdown_delay), self.stop
|
|
387
|
-
)
|
|
388
|
-
self.shutdown_timer.start()
|
|
389
|
-
|
|
390
|
-
def stop(self) -> None:
|
|
391
|
-
self.running = False
|
|
392
|
-
# Open a connection to stop the server
|
|
393
|
-
try:
|
|
394
|
-
# If the listener is on all interfaces, use localhost
|
|
395
|
-
if self.host == "0.0.0.0": # ALL_ADDRESSES:
|
|
396
|
-
address = LOCALHOST
|
|
397
|
-
else:
|
|
398
|
-
address = self.host
|
|
399
|
-
# Always connect to localhost
|
|
400
|
-
conn = Client((address, self.port))
|
|
401
|
-
frontend_send(conn, Action.STOP)
|
|
402
|
-
conn.close()
|
|
403
|
-
except Exception:
|
|
404
|
-
pass
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
def main(input_args: list[str] | None = None) -> None:
|
|
408
|
-
|
|
409
|
-
argument_parser = argparse.ArgumentParser()
|
|
410
|
-
|
|
411
|
-
argument_parser.add_argument(
|
|
412
|
-
"-a",
|
|
413
|
-
"--address",
|
|
414
|
-
type=str,
|
|
415
|
-
default=LOCALHOST,
|
|
416
|
-
help="Listening address, set it to the interface that will be used by the client",
|
|
417
|
-
)
|
|
418
|
-
argument_parser.add_argument("-p", "--port", type=int, default=BACKEND_PORT)
|
|
419
|
-
argument_parser.add_argument(
|
|
420
|
-
"-s",
|
|
421
|
-
"--shutdown-delay",
|
|
422
|
-
type=int,
|
|
423
|
-
default=None,
|
|
424
|
-
help="Delay before the backend shutdowns automatically",
|
|
425
|
-
)
|
|
426
|
-
argument_parser.add_argument("-q", "--quiet", default=False, action="store_true")
|
|
427
|
-
argument_parser.add_argument("-v", "--verbose", default=False, action="store_true")
|
|
428
|
-
|
|
429
|
-
args = argument_parser.parse_args(input_args)
|
|
430
|
-
|
|
431
|
-
backend = Backend(
|
|
432
|
-
host=args.address, port=args.port, backend_shutdown_delay=args.shutdown_delay
|
|
433
|
-
)
|
|
434
|
-
backend.start()
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if __name__ == "__main__":
|
|
438
|
-
main()
|
|
File without changes
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import socket
|
|
2
|
-
from multiprocessing.connection import Connection
|
|
3
|
-
|
|
4
|
-
BACKEND_REQUEST_DEFAULT_TIMEOUT = 0.5
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def get_conn_addresses(conn: Connection) -> tuple[tuple[str, int], tuple[str, int]]:
|
|
8
|
-
try:
|
|
9
|
-
fd = conn.fileno()
|
|
10
|
-
except OSError:
|
|
11
|
-
return (("closed", 0), ("closed", 0))
|
|
12
|
-
else:
|
|
13
|
-
sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
|
|
14
|
-
# try:
|
|
15
|
-
# TODO : Implement exception
|
|
16
|
-
# address, port = sock.getpeername() # (IP, port) tuple
|
|
17
|
-
peer_address = sock.getpeername()
|
|
18
|
-
sock_address = sock.getsockname()
|
|
19
|
-
return sock_address, peer_address
|
|
20
|
-
# except Exception:
|
|
21
|
-
# return (("error", 0), ("closed", 0))
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class ConnectionDescriptor:
|
|
25
|
-
def __init__(self, conn: Connection) -> None:
|
|
26
|
-
"""
|
|
27
|
-
Description of a multiprocessing Connection
|
|
28
|
-
"""
|
|
29
|
-
local, remote = get_conn_addresses(conn)
|
|
30
|
-
self._remote_address = remote[0]
|
|
31
|
-
self._remote_port = int(remote[1])
|
|
32
|
-
self._local_address = local[0]
|
|
33
|
-
self._local_port = int(local[1])
|
|
34
|
-
|
|
35
|
-
def remote(self) -> str:
|
|
36
|
-
return f"{self._remote_address}:{self._remote_port}"
|
|
37
|
-
|
|
38
|
-
def local(self) -> str:
|
|
39
|
-
return f"{self._local_address}:{self._local_port}"
|
|
40
|
-
|
|
41
|
-
def remote_address(self) -> str:
|
|
42
|
-
return self._remote_address
|
|
43
|
-
|
|
44
|
-
def remote_port(self) -> int:
|
|
45
|
-
return self._remote_port
|
|
46
|
-
|
|
47
|
-
def local_address(self) -> str:
|
|
48
|
-
return self._local_address
|
|
49
|
-
|
|
50
|
-
def local_port(self) -> int:
|
|
51
|
-
return self._local_port
|
|
52
|
-
|
|
53
|
-
def __str__(self) -> str:
|
|
54
|
-
return f"{self.local()}->{self.remote()}"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class NamedConnection(ConnectionDescriptor):
|
|
58
|
-
def __init__(self, conn: Connection) -> None:
|
|
59
|
-
super().__init__(conn)
|
|
60
|
-
self.conn = conn
|
|
61
|
-
|
|
62
|
-
def __str__(self) -> str:
|
|
63
|
-
return f"Connection {self.remote()}"
|
|
64
|
-
|
|
65
|
-
def __repr__(self) -> str:
|
|
66
|
-
return self.__str__()
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
# File : descriptors.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# Descriptors are classes that describe how an adapter is connected to its device.
|
|
6
|
-
# Depending on the protocol, they can hold strings, integers or enums
|
|
7
|
-
|
|
8
|
-
import re
|
|
9
|
-
from abc import abstractmethod
|
|
10
|
-
from dataclasses import dataclass
|
|
11
|
-
from enum import Enum
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Descriptor:
|
|
15
|
-
DETECTION_PATTERN = ""
|
|
16
|
-
|
|
17
|
-
def __init__(self) -> None:
|
|
18
|
-
return None
|
|
19
|
-
|
|
20
|
-
@staticmethod
|
|
21
|
-
@abstractmethod
|
|
22
|
-
def from_string(string: str) -> "Descriptor":
|
|
23
|
-
pass
|
|
24
|
-
|
|
25
|
-
@abstractmethod
|
|
26
|
-
def is_initialized(self) -> bool:
|
|
27
|
-
pass
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass
|
|
31
|
-
class SerialPortDescriptor(Descriptor):
|
|
32
|
-
DETECTION_PATTERN = r"(COM\d+|/dev[/\w\d]+):\d+"
|
|
33
|
-
port: str
|
|
34
|
-
baudrate: int | None = None
|
|
35
|
-
|
|
36
|
-
@staticmethod
|
|
37
|
-
def from_string(string: str) -> "SerialPortDescriptor":
|
|
38
|
-
parts = string.split(":")
|
|
39
|
-
port = parts[0]
|
|
40
|
-
baudrate = int(parts[1])
|
|
41
|
-
return SerialPortDescriptor(port, baudrate)
|
|
42
|
-
|
|
43
|
-
def set_default_baudrate(self, baudrate: int) -> bool:
|
|
44
|
-
if self.baudrate is not None:
|
|
45
|
-
self.baudrate = baudrate
|
|
46
|
-
return True
|
|
47
|
-
else:
|
|
48
|
-
return False
|
|
49
|
-
|
|
50
|
-
def __str__(self) -> str:
|
|
51
|
-
return f"{self.port}:{self.baudrate}"
|
|
52
|
-
|
|
53
|
-
def is_initialized(self) -> bool:
|
|
54
|
-
return self.baudrate is not None
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@dataclass
|
|
58
|
-
class IPDescriptor(Descriptor):
|
|
59
|
-
class Transport(Enum):
|
|
60
|
-
TCP = "TCP"
|
|
61
|
-
UDP = "UDP"
|
|
62
|
-
|
|
63
|
-
@classmethod
|
|
64
|
-
def from_str(cls, value: str) -> "IPDescriptor":
|
|
65
|
-
for member in cls:
|
|
66
|
-
if member.value.lower() == value.lower():
|
|
67
|
-
return member # type: ignore # TODO : Check this
|
|
68
|
-
raise ValueError(f"{value} is not a valid {cls.__name__}")
|
|
69
|
-
|
|
70
|
-
DETECTION_PATTERN = r"(\d+.\d+.\d+.\d+|[\w\.]+):\d+:(UDP|TCP)"
|
|
71
|
-
address: str
|
|
72
|
-
transport: Transport
|
|
73
|
-
port: int | None = None
|
|
74
|
-
# transport: Transport | None = None
|
|
75
|
-
|
|
76
|
-
@staticmethod
|
|
77
|
-
def from_string(string: str) -> "IPDescriptor":
|
|
78
|
-
parts = string.split(":")
|
|
79
|
-
address = parts[0]
|
|
80
|
-
port = int(parts[1])
|
|
81
|
-
transport = IPDescriptor.Transport(parts[2])
|
|
82
|
-
return IPDescriptor(address, transport, port)
|
|
83
|
-
|
|
84
|
-
def __str__(self) -> str:
|
|
85
|
-
return f"{self.address}:{self.port}:{self.Transport(self.transport).value}"
|
|
86
|
-
|
|
87
|
-
def is_initialized(self) -> bool:
|
|
88
|
-
return self.port is not None and self.transport is not None
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@dataclass
|
|
92
|
-
class VisaDescriptor(Descriptor):
|
|
93
|
-
# VISA Resource Address Examples
|
|
94
|
-
# GPIB (IEEE-488)
|
|
95
|
-
# GPIB0::14::INSTR
|
|
96
|
-
# # Serial (RS-232 or USB-Serial)
|
|
97
|
-
# ASRL1::INSTR # Windows COM1
|
|
98
|
-
# ASRL/dev/ttyUSB0::INSTR # Linux USB serial port
|
|
99
|
-
#
|
|
100
|
-
# # TCPIP INSTR (LXI/VXI-11/HiSLIP-compatible instruments)
|
|
101
|
-
# TCPIP0::192.168.1.100::INSTR
|
|
102
|
-
# TCPIP0::my-scope.local::inst0::INSTR
|
|
103
|
-
#
|
|
104
|
-
# # TCPIP SOCKET (Raw TCP communication)
|
|
105
|
-
# TCPIP0::192.168.1.42::5025::SOCKET
|
|
106
|
-
#
|
|
107
|
-
# # USB (USBTMC-compliant instruments)
|
|
108
|
-
# USB0::0x0957::0x1796::MY12345678::INSTR
|
|
109
|
-
#
|
|
110
|
-
# # VXI (Legacy modular instruments)
|
|
111
|
-
# VXI0::2::INSTR
|
|
112
|
-
#
|
|
113
|
-
# # PXI (Modular instrument chassis)
|
|
114
|
-
# PXI0::14::INSTR
|
|
115
|
-
DETECTION_PATTERN = r"([A-Z]+)(\d*|\/[^:]+)?::([^:]+)(?:::([^:]+))?(?:::([^:]+))?(?:::([^:]+))?::(INSTR|SOCKET)"
|
|
116
|
-
|
|
117
|
-
descriptor: str
|
|
118
|
-
|
|
119
|
-
class Interface(Enum):
|
|
120
|
-
GPIB = "GPIB"
|
|
121
|
-
SERIAL = "ASRL"
|
|
122
|
-
TCP = "TCPIP"
|
|
123
|
-
USB = "USB"
|
|
124
|
-
VXI = "VXI"
|
|
125
|
-
PXI = "PXI"
|
|
126
|
-
|
|
127
|
-
@staticmethod
|
|
128
|
-
def from_string(string: str) -> "VisaDescriptor":
|
|
129
|
-
if re.match(VisaDescriptor.DETECTION_PATTERN, string):
|
|
130
|
-
return VisaDescriptor(descriptor=string)
|
|
131
|
-
else:
|
|
132
|
-
raise ValueError(f"Could not parse descriptor : {string}")
|
|
133
|
-
|
|
134
|
-
def __str__(self) -> str:
|
|
135
|
-
return self.descriptor
|
|
136
|
-
|
|
137
|
-
def is_initialized(self) -> bool:
|
|
138
|
-
return True
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
descriptors: list[type[Descriptor]] = [
|
|
142
|
-
SerialPortDescriptor,
|
|
143
|
-
IPDescriptor,
|
|
144
|
-
VisaDescriptor,
|
|
145
|
-
]
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def adapter_descriptor_by_string(string_descriptor: str) -> Descriptor:
|
|
149
|
-
for descriptor in descriptors:
|
|
150
|
-
if re.match(descriptor.DETECTION_PATTERN, string_descriptor):
|
|
151
|
-
x = descriptor.from_string(string_descriptor)
|
|
152
|
-
return x
|
|
153
|
-
raise ValueError(f"Could not parse descriptor string : {string_descriptor}")
|