syndesi 0.4.1__py3-none-any.whl → 0.4.4__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 (44) hide show
  1. syndesi/__init__.py +16 -1
  2. syndesi/adapters/adapter.py +187 -108
  3. syndesi/adapters/auto.py +26 -15
  4. syndesi/adapters/backend/adapter_backend.py +57 -39
  5. syndesi/adapters/backend/adapter_session.py +30 -32
  6. syndesi/adapters/backend/backend.py +7 -2
  7. syndesi/adapters/backend/backend_status.py +0 -0
  8. syndesi/adapters/backend/backend_tools.py +1 -1
  9. syndesi/adapters/backend/descriptors.py +1 -1
  10. syndesi/adapters/backend/ip_backend.py +45 -42
  11. syndesi/adapters/backend/serialport_backend.py +23 -19
  12. syndesi/adapters/backend/stop_condition_backend.py +54 -30
  13. syndesi/adapters/backend/visa_backend.py +5 -5
  14. syndesi/adapters/ip.py +33 -27
  15. syndesi/adapters/ip_server.py +9 -3
  16. syndesi/adapters/serialport.py +20 -7
  17. syndesi/adapters/stop_condition.py +86 -135
  18. syndesi/adapters/timeout.py +2 -28
  19. syndesi/adapters/visa.py +11 -3
  20. syndesi/cli/backend_status.py +7 -9
  21. syndesi/cli/console.py +0 -53
  22. syndesi/cli/shell.py +3 -16
  23. syndesi/cli/shell_tools.py +0 -5
  24. syndesi/component.py +79 -0
  25. syndesi/protocols/delimited.py +7 -22
  26. syndesi/protocols/modbus.py +9 -8
  27. syndesi/protocols/protocol.py +7 -1
  28. syndesi/protocols/raw.py +4 -4
  29. syndesi/protocols/scpi.py +6 -5
  30. syndesi/scripts/syndesi.py +2 -4
  31. syndesi/tools/backend_api.py +6 -32
  32. syndesi/tools/backend_logger.py +0 -1
  33. syndesi/tools/errors.py +28 -9
  34. syndesi/tools/log.py +0 -87
  35. syndesi/tools/types.py +0 -43
  36. syndesi/version.py +5 -1
  37. syndesi-0.4.4.dist-info/METADATA +96 -0
  38. syndesi-0.4.4.dist-info/RECORD +61 -0
  39. syndesi-0.4.1.dist-info/METADATA +0 -123
  40. syndesi-0.4.1.dist-info/RECORD +0 -59
  41. {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/WHEEL +0 -0
  42. {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/entry_points.txt +0 -0
  43. {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/licenses/LICENSE +0 -0
  44. {syndesi-0.4.1.dist-info → syndesi-0.4.4.dist-info}/top_level.txt +0 -0
@@ -7,19 +7,18 @@
7
7
 
8
8
  import logging
9
9
  import socket
10
+ import time
10
11
  from abc import ABC, abstractmethod
11
12
  from collections.abc import Generator
12
13
  from dataclasses import dataclass
13
14
  from enum import Enum
14
15
  from multiprocessing.connection import Connection
15
16
  from threading import Thread
16
- import time
17
- from typing import Protocol, cast
18
-
19
- from syndesi.tools.types import NumberLike
17
+ from typing import Protocol
20
18
 
21
19
  from ...tools.backend_api import AdapterBackendStatus, Fragment
22
20
  from ...tools.log_settings import LoggerAlias
21
+ from ..stop_condition import StopConditionType
23
22
  from .descriptors import Descriptor
24
23
  from .stop_condition_backend import (
25
24
  ContinuationBackend,
@@ -27,8 +26,6 @@ from .stop_condition_backend import (
27
26
  TotalBackend,
28
27
  )
29
28
 
30
- from ..stop_condition import Continuation, StopConditionType
31
-
32
29
 
33
30
  class HasFileno(Protocol):
34
31
  def fileno(self) -> int:
@@ -64,7 +61,7 @@ class AdapterSignal:
64
61
  pass
65
62
 
66
63
 
67
- class AdapterDisconnected(AdapterSignal):
64
+ class AdapterDisconnectedSignal(AdapterSignal):
68
65
  def __str__(self) -> str:
69
66
  return "Adapter disconnected"
70
67
 
@@ -82,12 +79,13 @@ class AdapterDisconnected(AdapterSignal):
82
79
  # def __repr__(self) -> str:
83
80
  # return self.__str__()
84
81
 
82
+
85
83
  @dataclass
86
84
  class AdapterResponseTimeout(AdapterSignal):
87
- identifier : int
85
+ identifier: int
88
86
 
89
87
  def __str__(self) -> str:
90
- return f"Response timeout"
88
+ return "Response timeout"
91
89
 
92
90
  def __repr__(self) -> str:
93
91
  return self.__str__()
@@ -99,9 +97,9 @@ class AdapterReadPayload(AdapterSignal):
99
97
  stop_timestamp: float
100
98
  stop_condition_type: StopConditionType
101
99
  previous_read_buffer_used: bool
102
- response_timestamp : float | None
100
+ response_timestamp: float | None
103
101
  # Only used by client and set by frontend
104
- response_delay : float | None = None
102
+ response_delay: float | None = None
105
103
 
106
104
  def data(self) -> bytes:
107
105
  return b"".join([f.data for f in self.fragments])
@@ -118,9 +116,10 @@ class AdapterReadPayload(AdapterSignal):
118
116
  @dataclass
119
117
  class ResponseRequest:
120
118
  timestamp: float
121
- identifier : int
119
+ identifier: int
120
+
122
121
 
123
- def nmin(a : float | None, b : float | None) -> float | None:
122
+ def nmin(a: float | None, b: float | None) -> float | None:
124
123
  if a is None and b is None:
125
124
  return None
126
125
  elif a is None:
@@ -130,6 +129,7 @@ def nmin(a : float | None, b : float | None) -> float | None:
130
129
  else:
131
130
  return min(a, b)
132
131
 
132
+
133
133
  class AdapterBackend(ABC):
134
134
  class ThreadCommands(Enum):
135
135
  STOP = b"0"
@@ -168,7 +168,7 @@ class AdapterBackend(ABC):
168
168
  # None : No ask
169
169
  # float : Ask for a response to happen at the specified value at max
170
170
  self._response_request: ResponseRequest | None = None
171
- self._response_request_start : float | None = None
171
+ self._response_request_start: float | None = None
172
172
 
173
173
  self._first_fragment = True
174
174
 
@@ -211,7 +211,7 @@ class AdapterBackend(ABC):
211
211
  return self._previous_buffer.data == b""
212
212
 
213
213
  @abstractmethod
214
- def open(self) -> bool:
214
+ def open(self) -> None:
215
215
  """
216
216
  Start communication with the device
217
217
  """
@@ -246,10 +246,10 @@ class AdapterBackend(ABC):
246
246
  @abstractmethod
247
247
  def _socket_read(self) -> Fragment:
248
248
  raise NotImplementedError
249
-
250
- def _fragments_to_string(self, fragments : list[Fragment]) -> str:
249
+
250
+ def _fragments_to_string(self, fragments: list[Fragment]) -> str:
251
251
  if len(fragments) > 0:
252
- return '+'.join(repr(f.data) for f in fragments)
252
+ return "+".join(repr(f.data) for f in fragments)
253
253
  else:
254
254
  return str([])
255
255
 
@@ -261,9 +261,12 @@ class AdapterBackend(ABC):
261
261
  fragment_delta_t = float("nan")
262
262
  if fragment.data == b"":
263
263
  self.close()
264
- yield AdapterDisconnected()
264
+ yield AdapterDisconnectedSignal()
265
265
  else:
266
- self._logger.debug(f"New fragment {fragment_delta_t:+.3f} {fragment}" + (" (first)" if self._first_fragment else ""))
266
+ self._logger.debug(
267
+ f"New fragment {fragment_delta_t:+.3f} {fragment}"
268
+ + (" (first)" if self._first_fragment else "")
269
+ )
267
270
  if self._status == AdapterBackendStatus.CONNECTED:
268
271
  t = time.time()
269
272
 
@@ -272,7 +275,9 @@ class AdapterBackend(ABC):
272
275
 
273
276
  if self._response_request is not None:
274
277
  for stop_condition in self._stop_conditions:
275
- if isinstance(stop_condition, (ContinuationBackend, TotalBackend)):
278
+ if isinstance(
279
+ stop_condition, (ContinuationBackend, TotalBackend)
280
+ ):
276
281
  self._response_request = None
277
282
  break
278
283
 
@@ -287,15 +292,18 @@ class AdapterBackend(ABC):
287
292
  kept = fragment
288
293
 
289
294
  # Run each stop condition one after the other, if a stop is reached, stop evaluating
290
- stop_condition_type : StopConditionType
295
+ stop_condition_type: StopConditionType
291
296
  for stop_condition in self._stop_conditions:
292
- stop, kept, self._previous_buffer, self._next_timeout_timestamp = \
293
- stop_condition.evaluate(kept)
297
+ (
298
+ stop,
299
+ kept,
300
+ self._previous_buffer,
301
+ self._next_timeout_timestamp,
302
+ ) = stop_condition.evaluate(kept)
294
303
  if stop:
295
304
  stop_condition_type = stop_condition.type()
296
305
  break
297
306
 
298
-
299
307
  # if kept.data != b'':
300
308
  # self.fragments.append(kept)
301
309
 
@@ -303,14 +311,22 @@ class AdapterBackend(ABC):
303
311
 
304
312
  if stop:
305
313
  self._first_fragment = True
306
- self._logger.debug(f"Payload {self._fragments_to_string(self.fragments)} ({stop_condition_type.value})")
307
- if self._response_request_start is None or len(self.fragments) == 0:
314
+ self._logger.debug(
315
+ f"Payload {self._fragments_to_string(self.fragments)} ({stop_condition_type.value})"
316
+ )
317
+ if (
318
+ self._response_request_start is None
319
+ or len(self.fragments) == 0
320
+ ):
308
321
  response_delay = None
309
322
  else:
310
323
  if self.fragments[0].timestamp is None:
311
324
  response_delay = None
312
325
  else:
313
- response_delay = self.fragments[0].timestamp - self._response_request_start
326
+ response_delay = (
327
+ self.fragments[0].timestamp
328
+ - self._response_request_start
329
+ )
314
330
  self._response_request_start = None
315
331
  yield AdapterReadPayload(
316
332
  fragments=self.fragments,
@@ -318,9 +334,9 @@ class AdapterBackend(ABC):
318
334
  stop_condition_type=stop_condition_type,
319
335
  previous_read_buffer_used=False,
320
336
  response_timestamp=self.fragments[0].timestamp,
321
- response_delay=response_delay
337
+ response_delay=response_delay,
322
338
  )
323
- self._next_timeout_timestamp = None # Experiment !
339
+ self._next_timeout_timestamp = None # Experiment !
324
340
  self.fragments.clear()
325
341
 
326
342
  if len(self._previous_buffer.data) > 0 and stop:
@@ -341,8 +357,9 @@ class AdapterBackend(ABC):
341
357
  """
342
358
  self._response_request_start = time.time()
343
359
  self._logger.debug(f"Setup read [{identifier}] in {response_time:.3f} s")
344
- self._response_request = ResponseRequest(self._response_request_start + response_time, identifier)
345
-
360
+ self._response_request = ResponseRequest(
361
+ self._response_request_start + response_time, identifier
362
+ )
346
363
 
347
364
  @abstractmethod
348
365
  def is_opened(self) -> bool:
@@ -366,7 +383,9 @@ class AdapterBackend(ABC):
366
383
  response_delay = None
367
384
  else:
368
385
  if self.fragments[0].timestamp is not None:
369
- response_delay = self.fragments[0].timestamp - self._response_request_start
386
+ response_delay = (
387
+ self.fragments[0].timestamp - self._response_request_start
388
+ )
370
389
  else:
371
390
  response_delay = None
372
391
  self._response_request_start = None
@@ -376,8 +395,10 @@ class AdapterBackend(ABC):
376
395
  stop_condition_type=StopConditionType.TIMEOUT,
377
396
  previous_read_buffer_used=False,
378
397
  fragments=self.fragments,
379
- response_timestamp=self.fragments[0].timestamp if len(self.fragments) > 0 else None,
380
- response_delay=response_delay
398
+ response_timestamp=(
399
+ self.fragments[0].timestamp if len(self.fragments) > 0 else None
400
+ ),
401
+ response_delay=response_delay,
381
402
  )
382
403
  # Reset response request
383
404
  if self._response_request is not None:
@@ -388,8 +409,7 @@ class AdapterBackend(ABC):
388
409
  return output
389
410
 
390
411
  elif (
391
- self._next_timeout_origin
392
- == self.AdapterTimeoutEventOrigin.RESPONSE_REQUEST
412
+ self._next_timeout_origin == self.AdapterTimeoutEventOrigin.RESPONSE_REQUEST
393
413
  ):
394
414
  if self._response_request is not None:
395
415
  signal = AdapterResponseTimeout(self._response_request.identifier)
@@ -402,8 +422,6 @@ class AdapterBackend(ABC):
402
422
  min_timestamp = None
403
423
  self._next_timeout_origin = None
404
424
 
405
- t = time.time()
406
-
407
425
  if self._next_timeout_timestamp is not None:
408
426
  min_timestamp = self._next_timeout_timestamp
409
427
  self._next_timeout_origin = self.AdapterTimeoutEventOrigin.TIMEOUT
@@ -11,19 +11,18 @@ import threading
11
11
  import time
12
12
  from enum import Enum
13
13
  from multiprocessing.connection import Pipe, wait
14
- from typing import Any, Tuple
14
+ from typing import Any
15
15
 
16
- from syndesi.adapters.backend.stop_condition_backend import StopConditionBackend, stop_condition_to_backend
17
- from syndesi.tools.errors import make_error_description
16
+ from syndesi.adapters.backend.stop_condition_backend import (
17
+ stop_condition_to_backend,
18
+ )
19
+ from syndesi.tools.errors import AdapterError, make_error_description
18
20
  from syndesi.tools.types import NumberLike
19
21
 
20
22
  from ...tools.backend_api import Action, frontend_send
21
23
  from ...tools.log_settings import LoggerAlias
22
24
  from .adapter_backend import (
23
25
  AdapterBackend,
24
- AdapterDisconnected,
25
- #AdapterReadInit,
26
- AdapterReadPayload,
27
26
  Selectable,
28
27
  nmin,
29
28
  )
@@ -37,15 +36,16 @@ from .descriptors import (
37
36
  )
38
37
  from .ip_backend import IPBackend
39
38
  from .serialport_backend import SerialPortBackend
40
- #from .stop_condition_backend import stop_condition_from_list
39
+
40
+ # from .stop_condition_backend import stop_condition_from_list
41
41
  from .visa_backend import VisaBackend
42
- from pathlib import Path
43
42
 
44
43
 
45
44
  class TimeoutEvent(Enum):
46
45
  MONITORING = 0
47
46
  ADAPTER = 1
48
- #CONNECTIONS = 2
47
+ # CONNECTIONS = 2
48
+
49
49
 
50
50
  def get_adapter(descriptor: Descriptor) -> AdapterBackend:
51
51
  # The adapter doesn't exist, create it
@@ -99,18 +99,12 @@ class AdapterSession(threading.Thread):
99
99
  self._shutdown_counter_top = None
100
100
  self._shutdown_counter = None
101
101
 
102
- #self._timeout_events: list[tuple[TimeoutEvent, float]] = []
103
-
104
102
  self._read_init_id = 0
105
103
 
106
-
107
-
108
104
  def add_connection(self, conn: NamedConnection) -> None:
109
105
  with self._connections_lock:
110
106
  self.connections.append(conn)
111
- # os.write(self._new_connection_w, b"\x00")
112
107
  self._new_connection_w.send(b"\x00")
113
- self._logger.info(f"New client : {conn.remote()}")
114
108
 
115
109
  def _remove_connection(self, conn: NamedConnection) -> None:
116
110
  with self._connections_lock:
@@ -142,13 +136,12 @@ class AdapterSession(threading.Thread):
142
136
  except Exception as e:
143
137
  error_message = make_error_description(e)
144
138
 
145
-
146
139
  self._logger.critical(
147
140
  f"Error in {self._adapter.descriptor} session loop : {error_message}"
148
141
  )
149
142
  try:
150
143
  error_message = make_error_description(e)
151
-
144
+
152
145
  for conn in self.connections:
153
146
  frontend_send(conn.conn, Action.ERROR_GENERIC, error_message)
154
147
  except Exception:
@@ -167,15 +160,13 @@ class AdapterSession(threading.Thread):
167
160
  # The wait has a timeout set by the adapter, it corresponds to the current continuation/total timeout
168
161
 
169
162
  # Create a list of what is awaited
170
- wait_list: list[Selectable] = [
171
- conn.conn for conn in self.connections
172
- ]
163
+ wait_list: list[Selectable] = [conn.conn for conn in self.connections]
173
164
  adapter_fd = self._adapter.selectable()
174
165
  if adapter_fd is not None and adapter_fd.fileno() >= 0:
175
166
  wait_list.append(adapter_fd)
176
167
 
177
168
  wait_list.append(self._new_connection_r)
178
-
169
+
179
170
  timeout_timestamp = None
180
171
  event = None
181
172
 
@@ -183,8 +174,11 @@ class AdapterSession(threading.Thread):
183
174
  if adapter_timestamp is not None:
184
175
  timeout_timestamp = nmin(timeout_timestamp, adapter_timestamp)
185
176
  event = TimeoutEvent.ADAPTER
186
-
187
- if timeout_timestamp is None or self._next_monitoring_timestamp < timeout_timestamp:
177
+
178
+ if (
179
+ timeout_timestamp is None
180
+ or self._next_monitoring_timestamp < timeout_timestamp
181
+ ):
188
182
  timeout_timestamp = self._next_monitoring_timestamp
189
183
  event = TimeoutEvent.MONITORING
190
184
 
@@ -253,7 +247,6 @@ class AdapterSession(threading.Thread):
253
247
  return
254
248
  try:
255
249
  request = conn.conn.recv()
256
- request_timestamp = time.time()
257
250
  except (EOFError, ConnectionResetError) as e:
258
251
  # Probably a ping or an error
259
252
  self._logger.warning(
@@ -275,12 +268,15 @@ class AdapterSession(threading.Thread):
275
268
  self._adapter.set_stop_conditions(
276
269
  [stop_condition_to_backend(sc) for sc in request[1]]
277
270
  )
278
- if self._adapter.open():
271
+ try:
272
+ self._adapter.open()
273
+ except AdapterError as e:
279
274
  # Success !
280
- response_action = Action.OPEN
281
- else:
282
275
  response_action = Action.ERROR_FAILED_TO_OPEN
283
- extra_arguments = ("",)
276
+ extra_arguments = (str(e),)
277
+ else:
278
+ response_action = Action.OPEN
279
+ extra_arguments = ("",)
284
280
  case Action.WRITE:
285
281
  data = request[1]
286
282
  if self._adapter.is_opened():
@@ -302,9 +298,9 @@ class AdapterSession(threading.Thread):
302
298
  case Action.PING:
303
299
  response_action, extra_arguments = Action.PING, ()
304
300
  case Action.SET_STOP_CONDITIONs:
305
- self._adapter.set_stop_conditions([
306
- stop_condition_to_backend(sc) for sc in request[1]
307
- ])
301
+ self._adapter.set_stop_conditions(
302
+ [stop_condition_to_backend(sc) for sc in request[1]]
303
+ )
308
304
  response_action, extra_arguments = (
309
305
  Action.SET_STOP_CONDITIONs,
310
306
  (),
@@ -315,7 +311,9 @@ class AdapterSession(threading.Thread):
315
311
  case Action.START_READ:
316
312
  response_time = float(request[1])
317
313
  self._adapter.start_read(response_time, self._read_init_id)
318
- response_action, extra_arguments = Action.START_READ, (self._read_init_id,)
314
+ response_action, extra_arguments = Action.START_READ, (
315
+ self._read_init_id,
316
+ )
319
317
  self._read_init_id += 1
320
318
 
321
319
  # case Action.GET_BACKEND_TIME:
@@ -98,6 +98,8 @@ def is_request(x: object) -> TypeGuard[tuple[str, object]]:
98
98
 
99
99
  class Backend:
100
100
  MONITORING_DELAY = 0.5
101
+ NEW_CLIENT_REQUEST_TIMEOUT = 0.5
102
+
101
103
  _session_shutdown_delay: NumberLike | None
102
104
  _backend_shutdown_delay: NumberLike | None
103
105
  _backend_shutdown_timestamp: NumberLike | None
@@ -278,7 +280,9 @@ class Backend:
278
280
  # Wait for adapter
279
281
  # ready = wait([client.conn], timeout=0.1)
280
282
  # selectors to work on Unix and Windows
281
- ready, _, _ = select.select([client.conn], [], [], 0.1)
283
+ ready, _, _ = select.select(
284
+ [client.conn], [], [], self.NEW_CLIENT_REQUEST_TIMEOUT
285
+ )
282
286
  if len(ready) == 0:
283
287
  client.conn.close()
284
288
  return
@@ -290,6 +294,7 @@ class Backend:
290
294
  action = Action(adapter_request[0])
291
295
  if action == Action.SELECT_ADAPTER:
292
296
  adapter_descriptor = adapter_request[1]
297
+ self._logger.info(f"New client for {adapter_descriptor}")
293
298
  # If the session exists but it is dead, delete it
294
299
  if (
295
300
  adapter_descriptor in self.adapter_sessions
@@ -299,7 +304,7 @@ class Backend:
299
304
 
300
305
  if adapter_descriptor not in self.adapter_sessions:
301
306
  # Create the adapter backend thread
302
- self._logger.info(f"Creating adapter session for {adapter_descriptor}")
307
+ # self._logger.info(f"Creating adapter session for {adapter_descriptor}")
303
308
  thread = AdapterSession(
304
309
  adapter_descriptor, shutdown_delay=self._session_shutdown_delay
305
310
  ) # TODO : Put another delay here ?
File without changes
@@ -1,9 +1,9 @@
1
1
  import socket
2
2
  from multiprocessing.connection import Connection
3
3
 
4
-
5
4
  BACKEND_REQUEST_DEFAULT_TIMEOUT = 0.5
6
5
 
6
+
7
7
  def get_conn_addresses(conn: Connection) -> tuple[tuple[str, int], tuple[str, int]]:
8
8
  try:
9
9
  fd = conn.fileno()
@@ -71,7 +71,7 @@ class IPDescriptor(Descriptor):
71
71
  address: str
72
72
  transport: Transport
73
73
  port: int | None = None
74
- #transport: Transport | None = None
74
+ # transport: Transport | None = None
75
75
 
76
76
  @staticmethod
77
77
  def from_string(string: str) -> "IPDescriptor":
@@ -10,7 +10,13 @@ from typing import cast
10
10
 
11
11
  import _socket
12
12
 
13
- from ...tools.backend_api import AdapterBackendStatus, Fragment
13
+ from syndesi.tools.errors import AdapterConfigurationError, AdapterFailedToOpen
14
+
15
+ from ...tools.backend_api import (
16
+ DEFAULT_ADAPTER_OPEN_TIMEOUT,
17
+ AdapterBackendStatus,
18
+ Fragment,
19
+ )
14
20
  from .adapter_backend import AdapterBackend, HasFileno
15
21
  from .descriptors import IPDescriptor
16
22
 
@@ -55,42 +61,37 @@ class IPBackend(AdapterBackend):
55
61
  def selectable(self) -> HasFileno | None:
56
62
  return self._socket
57
63
 
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}")
64
+ def open(self):
65
+ if self._status == AdapterBackendStatus.CONNECTED:
66
+ self._logger.warning(f"Adapter {self.descriptor} already openend")
67
+ return
68
+
69
+ if self.descriptor.port is None:
70
+ raise AdapterConfigurationError("Cannot open adapter without specifying a port")
71
+
72
+ if self._socket is None:
73
+ if self.descriptor.transport == IPDescriptor.Transport.TCP:
74
+ self._socket = cast(
75
+ _socket.socket,
76
+ socket.socket(socket.AF_INET, socket.SOCK_STREAM),
77
+ )
78
+ elif self.descriptor.transport == IPDescriptor.Transport.UDP:
79
+ self._socket = cast(
80
+ _socket.socket, socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
81
+ )
85
82
  else:
86
- self._status = AdapterBackendStatus.CONNECTED
87
- self._logger.info(f"IP Adapter {self.descriptor} opened")
88
- output = True
83
+ raise AdapterConfigurationError(
84
+ f"Invalid transport protocol : {self.descriptor.transport}"
85
+ )
86
+ try:
87
+ self._socket.settimeout(DEFAULT_ADAPTER_OPEN_TIMEOUT)
88
+ self._socket.connect((self.descriptor.address, self.descriptor.port))
89
+ except OSError as e:
90
+ self._logger.error(f"Failed to open adapter {self.descriptor} : {e}")
91
+ raise AdapterFailedToOpen(str(e))
89
92
  else:
90
- self._logger.info(f"Adapter {self.descriptor} already openend")
91
- output = True
92
-
93
- return output
93
+ self._status = AdapterBackendStatus.CONNECTED
94
+ self._logger.info(f"IP Adapter {self.descriptor} opened")
94
95
 
95
96
  def close(self) -> bool:
96
97
  super().close()
@@ -118,14 +119,16 @@ class IPBackend(AdapterBackend):
118
119
  self._logger.error(f"Cannot write to closed adapter {self.descriptor}")
119
120
  return False
120
121
  try:
121
- self._socket.send(data)
122
- except (BrokenPipeError, OSError) as e:
122
+ ok = self._socket.send(data) == len(data)
123
+ except (BrokenPipeError, OSError):
123
124
  # Socket has been disconnected by the remote peer
124
- self._logger.error(f"Failed to write to adapter {self.descriptor} ({e})")
125
+ ok = False
126
+
127
+ if not ok:
128
+ self._logger.error(f"Failed to write to adapter {self.descriptor}")
125
129
  self.close()
126
- return False
127
- else:
128
- return True
130
+
131
+ return ok
129
132
 
130
133
  def _socket_read(self) -> Fragment:
131
134
  # This function is called only if the socket was ready
@@ -140,7 +143,7 @@ class IPBackend(AdapterBackend):
140
143
 
141
144
  if fragment.data == b"":
142
145
  # Socket disconnected
143
- self._logger.debug('## Socket disconnected')
146
+ self._logger.debug("## Socket disconnected")
144
147
  self.close()
145
148
 
146
149
  return fragment