syndesi 0.2.3__py3-none-any.whl → 0.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
syndesi/__init__.py CHANGED
@@ -1,6 +1,4 @@
1
- from .tools.log import set_log_file, set_log_level
1
+ from .tools.log import log_settings
2
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
3
+ from .adapters import IP, SerialPort
4
+ from .protocols import Modbus, Delimited, Raw, SCPI
syndesi/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = '0.2.3'
@@ -29,9 +29,7 @@ import socket
29
29
  import logging
30
30
  from time import time
31
31
  from dataclasses import dataclass
32
- from ..tools.others import DEFAULT
33
32
 
34
- DEFAULT_TIMEOUT = Timeout(response=5, continuation=200e-3, total=None)
35
33
  DEFAULT_STOP_CONDITION = None
36
34
 
37
35
 
@@ -72,7 +70,7 @@ class Adapter(ABC):
72
70
  DISCONNECTED = 0
73
71
  CONNECTED = 1
74
72
 
75
- def __init__(self, alias : str = '', stop_condition : Union[StopCondition, None] = DEFAULT, timeout : Union[float, Timeout] = DEFAULT) -> None:
73
+ def __init__(self, alias : str = '', stop_condition : Union[StopCondition, None] = ..., timeout : Union[float, Timeout] = ...) -> None:
76
74
  """
77
75
  Adapter instance
78
76
 
@@ -86,13 +84,21 @@ class Adapter(ABC):
86
84
  Default to None
87
85
  """
88
86
  super().__init__()
87
+
89
88
  self._alias = alias
90
89
 
91
- self._default_stop_condition = stop_condition == DEFAULT
90
+ self.is_default_timeout = timeout is Ellipsis
91
+ if self.is_default_timeout:
92
+ self._timeout = self._default_timeout()
93
+ else:
94
+ self._timeout = timeout_fuse(timeout, self._default_timeout())
95
+
96
+ self._default_stop_condition = stop_condition is Ellipsis
92
97
  if self._default_stop_condition:
93
98
  self._stop_condition = DEFAULT_STOP_CONDITION
94
99
  else:
95
100
  self._stop_condition = stop_condition
101
+
96
102
  self._read_queue = TimedQueue()
97
103
  self._thread : Union[Thread, None] = None
98
104
  self._status = self.Status.DISCONNECTED
@@ -103,16 +109,12 @@ class Adapter(ABC):
103
109
  # not used because of termination or length stop condition
104
110
  self._previous_buffer = b''
105
111
 
106
- self._default_timeout = timeout == DEFAULT
107
- if self._default_timeout:
108
- self._timeout = DEFAULT_TIMEOUT
109
- else:
110
- if is_number(timeout):
111
- self._timeout = Timeout(response=timeout, continuation=100e-3)
112
- elif isinstance(timeout, Timeout):
113
- self._timeout = timeout
114
- else:
115
- raise ValueError(f"Invalid timeout type : {type(timeout)}")
112
+ if not isinstance(self._timeout, Timeout):
113
+ raise ValueError('Timeout must be defined to initialize an Adapter base class')
114
+
115
+ @abstractmethod
116
+ def _default_timeout(self):
117
+ pass
116
118
 
117
119
  def set_timeout(self, timeout : Timeout):
118
120
  """
@@ -132,7 +134,7 @@ class Adapter(ABC):
132
134
  ----------
133
135
  default_timeout : Timeout or tuple or float
134
136
  """
135
- if self._default_timeout:
137
+ if self.is_default_timeout:
136
138
  self._logger.debug(f'Setting default timeout to {default_timeout}')
137
139
  self._timeout = default_timeout
138
140
  else:
@@ -203,8 +205,32 @@ class Adapter(ABC):
203
205
  """
204
206
  pass
205
207
 
208
+ @abstractmethod
209
+ def read(self, timeout : Timeout = ..., stop_condition : StopCondition = ..., return_metrics : bool = False) -> bytes:
210
+ pass
211
+
212
+
213
+ @abstractmethod
214
+ def _start_thread(self):
215
+ self._logger.debug("Starting read thread...")
216
+
217
+ def __del__(self):
218
+ self.close()
219
+
220
+ def query(self, data : Union[bytes, str], timeout : Timeout = ..., stop_condition : StopCondition = ..., return_metrics : bool = False) -> bytes:
221
+ """
222
+ Shortcut function that combines
223
+ - flush_read
224
+ - write
225
+ - read
226
+ """
227
+ self.flushRead()
228
+ self.write(data)
229
+ return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
230
+
206
231
 
207
- def read(self, timeout=DEFAULT, stop_condition=DEFAULT, return_metrics : bool = False) -> bytes:
232
+ class StreamAdapter(Adapter):
233
+ def read(self, timeout=..., stop_condition=..., return_metrics : bool = False) -> bytes:
208
234
  """
209
235
  Read data from the device
210
236
 
@@ -220,22 +246,21 @@ class Adapter(ABC):
220
246
  if self._status == self.Status.DISCONNECTED:
221
247
  self.open()
222
248
 
223
- # Use adapter values if no custom value is specified
224
- if timeout == DEFAULT:
249
+ # 29.08.24 Change timeout behavior
250
+ if timeout is ...:
251
+ # Use the class timeout
225
252
  timeout = self._timeout
226
- elif isinstance(timeout, float):
227
- timeout = Timeout(timeout)
253
+ else:
254
+ # Fuse it
255
+ timeout = timeout_fuse(timeout, self._timeout)
228
256
 
229
- if stop_condition == DEFAULT:
257
+ if stop_condition is ...:
230
258
  stop_condition = self._stop_condition
231
259
 
232
260
  # If the adapter is closed, open it
233
261
  if self._status == self.Status.DISCONNECTED:
234
262
  self.open()
235
263
 
236
- if self._thread is None or not self._thread.is_alive():
237
- self._start_thread()
238
-
239
264
  timeout_ms = timeout.initiate_read(len(self._previous_buffer) > 0)
240
265
 
241
266
  if stop_condition is not None:
@@ -337,22 +362,4 @@ class Adapter(ABC):
337
362
  total_time=timeout.total_time
338
363
  )
339
364
  else:
340
- return output
341
-
342
- @abstractmethod
343
- def _start_thread(self):
344
- pass
345
-
346
- def __del__(self):
347
- self.close()
348
-
349
- @abstractmethod
350
- def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
351
- """
352
- Shortcut function that combines
353
- - flush_read
354
- - write
355
- - read
356
- """
357
- pass
358
-
365
+ return output
syndesi/adapters/auto.py CHANGED
@@ -17,7 +17,7 @@ from typing import Union
17
17
  import re
18
18
  from . import Adapter, IP, SerialPort
19
19
 
20
- IP_PATTERN = '([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(:[0-9]+)*'
20
+ IP_PATTERN = '([0-9]+.[0-9]+.[0-9]+.[0-9]+)(:[0-9]+)*'
21
21
 
22
22
  WINDOWS_SERIAL_PATTERN = '(COM[0-9]+)(:[0-9]+)*'
23
23
  LINUX_SERIAL_PATTERN = '(/dev/tty[a-zA-Z0-9]+)(:[0-9]+)*'
syndesi/adapters/ip.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import socket
2
2
  from enum import Enum
3
- from .adapter import Adapter, AdapterDisconnected
3
+ from .adapter import StreamAdapter, AdapterDisconnected
4
4
  from ..tools.types import to_bytes
5
5
  from .timeout import Timeout, timeout_fuse
6
6
  from .stop_conditions import StopCondition
@@ -10,18 +10,10 @@ from typing import Union
10
10
  from time import time
11
11
  import argparse
12
12
  #from ..cli import shell
13
- from ..tools.others import DEFAULT
14
13
  import select
15
14
 
16
- class IP(Adapter):
17
- DEFAULT_TIMEOUT = Timeout(
18
- response=2,
19
- on_response='error',
20
- continuation=100e-3,
21
- on_continuation='return',
22
- total=5,
23
- on_total='error')
24
- DEFAULT_BUFFER_SIZE = 1024
15
+ class IP(StreamAdapter):
16
+ _DEFAULT_BUFFER_SIZE = 1024
25
17
  class Protocol(Enum):
26
18
  TCP = 'TCP'
27
19
  UDP = 'UDP'
@@ -30,10 +22,10 @@ class IP(Adapter):
30
22
  address : str,
31
23
  port : int = None,
32
24
  transport : str = 'TCP',
33
- timeout : Union[Timeout, float] = DEFAULT,
34
- stop_condition : StopCondition = DEFAULT,
25
+ timeout : Union[Timeout, float] = ...,
26
+ stop_condition : StopCondition = ...,
35
27
  alias : str = '',
36
- buffer_size : int = DEFAULT_BUFFER_SIZE,
28
+ buffer_size : int = _DEFAULT_BUFFER_SIZE,
37
29
  _socket : socket.socket = None):
38
30
  """
39
31
  IP stack adapter
@@ -56,13 +48,9 @@ class IP(Adapter):
56
48
  Socket buffer size, may be removed in the future
57
49
  socket : socket.socket
58
50
  Specify a custom socket, this is reserved for server application
59
- """
60
- if timeout == DEFAULT:
61
- timeout = self.DEFAULT_TIMEOUT
62
- else:
63
- timeout = timeout_fuse(timeout, self.DEFAULT_TIMEOUT)
64
-
51
+ """
65
52
  super().__init__(alias=alias, timeout=timeout, stop_condition=stop_condition)
53
+
66
54
  self._transport = self.Protocol(transport)
67
55
  self._is_server = _socket is not None
68
56
 
@@ -81,6 +69,21 @@ class IP(Adapter):
81
69
  self._port = port
82
70
  self._buffer_size = buffer_size
83
71
 
72
+ def __str__(self) -> str:
73
+ return f'IP({self._address}:{self._port})'
74
+
75
+ def __repr__(self) -> str:
76
+ return self.__str__()
77
+
78
+ def _default_timeout(self):
79
+ return Timeout(
80
+ response=5,
81
+ on_response='error',
82
+ continuation=100e-3,
83
+ on_continuation='return',
84
+ total=5,
85
+ on_total='error')
86
+
84
87
  def set_default_port(self, port):
85
88
  """
86
89
  Sets IP port if no port has been set yet.
@@ -101,9 +104,12 @@ class IP(Adapter):
101
104
  if self._port is None:
102
105
  raise ValueError(f"Cannot open adapter without specifying a port")
103
106
 
104
- self._logger.debug(f"Adapter {self._alias} connect to ({self._address}, {self._port})")
107
+ self._logger.debug(f"Adapter {self._alias + ' ' if self._alias != '' else ''}connect to ({self._address}, {self._port})")
105
108
  self._socket.connect((self._address, self._port))
106
109
  self._status = self.Status.CONNECTED
110
+ if self._thread is None or not self._thread.is_alive():
111
+ self._start_thread()
112
+
107
113
  self._logger.info(f"Adapter {self._alias} opened !")
108
114
 
109
115
  def close(self):
@@ -122,7 +128,7 @@ class IP(Adapter):
122
128
  def write(self, data : Union[bytes, str]):
123
129
  data = to_bytes(data)
124
130
  if self._status == self.Status.DISCONNECTED:
125
- self._logger.info(f"Adapter {self._alias} is closed, opening...")
131
+ self._logger.info(f"Adapter {self._alias + ' ' if self._alias != '' else ''}is closed, opening...")
126
132
  self.open()
127
133
  write_start = time()
128
134
  self._socket.send(data)
@@ -130,18 +136,14 @@ class IP(Adapter):
130
136
  self._logger.debug(f"Write [{write_duration*1e3:.3f}ms]: {repr(data)}")
131
137
 
132
138
  def _start_thread(self):
133
- self._logger.debug("Starting read thread...")
139
+ super()._start_thread()
134
140
  if self._thread is None or not self._thread.is_alive():
135
141
  self._thread = Thread(target=self._read_thread, daemon=True, args=(self._socket, self._read_queue, self._thread_stop_read))
136
142
  self._thread.start()
137
143
 
138
- # # EXPERIMENTAL
139
- # def read_thread_alive(self):
140
- # return self._thread.is_alive()
141
-
142
144
  def _read_thread(self, socket : socket.socket, read_queue : TimedQueue, stop : socket.socket):
143
145
  # Using select.select works on both Windows and Linux as long as the inputs are all sockets
144
- while True: # TODO : Add stop_pipe ? Maybe it was removed ?
146
+ while True:
145
147
 
146
148
  try:
147
149
  ready, _, _ = select.select([socket, stop], [], [])
@@ -172,8 +174,6 @@ class IP(Adapter):
172
174
  def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
173
175
  if self._is_server:
174
176
  raise SystemError("Cannot query on server adapters")
175
- self.flushRead()
176
- self.write(data)
177
- return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
177
+ return super().query(data=data, timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
178
178
 
179
179
 
@@ -8,31 +8,19 @@ import socket
8
8
  import sys
9
9
  #from collections.abc import Sequence
10
10
 
11
- from .adapter import Adapter, AdapterDisconnected
11
+ from .adapter import Adapter, StreamAdapter, AdapterDisconnected
12
12
  from ..tools.types import to_bytes
13
13
  from .stop_conditions import *
14
- from .timeout import Timeout
14
+ from .timeout import Timeout, timeout_fuse
15
15
  from .timed_queue import TimedQueue
16
- #from ..cli import shell
17
- from ..tools.others import DEFAULT
18
16
 
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
-
29
-
30
- class SerialPort(Adapter):
17
+ class SerialPort(StreamAdapter):
31
18
  def __init__(self,
32
19
  port : str,
33
- baudrate : int,
34
- timeout : Union[Timeout, float] = DEFAULT,
35
- stop_condition : StopCondition = DEFAULT,
20
+ baudrate : int = ...,
21
+ timeout : Union[Timeout, float] = ...,
22
+ stop_condition : StopCondition = ...,
23
+ alias : str = '',
36
24
  rts_cts : bool = False): # rts_cts experimental
37
25
  """
38
26
  Serial communication adapter
@@ -41,32 +29,87 @@ class SerialPort(Adapter):
41
29
  ----------
42
30
  port : str
43
31
  Serial port (COMx or ttyACMx)
44
- """
45
- if timeout == DEFAULT:
46
- timeout = DEFAULT_TIMEOUT
47
-
48
- super().__init__(timeout=timeout, stop_condition=stop_condition)
49
- self._logger.info(f"Setting up SerialPort adapter timeout:{timeout}, stop_condition:{stop_condition}")
32
+ """
33
+ super().__init__(timeout=timeout, stop_condition=stop_condition, alias=alias)
34
+
50
35
  self._port_name = port
36
+ self._baudrate_set = baudrate is not ...
51
37
  self._baudrate = baudrate
52
- self._port = serial.Serial(port=self._port_name, baudrate=self._baudrate)
53
- if self._port.isOpen():
54
- self._status = self.Status.CONNECTED
55
- else:
56
- self._status = self.Status.DISCONNECTED
38
+ self._logger.info(f"Setting up SerialPort adapter on {self._port_name} with baudrate={baudrate}, timeout={timeout} and stop_condition={stop_condition}")
39
+ self._port = None
40
+
41
+ self.open()
42
+
57
43
 
58
44
  self._rts_cts = rts_cts
59
45
 
46
+ def _default_timeout(self):
47
+ return Timeout(
48
+ response=2,
49
+ on_response='error',
50
+ continuation=100e-3,
51
+ on_continuation='return',
52
+ total=None,
53
+ on_total='error')
54
+
55
+ def __str__(self) -> str:
56
+ return f'Serial({self._port_name}:{self._baudrate})'
57
+
58
+ def __repr__(self) -> str:
59
+ return self.__str__()
60
+
61
+ def set_baudrate(self, baudrate):
62
+ """
63
+ Set baudrate
64
+
65
+ Parameters
66
+ ----------
67
+ baudrate : int
68
+ """
69
+ is_connected = self._status == self.Status.CONNECTED
70
+
71
+ if is_connected:
72
+ self.close()
73
+
74
+ self._baudrate = baudrate
75
+
76
+ if is_connected:
77
+ self.open()
78
+
79
+ def set_default_baudrate(self, baudrate):
80
+ """
81
+ Sets the default baudrate
82
+
83
+ Parameters
84
+ ----------
85
+ baudrate : int
86
+ """
87
+ if not self._baudrate_set:
88
+ self._baudrate = baudrate
89
+
60
90
  def flushRead(self):
61
91
  self._port.flush()
92
+ super().flushRead()
62
93
 
63
94
  def open(self):
64
- self._port.open()
65
- # Flush the input buffer
66
- buf = b'0'
67
- while buf:
68
- buf = os.read(self._port.fd)
95
+ if self._baudrate is ...:
96
+ raise ValueError('Baudrate must be set, please use set_baudrate')
97
+
98
+ if self._port is None:
99
+ self._port = serial.Serial(port=self._port_name, baudrate=self._baudrate)
100
+ elif not self._port.isOpen():
101
+ self._port.open()
102
+ # # Flush the input buffer
103
+ self.flushRead()
104
+
105
+ if self._port.isOpen():
106
+ self._status = self.Status.CONNECTED
107
+ else:
108
+ self._status = self.Status.DISCONNECTED
109
+
69
110
  self._logger.info("Adapter opened !")
111
+ if self._thread is None or not self._thread.is_alive():
112
+ self._start_thread()
70
113
 
71
114
  def close(self):
72
115
  super().close()
@@ -96,7 +139,7 @@ class SerialPort(Adapter):
96
139
  """
97
140
  Start the read thread
98
141
  """
99
- self._logger.debug("Starting read thread...")
142
+ super()._start_thread()
100
143
  if self._thread is None or not self._thread.is_alive():
101
144
  self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue, self._thread_stop_read))
102
145
  self._thread.start()
@@ -116,8 +159,8 @@ class SerialPort(Adapter):
116
159
  else:
117
160
  # Read data from the serialport with a timeout, if the timeout occurs, read again.
118
161
  # This is to avoid having a crazy fast loop
119
- data = port.read()
120
- if len(data) > 0:
162
+ fragment = port.read()
163
+ if len(fragment) > 0:
121
164
  read_queue.put(fragment)
122
165
  else:
123
166
  ready, _, _ = select.select([self._port.fd, stop], [], [])
@@ -133,9 +176,9 @@ class SerialPort(Adapter):
133
176
  else:
134
177
  fragment = port.read(in_waiting)
135
178
  if fragment:
136
- read_queue.put(fragment)
179
+ read_queue.put(fragment)
137
180
 
138
- def read(self, timeout=DEFAULT, stop_condition=DEFAULT, return_metrics: bool = False) -> bytes:
181
+ def read(self, timeout=..., stop_condition=..., return_metrics: bool = False) -> bytes:
139
182
  """
140
183
  Read data from the device
141
184
 
@@ -157,26 +200,4 @@ class SerialPort(Adapter):
157
200
  output = super().read(timeout, stop_condition, return_metrics)
158
201
  if self._rts_cts: # Experimental
159
202
  self._port.setRTS(False)
160
- return output
161
-
162
- def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
163
- self.flushRead()
164
- self.write(data)
165
- return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
166
-
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
- # }
203
+ return output
@@ -1,4 +1,4 @@
1
- from typing import Union, Tuple
1
+ from typing import Union, Tuple, Callable
2
2
  from enum import Enum
3
3
  from time import time
4
4
 
@@ -140,6 +140,7 @@ class Termination(StopCondition):
140
140
  def __str__(self) -> str:
141
141
  return f'Termination({repr(self._termination)})'
142
142
 
143
+ # TODO : Add a "allow_longer" parameter ? If more than N data is received, keep it instead of raising an error ?
143
144
  class Length(StopCondition):
144
145
  def __init__(self, N : int) -> None:
145
146
  """
@@ -164,10 +165,46 @@ class Length(StopCondition):
164
165
  deferred_fragment = data[remaining_bytes:]
165
166
  self._counter += len(kept_fragment)
166
167
  remaining_bytes = self._N - self._counter
168
+ # TODO : remaining_bytes <= 0 ? Alongside above TODO maybe
167
169
  return remaining_bytes == 0, kept_fragment, deferred_fragment
168
170
 
169
171
  def __repr__(self) -> str:
170
172
  return self.__str__()
171
173
 
172
174
  def __str__(self) -> str:
173
- return f'Length({self._N})'
175
+ return f'Length({self._N})'
176
+
177
+
178
+
179
+ # class Lambda(StopCondition):
180
+ # class LambdaReturn:
181
+ # ERROR = 'error' # Discard everything and raise an error
182
+ # VALID = 'valid' # Return everything
183
+ # KEEP_N = 'keep_n' # Keep the first N bytes
184
+ # CONTINUE = 'continue' # Keep reading
185
+
186
+ # def __init__(self, _lambda : Callable) -> None:
187
+ # super().__init__()
188
+ # self._lambda = _lambda
189
+
190
+ # def initiate_read(self) -> Union[float, None]:
191
+ # return None
192
+
193
+ # def evaluate(self, fragment: bytes) -> Tuple[bool, Union[float, None]]:
194
+ # lambda_return, N = self._lambda(fragment)
195
+ # match lambda_return:
196
+ # case self.LambdaReturn.ERROR:
197
+ # raise RuntimeError(f"Couldn't apply Lambda condition on fragment : {fragment}")
198
+ # case self.LambdaReturn.VALID:
199
+ # return True, fragment, b''
200
+ # case self.LambdaReturn.KEEP_N:
201
+ # return True, fragment[:N], fragment[N:]
202
+ # case self.LambdaReturn.CONTINUE:
203
+ # return False, fragment, b'' # TODO : Check this
204
+
205
+ # def __repr__(self) -> str:
206
+ # return self.__str__()
207
+
208
+ # def __str__(self) -> str:
209
+ # return f'Lambda(...)'
210
+