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,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)