syndesi 0.2.2__tar.gz → 0.2.4__tar.gz

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 (49) hide show
  1. {syndesi-0.2.2/syndesi.egg-info → syndesi-0.2.4}/PKG-INFO +2 -3
  2. {syndesi-0.2.2 → syndesi-0.2.4}/README.md +1 -1
  3. {syndesi-0.2.2 → syndesi-0.2.4}/setup.py +7 -3
  4. syndesi-0.2.4/syndesi/__init__.py +4 -0
  5. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/adapter.py +108 -64
  6. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/auto.py +1 -1
  7. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/ip.py +74 -45
  8. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/proxy.py +15 -8
  9. syndesi-0.2.4/syndesi/adapters/serialport.py +203 -0
  10. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/stop_conditions.py +39 -2
  11. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/timeout.py +95 -63
  12. syndesi-0.2.4/syndesi/adapters/visa.py +103 -0
  13. {syndesi-0.2.2/syndesi → syndesi-0.2.4/syndesi/api}/__init__.py +0 -0
  14. syndesi-0.2.4/syndesi/cli/adapter.py +134 -0
  15. syndesi-0.2.4/syndesi/cli/command.py +29 -0
  16. syndesi-0.2.4/syndesi/cli/syndesi.py +45 -0
  17. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/__init__.py +2 -1
  18. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/delimited.py +15 -6
  19. syndesi-0.2.4/syndesi/protocols/modbus.py +1303 -0
  20. syndesi-0.2.4/syndesi/protocols/protocol.py +24 -0
  21. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/raw.py +1 -1
  22. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/scpi.py +22 -18
  23. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/proxy/proxy_api.py +39 -10
  24. syndesi-0.2.4/syndesi/tools/log.py +75 -0
  25. syndesi-0.2.4/syndesi/version.py +1 -0
  26. {syndesi-0.2.2 → syndesi-0.2.4/syndesi.egg-info}/PKG-INFO +2 -3
  27. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi.egg-info/SOURCES.txt +6 -3
  28. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi.egg-info/entry_points.txt +1 -1
  29. syndesi-0.2.2/LICENSE +0 -674
  30. syndesi-0.2.2/syndesi/adapters/serialport.py +0 -139
  31. syndesi-0.2.2/syndesi/adapters/visa.py +0 -56
  32. syndesi-0.2.2/syndesi/protocols/protocol.py +0 -17
  33. syndesi-0.2.2/syndesi/tools/log.py +0 -107
  34. syndesi-0.2.2/syndesi/tools/others.py +0 -1
  35. syndesi-0.2.2/syndesi/tools/shell.py +0 -111
  36. {syndesi-0.2.2 → syndesi-0.2.4}/setup.cfg +0 -0
  37. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/__init__.py +0 -0
  38. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/ip_server.py +0 -0
  39. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/timed_queue.py +0 -0
  40. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/api/api.py +0 -0
  41. {syndesi-0.2.2/syndesi/api → syndesi-0.2.4/syndesi/cli}/__init__.py +0 -0
  42. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/sdp.py +0 -0
  43. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/proxy/__init__.py +0 -0
  44. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/proxy/proxy.py +0 -0
  45. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/tools/__init__.py +0 -0
  46. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/tools/exceptions.py +0 -0
  47. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/tools/types.py +0 -0
  48. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi.egg-info/dependency_links.txt +0 -0
  49. {syndesi-0.2.2 → syndesi-0.2.4}/syndesi.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: syndesi
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Syndesi
5
5
  Author: Sebastien Deriaz
6
6
  Author-email: sebastien.deriaz1@gmail.com
@@ -12,7 +12,6 @@ Classifier: Operating System :: Unix
12
12
  Classifier: Operating System :: MacOS :: MacOS X
13
13
  Classifier: Operating System :: Microsoft :: Windows
14
14
  Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
15
 
17
16
  # Syndesi Python Implementation
18
17
 
@@ -40,7 +39,7 @@ To instantiate a device, one must import the device and a suitable adapter
40
39
 
41
40
  ```python
42
41
  # 1) Import the device
43
- from syndesi.drivers.instruments.mutlimeters.siglent.SDM3055 import SDM3055
42
+ from syndesi.drivers.instruments.multimeters.siglent.SDM3055 import SDM3055
44
43
  # 2) Import the adapter
45
44
  from syndesi.adapters import IP
46
45
 
@@ -24,7 +24,7 @@ To instantiate a device, one must import the device and a suitable adapter
24
24
 
25
25
  ```python
26
26
  # 1) Import the device
27
- from syndesi.drivers.instruments.mutlimeters.siglent.SDM3055 import SDM3055
27
+ from syndesi.drivers.instruments.multimeters.siglent.SDM3055 import SDM3055
28
28
  # 2) Import the adapter
29
29
  from syndesi.adapters import IP
30
30
 
@@ -1,15 +1,19 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- VERSION = '0.2.2'
4
3
  DESCRIPTION = 'Syndesi'
5
4
 
5
+ __version__ : str
6
+ # Load __version__ from file
7
+ with open('syndesi/version.py', 'r', encoding='utf-8') as f:
8
+ exec(f.read())
9
+
6
10
  with open("README.md", "r", encoding="utf-8") as fh:
7
11
  long_description = fh.read()
8
12
 
9
13
  # Setting up
10
14
  setup(
11
15
  name="syndesi",
12
- version=VERSION,
16
+ version=__version__,
13
17
  author="Sebastien Deriaz",
14
18
  author_email="sebastien.deriaz1@gmail.com",
15
19
  description=DESCRIPTION,
@@ -17,7 +21,7 @@ setup(
17
21
  long_description=long_description,
18
22
  entry_points = {
19
23
  'console_scripts': [
20
- 'syndesi=syndesi.shell.syndesi:main',
24
+ 'syndesi=syndesi.cli.syndesi:main',
21
25
  'syndesi-proxy=syndesi.proxy.proxy:main'],
22
26
  },
23
27
  packages=find_packages(),
@@ -0,0 +1,4 @@
1
+ from .tools.log import log_settings
2
+
3
+ from .adapters import IP, SerialPort
4
+ from .protocols import Modbus, Delimited, Raw, SCPI
@@ -14,7 +14,6 @@
14
14
  #
15
15
  # An adapter is meant to work with bytes objects but it can accept strings.
16
16
  # Strings will automatically be converted to bytes using utf-8 encoding
17
- #
18
17
 
19
18
  from abc import abstractmethod, ABC
20
19
  from .timed_queue import TimedQueue
@@ -26,12 +25,11 @@ from .timeout import Timeout, TimeoutException, timeout_fuse
26
25
  from typing import Union
27
26
  from ..tools.types import is_number
28
27
  from ..tools.log import LoggerAlias
28
+ import socket
29
29
  import logging
30
30
  from time import time
31
31
  from dataclasses import dataclass
32
- from ..tools.others import DEFAULT
33
32
 
34
- DEFAULT_TIMEOUT = Timeout(response=1, continuation=100e-3, total=None)
35
33
  DEFAULT_STOP_CONDITION = None
36
34
 
37
35
 
@@ -48,7 +46,7 @@ STOP_DESIGNATORS = {
48
46
  Termination : 'ST',
49
47
  Length : 'SL'
50
48
  },
51
- 'previous-read-buffer' : 'RB'
49
+ 'previous-buffer' : 'PB'
52
50
  }
53
51
 
54
52
  class Origin(Enum):
@@ -72,7 +70,7 @@ class Adapter(ABC):
72
70
  DISCONNECTED = 0
73
71
  CONNECTED = 1
74
72
 
75
- def __init__(self, alias : str = '', stop_condition : Union[StopCondition, None] = DEFAULT, timeout : Union[float, Timeout] = DEFAULT) -> None:
73
+ def __init__(self, alias : str = '', stop_condition : Union[StopCondition, None] = ..., timeout : Union[float, Timeout] = ...) -> None:
76
74
  """
77
75
  Adapter instance
78
76
 
@@ -81,37 +79,52 @@ class Adapter(ABC):
81
79
  alias : str
82
80
  The alias is used to identify the class in the logs
83
81
  timeout : float or Timeout instance
84
- Default timeout is Timeout(response=1, continuation=0.1, total=None)
82
+ Default timeout is Timeout(response=5, continuation=0.2, total=None)
85
83
  stop_condition : StopCondition or None
86
84
  Default to None
87
85
  """
88
86
  super().__init__()
87
+
89
88
  self._alias = alias
90
89
 
91
- self._default_stop_condition = stop_condition == DEFAULT
90
+ self.is_default_timeout = timeout is Ellipsis
91
+ if self.is_default_timeout:
92
+ self._timeout = self._default_timeout()
93
+ else:
94
+ self._timeout = timeout_fuse(timeout, self._default_timeout())
95
+
96
+ self._default_stop_condition = stop_condition is Ellipsis
92
97
  if self._default_stop_condition:
93
98
  self._stop_condition = DEFAULT_STOP_CONDITION
94
99
  else:
95
100
  self._stop_condition = stop_condition
101
+
96
102
  self._read_queue = TimedQueue()
97
103
  self._thread : Union[Thread, None] = None
98
104
  self._status = self.Status.DISCONNECTED
99
105
  self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
106
+ self._thread_stop_read, self._thread_stop_write = socket.socketpair()
100
107
 
101
108
  # Buffer for data that has been pulled from the queue but
102
109
  # not used because of termination or length stop condition
103
- self._previous_read_buffer = b''
110
+ self._previous_buffer = b''
104
111
 
105
- self._default_timeout = timeout == DEFAULT
106
- if self._default_timeout:
107
- self._timeout = DEFAULT_TIMEOUT
108
- else:
109
- if is_number(timeout):
110
- self._timeout = Timeout(response=timeout, continuation=100e-3)
111
- elif isinstance(timeout, Timeout):
112
- self._timeout = timeout
113
- else:
114
- raise ValueError(f"Invalid timeout type : {type(timeout)}")
112
+ if not isinstance(self._timeout, Timeout):
113
+ raise ValueError('Timeout must be defined to initialize an Adapter base class')
114
+
115
+ @abstractmethod
116
+ def _default_timeout(self):
117
+ pass
118
+
119
+ def set_timeout(self, timeout : Timeout):
120
+ """
121
+ Overwrite timeout
122
+
123
+ Parameters
124
+ ----------
125
+ timeout : Timeout
126
+ """
127
+ self._timeout = timeout
115
128
 
116
129
  def set_default_timeout(self, default_timeout : Union[Timeout, tuple, float]):
117
130
  """
@@ -121,10 +134,23 @@ class Adapter(ABC):
121
134
  ----------
122
135
  default_timeout : Timeout or tuple or float
123
136
  """
124
- if self._default_timeout:
137
+ if self.is_default_timeout:
138
+ self._logger.debug(f'Setting default timeout to {default_timeout}')
125
139
  self._timeout = default_timeout
126
140
  else:
141
+ log = f'Fusing timeouts {self._timeout}+{default_timeout} -> '
127
142
  self._timeout = timeout_fuse(self._timeout, default_timeout)
143
+ self._logger.debug(f'{log}{self._timeout}')
144
+
145
+ def set_stop_condition(self, stop_condition):
146
+ """
147
+ Overwrite the stop-condition
148
+
149
+ Parameters
150
+ ----------
151
+ stop_condition : StopCondition
152
+ """
153
+ self._stop_condition = stop_condition
128
154
 
129
155
  def set_default_stop_condition(self, stop_condition):
130
156
  """
@@ -142,7 +168,17 @@ class Adapter(ABC):
142
168
  Flush the input buffer
143
169
  """
144
170
  self._read_queue.clear()
145
- self._previous_read_buffer = b''
171
+ self._previous_buffer = b''
172
+
173
+ def previous_read_buffer_empty(self):
174
+ """
175
+ Check whether the previous read buffer is empty
176
+
177
+ Returns
178
+ -------
179
+ empty : bool
180
+ """
181
+ return self._previous_buffer == b''
146
182
 
147
183
  @abstractmethod
148
184
  def open(self):
@@ -151,12 +187,12 @@ class Adapter(ABC):
151
187
  """
152
188
  pass
153
189
 
154
- @abstractmethod
155
190
  def close(self):
156
191
  """
157
192
  Stop communication with the device
158
193
  """
159
- pass
194
+ self._logger.debug('Closing adapter and stopping read thread')
195
+ self._thread_stop_write.send(b'1')
160
196
 
161
197
  @abstractmethod
162
198
  def write(self, data : Union[bytes, str]):
@@ -168,12 +204,33 @@ class Adapter(ABC):
168
204
  data : bytes or str
169
205
  """
170
206
  pass
207
+
208
+ @abstractmethod
209
+ def read(self, timeout : Timeout = ..., stop_condition : StopCondition = ..., return_metrics : bool = False) -> bytes:
210
+ pass
171
211
 
172
- # TODO : Return None or b'' when read thread is killed while reading
173
- # This is to detect if a server socket has been closed
174
212
 
213
+ @abstractmethod
214
+ def _start_thread(self):
215
+ self._logger.debug("Starting read thread...")
216
+
217
+ def __del__(self):
218
+ self.close()
175
219
 
176
- def read(self, timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
220
+ def query(self, data : Union[bytes, str], timeout : Timeout = ..., stop_condition : StopCondition = ..., return_metrics : bool = False) -> bytes:
221
+ """
222
+ Shortcut function that combines
223
+ - flush_read
224
+ - write
225
+ - read
226
+ """
227
+ self.flushRead()
228
+ self.write(data)
229
+ return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
230
+
231
+
232
+ class StreamAdapter(Adapter):
233
+ def read(self, timeout=..., stop_condition=..., return_metrics : bool = False) -> bytes:
177
234
  """
178
235
  Read data from the device
179
236
 
@@ -189,35 +246,40 @@ class Adapter(ABC):
189
246
  if self._status == self.Status.DISCONNECTED:
190
247
  self.open()
191
248
 
192
- # Use adapter values if no custom value is specified
193
- if timeout is None:
249
+ # 29.08.24 Change timeout behavior
250
+ if timeout is ...:
251
+ # Use the class timeout
194
252
  timeout = self._timeout
195
- elif isinstance(timeout, float):
196
- timeout = Timeout(timeout)
197
-
253
+ else:
254
+ # Fuse it
255
+ timeout = timeout_fuse(timeout, self._timeout)
198
256
 
199
- if stop_condition is None:
257
+ if stop_condition is ...:
200
258
  stop_condition = self._stop_condition
201
259
 
202
260
  # If the adapter is closed, open it
203
261
  if self._status == self.Status.DISCONNECTED:
204
262
  self.open()
205
263
 
206
- if self._thread is None or not self._thread.is_alive():
207
- self._start_thread()
208
-
209
- timeout_ms = timeout.initiate_read(len(self._previous_read_buffer) > 0)
264
+ timeout_ms = timeout.initiate_read(len(self._previous_buffer) > 0)
210
265
 
211
266
  if stop_condition is not None:
212
267
  stop_condition.initiate_read()
213
268
 
269
+
214
270
  deferred_buffer = b''
215
271
 
216
272
  # Start with the deferred buffer
217
273
  # TODO : Check if data could be lost here, like the data is put in the previous_read_buffer and is never
218
274
  # read back again because there's no stop condition
219
- if len(self._previous_read_buffer) > 0 and stop_condition is not None:
220
- stop, output, self._previous_read_buffer = stop_condition.evaluate(self._previous_read_buffer)
275
+ if len(self._previous_buffer) > 0:# and stop_condition is not None:
276
+ self._logger.debug(f'Using previous buffer ({self._previous_buffer})')
277
+ if stop_condition is not None:
278
+ stop, output, self._previous_buffer = stop_condition.evaluate(self._previous_buffer)
279
+ else:
280
+ stop = True
281
+ output = self._previous_buffer
282
+ self._previous_buffer = b''
221
283
  previous_read_buffer_used = True
222
284
  else:
223
285
  stop = False
@@ -231,8 +293,8 @@ class Adapter(ABC):
231
293
  (timestamp, fragment) = self._read_queue.get(timeout_ms)
232
294
  n_fragments += 1
233
295
 
234
- if fragment == b'':
235
- raise AdapterDisconnected()
296
+ if isinstance(fragment, AdapterDisconnected):
297
+ raise fragment
236
298
 
237
299
  # 1) Evaluate the timeout
238
300
  stop, timeout_ms = timeout.evaluate(timestamp)
@@ -248,10 +310,10 @@ class Adapter(ABC):
248
310
  output += fragment
249
311
  elif data_strategy == Timeout.OnTimeoutStrategy.STORE:
250
312
  # Store the data
251
- self._previous_read_buffer = output
313
+ self._previous_buffer = output
252
314
  output = b''
253
315
  elif data_strategy == Timeout.OnTimeoutStrategy.ERROR:
254
- raise TimeoutException(origin)
316
+ raise TimeoutException(origin, timeout._stop_source_overtime, timeout._stop_source_limit)
255
317
  break
256
318
  else:
257
319
  origin = None
@@ -267,7 +329,7 @@ class Adapter(ABC):
267
329
  stop, kept_fragment, deferred_buffer = stop_condition.evaluate(fragment)
268
330
  output += kept_fragment
269
331
  if stop:
270
- self._previous_read_buffer = deferred_buffer
332
+ self._previous_buffer = deferred_buffer
271
333
  else:
272
334
  output += fragment
273
335
  if stop:
@@ -279,11 +341,11 @@ class Adapter(ABC):
279
341
  else:
280
342
  designator = STOP_DESIGNATORS['stop_condition'][type(stop_condition)]
281
343
  else:
282
- designator = STOP_DESIGNATORS['previous-read-buffer']
344
+ designator = STOP_DESIGNATORS['previous-buffer']
283
345
 
284
346
  read_duration = time() - read_start
285
- if self._previous_read_buffer:
286
- self._logger.debug(f'Read [{designator}, {read_duration*1e3:.3f}ms] : {output} , previous read buffer : {self._previous_read_buffer}')
347
+ if self._previous_buffer:
348
+ self._logger.debug(f'Read [{designator}, {read_duration*1e3:.3f}ms] : {output} , previous read buffer : {self._previous_buffer}')
287
349
  else:
288
350
  self._logger.debug(f'Read [{designator}, {read_duration*1e3:.3f}ms] : {output}')
289
351
 
@@ -300,22 +362,4 @@ class Adapter(ABC):
300
362
  total_time=timeout.total_time
301
363
  )
302
364
  else:
303
- return output
304
-
305
- @abstractmethod
306
- def _start_thread(self):
307
- pass
308
-
309
- def __del__(self):
310
- self.close()
311
-
312
- @abstractmethod
313
- def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
314
- """
315
- Shortcut function that combines
316
- - flush_read
317
- - write
318
- - read
319
- """
320
- pass
321
-
365
+ return output
@@ -17,7 +17,7 @@ from typing import Union
17
17
  import re
18
18
  from . import Adapter, IP, SerialPort
19
19
 
20
- IP_PATTERN = '([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(:[0-9]+)*'
20
+ IP_PATTERN = '([0-9]+.[0-9]+.[0-9]+.[0-9]+)(:[0-9]+)*'
21
21
 
22
22
  WINDOWS_SERIAL_PATTERN = '(COM[0-9]+)(:[0-9]+)*'
23
23
  LINUX_SERIAL_PATTERN = '(/dev/tty[a-zA-Z0-9]+)(:[0-9]+)*'
@@ -1,26 +1,19 @@
1
1
  import socket
2
2
  from enum import Enum
3
- from .adapter import Adapter
3
+ from .adapter import StreamAdapter, AdapterDisconnected
4
4
  from ..tools.types import to_bytes
5
- from .timeout import Timeout
5
+ from .timeout import Timeout, timeout_fuse
6
+ from .stop_conditions import StopCondition
6
7
  from threading import Thread
7
8
  from .timed_queue import TimedQueue
8
9
  from typing import Union
9
10
  from time import time
10
11
  import argparse
11
- from ..tools import shell
12
+ #from ..cli import shell
13
+ import select
12
14
 
13
- class IP(Adapter):
14
- DEFAULT_RESPONSE_TIMEOUT = 1
15
- DEFAULT_CONTINUATION_TIMEOUT = 1e-3
16
- DEFAULT_TOTAL_TIMEOUT = 5
17
-
18
-
19
- DEFAULT_TIMEOUT = Timeout(
20
- response=DEFAULT_RESPONSE_TIMEOUT,
21
- continuation=DEFAULT_CONTINUATION_TIMEOUT,
22
- total=DEFAULT_TOTAL_TIMEOUT)
23
- DEFAULT_BUFFER_SIZE = 1024
15
+ class IP(StreamAdapter):
16
+ _DEFAULT_BUFFER_SIZE = 1024
24
17
  class Protocol(Enum):
25
18
  TCP = 'TCP'
26
19
  UDP = 'UDP'
@@ -29,10 +22,10 @@ class IP(Adapter):
29
22
  address : str,
30
23
  port : int = None,
31
24
  transport : str = 'TCP',
32
- timeout : Union[Timeout, float] = DEFAULT_TIMEOUT,
33
- stop_condition = None,
25
+ timeout : Union[Timeout, float] = ...,
26
+ stop_condition : StopCondition = ...,
34
27
  alias : str = '',
35
- buffer_size : int = DEFAULT_BUFFER_SIZE,
28
+ buffer_size : int = _DEFAULT_BUFFER_SIZE,
36
29
  _socket : socket.socket = None):
37
30
  """
38
31
  IP stack adapter
@@ -55,8 +48,9 @@ class IP(Adapter):
55
48
  Socket buffer size, may be removed in the future
56
49
  socket : socket.socket
57
50
  Specify a custom socket, this is reserved for server application
58
- """
51
+ """
59
52
  super().__init__(alias=alias, timeout=timeout, stop_condition=stop_condition)
53
+
60
54
  self._transport = self.Protocol(transport)
61
55
  self._is_server = _socket is not None
62
56
 
@@ -75,6 +69,21 @@ class IP(Adapter):
75
69
  self._port = port
76
70
  self._buffer_size = buffer_size
77
71
 
72
+ def __str__(self) -> str:
73
+ return f'IP({self._address}:{self._port})'
74
+
75
+ def __repr__(self) -> str:
76
+ return self.__str__()
77
+
78
+ def _default_timeout(self):
79
+ return Timeout(
80
+ response=5,
81
+ on_response='error',
82
+ continuation=100e-3,
83
+ on_continuation='return',
84
+ total=5,
85
+ on_total='error')
86
+
78
87
  def set_default_port(self, port):
79
88
  """
80
89
  Sets IP port if no port has been set yet.
@@ -95,12 +104,22 @@ class IP(Adapter):
95
104
  if self._port is None:
96
105
  raise ValueError(f"Cannot open adapter without specifying a port")
97
106
 
98
- self._logger.debug(f"Adapter {self._alias} connect to ({self._address}, {self._port})")
107
+ self._logger.debug(f"Adapter {self._alias + ' ' if self._alias != '' else ''}connect to ({self._address}, {self._port})")
99
108
  self._socket.connect((self._address, self._port))
100
109
  self._status = self.Status.CONNECTED
110
+ if self._thread is None or not self._thread.is_alive():
111
+ self._start_thread()
112
+
101
113
  self._logger.info(f"Adapter {self._alias} opened !")
102
114
 
103
115
  def close(self):
116
+ super().close()
117
+ if self._thread is not None and self._thread.is_alive():
118
+ try:
119
+ self._thread.join()
120
+ except RuntimeError:
121
+ # If the thread cannot be joined, then so be it
122
+ pass
104
123
  if hasattr(self, '_socket'):
105
124
  self._socket.close()
106
125
  self._logger.info("Adapter closed !")
@@ -109,42 +128,52 @@ class IP(Adapter):
109
128
  def write(self, data : Union[bytes, str]):
110
129
  data = to_bytes(data)
111
130
  if self._status == self.Status.DISCONNECTED:
112
- self._logger.info(f"Adapter {self._alias} is closed, opening...")
131
+ self._logger.info(f"Adapter {self._alias + ' ' if self._alias != '' else ''}is closed, opening...")
113
132
  self.open()
114
133
  write_start = time()
115
134
  self._socket.send(data)
116
135
  write_duration = time() - write_start
117
- self._logger.debug(f"Written [{write_duration*1e3:.3f}ms]: {repr(data)}")
136
+ self._logger.debug(f"Write [{write_duration*1e3:.3f}ms]: {repr(data)}")
118
137
 
119
138
  def _start_thread(self):
120
- self._logger.debug("Starting read thread...")
121
- self._thread = Thread(target=self._read_thread, daemon=True, args=(self._socket, self._read_queue))
122
- self._thread.start()
123
-
124
- # EXPERIMENTAL
125
- def read_thread_alive(self):
126
- return self._thread.is_alive()
127
-
128
-
129
- def _read_thread(self, socket : socket.socket, read_queue : TimedQueue):
130
- while True: # TODO : Add stop_pipe ? Maybe it was removed ?
139
+ super()._start_thread()
140
+ if self._thread is None or not self._thread.is_alive():
141
+ self._thread = Thread(target=self._read_thread, daemon=True, args=(self._socket, self._read_queue, self._thread_stop_read))
142
+ self._thread.start()
143
+
144
+ def _read_thread(self, socket : socket.socket, read_queue : TimedQueue, stop : socket.socket):
145
+ # Using select.select works on both Windows and Linux as long as the inputs are all sockets
146
+ while True:
147
+
131
148
  try:
132
- payload = socket.recv(self._buffer_size)
133
- if len(payload) == self._buffer_size and self._transport == self.Protocol.UDP:
134
- self._logger.warning("Warning, inbound UDP data may have been lost (max buffer size attained)")
135
- except OSError:
136
- break
137
- # If payload is empty, it means the socket has been disconnected
138
- if payload == b'':
139
- read_queue.put(payload)
140
- break
141
- read_queue.put(payload)
149
+ ready, _, _ = select.select([socket, stop], [], [])
150
+ except ValueError:
151
+ # File desctiptor is s negative integer
152
+ read_queue.put(AdapterDisconnected())
153
+ else:
154
+ if stop in ready:
155
+ # Stop the thread
156
+ stop.recv(1)
157
+ break
158
+ elif socket in ready:
159
+ # Read from the socket
160
+ try:
161
+ payload = socket.recv(self._buffer_size)
162
+ except ConnectionRefusedError:
163
+ # TODO : Check if this is the right way of doing it
164
+ read_queue.put(AdapterDisconnected())
165
+ else:
166
+ if len(payload) == self._buffer_size and self._transport == self.Protocol.UDP:
167
+ self._logger.warning("Warning, inbound UDP data may have been lost (max buffer size attained)")
168
+ if payload == b'':
169
+ read_queue.put(AdapterDisconnected())
170
+ break
171
+ else:
172
+ read_queue.put(payload)
142
173
 
143
174
  def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
144
175
  if self._is_server:
145
176
  raise SystemError("Cannot query on server adapters")
146
- self.flushRead()
147
- self.write(data)
148
- return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
177
+ return super().query(data=data, timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
149
178
 
150
179
 
@@ -46,16 +46,24 @@ class Proxy(Adapter):
46
46
 
47
47
  if isinstance(proxy_adapter, IP):
48
48
  proxy_adapter.set_default_port(DEFAULT_PORT)
49
-
50
49
  if isinstance(self._remote, IP):
51
50
  self._proxy.query(IPInstanciate(
52
- address=self._remote._address,
53
- port=self._remote._port,
54
- transport=self._remote._transport,
55
- buffer_size=self._remote._buffer_size
56
- ).encode())
51
+ address=self._remote._address,
52
+ port=self._remote._port,
53
+ transport=self._remote._transport,
54
+ buffer_size=self._remote._buffer_size,
55
+ ).encode())
57
56
  elif isinstance(self._remote, SerialPort):
58
- self._proxy.query()
57
+ self._proxy.query(SerialPortInstanciate(
58
+ port=self._remote._port_name,
59
+ baudrate=self._remote._baudrate,
60
+ timeout=timeout_to_api(self._remote._timeout),
61
+ stop_condition=stop_condition_to_api(self._remote._stop_condition)
62
+ ))
63
+ elif isinstance(self._remote, VISA):
64
+ self._proxy.query(VisaInstanciate(
65
+ resource=self._remote._resource,
66
+ ))
59
67
 
60
68
  def check(self, status : ReturnStatus):
61
69
  if not status.success:
@@ -82,7 +90,6 @@ class Proxy(Adapter):
82
90
  else:
83
91
  raise RuntimeError(f"Invalid return : {type(output)}")
84
92
 
85
-
86
93
  def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
87
94
  self.check(parse(self._proxy.query(AdapterFlushRead().encode())))
88
95
  self.check(parse(self._proxy.query(AdapterWrite(data).encode())))