syndesi 0.1.6__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ from .adapter import Adapter
2
2
  from .ip import IP
3
3
  from .serialport import SerialPort
4
4
  from .visa import VISA
5
+ from .proxy import Proxy
5
6
 
6
7
  from .timeout import Timeout
7
- from .stop_conditions import Termination, Length, StopCondition
8
+ from .stop_conditions import Termination, Length, StopCondition
@@ -29,10 +29,14 @@ from ..tools.log import LoggerAlias
29
29
  import logging
30
30
  from time import time
31
31
  from dataclasses import dataclass
32
- from ..tools.others import default_argument, is_default_argument
32
+ from ..tools.others import DEFAULT
33
33
 
34
- DEFAULT_TIMEOUT = default_argument(Timeout(response=1, continuation=100e-3, total=None))
35
- DEFAULT_STOP_CONDITION = default_argument(StopCondition())
34
+ DEFAULT_TIMEOUT = Timeout(response=1, continuation=100e-3, total=None)
35
+ DEFAULT_STOP_CONDITION = None
36
+
37
+
38
+ class AdapterDisconnected(Exception):
39
+ pass
36
40
 
37
41
  STOP_DESIGNATORS = {
38
42
  'timeout' : {
@@ -68,10 +72,7 @@ class Adapter(ABC):
68
72
  DISCONNECTED = 0
69
73
  CONNECTED = 1
70
74
 
71
- def __init__(self,
72
- alias : str = '',
73
- timeout : Union[float, Timeout] = DEFAULT_TIMEOUT,
74
- stop_condition : Union[StopCondition, None] = DEFAULT_STOP_CONDITION):
75
+ def __init__(self, alias : str = '', stop_condition : Union[StopCondition, None] = DEFAULT, timeout : Union[float, Timeout] = DEFAULT) -> None:
75
76
  """
76
77
  Adapter instance
77
78
 
@@ -84,24 +85,33 @@ class Adapter(ABC):
84
85
  stop_condition : StopCondition or None
85
86
  Default to None
86
87
  """
88
+ super().__init__()
89
+ self._alias = alias
87
90
 
88
- if is_number(timeout):
89
- self._timeout = Timeout(response=timeout, continuation=100e-3)
90
- elif isinstance(timeout, Timeout):
91
- self._timeout = timeout
91
+ self._default_stop_condition = stop_condition == DEFAULT
92
+ if self._default_stop_condition:
93
+ self._stop_condition = DEFAULT_STOP_CONDITION
92
94
  else:
93
- raise ValueError(f"Invalid timeout type : {type(timeout)}")
94
-
95
- self._stop_condition = stop_condition
95
+ self._stop_condition = stop_condition
96
96
  self._read_queue = TimedQueue()
97
97
  self._thread : Union[Thread, None] = None
98
98
  self._status = self.Status.DISCONNECTED
99
+ self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
100
+
99
101
  # Buffer for data that has been pulled from the queue but
100
102
  # not used because of termination or length stop condition
101
103
  self._previous_read_buffer = b''
102
104
 
103
- self._alias = alias
104
- self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
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)}")
105
115
 
106
116
  def set_default_timeout(self, default_timeout : Union[Timeout, tuple, float]):
107
117
  """
@@ -111,7 +121,10 @@ class Adapter(ABC):
111
121
  ----------
112
122
  default_timeout : Timeout or tuple or float
113
123
  """
114
- self._timeout = timeout_fuse(self._timeout, default_timeout)
124
+ if self._default_timeout:
125
+ self._timeout = default_timeout
126
+ else:
127
+ self._timeout = timeout_fuse(self._timeout, default_timeout)
115
128
 
116
129
  def set_default_stop_condition(self, stop_condition):
117
130
  """
@@ -121,9 +134,8 @@ class Adapter(ABC):
121
134
  ----------
122
135
  stop_condition : StopCondition
123
136
  """
124
- if is_default_argument(self._stop_condition):
137
+ if self._default_stop_condition:
125
138
  self._stop_condition = stop_condition
126
-
127
139
 
128
140
  def flushRead(self):
129
141
  """
@@ -145,7 +157,7 @@ class Adapter(ABC):
145
157
  Stop communication with the device
146
158
  """
147
159
  pass
148
-
160
+
149
161
  @abstractmethod
150
162
  def write(self, data : Union[bytes, str]):
151
163
  """
@@ -156,14 +168,11 @@ class Adapter(ABC):
156
168
  data : bytes or str
157
169
  """
158
170
  pass
159
-
160
- @abstractmethod
161
- def _start_thread(self):
162
- """
163
- Initiate the read thread
164
- """
165
- pass
166
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
+
175
+
167
176
  def read(self, timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
168
177
  """
169
178
  Read data from the device
@@ -222,6 +231,9 @@ class Adapter(ABC):
222
231
  (timestamp, fragment) = self._read_queue.get(timeout_ms)
223
232
  n_fragments += 1
224
233
 
234
+ if fragment == b'':
235
+ raise AdapterDisconnected()
236
+
225
237
  # 1) Evaluate the timeout
226
238
  stop, timeout_ms = timeout.evaluate(timestamp)
227
239
  if stop:
@@ -290,6 +302,13 @@ class Adapter(ABC):
290
302
  else:
291
303
  return output
292
304
 
305
+ @abstractmethod
306
+ def _start_thread(self):
307
+ pass
308
+
309
+ def __del__(self):
310
+ self.close()
311
+
293
312
  @abstractmethod
294
313
  def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
295
314
  """
@@ -299,6 +318,4 @@ class Adapter(ABC):
299
318
  - read
300
319
  """
301
320
  pass
302
-
303
- def __del__(self):
304
- self.close()
321
+
syndesi/adapters/ip.py CHANGED
@@ -7,22 +7,24 @@ from threading import Thread
7
7
  from .timed_queue import TimedQueue
8
8
  from typing import Union
9
9
  from time import time
10
+ import argparse
11
+ from ..tools import shell
10
12
 
11
- DEFAULT_RESPONSE_TIMEOUT = 1
12
- DEFAULT_CONTINUATION_TIMEOUT = 1e-3
13
- DEFAULT_TOTAL_TIMEOUT = 5
14
-
15
- DEFAULT_BUFFER_SIZE = 1024
13
+ class IP(Adapter):
14
+ DEFAULT_RESPONSE_TIMEOUT = 1
15
+ DEFAULT_CONTINUATION_TIMEOUT = 1e-3
16
+ DEFAULT_TOTAL_TIMEOUT = 5
16
17
 
17
- DEFAULT_TIMEOUT = Timeout(
18
- response=DEFAULT_RESPONSE_TIMEOUT,
19
- continuation=DEFAULT_CONTINUATION_TIMEOUT,
20
- total=DEFAULT_TOTAL_TIMEOUT)
21
18
 
22
- class IP(Adapter):
19
+ DEFAULT_TIMEOUT = Timeout(
20
+ response=DEFAULT_RESPONSE_TIMEOUT,
21
+ continuation=DEFAULT_CONTINUATION_TIMEOUT,
22
+ total=DEFAULT_TOTAL_TIMEOUT)
23
+ DEFAULT_BUFFER_SIZE = 1024
23
24
  class Protocol(Enum):
24
25
  TCP = 'TCP'
25
26
  UDP = 'UDP'
27
+
26
28
  def __init__(self,
27
29
  address : str,
28
30
  port : int = None,
@@ -30,7 +32,8 @@ class IP(Adapter):
30
32
  timeout : Union[Timeout, float] = DEFAULT_TIMEOUT,
31
33
  stop_condition = None,
32
34
  alias : str = '',
33
- buffer_size : int = DEFAULT_BUFFER_SIZE):
35
+ buffer_size : int = DEFAULT_BUFFER_SIZE,
36
+ _socket : socket.socket = None):
34
37
  """
35
38
  IP stack adapter
36
39
 
@@ -50,17 +53,24 @@ class IP(Adapter):
50
53
  Specify an alias for this adapter, '' by default
51
54
  buffer_size : int
52
55
  Socket buffer size, may be removed in the future
56
+ socket : socket.socket
57
+ Specify a custom socket, this is reserved for server application
53
58
  """
54
59
  super().__init__(alias=alias, timeout=timeout, stop_condition=stop_condition)
55
60
  self._transport = self.Protocol(transport)
56
- if self._transport == self.Protocol.TCP:
57
- self._logger.info("Setting up TCP IP adapter")
61
+ self._is_server = _socket is not None
62
+
63
+ self._logger.info(f"Setting up {self._transport.value} IP adapter ({'server' if self._is_server else 'client'})")
64
+
65
+ if self._is_server:
66
+ # Server
67
+ self._socket = _socket
68
+ self._status = self.Status.CONNECTED
69
+ elif self._transport == self.Protocol.TCP:
58
70
  self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
59
71
  elif self._transport == self.Protocol.UDP:
60
- self._logger.info("Setting up UDP IP adapter")
61
72
  self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
62
- else:
63
- raise ValueError("Invalid protocol")
73
+
64
74
  self._address = address
65
75
  self._port = port
66
76
  self._buffer_size = buffer_size
@@ -80,45 +90,61 @@ class IP(Adapter):
80
90
  self._port = port
81
91
 
82
92
  def open(self):
93
+ if self._is_server:
94
+ raise SystemError("Cannot open server socket. It must be passed already opened")
83
95
  if self._port is None:
84
96
  raise ValueError(f"Cannot open adapter without specifying a port")
97
+
98
+ self._logger.debug(f"Adapter {self._alias} connect to ({self._address}, {self._port})")
85
99
  self._socket.connect((self._address, self._port))
86
100
  self._status = self.Status.CONNECTED
87
- self._logger.info("Adapter opened !")
101
+ self._logger.info(f"Adapter {self._alias} opened !")
88
102
 
89
103
  def close(self):
90
104
  if hasattr(self, '_socket'):
91
105
  self._socket.close()
92
106
  self._logger.info("Adapter closed !")
107
+ self._status = self.Status.DISCONNECTED
93
108
 
94
109
  def write(self, data : Union[bytes, str]):
95
110
  data = to_bytes(data)
96
111
  if self._status == self.Status.DISCONNECTED:
97
- self._logger.info("Adapter is closed, opening...")
112
+ self._logger.info(f"Adapter {self._alias} is closed, opening...")
98
113
  self.open()
99
114
  write_start = time()
100
115
  self._socket.send(data)
101
116
  write_duration = time() - write_start
102
117
  self._logger.debug(f"Written [{write_duration*1e3:.3f}ms]: {repr(data)}")
103
118
 
119
+ 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
+
104
129
  def _read_thread(self, socket : socket.socket, read_queue : TimedQueue):
105
- while True:
130
+ while True: # TODO : Add stop_pipe ? Maybe it was removed ?
106
131
  try:
107
132
  payload = socket.recv(self._buffer_size)
108
133
  if len(payload) == self._buffer_size and self._transport == self.Protocol.UDP:
109
134
  self._logger.warning("Warning, inbound UDP data may have been lost (max buffer size attained)")
110
135
  except OSError:
111
136
  break
112
- if not payload:
137
+ # If payload is empty, it means the socket has been disconnected
138
+ if payload == b'':
139
+ read_queue.put(payload)
113
140
  break
114
141
  read_queue.put(payload)
115
142
 
116
- def _start_thread(self):
117
- self._logger.debug("Starting read thread...")
118
- self._thread = Thread(target=self._read_thread, daemon=True, args=(self._socket, self._read_queue))
119
- self._thread.start()
120
-
121
143
  def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
144
+ if self._is_server:
145
+ raise SystemError("Cannot query on server adapters")
122
146
  self.flushRead()
123
147
  self.write(data)
124
- return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
148
+ return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
149
+
150
+
@@ -0,0 +1,92 @@
1
+ # proxy.py
2
+ # Sébastien Deriaz
3
+ # 09.04.2024
4
+ #
5
+ # The proxy adapter allows for commands to be issued on a different device
6
+ # The goal is to istanciate a class as such :
7
+ #
8
+ # Only adapter :
9
+ # # The proxy computer is accessed with 192.168.1.1
10
+ # # The device (connected to the remote computer) is accessed with 192.168.2.1
11
+ # my_adapter = Proxy('192.168.1.1', IP('192.168.2.1'))
12
+ #
13
+ # Protocol :
14
+ # my_protocol = SCPI(Proxy('192.168.1.1', Serial('/dev/ttyUSB0')))
15
+ #
16
+ #
17
+ # Driver :
18
+ # my_device = Driver(Proxy('192.168.1.1', VISA('...')))
19
+
20
+ from enum import Enum
21
+ from typing import Union
22
+
23
+ from .adapter import Adapter
24
+ from . import IP, SerialPort, VISA
25
+ from ..proxy.proxy_api import *
26
+ from ..api.api import parse
27
+
28
+ DEFAULT_PORT = 2608
29
+
30
+ class Proxy(Adapter):
31
+ def __init__(self, proxy_adapter : Adapter, remote_adapter : Adapter):
32
+ """
33
+ Proxy adapter
34
+
35
+ Parameters
36
+ ----------
37
+ proxy_adapter : Adapter
38
+ Adapter to connect to the proxy server
39
+ remote_adapter : Adapter
40
+ Adapter to instanciate onto the proxy server
41
+ """
42
+ super().__init__()
43
+
44
+ self._proxy = proxy_adapter
45
+ self._remote = remote_adapter
46
+
47
+ if isinstance(proxy_adapter, IP):
48
+ proxy_adapter.set_default_port(DEFAULT_PORT)
49
+
50
+ if isinstance(self._remote, IP):
51
+ 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())
57
+ elif isinstance(self._remote, SerialPort):
58
+ self._proxy.query()
59
+
60
+ def check(self, status : ReturnStatus):
61
+ if not status.success:
62
+ # There is an error
63
+ raise ProxyException(status.error_message)
64
+
65
+ def open(self):
66
+ if isinstance(self._remote, IP):
67
+ self._proxy.query(AdapterOpen().encode())
68
+
69
+ def close(self):
70
+ self._proxy.query(AdapterClose().encode())
71
+
72
+ def write(self, data : Union[bytes, str]):
73
+ self._proxy.query(AdapterWrite(data).encode())
74
+
75
+ def read(self, timeout=None, stop_condition=None, return_metrics : bool = False):
76
+ output : AdapterReadReturn
77
+ output = parse(self._proxy.query(AdapterRead().encode()))
78
+ if isinstance(output, AdapterReadReturn):
79
+ return output.data
80
+ elif isinstance(output, ReturnStatus):
81
+ raise ProxyException(output.error_message)
82
+ else:
83
+ raise RuntimeError(f"Invalid return : {type(output)}")
84
+
85
+
86
+ def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
87
+ self.check(parse(self._proxy.query(AdapterFlushRead().encode())))
88
+ self.check(parse(self._proxy.query(AdapterWrite(data).encode())))
89
+ return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
90
+
91
+ def _start_thread(self):
92
+ pass
@@ -1,12 +1,18 @@
1
- from .adapter import Adapter
1
+ import os
2
2
  import serial
3
+ from threading import Thread
4
+ from typing import Union
5
+ import select
6
+ import argparse
7
+ #from collections.abc import Sequence
8
+
9
+ from .adapter import Adapter
3
10
  from ..tools.types import to_bytes
4
11
  from .stop_conditions import *
5
12
  from .timeout import Timeout
6
13
  from .timed_queue import TimedQueue
7
- from threading import Thread
8
- from typing import Union
9
- import select
14
+ from ..tools import shell
15
+ from ..tools.others import DEFAULT
10
16
 
11
17
  # From pyserial - serialposix.py
12
18
  import fcntl
@@ -17,16 +23,15 @@ if hasattr(termios, 'TIOCINQ'):
17
23
  else:
18
24
  TIOCINQ = getattr(termios, 'FIONREAD', 0x541B)
19
25
  TIOCM_zero_str = struct.pack('I', 0)
20
- import os
21
26
 
22
- DEFAULT_TIMEOUT = Timeout(response=0.5, continuation=10e-3, total=None)
27
+ DEFAULT_TIMEOUT = Timeout(response=1, continuation=200e-3, total=None)
23
28
 
24
29
  class SerialPort(Adapter):
25
30
  def __init__(self,
26
31
  port : str,
27
32
  baudrate : int,
28
- timeout : Union[Timeout, float] = DEFAULT_TIMEOUT,
29
- stop_condition : StopCondition = None,
33
+ timeout : Union[Timeout, float] = DEFAULT,
34
+ stop_condition : StopCondition = DEFAULT,
30
35
  rts_cts : bool = False): # rts_cts experimental
31
36
  """
32
37
  Serial communication adapter
@@ -36,8 +41,11 @@ class SerialPort(Adapter):
36
41
  port : str
37
42
  Serial port (COMx or ttyACMx)
38
43
  """
44
+ if timeout == DEFAULT:
45
+ timeout = DEFAULT_TIMEOUT
46
+
39
47
  super().__init__(timeout=timeout, stop_condition=stop_condition)
40
- self._logger.info("Setting up SerialPort adapter")
48
+ self._logger.info(f"Setting up SerialPort adapter timeout:{timeout}, stop_condition:{stop_condition}")
41
49
  self._port = serial.Serial(port=port, baudrate=baudrate)
42
50
  if self._port.isOpen():
43
51
  self._status = self.Status.CONNECTED
@@ -60,10 +68,11 @@ class SerialPort(Adapter):
60
68
  self._logger.info("Adapter opened !")
61
69
 
62
70
  def close(self):
63
- if self._thread.is_alive():
71
+ if self._thread is not None and self._thread.is_alive():
64
72
  os.write(self._stop_event_pipe_write, b'1')
65
73
  self._thread.join()
66
- self._port.close()
74
+ if hasattr(self, '_port'):
75
+ self._port.close()
67
76
  self._logger.info("Adapter closed !")
68
77
 
69
78
  def write(self, data : bytes):
@@ -78,6 +87,9 @@ class SerialPort(Adapter):
78
87
  self._logger.debug(f"Written [{write_duration*1e3:.3f}ms]: {repr(data)}")
79
88
 
80
89
  def _start_thread(self):
90
+ """
91
+ Start the read thread
92
+ """
81
93
  self._logger.debug("Starting read thread...")
82
94
  if self._thread is None or not self._thread.is_alive():
83
95
  self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue, self._stop_event_pipe))
@@ -87,7 +99,8 @@ class SerialPort(Adapter):
87
99
  while True:
88
100
  # It looks like using the raw implementation of port.in_waiting and port.read is better, there's no more warnings
89
101
  # Equivalent of port.in_waiting :
90
- in_waiting = struct.unpack('I', fcntl.ioctl(port.fd, TIOCINQ, TIOCM_zero_str))[0]
102
+ #in_waiting = struct.unpack('I', fcntl.ioctl(port.fd, TIOCINQ, TIOCM_zero_str))[0]
103
+ in_waiting = self._port.in_waiting # This is a temporary fix to get windows compatiblity back, an error might pop up
91
104
  if in_waiting == 0:
92
105
  ready, _, _ = select.select([port.fd, stop_event_pipe], [], [], None)
93
106
  if stop_event_pipe in ready:
@@ -125,4 +138,21 @@ class SerialPort(Adapter):
125
138
  def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
126
139
  self.flushRead()
127
140
  self.write(data)
128
- return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
141
+ return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
142
+
143
+ def shell_parse(inp: str):
144
+ parser = argparse.ArgumentParser(
145
+ prog='',
146
+ description='Serial port shell parser',
147
+ epilog='')
148
+ # Parse subcommand
149
+ parser.add_argument('--' + shell.Arguments.PORT.value, type=str)
150
+ parser.add_argument('--' + shell.Arguments.BAUDRATE.value, type=int)
151
+ parser.add_argument('--' + shell.Arguments.ENABLE_RTS_CTS.value, action='store_true')
152
+ args = parser.parse_args(inp.split())
153
+
154
+ return {
155
+ 'port' : getattr(args, shell.Arguments.PORT.value),
156
+ 'baudrate' : getattr(args, shell.Arguments.BAUDRATE.value),
157
+ 'rts_cts' : bool(getattr(args, shell.Arguments.ENABLE_RTS_CTS.value))
158
+ }
@@ -60,7 +60,13 @@ class Termination(StopCondition):
60
60
  sequence : bytes
61
61
  """
62
62
  super().__init__()
63
- self._termination = sequence
63
+ if isinstance(sequence, str):
64
+ self._termination = sequence.encode('utf-8')
65
+ elif isinstance(sequence, bytes):
66
+ self._termination = sequence
67
+ else:
68
+ raise ValueError(f"Invalid termination sequence type : {type(sequence)}")
69
+
64
70
  #self._fragment_store = b''
65
71
  #self._sequence_index = 0
66
72
 
@@ -128,6 +134,12 @@ class Termination(StopCondition):
128
134
  self._fragment_store = b''
129
135
  return output
130
136
 
137
+ def __repr__(self) -> str:
138
+ return self.__str__()
139
+
140
+ def __str__(self) -> str:
141
+ return f'Termination({repr(self._termination)})'
142
+
131
143
  class Length(StopCondition):
132
144
  def __init__(self, N : int) -> None:
133
145
  """
@@ -152,4 +164,10 @@ class Length(StopCondition):
152
164
  deferred_fragment = data[remaining_bytes:]
153
165
  self._counter += len(kept_fragment)
154
166
  remaining_bytes = self._N - self._counter
155
- return remaining_bytes == 0, kept_fragment, deferred_fragment
167
+ return remaining_bytes == 0, kept_fragment, deferred_fragment
168
+
169
+ def __repr__(self) -> str:
170
+ return self.__str__()
171
+
172
+ def __str__(self) -> str:
173
+ return f'Length({self._N})'
@@ -5,7 +5,7 @@
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
8
+ #from ..tools.others import is_default_argument
9
9
 
10
10
 
11
11
  class Timeout():
@@ -235,6 +235,15 @@ class Timeout():
235
235
  """
236
236
  return self._data_strategy, self._last_data_strategy_origin
237
237
 
238
+ 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 ''
242
+ return f'Timeout({response}{continuation}{total})'
243
+
244
+ def __repr__(self) -> str:
245
+ return self.__str__()
246
+
238
247
 
239
248
  class TimeoutException(Exception):
240
249
  def __init__(self, type : Timeout.TimeoutType) -> None:
@@ -264,10 +273,11 @@ def timeout_fuse(high_priority, low_priority):
264
273
  low = low_priority if isinstance(low_priority, Timeout) else Timeout(low_priority)
265
274
 
266
275
  # 3) If one is the default, take the other
267
- if is_default_argument(high):
268
- return low
269
- if is_default_argument(low):
270
- return high
276
+ # if is_default_argument(high):
277
+ # return low
278
+ # if is_default_argument(low):
279
+ # return high
280
+ # 05.06.2024 : Removed because is_default_argument is obsolete, use DEFAULT is necessary
271
281
 
272
282
  new_attr = {}
273
283
  # 4) Select with parameter to keep based on where it has been set
File without changes
syndesi/api/api.py ADDED
@@ -0,0 +1,77 @@
1
+ import quopri
2
+ from typing import Tuple, Union, Dict
3
+ from enum import Enum
4
+ from dataclasses import dataclass, fields, Field
5
+ import json
6
+
7
+ APIS = {}
8
+
9
+ def register_api(apis : dict):
10
+ """
11
+ Register apis
12
+
13
+ Parameters
14
+ ----------
15
+ apis : dict
16
+ {'action' : APICall} class dictionary
17
+ """
18
+ APIS.update(apis)
19
+
20
+ ACTION_ATTRIBUTE = 'action'
21
+
22
+ class APIItem:
23
+ pass
24
+
25
+ class APICall:
26
+ action = ''
27
+ _keyword = ''
28
+
29
+ def encode(self) -> bytes:
30
+ cls_fields: Tuple[Field, ...] = fields(self)
31
+ data = {}
32
+
33
+ # Add action field
34
+ data[ACTION_ATTRIBUTE] = self.action
35
+ # Add other fields
36
+ for field in cls_fields:
37
+ field_data = getattr(self, field.name)
38
+ if isinstance(field_data, Enum):
39
+ entry = field_data.value
40
+ elif isinstance(field_data, bytes):
41
+ entry = quopri.encodestring(field_data).decode('ASCII')
42
+ elif isinstance(field_data, str):
43
+ entry = quopri.encodestring(field_data.encode('utf-8')).decode('ASCII')
44
+ else:
45
+ entry = field_data
46
+
47
+ data[field.name] = entry
48
+ return json.dumps(data)
49
+
50
+ def parse(data : Union[str, bytes]) -> APICall:
51
+ json_data = json.loads(data)
52
+ action = json_data[ACTION_ATTRIBUTE]
53
+ json_data.pop(ACTION_ATTRIBUTE)
54
+ arguments = json_data
55
+ # Find the right API call and return it
56
+ if action not in APIS:
57
+ raise RuntimeError(f"API action '{action}' not registered")
58
+
59
+ converted_arguments = {}
60
+ # Convert each argument according to the class field types
61
+ api_fields: Tuple[Field, ...] = fields(APIS[action])
62
+
63
+ for field in api_fields:
64
+ if field.name not in arguments:
65
+ raise RuntimeError(f"Field '{field.name}' missing from arguments")
66
+
67
+ if field.type == bytes:
68
+ # Convert back
69
+ converted_arguments[field.name] = quopri.decodestring(arguments[field.name])
70
+ elif field.type == str:
71
+ converted_arguments[field.name] = quopri.decodestring(arguments[field.name]).decode('utf-8')
72
+ elif field.type == Enum:
73
+ converted_arguments[field.name] = field.type(arguments[field.name])
74
+ else:
75
+ converted_arguments[field.name] = arguments[field.name]
76
+
77
+ return APIS[action](**converted_arguments)
@@ -1,12 +1,12 @@
1
1
  from .protocol import Protocol
2
- from ..adapters import Adapter
2
+ from ..adapters import Adapter, Timeout, Termination
3
3
  from ..tools.types import assert_byte_instance, assert_byte_instance
4
4
  from time import time
5
5
  import warnings
6
6
 
7
7
 
8
8
  class Delimited(Protocol):
9
- def __init__(self, adapter : Adapter, termination='\n', format_response=True) -> None:
9
+ def __init__(self, adapter : Adapter, termination='\n', format_response=True, encoding : str = 'utf-8', timeout : Timeout = None) -> None:
10
10
  """
11
11
  Protocol with delimiter, like LF, CR, etc... '\\n' is used by default
12
12
 
@@ -15,18 +15,21 @@ class Delimited(Protocol):
15
15
  Parameters
16
16
  ----------
17
17
  adapter : IAdapter
18
- end : bytes
18
+ termination : bytes
19
19
  Command termination, '\\n' by default
20
20
  format_response : bool
21
- Apply formatting to the response (i.e removing the termination)
21
+ Apply formatting to the response (i.e removing the termination), True by default
22
+ encoding : str
23
+ timeout : Timeout
24
+ None by default (default timeout)
22
25
  """
23
- super().__init__(adapter)
24
-
25
- # Temporary solution before implementing stop conditions
26
- self._buffer = ''
26
+ adapter.set_default_stop_condition(stop_condition=Termination(sequence=termination))
27
+ super().__init__(adapter, timeout=timeout)
27
28
 
28
29
  if not isinstance(termination, str):
29
30
  raise ValueError(f"end argument must be of type str, not {type(termination)}")
31
+
32
+ self._encoding = encoding
30
33
  self._termination = termination
31
34
  self._response_formatting = format_response
32
35
 
@@ -62,40 +65,24 @@ class Delimited(Protocol):
62
65
  self.write(command)
63
66
  return self.read()
64
67
 
65
- # Note : for later revisions of the delimited module, the buffer should be removed as the
66
- # adapter will take care of that using the stop conditions
67
- #
68
- # For now the delimited module will take care of it
69
- #
70
- # Stop conditions should also be added inside the delimited module (unclear yet how)
71
-
72
- def read(self, timeout=2) -> str:
68
+ def read(self, timeout : Timeout = None, decode : str = True) -> str:
73
69
  """
74
70
  Reads command and formats it as a str
71
+
72
+ Parameters
73
+ ----------
74
+ timeout : Timeout
75
+ decode : bool
76
+ Decode incoming data, True by default
75
77
  """
76
- if self._termination not in self._buffer:
77
- # Read the adapter only if there isn't a fragment already in the buffer
78
- start = time()
79
- while True:
80
- # Continuously read the adapter as long as no termination is caught
81
- data = self._from_bytes(self._adapter.read())
82
- self._buffer += data
83
- if self._termination in data or time() > start + timeout:
84
- break
85
78
 
86
79
  # Send up to the termination
87
- fragment, self._buffer = self._buffer.split(self._termination, maxsplit=1)
80
+ data = self._adapter.read(timeout=timeout)
81
+ if decode:
82
+ data = data.decode(self._encoding)
88
83
  if self._response_formatting:
89
84
  # Only send the fragment (no termination)
90
- return fragment
85
+ return data
91
86
  else:
92
87
  # Add the termination back in
93
- return fragment + self._termination
94
-
95
- def read_raw(self) -> bytes:
96
- """
97
- Returns the raw bytes instead of str
98
- """
99
- if len(self._buffer) > 0:
100
- warnings.warn("Warning : The buffer wasn't empty, standard (non raw) data is still in it")
101
- return self._adapter.read()
88
+ return data + self._termination
syndesi/protocols/scpi.py CHANGED
@@ -1,7 +1,8 @@
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
4
+ #from ..tools.others import is_default_argument
5
+ from ..tools.others import DEFAULT
5
6
 
6
7
  class SCPI(Protocol):
7
8
  DEFAULT_PORT = 5025
@@ -32,7 +33,7 @@ class SCPI(Protocol):
32
33
  self._adapter.set_default_port(self.DEFAULT_PORT)
33
34
 
34
35
  self._adapter.set_default_timeout(timeout)
35
- if is_default_argument(self._adapter._stop_condition):
36
+ if self._adapter._stop_condition != DEFAULT:
36
37
  raise ValueError('A conflicting stop-condition has been set for this adapter')
37
38
  self._adapter._stop_condition = Termination(self._receive_termination.encode(encoding=encoding))
38
39
 
File without changes
syndesi/proxy/proxy.py ADDED
@@ -0,0 +1,136 @@
1
+ # proxy.py
2
+ # Sébastien Deriaz
3
+ # 28.05.2024
4
+ import argparse
5
+ from enum import Enum
6
+ from ..adapters import SerialPort
7
+ from ..adapters.adapter import AdapterDisconnected
8
+ from ..adapters.proxy import DEFAULT_PORT
9
+ from ..adapters.ip_server import IPServer
10
+ from typing import Union
11
+ from .proxy_api import *
12
+ from ..api.api import *
13
+ import logging
14
+ from ..tools.log import LoggerAlias, set_log_stream
15
+
16
+ class AdapterType(Enum):
17
+ SERIAL = 'serial'
18
+ IP = 'ip'
19
+
20
+ DEFAULT_BAUDRATE = 115200
21
+
22
+ def main():
23
+ parser = argparse.ArgumentParser(
24
+ prog='syndesi-proxy',
25
+ description='Syndesi proxy server',
26
+ epilog='')
27
+ # Parse subcommand
28
+ parser.add_argument('-t', '--adapter_type', choices=[x.value for x in AdapterType], default=AdapterType.IP)
29
+ parser.add_argument('-p', '--port', type=int, default=DEFAULT_PORT, help='IP port')
30
+ parser.add_argument('-a', '--address', default=None, type=str, help='IP address or serial port')
31
+ parser.add_argument('-b', '--baudrate', type=int, default=DEFAULT_BAUDRATE, help='Serial baudrate')
32
+ parser.add_argument('-v', '--verbose', action='store_true')
33
+
34
+ args = parser.parse_args()
35
+
36
+ if args.verbose:
37
+ set_log_stream(True, 'DEBUG')
38
+
39
+ proxy_server = ProxyServer(adapter_type=args.adapter_type, port=args.port, address=args.address, baudrate=args.baudrate)
40
+
41
+ proxy_server.start()
42
+
43
+ class ProxyServer:
44
+ def __init__(self, adapter_type : AdapterType, port : Union[str, int], address : str, baudrate : int) -> None:
45
+ self._adapter_type = AdapterType(adapter_type)
46
+ self._adapter = None
47
+ self._port = port
48
+ self._address = address
49
+ self._baudrate = baudrate
50
+ self._logger = logging.getLogger(LoggerAlias.PROXY_SERVER.value)
51
+ self._logger.info('Initializing proxy server')
52
+
53
+ def start(self):
54
+ self._logger.info(f"Starting proxy server with {self._adapter_type.value} adapter")
55
+
56
+ if self._adapter_type == AdapterType.SERIAL:
57
+ # If adapter type is serial, create the adapter directly
58
+ self._master_adapter = SerialPort(self._address, baudrate=self._baudrate)
59
+ elif self._adapter_type == AdapterType.IP:
60
+ # Otherwise, create a server to get IP clients
61
+ server = IPServer(port=self._port, transport='TCP', address=self._address, max_clients=1, stop_condition=None)
62
+ server.open()
63
+
64
+
65
+ # If the adapter type is IP, use the external while loop to get clients
66
+ while True:
67
+ self._master_adapter = server.get_client()
68
+ self._logger.info(f'Client connected : {self._master_adapter._address}:{self._master_adapter._port}')
69
+
70
+ while True:
71
+ try:
72
+ call_raw = self._master_adapter.read()
73
+ except AdapterDisconnected:
74
+ self._logger.info('Client disconnected')
75
+ break
76
+
77
+ api_call = parse(call_raw)
78
+
79
+ self._logger.debug(f'Received {type(api_call)}')
80
+
81
+ output = self.manage_call(api_call)
82
+
83
+ self._master_adapter.write(output.encode())
84
+
85
+ if self._adapter_type == AdapterType.IP:
86
+ if not self._master_adapter.read_thread_alive():
87
+ break
88
+
89
+ # Loop only if we need to get a new client
90
+ if self._adapter_type != AdapterType.IP:
91
+ break
92
+
93
+ def manage_call(self, c : APICall) -> APICall:
94
+ output = None
95
+ # IP Specific
96
+ if isinstance(c, IPInstanciate):
97
+ self._adapter = IP(
98
+ address=c.address,
99
+ port=c.port)
100
+ output = ReturnStatus(True)
101
+ # Serial specific
102
+ if isinstance(c, SerialPortInstanciate):
103
+ self._adapter = SerialPort(
104
+ port=c.port,
105
+ baudrate=c.baudrate
106
+ )
107
+ # Adapter
108
+ elif isinstance(c, AdapterOpen):
109
+ if self._adapter is None:
110
+ output = ReturnStatus(False, 'Cannot open uninstanciated adapter')
111
+ self._adapter.open()
112
+ output = ReturnStatus(True)
113
+ elif isinstance(c, AdapterClose):
114
+ if self._adapter is None:
115
+ output = ReturnStatus(False, 'Cannot close uninstanciated adapter')
116
+ else:
117
+ self._adapter.close()
118
+ output = ReturnStatus(True)
119
+ elif isinstance(c, AdapterWrite):
120
+ if self._adapter is None:
121
+ output = ReturnStatus(False, 'Cannot write to uninstanciated adapter')
122
+ else:
123
+ self._adapter.write(c.data)
124
+ output = ReturnStatus(True)
125
+ elif isinstance(c, AdapterFlushRead):
126
+ self._adapter.flushRead()
127
+ output = ReturnStatus(True)
128
+ elif isinstance(c, AdapterRead):
129
+ # TODO : Implement return_metrics
130
+ data = self._adapter.read()
131
+ output = AdapterReadReturn(data=data)
132
+
133
+ return output
134
+
135
+ if __name__ == '__main__':
136
+ main()
@@ -0,0 +1,93 @@
1
+ # proxy_api.py
2
+ # Sébastien Deriaz
3
+ # 29.05.2024
4
+
5
+ import sys
6
+ from dataclasses import dataclass
7
+
8
+ from ..api.api import APICall, ACTION_ATTRIBUTE, register_api, APIItem
9
+
10
+ class ProxyException(Exception):
11
+ pass
12
+
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
+ @dataclass
23
+ class TimeoutAPI(APIItem):
24
+ name = 'timeout'
25
+ response : float
26
+ continuation : float
27
+ total : float
28
+ on_response : str
29
+ on_continuation : str
30
+ on_total : str
31
+
32
+ @dataclass
33
+ class StopConditionAPI(APIItem):
34
+ pass
35
+
36
+ @dataclass
37
+ class TerminationAPI(StopConditionAPI):
38
+ name = 'termination'
39
+ sequence : bytes
40
+
41
+ @dataclass
42
+ class LengthAPI(StopConditionAPI):
43
+ name = 'length'
44
+ length : int
45
+
46
+ # Serial specific
47
+ @dataclass
48
+ class SerialPortInstanciate(APICall):
49
+ action = 'serial_adapter_inst'
50
+ port : str
51
+ baudrate : int
52
+ timeout : TimeoutAPI
53
+ stop_condition : StopConditionAPI
54
+ rts_cts : bool
55
+
56
+ # Adapters common
57
+ @dataclass
58
+ class AdapterOpen(APICall):
59
+ action = 'adapter_open'
60
+
61
+ @dataclass
62
+ class AdapterClose(APICall):
63
+ action = 'adapter_close'
64
+
65
+ @dataclass
66
+ class AdapterWrite(APICall):
67
+ action = 'adapter_write'
68
+ data : bytes
69
+
70
+ @dataclass
71
+ class AdapterFlushRead(APICall):
72
+ action = 'adapter_flush_read'
73
+
74
+ @dataclass
75
+ class AdapterRead(APICall):
76
+ action = 'adapter_read'
77
+
78
+ @dataclass
79
+ class AdapterReadReturn(APICall):
80
+ action = 'adapter_read_return'
81
+ data : bytes
82
+ return_metrics : dict = None
83
+
84
+ @dataclass
85
+ class ReturnStatus(APICall):
86
+ action = 'return_status'
87
+ success : bool
88
+ error_message : str = ''
89
+
90
+ # Register apis
91
+ current_module = sys.modules[__name__]
92
+ API_CALLS_PER_ACTION = {getattr(obj, ACTION_ATTRIBUTE) : obj for obj in current_module.__dict__.values() if hasattr(obj, ACTION_ATTRIBUTE)}
93
+ register_api(API_CALLS_PER_ACTION)
syndesi/tools/log.py CHANGED
@@ -11,6 +11,7 @@ from typing import List, Union
11
11
  class LoggerAlias(Enum):
12
12
  ADAPTER = 'adapter'
13
13
  PROTOCOL = 'protocol'
14
+ PROXY_SERVER = 'proxy_server'
14
15
 
15
16
  default_formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s')
16
17
 
syndesi/tools/others.py CHANGED
@@ -1,10 +1 @@
1
- DEFAULT_ATTRIBUTE_FLAG_NAME = '_is_default'
2
-
3
-
4
- def default_argument(instance):
5
- setattr(instance, DEFAULT_ATTRIBUTE_FLAG_NAME, True)
6
- return instance
7
-
8
- def is_default_argument(instance):
9
- return hasattr(instance, DEFAULT_ATTRIBUTE_FLAG_NAME)
10
-
1
+ DEFAULT = 'default'
syndesi/tools/types.py CHANGED
@@ -72,7 +72,7 @@ def to_bytes(data):
72
72
  elif isinstance(data, str):
73
73
  return data.encode('utf-8')
74
74
  else:
75
- return ValueError(f"Invalid data type : {type(data)}")
75
+ raise ValueError(f"Invalid data type : {type(data)}")
76
76
 
77
77
 
78
78
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: syndesi
3
- Version: 0.1.6
3
+ Version: 0.2.0
4
4
  Summary: Syndesi
5
5
  Author: Sebastien Deriaz
6
6
  Author-email: sebastien.deriaz1@gmail.com
@@ -2,19 +2,22 @@ experiments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  syndesi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  syndesi/adapters/IP.py,sha256=4QFxFZeZLDHCpPqtpGQuqmT9pjLiKUNT6zjqkoCZBfE,2093
4
4
  syndesi/adapters/VISA.py,sha256=GW4ZaW2cQ-0AmAOTeFcxZZz9E2V8URozR4a0Wpqpvgs,1119
5
- syndesi/adapters/__init__.py,sha256=Nwkpd3x4bmUMNZK5aO761IxFeKcTamZvdk7lbrU0HHs,199
6
- syndesi/adapters/adapter.py,sha256=X1U8lNWH1TkFcPXpJqZqV5PvJYGLiv3Y4mgm2SVjcsA,10286
5
+ syndesi/adapters/__init__.py,sha256=Ya9cfzEubB35225S3HD-NI8UkfzS0P6uVZkgG_tTDTI,225
6
+ syndesi/adapters/adapter.py,sha256=Y3P_uGHZEEsJQw7gME3oEhzYaPhClPxq67j5FKXsvbU,10824
7
7
  syndesi/adapters/auto.py,sha256=ONniaTqYuotdLg88PdyUY87gzLglWzejEURqDw3Q4dE,1688
8
8
  syndesi/adapters/iadapter.py,sha256=45scoY1khaSdZdIPETb0IuColPuEEE6LRw9OtHGl5v0,6884
9
- syndesi/adapters/ip.py,sha256=XZHKhXWwv1ud4ljsY557l-SBV0McPT4aJ2K76v40ApU,4399
9
+ syndesi/adapters/ip.py,sha256=GWWxaRgmlcqdA2vb7OAYBAdjJdXimE0l3y3PE31AwjE,5422
10
10
  syndesi/adapters/ip_server.py,sha256=wFMF_uPvVnYHPqAkAOvIvRTrMh_WMtK53hUorzD9L5s,3802
11
+ syndesi/adapters/proxy.py,sha256=Vdfh7UZLf3tJ68MItVkQijOnOT5iQaqDbvmKH9P7nhU,3011
11
12
  syndesi/adapters/remote.py,sha256=XaaMGVSq7ygEijbETlsy-2cHSmMhZmmjA6UCm7hBTmE,552
12
13
  syndesi/adapters/serial.py,sha256=cDp4lFt1f6Z6pRTWRXn0mjT_p0mZgAxqiJgtoXnEHDk,877
13
- syndesi/adapters/serialport.py,sha256=1lvQvYPrffcB2F6Y43mVEjx0g64XAurbNUfjFhJbMrg,4804
14
- syndesi/adapters/stop_conditions.py,sha256=X9PDQXHw3XJ16iFoPLJkhntYlfEhTWyye1gC8b75teI,5070
14
+ syndesi/adapters/serialport.py,sha256=Uh64McFdvHqZJJfZXIZnqgU-cPvUhRnz91gLecEQJRg,6025
15
+ syndesi/adapters/stop_conditions.py,sha256=-dWUpgXbFEtONBnvk04gjFUhcprf_KJb28aFMgsps4A,5599
15
16
  syndesi/adapters/timed_queue.py,sha256=F-Bwj91cR_iLN2TdCkFL4WfvlJXovhyRF7K3dm30g50,912
16
- syndesi/adapters/timeout.py,sha256=dCygGczGMvS0NV5QSSg-eJHOp8fhQWwmnb85Lus_Ksg,11081
17
+ syndesi/adapters/timeout.py,sha256=fH5MIJIUEqgDXtF9EgxZgjgAQ0mdeoybcmFznutXINo,11656
17
18
  syndesi/adapters/visa.py,sha256=CHOGT6ZXhW3bROdFDYVEI9UXrXQbKgGnudxi14nFRaU,1304
19
+ syndesi/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ syndesi/api/api.py,sha256=OvoAkE__r9-VVdVgZs88707AC36d5R5J36DkQMdJgfs,2352
18
21
  syndesi/descriptors/IP.py,sha256=vr7UbD9B11QJLbpIMycqs2Aqq41IY_WWD_JjeZ5b1FY,124
19
22
  syndesi/descriptors/Serial.py,sha256=pO3MtQpWxV_gdSWsGLQ6nf-cANB1rLy8YSEDAK2Tb5I,127
20
23
  syndesi/descriptors/VISA.py,sha256=-OSp7ll2ZHme-Oj736h1wd96HHYGNy4EtGtajzNNvVE,706
@@ -32,26 +35,29 @@ syndesi/descriptors/syndesi/payload.py,sha256=SplKy_Fu4XXcgMY12ed162gS--otUKkPtA
32
35
  syndesi/descriptors/syndesi/sdid.py,sha256=0B_gHdRhjauW1aY-JF8B60gEY9Cz1TYPiKE3QnhePTM,346
33
36
  syndesi/protocols/__init__.py,sha256=0lLF9oH-K_nMp8OoztFfsidFmed2TBBffnN_zJT3f-4,145
34
37
  syndesi/protocols/commands.py,sha256=iZusNzht5zK31Uxdx3e2S4mXIdqcz5jBNuozajWuNQM,1898
35
- syndesi/protocols/delimited.py,sha256=LBX9FiLFzEyjoRMowwwDo135rww5iU9dWmaw7XliABA,3609
38
+ syndesi/protocols/delimited.py,sha256=-F36ZvyIrAqO4bpxY-ZAXFM_zXi-CJluZj5M2tXGq_A,2997
36
39
  syndesi/protocols/iprotocol.py,sha256=TwAiu1Fxq9-vpxGu8f7WuADgeBvTORSIIwJ1vABqhuo,247
37
40
  syndesi/protocols/protocol.py,sha256=yRgy_5Sj4LFA-CAAdFX0ZkC7m8GE5NuLv2aSGI5rwKQ,402
38
41
  syndesi/protocols/raw.py,sha256=wqo2sIud1eWqQ-akGhwhOT8aI3SLiu4jlKJ5MgmhPbU,1019
39
- syndesi/protocols/scpi.py,sha256=GUXDWMoGyQwpT50DNaWrid0ENVzE_dfVkfxbVYt0l9M,3436
42
+ syndesi/protocols/scpi.py,sha256=gU38dfaOhBJVmgDzLkXSqBqjP32uKLzRM5_jZCdCtOQ,3462
40
43
  syndesi/protocols/sdp.py,sha256=PQe8zwR2xRiKPVvZL8BqHKvxflyg5dJeV4BAaDMnNw8,313
44
+ syndesi/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
+ syndesi/proxy/proxy.py,sha256=hsxRru-uJ_L9TIfTPqIFG_X1eOn3SR2forFrL70q0DE,5028
46
+ syndesi/proxy/proxy_api.py,sha256=4dhprayG-iWIxEOdnpYwGhfi1cBA_mhuE1FilXpk8X0,1886
41
47
  syndesi/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
42
48
  syndesi/tools/exceptions.py,sha256=KXMqrmwoTxfcGFUtjzQLlFS-QhKqwMTsnhDfQveDirc,303
43
- syndesi/tools/log.py,sha256=j6y4_Y2y1f4HX0CVsL70svV7HRRzRhuwXtXKruXN29U,3508
49
+ syndesi/tools/log.py,sha256=zOPQA4ZSDU8qCwomS4w61vd9KJ3yTprAnOWYyV7tliM,3542
44
50
  syndesi/tools/logger.py,sha256=2Li7mRBtf06BXdGBa-I5aC4y4r2CxWEI8nmC6wh4-GE,3184
45
- syndesi/tools/others.py,sha256=r4Ad3TNOb5Lj1jnpIw4PPJTCOrCOVzT9ryAoqY-INGg,258
51
+ syndesi/tools/others.py,sha256=y7sCWrnsgA3ih1eauucmOUbxHW6lVA120_g3co0MxDI,19
46
52
  syndesi/tools/remote_api.py,sha256=crokpha5PQQI5v5KtYe34X7N-rfKi4JGjN-Z6vyCSmo,3003
47
53
  syndesi/tools/remote_server.py,sha256=jSZZYzr6_kUo5Nb8_tA_whQceAcuKJrvSO3IdU94fBY,4830
48
54
  syndesi/tools/shell.py,sha256=A13htgZ0FsFfe8GMlv-4P_w0pv44ca5r4yshz990uqs,3556
49
55
  syndesi/tools/stop_conditions.py,sha256=_H-GPczrLTbPRHYpoPIItOqmPnemwn34QbKFn5GD6Uc,4824
50
- syndesi/tools/types.py,sha256=GAv3BCSZcCGsYH1milxKVIPmBkSxLfVcinqnuFqXflk,1631
51
- syndesi-0.1.6.data/scripts/syndesi,sha256=1y5vfiQPEissF1XsH7pfJYIKf1Lp3ScZWFtPdGELNws,956
56
+ syndesi/tools/types.py,sha256=rEpz5wEYGcJgYkRffRP9sYAhKv8X6DNCXIeI8Zcsvfc,1630
52
57
  tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
- syndesi-0.1.6.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
54
- syndesi-0.1.6.dist-info/METADATA,sha256=_M69G9-kc-gtdlCrB0XrgrG5JhP7SQkdKy9SslS9Tgo,3468
55
- syndesi-0.1.6.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
56
- syndesi-0.1.6.dist-info/top_level.txt,sha256=HrY36JU6hFYp_6qv-GuVBBtHYYemn8qhCrqpvXBd1Lg,8
57
- syndesi-0.1.6.dist-info/RECORD,,
58
+ syndesi-0.2.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
59
+ syndesi-0.2.0.dist-info/METADATA,sha256=YSmHuvnssqIjSU90FZLY5ZuOKZW4_iDfmRBfbovEeyU,3468
60
+ syndesi-0.2.0.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
61
+ syndesi-0.2.0.dist-info/entry_points.txt,sha256=Q_38BGDXKEFxNPSXtkT8brn6f8lmOrkdkEv0IOV-YOM,96
62
+ syndesi-0.2.0.dist-info/top_level.txt,sha256=HrY36JU6hFYp_6qv-GuVBBtHYYemn8qhCrqpvXBd1Lg,8
63
+ syndesi-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ syndesi = syndesi.shell.syndesi:main
3
+ syndesi-proxy = syndesi.proxy.proxy:main
@@ -1,57 +0,0 @@
1
- #!python
2
-
3
- # Syndesi CLI
4
- import argparse
5
- from cmd import Cmd
6
- from enum import Enum
7
-
8
- class MyPrompt(Cmd):
9
- prompt = '❯ '
10
- intro = "Welcome! Type ? to list commands"
11
-
12
- def do_exit(self, inp):
13
- print("Bye !")
14
- return True
15
-
16
- def do_connect(self, inp):
17
- print("Device")
18
-
19
- def default(self, inp):
20
- print(f"Entered : {inp}")
21
-
22
-
23
- do_EOF = do_exit # Allow CTRL+d to exit
24
-
25
-
26
- class SubCommands(Enum):
27
- SHELL = 'shell'
28
-
29
-
30
-
31
-
32
- def connect():
33
- print("Entering connect subcommand...")
34
-
35
- p = MyPrompt()
36
- p.cmdloop()
37
-
38
-
39
-
40
-
41
- def main():
42
- parser = argparse.ArgumentParser(
43
- prog='syndesi',
44
- description='Syndesi command line interface',
45
- epilog='')
46
- # Parse subcommand
47
- parser.add_argument('subcommand', choices=[SubCommands.SHELL.value])
48
-
49
- args = parser.parse_args()
50
-
51
- if args.subcommand == SubCommands.SHELL.value:
52
- connect()
53
-
54
-
55
-
56
- if __name__ == '__main__':
57
- main()