syndesi 0.2.2__tar.gz → 0.2.3__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 (41) hide show
  1. {syndesi-0.2.2/syndesi.egg-info → syndesi-0.2.3}/PKG-INFO +1 -1
  2. {syndesi-0.2.2 → syndesi-0.2.3}/setup.py +2 -2
  3. syndesi-0.2.3/syndesi/__init__.py +6 -0
  4. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/adapter.py +63 -26
  5. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/ip.py +60 -31
  6. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/proxy.py +15 -8
  7. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/serialport.py +75 -32
  8. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/timeout.py +44 -19
  9. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/visa.py +5 -4
  10. {syndesi-0.2.2/syndesi → syndesi-0.2.3/syndesi/api}/__init__.py +0 -0
  11. {syndesi-0.2.2/syndesi/tools → syndesi-0.2.3/syndesi/cli}/shell.py +44 -14
  12. syndesi-0.2.3/syndesi/cli/syndesi.py +30 -0
  13. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/delimited.py +7 -3
  14. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/protocol.py +3 -0
  15. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/raw.py +2 -1
  16. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/scpi.py +19 -15
  17. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/proxy/proxy_api.py +39 -10
  18. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/log.py +9 -9
  19. {syndesi-0.2.2 → syndesi-0.2.3/syndesi.egg-info}/PKG-INFO +1 -1
  20. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi.egg-info/SOURCES.txt +3 -1
  21. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi.egg-info/entry_points.txt +1 -1
  22. {syndesi-0.2.2 → syndesi-0.2.3}/LICENSE +0 -0
  23. {syndesi-0.2.2 → syndesi-0.2.3}/README.md +0 -0
  24. {syndesi-0.2.2 → syndesi-0.2.3}/setup.cfg +0 -0
  25. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/__init__.py +0 -0
  26. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/auto.py +0 -0
  27. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/ip_server.py +0 -0
  28. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/stop_conditions.py +0 -0
  29. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/timed_queue.py +0 -0
  30. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/api/api.py +0 -0
  31. {syndesi-0.2.2/syndesi/api → syndesi-0.2.3/syndesi/cli}/__init__.py +0 -0
  32. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/__init__.py +0 -0
  33. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/sdp.py +0 -0
  34. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/proxy/__init__.py +0 -0
  35. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/proxy/proxy.py +0 -0
  36. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/__init__.py +0 -0
  37. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/exceptions.py +0 -0
  38. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/others.py +0 -0
  39. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/types.py +0 -0
  40. {syndesi-0.2.2 → syndesi-0.2.3}/syndesi.egg-info/dependency_links.txt +0 -0
  41. {syndesi-0.2.2 → syndesi-0.2.3}/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.3
4
4
  Summary: Syndesi
5
5
  Author: Sebastien Deriaz
6
6
  Author-email: sebastien.deriaz1@gmail.com
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- VERSION = '0.2.2'
3
+ VERSION = '0.2.3'
4
4
  DESCRIPTION = 'Syndesi'
5
5
 
6
6
  with open("README.md", "r", encoding="utf-8") as fh:
@@ -17,7 +17,7 @@ setup(
17
17
  long_description=long_description,
18
18
  entry_points = {
19
19
  'console_scripts': [
20
- 'syndesi=syndesi.shell.syndesi:main',
20
+ 'syndesi=syndesi.cli.syndesi:main',
21
21
  'syndesi-proxy=syndesi.proxy.proxy:main'],
22
22
  },
23
23
  packages=find_packages(),
@@ -0,0 +1,6 @@
1
+ from .tools.log import set_log_file, set_log_level
2
+
3
+ from .adapters.ip import IP
4
+ from .adapters.serialport import SerialPort
5
+ from .protocols.delimited import Delimited
6
+ from .protocols.scpi import 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,13 @@ 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
32
  from ..tools.others import DEFAULT
33
33
 
34
- DEFAULT_TIMEOUT = Timeout(response=1, continuation=100e-3, total=None)
34
+ DEFAULT_TIMEOUT = Timeout(response=5, continuation=200e-3, total=None)
35
35
  DEFAULT_STOP_CONDITION = None
36
36
 
37
37
 
@@ -48,7 +48,7 @@ STOP_DESIGNATORS = {
48
48
  Termination : 'ST',
49
49
  Length : 'SL'
50
50
  },
51
- 'previous-read-buffer' : 'RB'
51
+ 'previous-buffer' : 'PB'
52
52
  }
53
53
 
54
54
  class Origin(Enum):
@@ -81,7 +81,7 @@ class Adapter(ABC):
81
81
  alias : str
82
82
  The alias is used to identify the class in the logs
83
83
  timeout : float or Timeout instance
84
- Default timeout is Timeout(response=1, continuation=0.1, total=None)
84
+ Default timeout is Timeout(response=5, continuation=0.2, total=None)
85
85
  stop_condition : StopCondition or None
86
86
  Default to None
87
87
  """
@@ -97,10 +97,11 @@ class Adapter(ABC):
97
97
  self._thread : Union[Thread, None] = None
98
98
  self._status = self.Status.DISCONNECTED
99
99
  self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
100
+ self._thread_stop_read, self._thread_stop_write = socket.socketpair()
100
101
 
101
102
  # Buffer for data that has been pulled from the queue but
102
103
  # not used because of termination or length stop condition
103
- self._previous_read_buffer = b''
104
+ self._previous_buffer = b''
104
105
 
105
106
  self._default_timeout = timeout == DEFAULT
106
107
  if self._default_timeout:
@@ -113,6 +114,16 @@ class Adapter(ABC):
113
114
  else:
114
115
  raise ValueError(f"Invalid timeout type : {type(timeout)}")
115
116
 
117
+ def set_timeout(self, timeout : Timeout):
118
+ """
119
+ Overwrite timeout
120
+
121
+ Parameters
122
+ ----------
123
+ timeout : Timeout
124
+ """
125
+ self._timeout = timeout
126
+
116
127
  def set_default_timeout(self, default_timeout : Union[Timeout, tuple, float]):
117
128
  """
118
129
  Set the default timeout for this adapter. If a previous timeout has been set, it will be fused
@@ -122,9 +133,22 @@ class Adapter(ABC):
122
133
  default_timeout : Timeout or tuple or float
123
134
  """
124
135
  if self._default_timeout:
136
+ self._logger.debug(f'Setting default timeout to {default_timeout}')
125
137
  self._timeout = default_timeout
126
138
  else:
139
+ log = f'Fusing timeouts {self._timeout}+{default_timeout} -> '
127
140
  self._timeout = timeout_fuse(self._timeout, default_timeout)
141
+ self._logger.debug(f'{log}{self._timeout}')
142
+
143
+ def set_stop_condition(self, stop_condition):
144
+ """
145
+ Overwrite the stop-condition
146
+
147
+ Parameters
148
+ ----------
149
+ stop_condition : StopCondition
150
+ """
151
+ self._stop_condition = stop_condition
128
152
 
129
153
  def set_default_stop_condition(self, stop_condition):
130
154
  """
@@ -142,7 +166,17 @@ class Adapter(ABC):
142
166
  Flush the input buffer
143
167
  """
144
168
  self._read_queue.clear()
145
- self._previous_read_buffer = b''
169
+ self._previous_buffer = b''
170
+
171
+ def previous_read_buffer_empty(self):
172
+ """
173
+ Check whether the previous read buffer is empty
174
+
175
+ Returns
176
+ -------
177
+ empty : bool
178
+ """
179
+ return self._previous_buffer == b''
146
180
 
147
181
  @abstractmethod
148
182
  def open(self):
@@ -151,12 +185,12 @@ class Adapter(ABC):
151
185
  """
152
186
  pass
153
187
 
154
- @abstractmethod
155
188
  def close(self):
156
189
  """
157
190
  Stop communication with the device
158
191
  """
159
- pass
192
+ self._logger.debug('Closing adapter and stopping read thread')
193
+ self._thread_stop_write.send(b'1')
160
194
 
161
195
  @abstractmethod
162
196
  def write(self, data : Union[bytes, str]):
@@ -168,12 +202,9 @@ class Adapter(ABC):
168
202
  data : bytes or str
169
203
  """
170
204
  pass
171
-
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
205
 
175
206
 
176
- def read(self, timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
207
+ def read(self, timeout=DEFAULT, stop_condition=DEFAULT, return_metrics : bool = False) -> bytes:
177
208
  """
178
209
  Read data from the device
179
210
 
@@ -190,13 +221,12 @@ class Adapter(ABC):
190
221
  self.open()
191
222
 
192
223
  # Use adapter values if no custom value is specified
193
- if timeout is None:
224
+ if timeout == DEFAULT:
194
225
  timeout = self._timeout
195
226
  elif isinstance(timeout, float):
196
227
  timeout = Timeout(timeout)
197
228
 
198
-
199
- if stop_condition is None:
229
+ if stop_condition == DEFAULT:
200
230
  stop_condition = self._stop_condition
201
231
 
202
232
  # If the adapter is closed, open it
@@ -206,18 +236,25 @@ class Adapter(ABC):
206
236
  if self._thread is None or not self._thread.is_alive():
207
237
  self._start_thread()
208
238
 
209
- timeout_ms = timeout.initiate_read(len(self._previous_read_buffer) > 0)
239
+ timeout_ms = timeout.initiate_read(len(self._previous_buffer) > 0)
210
240
 
211
241
  if stop_condition is not None:
212
242
  stop_condition.initiate_read()
213
243
 
244
+
214
245
  deferred_buffer = b''
215
246
 
216
247
  # Start with the deferred buffer
217
248
  # TODO : Check if data could be lost here, like the data is put in the previous_read_buffer and is never
218
249
  # 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)
250
+ if len(self._previous_buffer) > 0:# and stop_condition is not None:
251
+ self._logger.debug(f'Using previous buffer ({self._previous_buffer})')
252
+ if stop_condition is not None:
253
+ stop, output, self._previous_buffer = stop_condition.evaluate(self._previous_buffer)
254
+ else:
255
+ stop = True
256
+ output = self._previous_buffer
257
+ self._previous_buffer = b''
221
258
  previous_read_buffer_used = True
222
259
  else:
223
260
  stop = False
@@ -231,8 +268,8 @@ class Adapter(ABC):
231
268
  (timestamp, fragment) = self._read_queue.get(timeout_ms)
232
269
  n_fragments += 1
233
270
 
234
- if fragment == b'':
235
- raise AdapterDisconnected()
271
+ if isinstance(fragment, AdapterDisconnected):
272
+ raise fragment
236
273
 
237
274
  # 1) Evaluate the timeout
238
275
  stop, timeout_ms = timeout.evaluate(timestamp)
@@ -248,10 +285,10 @@ class Adapter(ABC):
248
285
  output += fragment
249
286
  elif data_strategy == Timeout.OnTimeoutStrategy.STORE:
250
287
  # Store the data
251
- self._previous_read_buffer = output
288
+ self._previous_buffer = output
252
289
  output = b''
253
290
  elif data_strategy == Timeout.OnTimeoutStrategy.ERROR:
254
- raise TimeoutException(origin)
291
+ raise TimeoutException(origin, timeout._stop_source_overtime, timeout._stop_source_limit)
255
292
  break
256
293
  else:
257
294
  origin = None
@@ -267,7 +304,7 @@ class Adapter(ABC):
267
304
  stop, kept_fragment, deferred_buffer = stop_condition.evaluate(fragment)
268
305
  output += kept_fragment
269
306
  if stop:
270
- self._previous_read_buffer = deferred_buffer
307
+ self._previous_buffer = deferred_buffer
271
308
  else:
272
309
  output += fragment
273
310
  if stop:
@@ -279,11 +316,11 @@ class Adapter(ABC):
279
316
  else:
280
317
  designator = STOP_DESIGNATORS['stop_condition'][type(stop_condition)]
281
318
  else:
282
- designator = STOP_DESIGNATORS['previous-read-buffer']
319
+ designator = STOP_DESIGNATORS['previous-buffer']
283
320
 
284
321
  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}')
322
+ if self._previous_buffer:
323
+ self._logger.debug(f'Read [{designator}, {read_duration*1e3:.3f}ms] : {output} , previous read buffer : {self._previous_buffer}')
287
324
  else:
288
325
  self._logger.debug(f'Read [{designator}, {read_duration*1e3:.3f}ms] : {output}')
289
326
 
@@ -1,25 +1,26 @@
1
1
  import socket
2
2
  from enum import Enum
3
- from .adapter import Adapter
3
+ from .adapter import Adapter, 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
+ from ..tools.others import DEFAULT
14
+ import select
12
15
 
13
16
  class IP(Adapter):
14
- DEFAULT_RESPONSE_TIMEOUT = 1
15
- DEFAULT_CONTINUATION_TIMEOUT = 1e-3
16
- DEFAULT_TOTAL_TIMEOUT = 5
17
-
18
-
19
17
  DEFAULT_TIMEOUT = Timeout(
20
- response=DEFAULT_RESPONSE_TIMEOUT,
21
- continuation=DEFAULT_CONTINUATION_TIMEOUT,
22
- total=DEFAULT_TOTAL_TIMEOUT)
18
+ response=2,
19
+ on_response='error',
20
+ continuation=100e-3,
21
+ on_continuation='return',
22
+ total=5,
23
+ on_total='error')
23
24
  DEFAULT_BUFFER_SIZE = 1024
24
25
  class Protocol(Enum):
25
26
  TCP = 'TCP'
@@ -29,8 +30,8 @@ class IP(Adapter):
29
30
  address : str,
30
31
  port : int = None,
31
32
  transport : str = 'TCP',
32
- timeout : Union[Timeout, float] = DEFAULT_TIMEOUT,
33
- stop_condition = None,
33
+ timeout : Union[Timeout, float] = DEFAULT,
34
+ stop_condition : StopCondition = DEFAULT,
34
35
  alias : str = '',
35
36
  buffer_size : int = DEFAULT_BUFFER_SIZE,
36
37
  _socket : socket.socket = None):
@@ -56,6 +57,11 @@ class IP(Adapter):
56
57
  socket : socket.socket
57
58
  Specify a custom socket, this is reserved for server application
58
59
  """
60
+ if timeout == DEFAULT:
61
+ timeout = self.DEFAULT_TIMEOUT
62
+ else:
63
+ timeout = timeout_fuse(timeout, self.DEFAULT_TIMEOUT)
64
+
59
65
  super().__init__(alias=alias, timeout=timeout, stop_condition=stop_condition)
60
66
  self._transport = self.Protocol(transport)
61
67
  self._is_server = _socket is not None
@@ -101,6 +107,13 @@ class IP(Adapter):
101
107
  self._logger.info(f"Adapter {self._alias} opened !")
102
108
 
103
109
  def close(self):
110
+ super().close()
111
+ if self._thread is not None and self._thread.is_alive():
112
+ try:
113
+ self._thread.join()
114
+ except RuntimeError:
115
+ # If the thread cannot be joined, then so be it
116
+ pass
104
117
  if hasattr(self, '_socket'):
105
118
  self._socket.close()
106
119
  self._logger.info("Adapter closed !")
@@ -114,31 +127,47 @@ class IP(Adapter):
114
127
  write_start = time()
115
128
  self._socket.send(data)
116
129
  write_duration = time() - write_start
117
- self._logger.debug(f"Written [{write_duration*1e3:.3f}ms]: {repr(data)}")
130
+ self._logger.debug(f"Write [{write_duration*1e3:.3f}ms]: {repr(data)}")
118
131
 
119
132
  def _start_thread(self):
120
133
  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()
134
+ if self._thread is None or not self._thread.is_alive():
135
+ self._thread = Thread(target=self._read_thread, daemon=True, args=(self._socket, self._read_queue, self._thread_stop_read))
136
+ self._thread.start()
127
137
 
138
+ # # EXPERIMENTAL
139
+ # def read_thread_alive(self):
140
+ # return self._thread.is_alive()
128
141
 
129
- def _read_thread(self, socket : socket.socket, read_queue : TimedQueue):
142
+ def _read_thread(self, socket : socket.socket, read_queue : TimedQueue, stop : socket.socket):
143
+ # Using select.select works on both Windows and Linux as long as the inputs are all sockets
130
144
  while True: # TODO : Add stop_pipe ? Maybe it was removed ?
145
+
131
146
  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)
147
+ ready, _, _ = select.select([socket, stop], [], [])
148
+ except ValueError:
149
+ # File desctiptor is s negative integer
150
+ read_queue.put(AdapterDisconnected())
151
+ else:
152
+ if stop in ready:
153
+ # Stop the thread
154
+ stop.recv(1)
155
+ break
156
+ elif socket in ready:
157
+ # Read from the socket
158
+ try:
159
+ payload = socket.recv(self._buffer_size)
160
+ except ConnectionRefusedError:
161
+ # TODO : Check if this is the right way of doing it
162
+ read_queue.put(AdapterDisconnected())
163
+ else:
164
+ if len(payload) == self._buffer_size and self._transport == self.Protocol.UDP:
165
+ self._logger.warning("Warning, inbound UDP data may have been lost (max buffer size attained)")
166
+ if payload == b'':
167
+ read_queue.put(AdapterDisconnected())
168
+ break
169
+ else:
170
+ read_queue.put(payload)
142
171
 
143
172
  def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
144
173
  if self._is_server:
@@ -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())))
@@ -4,17 +4,28 @@ from threading import Thread
4
4
  from typing import Union
5
5
  import select
6
6
  import argparse
7
+ import socket
8
+ import sys
7
9
  #from collections.abc import Sequence
8
10
 
9
- from .adapter import Adapter
11
+ from .adapter import Adapter, AdapterDisconnected
10
12
  from ..tools.types import to_bytes
11
13
  from .stop_conditions import *
12
14
  from .timeout import Timeout
13
15
  from .timed_queue import TimedQueue
14
- from ..tools import shell
16
+ #from ..cli import shell
15
17
  from ..tools.others import DEFAULT
16
18
 
17
- DEFAULT_TIMEOUT = Timeout(response=1, continuation=200e-3, total=None)
19
+ DEFAULT_TIMEOUT = Timeout(
20
+ response=1,
21
+ on_response='error',
22
+ continuation=200e-3,
23
+ on_continuation='return',
24
+ total=None,
25
+ on_total='error')
26
+
27
+
28
+
18
29
 
19
30
  class SerialPort(Adapter):
20
31
  def __init__(self,
@@ -36,7 +47,9 @@ class SerialPort(Adapter):
36
47
 
37
48
  super().__init__(timeout=timeout, stop_condition=stop_condition)
38
49
  self._logger.info(f"Setting up SerialPort adapter timeout:{timeout}, stop_condition:{stop_condition}")
39
- self._port = serial.Serial(port=port, baudrate=baudrate)
50
+ self._port_name = port
51
+ self._baudrate = baudrate
52
+ self._port = serial.Serial(port=self._port_name, baudrate=self._baudrate)
40
53
  if self._port.isOpen():
41
54
  self._status = self.Status.CONNECTED
42
55
  else:
@@ -56,6 +69,13 @@ class SerialPort(Adapter):
56
69
  self._logger.info("Adapter opened !")
57
70
 
58
71
  def close(self):
72
+ super().close()
73
+ if self._thread is not None and self._thread.is_alive():
74
+ try:
75
+ self._thread.join()
76
+ except RuntimeError:
77
+ # If the thread cannot be joined, then so be it
78
+ pass
59
79
  if hasattr(self, '_port'):
60
80
  # Close and the read thread will die by itself
61
81
  self._port.close()
@@ -70,7 +90,7 @@ class SerialPort(Adapter):
70
90
  write_start = time()
71
91
  self._port.write(data)
72
92
  write_duration = time() - write_start
73
- self._logger.debug(f"Written [{write_duration*1e3:.3f}ms]: {repr(data)}")
93
+ self._logger.debug(f"Write [{write_duration*1e3:.3f}ms]: {repr(data)}")
74
94
 
75
95
  def _start_thread(self):
76
96
  """
@@ -78,21 +98,44 @@ class SerialPort(Adapter):
78
98
  """
79
99
  self._logger.debug("Starting read thread...")
80
100
  if self._thread is None or not self._thread.is_alive():
81
- self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue))
101
+ self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue, self._thread_stop_read))
82
102
  self._thread.start()
83
103
 
84
- def _read_thread(self, port : serial.Serial , read_queue : TimedQueue):
85
- # NOTE : There should be some way to kill the thread, maybe check for an error on in_waiting but couldn't find it so far
104
+ def _read_thread(self, port : serial.Serial,read_queue : TimedQueue, stop : socket.socket):
105
+ # On linux, it is possivle to use the select.select for both serial port and stop socketpair.
106
+ # On Windows, this is not possible. so the port timeout is used instead.
107
+ if sys.platform == 'win32':
108
+ port.timeout = 0.1
86
109
  while True:
87
110
  # Check how many bytes are available
88
- in_waiting = self._port.in_waiting # This is a temporary fix to get windows compatiblity back, an error might pop up
89
- if in_waiting > 0:
90
- # Read those bytes
91
- fragment = port.read(in_waiting)
92
- if fragment:
93
- read_queue.put(fragment)
94
-
95
- def read(self, timeout=None, stop_condition=None, return_metrics: bool = False) -> bytes:
111
+ if sys.platform == 'win32':
112
+ ready, _, _ = select.select([stop], [], [], 0)
113
+ if stop in ready:
114
+ # Stop the read thread
115
+ break
116
+ else:
117
+ # Read data from the serialport with a timeout, if the timeout occurs, read again.
118
+ # This is to avoid having a crazy fast loop
119
+ data = port.read()
120
+ if len(data) > 0:
121
+ read_queue.put(fragment)
122
+ else:
123
+ ready, _, _ = select.select([self._port.fd, stop], [], [])
124
+ if stop in ready:
125
+ # Stop the read thread
126
+ break
127
+ elif self._port.fd in ready:
128
+ try:
129
+ in_waiting = self._port.in_waiting
130
+ except OSError:
131
+ # Input/output error, the port was disconnected
132
+ read_queue.put(AdapterDisconnected)
133
+ else:
134
+ fragment = port.read(in_waiting)
135
+ if fragment:
136
+ read_queue.put(fragment)
137
+
138
+ def read(self, timeout=DEFAULT, stop_condition=DEFAULT, return_metrics: bool = False) -> bytes:
96
139
  """
97
140
  Read data from the device
98
141
 
@@ -121,19 +164,19 @@ class SerialPort(Adapter):
121
164
  self.write(data)
122
165
  return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
123
166
 
124
- def shell_parse(inp: str):
125
- parser = argparse.ArgumentParser(
126
- prog='',
127
- description='Serial port shell parser',
128
- epilog='')
129
- # Parse subcommand
130
- parser.add_argument('--' + shell.Arguments.PORT.value, type=str)
131
- parser.add_argument('--' + shell.Arguments.BAUDRATE.value, type=int)
132
- parser.add_argument('--' + shell.Arguments.ENABLE_RTS_CTS.value, action='store_true')
133
- args = parser.parse_args(inp.split())
134
-
135
- return {
136
- 'port' : getattr(args, shell.Arguments.PORT.value),
137
- 'baudrate' : getattr(args, shell.Arguments.BAUDRATE.value),
138
- 'rts_cts' : bool(getattr(args, shell.Arguments.ENABLE_RTS_CTS.value))
139
- }
167
+ # def shell_parse(inp: str):
168
+ # parser = argparse.ArgumentParser(
169
+ # prog='',
170
+ # description='Serial port shell parser',
171
+ # epilog='')
172
+ # # Parse subcommand
173
+ # parser.add_argument('--' + shell.Arguments.PORT.value, type=str)
174
+ # parser.add_argument('--' + shell.Arguments.BAUDRATE.value, type=int)
175
+ # parser.add_argument('--' + shell.Arguments.ENABLE_RTS_CTS.value, action='store_true')
176
+ # args = parser.parse_args(inp.split())
177
+
178
+ # return {
179
+ # 'port' : getattr(args, shell.Arguments.PORT.value),
180
+ # 'baudrate' : getattr(args, shell.Arguments.BAUDRATE.value),
181
+ # 'rts_cts' : bool(getattr(args, shell.Arguments.ENABLE_RTS_CTS.value))
182
+ # }
@@ -5,7 +5,6 @@
5
5
  from enum import Enum
6
6
  from typing import Union, Tuple
7
7
  from time import time
8
- #from ..tools.others import is_default_argument
9
8
 
10
9
 
11
10
  class Timeout():
@@ -16,9 +15,9 @@ class Timeout():
16
15
  ERROR = 'error' # If a timeout is reached, raise an error
17
16
 
18
17
  class TimeoutType(Enum):
19
- RESPONSE = 0
20
- CONTINUATION = 1
21
- TOTAL = 2
18
+ RESPONSE = 'response'
19
+ CONTINUATION = 'continuation'
20
+ TOTAL = 'total'
22
21
 
23
22
  class _State(Enum):
24
23
  WAIT_FOR_RESPONSE = 0
@@ -26,12 +25,12 @@ class Timeout():
26
25
 
27
26
  DEFAULT_CONTINUATION = 5e-3
28
27
  DEFAULT_TOTAL = None
29
- DEFAULT_ON_RESPONSE = OnTimeoutStrategy.DISCARD
28
+ DEFAULT_ON_RESPONSE = OnTimeoutStrategy.ERROR
30
29
  DEFAULT_ON_CONTINUATION = OnTimeoutStrategy.RETURN
31
30
  DEFAULT_ON_TOTAL = OnTimeoutStrategy.RETURN
32
31
 
33
32
  def __init__(self,
34
- response,
33
+ response=None,
35
34
  continuation=None,
36
35
  total=None,
37
36
  on_response=None,
@@ -151,6 +150,8 @@ class Timeout():
151
150
  def evaluate(self, timestamp : float) -> Tuple[bool, Union[float, None]]:
152
151
  stop = False
153
152
  self._data_strategy = None
153
+ self._stop_source_overtime = '###' # When a timeout occurs, store the value that exceeded its value here
154
+ self._stop_source_limit = '###' # And store the limit value here
154
155
 
155
156
  # First check if the timestamp is None, that would mean the timeout was reached in the queue
156
157
  if timestamp is None:
@@ -158,15 +159,17 @@ class Timeout():
158
159
  match self._queue_timeout_type:
159
160
  case self.TimeoutType.RESPONSE:
160
161
  self._data_strategy = self._on_response
161
- self.response_time = self._output_timeout # This is a test
162
+ self._stop_source_limit = self._response
162
163
  case self.TimeoutType.CONTINUATION:
163
164
  self._data_strategy = self._on_continuation
164
- self.continuation_times.append(self._output_timeout) # This is a test
165
+ self._stop_source_limit = self._continuation
165
166
  case self.TimeoutType.TOTAL:
166
- self.total_time = self._output_timeout # This is a test
167
167
  self._data_strategy = self._on_total
168
+ self._stop_source_limit = self._total
169
+ self._stop_source_overtime = None # We do not have the exceed time, but None will be printed as '---'
168
170
  self._last_data_strategy_origin = self._queue_timeout_type
169
- stop = True
171
+ stop = True
172
+
170
173
  else:
171
174
  # Check total
172
175
  if self._total is not None:
@@ -175,23 +178,27 @@ class Timeout():
175
178
  stop = True
176
179
  self._data_strategy = self._on_total
177
180
  self._last_data_strategy_origin = self.TimeoutType.TOTAL
181
+ self._stop_source_overtime = self.total_time
182
+ self._stop_source_limit = self._total
178
183
  # Check continuation
179
- # elif
180
- if self._continuation is not None and self._state == self._State.CONTINUATION and self._last_timestamp is not None:
184
+ elif self._continuation is not None and self._state == self._State.CONTINUATION and self._last_timestamp is not None:
181
185
  continuation_time = timestamp - self._last_timestamp
182
186
  self.continuation_times.append(continuation_time)
183
187
  if continuation_time >= self._continuation:
184
188
  stop = True
185
189
  self._data_strategy = self._on_continuation
186
190
  self._last_data_strategy_origin = self.TimeoutType.CONTINUATION
191
+ self._stop_source_overtime = continuation_time
192
+ self._stop_source_limit = self._continuation
187
193
  # Check response time
188
- # elif
189
- if self._response is not None and self._state == self._State.WAIT_FOR_RESPONSE:
194
+ elif self._response is not None and self._state == self._State.WAIT_FOR_RESPONSE:
190
195
  self.response_time = timestamp - self._start_time
191
196
  if self.response_time >= self._response:
192
197
  stop = True
193
198
  self._data_strategy = self._on_response
194
199
  self._last_data_strategy_origin = self.TimeoutType.RESPONSE
200
+ self._stop_source_overtime = self.response_time
201
+ self._stop_source_limit = self._response
195
202
 
196
203
  self._output_timeout = None
197
204
  # If we continue
@@ -236,9 +243,9 @@ class Timeout():
236
243
  return self._data_strategy, self._last_data_strategy_origin
237
244
 
238
245
  def __str__(self) -> str:
239
- response = f'r:{self._response:.3f}ms/{self._on_response},' if self._response is not None else ''
240
- continuation = f'c:{self._continuation:.3f}ms/{self._on_continuation},' if self._continuation is not None else ''
241
- total = f't:{self._total:.3f}ms/{self._on_total}' if self._total is not None else ''
246
+ response = f'r:{self._response*1e3:.3f}ms/{self._on_response.value},' if self._response is not None else ''
247
+ continuation = f'c:{self._continuation*1e3:.3f}ms/{self._on_continuation.value},' if self._continuation is not None else ''
248
+ total = f't:{self._total*1e3:.3f}ms/{self._on_total.value}' if self._total is not None else ''
242
249
  return f'Timeout({response}{continuation}{total})'
243
250
 
244
251
  def __repr__(self) -> str:
@@ -246,10 +253,28 @@ class Timeout():
246
253
 
247
254
 
248
255
  class TimeoutException(Exception):
249
- def __init__(self, type : Timeout.TimeoutType) -> None:
256
+ def __init__(self, type : Timeout.TimeoutType, value : float, limit : float) -> None:
250
257
  super().__init__()
251
258
  self._type = type
259
+ self._value = value
260
+ self._limit = limit
261
+
262
+ def __str__(self) -> str:
263
+ try:
264
+ value_string = f'{self._value*1e3:.3f}ms'
265
+ except (ValueError, TypeError):
266
+ value_string = 'not received'
267
+
268
+ try:
269
+ limit_string = f'{self._limit*1e3:.3f}ms'
270
+ except (ValueError, TypeError):
271
+ limit_string = 'not received'
272
+
252
273
 
274
+ return f'{self._type.value} : {value_string} / {limit_string}'
275
+
276
+ def __repr__(self) -> str:
277
+ return self.__str__()
253
278
 
254
279
  def timeout_fuse(high_priority, low_priority):
255
280
  """
@@ -292,6 +317,6 @@ def timeout_fuse(high_priority, low_priority):
292
317
  L = getattr(low, attr)
293
318
  # Use low priority if the default value is used in high priority
294
319
  new_attr[attr.removeprefix('_')] = L if high._defaults[attr] else H
295
-
320
+
296
321
  return Timeout(**new_attr)
297
322
 
@@ -6,17 +6,18 @@ from ..tools.types import to_bytes
6
6
  from typing import Union
7
7
 
8
8
  class VISA(Adapter):
9
- def __init__(self, descriptor : str):
9
+ def __init__(self, resource : str):
10
10
  """
11
11
  USB VISA stack adapter
12
12
 
13
13
  Parameters
14
14
  ----------
15
- descriptor : str
16
- IP description
15
+ resource : str
16
+ resource address string
17
17
  """
18
+ self._resource = resource
18
19
  self._rm = ResourceManager()
19
- self._inst = self._rm.open_resource(descriptor)
20
+ self._inst = self._rm.open_resource(self._resource)
20
21
  self._inst.write_termination = ''
21
22
  self._inst.read_termination = ''
22
23
 
@@ -3,22 +3,45 @@
3
3
  # 30.04.2024
4
4
  from enum import Enum
5
5
  from cmd import Cmd
6
- from syndesi.adapters import *
6
+ from ..adapters import *
7
+ from ..protocols import Delimited
8
+ from ..tools.log import set_log_file, set_log_level, LoggerAlias
7
9
  import argparse
10
+ import logging
8
11
  import shlex
9
12
  import sys
10
13
  import os
11
- from colorist import ColorRGB
14
+ #from colorist import ColorRGB
12
15
 
13
16
  VERSION = 0.1
14
17
 
15
18
  class ShellPrompt(Cmd):
19
+ _logger = logging.getLogger(LoggerAlias.CLI.value)
16
20
  __hiden_methods = ('do_EOF','do_clear','do_cls')
17
- PROMPT_COLOR = ColorRGB(28, 90, 145)
21
+ #PROMPT_COLOR = ColorRGB(28, 90, 145)
18
22
 
19
- prompt = f'{PROMPT_COLOR}❯ {PROMPT_COLOR.OFF}'
23
+ #prompt = f'{PROMPT_COLOR}❯ {PROMPT_COLOR.OFF}'
24
+ prompt = f'❯ '
20
25
  intro = "Welcome to the Syndesi Shell! Type ? to list commands"
21
26
 
27
+ common_parser = argparse.ArgumentParser(add_help=False)
28
+ common_parser.add_argument('-v', '--verbose', help='Print logging informations', action='store_true', default=False)
29
+ common_parser.add_argument('-d', '--debug', help='Print debug informations', action='store_true', default=False)
30
+ common_parser.add_argument('--log-file', type=str, default='')
31
+
32
+ def _parse_common_args(self, args):
33
+ common_args, other_args = self.common_parser.parse_known_args(args)
34
+ if common_args.debug:
35
+ set_log_level('DEBUG')
36
+ elif common_args.verbose:
37
+ set_log_level('INFO')
38
+
39
+ if common_args.log_file != '': # TODO : test this
40
+ set_log_file(common_args.log_file)
41
+
42
+ return other_args
43
+
44
+
22
45
  def get_names(self):
23
46
  return [n for n in dir(self.__class__) if n not in self.__hiden_methods]
24
47
 
@@ -31,25 +54,32 @@ class ShellPrompt(Cmd):
31
54
  self._adapter = SerialPort(**SerialPort.shell_parse(inp))
32
55
 
33
56
  def do_ip(self, inp):
34
- """Open IP adapter
35
- -p / --port : port number
36
- --ip : ip address
37
- -t / --transport : TCP or UDP
38
- """
39
- parser = argparse.ArgumentParser()
57
+ """Open IP adapter with delimited protocol (\\n at the end of each line by default)"""
58
+ arguments = shlex.split(inp)
59
+ other_arguments = self._parse_common_args(arguments)
60
+
61
+ parser = argparse.ArgumentParser(
62
+ prog='ip'
63
+ )
64
+ parser.add_argument('ip', type=str)
40
65
  parser.add_argument('-p', '--port', type=int, required=True)
41
- parser.add_argument('--ip', type=str, required=True)
42
66
  parser.add_argument('-t', '--transport', type=str, choices=['UDP', 'TCP'], default='TCP', required=False)
67
+ parser.add_argument('-e', '--end', help='Newline character, \\n by default, on unix systems, use -e \'\\\\n\' to circumvent backslash substitution', default='\n', required=False)
43
68
  try:
44
- args = parser.parse_args(shlex.split(inp))
69
+ args = parser.parse_args(other_arguments)
45
70
  except SystemExit as e:
46
71
  pass
47
72
  else:
48
- self._adapter = IP(address=args.ip, port=args.port, transport=args.transport)
73
+ end_string : str = args.end
74
+ termination = end_string.encode('utf-8').decode('unicode_escape')
75
+ self._logger.debug(f'Opening Delimited IP with termination : {repr(termination)}')
76
+ self._adapter = Delimited(IP(address=args.ip, port=args.port, transport=args.transport), termination=termination)
77
+ self._adapter._adapter.open()
78
+ print(f"Successfully opened IP adapter at {args.ip}:{args.port} ({repr(termination)} termination)")
49
79
 
50
80
  def default(self, inp):
51
81
  if hasattr(self, '_adapter'):
52
- cmd = inp + '\n'
82
+ cmd = inp
53
83
  output = self._adapter.query(cmd)
54
84
  print(output)
55
85
  else:
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Syndesi CLI
4
+ import argparse
5
+ from enum import Enum
6
+ from syndesi.cli.shell import ShellPrompt
7
+
8
+ class SubCommands(Enum):
9
+ SHELL = 'shell'
10
+
11
+ def main():
12
+ parser = argparse.ArgumentParser(
13
+ prog='syndesi',
14
+ description='Syndesi command line interface',
15
+ epilog='')
16
+ # Parse subcommand
17
+ parser.add_argument('subcommand', choices=[SubCommands.SHELL.value])
18
+
19
+ args, extra_args = parser.parse_known_args()
20
+
21
+ if args.subcommand == SubCommands.SHELL.value:
22
+ p = ShellPrompt()
23
+ if len(extra_args):
24
+ p.cmdqueue.append(' '.join(extra_args))
25
+ p.cmdloop()
26
+
27
+
28
+
29
+ if __name__ == '__main__':
30
+ main()
@@ -1,12 +1,13 @@
1
1
  from .protocol import Protocol
2
2
  from ..adapters import Adapter, Timeout, Termination
3
3
  from ..tools.types import assert_byte_instance, assert_byte_instance
4
+ from ..tools.others import DEFAULT
4
5
  from time import time
5
6
  import warnings
6
7
 
7
8
 
8
9
  class Delimited(Protocol):
9
- def __init__(self, adapter : Adapter, termination='\n', format_response=True, encoding : str = 'utf-8', timeout : Timeout = None) -> None:
10
+ def __init__(self, adapter : Adapter, termination='\n', format_response=True, encoding : str = 'utf-8', timeout : Timeout = DEFAULT) -> None:
10
11
  """
11
12
  Protocol with delimiter, like LF, CR, etc... '\\n' is used by default
12
13
 
@@ -65,7 +66,7 @@ class Delimited(Protocol):
65
66
  self.write(command)
66
67
  return self.read()
67
68
 
68
- def read(self, timeout : Timeout = None, decode : str = True) -> str:
69
+ def read(self, timeout : Timeout = DEFAULT, decode : str = True) -> str:
69
70
  """
70
71
  Reads command and formats it as a str
71
72
 
@@ -79,7 +80,10 @@ class Delimited(Protocol):
79
80
  # Send up to the termination
80
81
  data = self._adapter.read(timeout=timeout)
81
82
  if decode:
82
- data = data.decode(self._encoding)
83
+ try:
84
+ data = data.decode(self._encoding)
85
+ except UnicodeDecodeError as e:
86
+ raise ValueError(f'Failed to decode {data} to {self._encoding} ({e})')
83
87
  if self._response_formatting:
84
88
  # Only send the fragment (no termination)
85
89
  return data
@@ -7,6 +7,9 @@ class Protocol:
7
7
  self._adapter = auto_adapter(adapter)
8
8
  self._adapter.set_default_timeout(timeout)
9
9
 
10
+ def flushRead(self):
11
+ self._adapter.flushRead()
12
+
10
13
  def write(self, data):
11
14
  pass
12
15
 
@@ -1,12 +1,13 @@
1
1
  from ..adapters import Adapter, Timeout
2
2
  from .protocol import Protocol
3
+ from ..tools.others import DEFAULT
3
4
 
4
5
 
5
6
  # Raw protocols provide the user with the binary data directly,
6
7
  # without converting it to string first
7
8
 
8
9
  class Raw(Protocol):
9
- def __init__(self, adapter: Adapter, timeout : Timeout = None) -> None:
10
+ def __init__(self, adapter: Adapter, timeout : Timeout = DEFAULT) -> None:
10
11
  """
11
12
  Raw device, no presentation and application layers
12
13
 
@@ -1,12 +1,13 @@
1
1
  from ..adapters import Adapter, IP, Timeout, Termination, StopCondition
2
2
  from .protocol import Protocol
3
3
  from ..tools.types import is_byte_instance
4
- #from ..tools.others import is_default_argument
5
4
  from ..tools.others import DEFAULT
6
5
 
6
+ DEFAULT_TIMEOUT = Timeout(response=10, continuation=0.5, total=None, on_response='error', on_continuation='error')
7
+
7
8
  class SCPI(Protocol):
8
9
  DEFAULT_PORT = 5025
9
- def __init__(self, adapter: Adapter, send_termination = '\n', receive_termination = None, timeout : Timeout = None, encoding : str = 'utf-8') -> None:
10
+ def __init__(self, adapter: Adapter, send_termination = '\n', receive_termination = None, timeout : Timeout = DEFAULT, encoding : str = 'utf-8') -> None:
10
11
  """
11
12
  SDP (Syndesi Device Protocol) compatible device
12
13
 
@@ -20,22 +21,25 @@ class SCPI(Protocol):
20
21
  timeout : Timeout/float/tuple
21
22
  Set device timeout
22
23
  """
23
- super().__init__(adapter=adapter, timeout=timeout)
24
-
24
+ self._encoding = encoding
25
+ # Set the default timeout
26
+ if timeout == DEFAULT:
27
+ timeout = DEFAULT_TIMEOUT
28
+
25
29
  if receive_termination is None:
26
30
  self._receive_termination = send_termination
27
31
  else:
28
32
  self._receive_termination = receive_termination
29
-
30
33
  self._send_termination = send_termination
31
-
32
- if isinstance(self._adapter, IP):
33
- self._adapter.set_default_port(self.DEFAULT_PORT)
34
-
35
- self._adapter.set_default_timeout(timeout)
36
- if self._adapter._stop_condition != DEFAULT:
37
- raise ValueError('A conflicting stop-condition has been set for this adapter')
38
- self._adapter._stop_condition = Termination(self._receive_termination.encode(encoding=encoding))
34
+ # Configure the adapter for stop-condition mode (timeouts will raise errors)
35
+ if not adapter._default_stop_condition:
36
+ raise ValueError('No stop-conditions can be set for an adapter used by SCPI protocol')
37
+ adapter.set_stop_condition(Termination(self._receive_termination.encode(self._encoding)))
38
+ adapter.set_timeout(timeout)
39
+ if isinstance(adapter, IP):
40
+ adapter.set_default_port(self.DEFAULT_PORT)
41
+ # Give the adapter to the Protocol base class
42
+ super().__init__(adapter=adapter, timeout=timeout)
39
43
 
40
44
  def _to_bytes(self, command):
41
45
  if isinstance(command, str):
@@ -65,12 +69,12 @@ class SCPI(Protocol):
65
69
  payload = self._to_bytes(self._formatCommand(command))
66
70
  self._adapter.write(payload)
67
71
 
68
- def query(self, command : str, timeout : Timeout = None, stop_condition : StopCondition = None, return_metrics : bool = False) -> str:
72
+ def query(self, command : str, timeout : Timeout = DEFAULT, stop_condition : StopCondition = DEFAULT, return_metrics : bool = False) -> str:
69
73
  self._adapter.flushRead()
70
74
  self.write(command)
71
75
  return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
72
76
 
73
- def read(self, timeout : Timeout = None, stop_condition : StopCondition = None, return_metrics : bool = False) -> str:
77
+ def read(self, timeout : Timeout = DEFAULT, stop_condition : StopCondition = None, return_metrics : bool = False) -> str:
74
78
  output = self._from_bytes(self._adapter.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics))
75
79
  return self._unformatCommand(output)
76
80
 
@@ -4,21 +4,14 @@
4
4
 
5
5
  import sys
6
6
  from dataclasses import dataclass
7
+ from ..adapters.stop_conditions import Length, Termination, StopCondition
8
+ from ..adapters.timeout import Timeout
7
9
 
8
10
  from ..api.api import APICall, ACTION_ATTRIBUTE, register_api, APIItem
9
11
 
10
12
  class ProxyException(Exception):
11
13
  pass
12
14
 
13
- # IP specific
14
- @dataclass
15
- class IPInstanciate(APICall):
16
- action = 'ip_adapter_inst'
17
- address : str
18
- port : int
19
- transport : str
20
- buffer_size : int
21
-
22
15
  @dataclass
23
16
  class TimeoutAPI(APIItem):
24
17
  name = 'timeout'
@@ -29,10 +22,22 @@ class TimeoutAPI(APIItem):
29
22
  on_continuation : str
30
23
  on_total : str
31
24
 
25
+ def timeout_to_api(timeout : Timeout) -> TimeoutAPI:
26
+ if timeout is None:
27
+ return None
28
+ else:
29
+ return TimeoutAPI(
30
+ response=timeout._response,
31
+ continuation=timeout._continuation,
32
+ total=timeout._total,
33
+ on_response=timeout._on_response,
34
+ on_continuation=timeout._on_continuation,
35
+ on_total=timeout._on_total
36
+ )
37
+
32
38
  @dataclass
33
39
  class StopConditionAPI(APIItem):
34
40
  pass
35
-
36
41
  @dataclass
37
42
  class TerminationAPI(StopConditionAPI):
38
43
  name = 'termination'
@@ -43,6 +48,23 @@ class LengthAPI(StopConditionAPI):
43
48
  name = 'length'
44
49
  length : int
45
50
 
51
+ def stop_condition_to_api(stop_condition : StopCondition):
52
+ if stop_condition is None:
53
+ return None
54
+ elif isinstance(stop_condition, Length):
55
+ return LengthAPI(length=stop_condition._N)
56
+ elif isinstance(stop_condition, Termination):
57
+ return TerminationAPI(sequence=stop_condition._termination)
58
+ # IP specific
59
+ @dataclass
60
+ class IPInstanciate(APICall):
61
+ action = 'ip_adapter_inst'
62
+ address : str
63
+ port : int
64
+ transport : str
65
+ buffer_size : int
66
+ timeout : TimeoutAPI
67
+
46
68
  # Serial specific
47
69
  @dataclass
48
70
  class SerialPortInstanciate(APICall):
@@ -53,6 +75,13 @@ class SerialPortInstanciate(APICall):
53
75
  stop_condition : StopConditionAPI
54
76
  rts_cts : bool
55
77
 
78
+ # VISA specific
79
+ @dataclass
80
+ class VisaInstanciate(APICall):
81
+ action = 'visa_instanciate'
82
+ resource : str
83
+
84
+
56
85
  # Adapters common
57
86
  @dataclass
58
87
  class AdapterOpen(APICall):
@@ -9,9 +9,10 @@ import logging
9
9
  from typing import List, Union
10
10
 
11
11
  class LoggerAlias(Enum):
12
- ADAPTER = 'adapter'
13
- PROTOCOL = 'protocol'
14
- PROXY_SERVER = 'proxy_server'
12
+ ADAPTER = 'syndesi.adapter'
13
+ PROTOCOL = 'syndesi.protocol'
14
+ PROXY_SERVER = 'syndesi.proxy_server'
15
+ CLI = 'syndesi.cli'
15
16
 
16
17
  default_formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s')
17
18
 
@@ -53,21 +54,19 @@ def set_log_file(file : str, level : Union[str, int], loggers : Union[List[Logge
53
54
  file_handler.setFormatter(default_formatter)
54
55
  # 3) Add to the designated loggers
55
56
  for l in LoggerAlias:
56
- if l in loggers or loggers == 'all':
57
+ if loggers == 'all' or l.value in loggers:
57
58
  logger = logging.getLogger(l.value)
58
59
  logger.addHandler(file_handler)
59
60
  logger.setLevel(level)
60
61
  else:
61
62
  file_handler = None
62
63
 
63
- def set_log_stream(enabled : bool, level : Union[str, int], loggers : Union[List[LoggerAlias], str] = 'all'):
64
+ def set_log_level(level : Union[str, int], loggers : Union[List[LoggerAlias], str] = 'all'):
64
65
  """
65
- Set stdout/stderr log mode
66
+ Set log level, everything below or equal to the given level will be outputed to stdout/stderr.
66
67
 
67
68
  Parameters
68
69
  ----------
69
- enabled : bool
70
- enable / disable log to stdout / stderr
71
70
  level : str or logging level
72
71
  info : 'INFO' or logging.INFO
73
72
  critical : 'CRITICAL' or logging.CRITICAL
@@ -75,6 +74,7 @@ def set_log_stream(enabled : bool, level : Union[str, int], loggers : Union[List
75
74
  warning : 'WARNING' or logging.WARNING
76
75
  info : 'INFO' or logging.INFO
77
76
  debug : 'DEBUG' or logging.DEBUG
77
+ None will disable logging
78
78
  loggers : list of LoggerAlias or 'all'
79
79
  Which loggers to save to the file, 'all' by default
80
80
  """
@@ -93,7 +93,7 @@ def set_log_stream(enabled : bool, level : Union[str, int], loggers : Union[List
93
93
  if isinstance(h, logging.StreamHandler):
94
94
  logger.removeHandler(h)
95
95
 
96
- if enabled:
96
+ if level is not None:
97
97
  # 2) Create the new stream handler
98
98
  stream_handler = logging.StreamHandler()
99
99
  stream_handler.setFormatter(default_formatter)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: syndesi
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Syndesi
5
5
  Author: Sebastien Deriaz
6
6
  Author-email: sebastien.deriaz1@gmail.com
@@ -20,6 +20,9 @@ syndesi/adapters/timeout.py
20
20
  syndesi/adapters/visa.py
21
21
  syndesi/api/__init__.py
22
22
  syndesi/api/api.py
23
+ syndesi/cli/__init__.py
24
+ syndesi/cli/shell.py
25
+ syndesi/cli/syndesi.py
23
26
  syndesi/protocols/__init__.py
24
27
  syndesi/protocols/delimited.py
25
28
  syndesi/protocols/protocol.py
@@ -33,5 +36,4 @@ syndesi/tools/__init__.py
33
36
  syndesi/tools/exceptions.py
34
37
  syndesi/tools/log.py
35
38
  syndesi/tools/others.py
36
- syndesi/tools/shell.py
37
39
  syndesi/tools/types.py
@@ -1,3 +1,3 @@
1
1
  [console_scripts]
2
- syndesi = syndesi.shell.syndesi:main
2
+ syndesi = syndesi.cli.syndesi:main
3
3
  syndesi-proxy = syndesi.proxy.proxy:main
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes