syndesi 0.2.1__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.1/syndesi.egg-info → syndesi-0.2.3}/PKG-INFO +1 -1
  2. {syndesi-0.2.1 → syndesi-0.2.3}/setup.py +2 -2
  3. syndesi-0.2.3/syndesi/__init__.py +6 -0
  4. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/adapter.py +63 -26
  5. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/ip.py +60 -31
  6. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/proxy.py +15 -8
  7. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/serialport.py +74 -50
  8. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/timeout.py +44 -19
  9. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/visa.py +5 -4
  10. {syndesi-0.2.1/syndesi → syndesi-0.2.3/syndesi/api}/__init__.py +0 -0
  11. {syndesi-0.2.1/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.1 → syndesi-0.2.3}/syndesi/protocols/delimited.py +7 -3
  14. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/protocols/protocol.py +3 -0
  15. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/protocols/raw.py +2 -1
  16. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/protocols/scpi.py +19 -15
  17. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/proxy/proxy_api.py +39 -10
  18. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/tools/log.py +9 -9
  19. {syndesi-0.2.1 → syndesi-0.2.3/syndesi.egg-info}/PKG-INFO +1 -1
  20. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi.egg-info/SOURCES.txt +3 -1
  21. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi.egg-info/entry_points.txt +1 -1
  22. {syndesi-0.2.1 → syndesi-0.2.3}/LICENSE +0 -0
  23. {syndesi-0.2.1 → syndesi-0.2.3}/README.md +0 -0
  24. {syndesi-0.2.1 → syndesi-0.2.3}/setup.cfg +0 -0
  25. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/__init__.py +0 -0
  26. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/auto.py +0 -0
  27. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/ip_server.py +0 -0
  28. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/stop_conditions.py +0 -0
  29. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/adapters/timed_queue.py +0 -0
  30. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/api/api.py +0 -0
  31. {syndesi-0.2.1/syndesi/api → syndesi-0.2.3/syndesi/cli}/__init__.py +0 -0
  32. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/protocols/__init__.py +0 -0
  33. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/protocols/sdp.py +0 -0
  34. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/proxy/__init__.py +0 -0
  35. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/proxy/proxy.py +0 -0
  36. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/tools/__init__.py +0 -0
  37. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/tools/exceptions.py +0 -0
  38. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/tools/others.py +0 -0
  39. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi/tools/types.py +0 -0
  40. {syndesi-0.2.1 → syndesi-0.2.3}/syndesi.egg-info/dependency_links.txt +0 -0
  41. {syndesi-0.2.1 → 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.1
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.1'
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,27 +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
- # From pyserial - serialposix.py
18
- import fcntl
19
- import termios
20
- import struct
21
- if hasattr(termios, 'TIOCINQ'):
22
- TIOCINQ = termios.TIOCINQ
23
- else:
24
- TIOCINQ = getattr(termios, 'FIONREAD', 0x541B)
25
- TIOCM_zero_str = struct.pack('I', 0)
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
+
26
28
 
27
- DEFAULT_TIMEOUT = Timeout(response=1, continuation=200e-3, total=None)
28
29
 
29
30
  class SerialPort(Adapter):
30
31
  def __init__(self,
@@ -46,15 +47,15 @@ class SerialPort(Adapter):
46
47
 
47
48
  super().__init__(timeout=timeout, stop_condition=stop_condition)
48
49
  self._logger.info(f"Setting up SerialPort adapter timeout:{timeout}, stop_condition:{stop_condition}")
49
- 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)
50
53
  if self._port.isOpen():
51
54
  self._status = self.Status.CONNECTED
52
55
  else:
53
56
  self._status = self.Status.DISCONNECTED
54
57
 
55
58
  self._rts_cts = rts_cts
56
-
57
- self._stop_event_pipe, self._stop_event_pipe_write = os.pipe()
58
59
 
59
60
  def flushRead(self):
60
61
  self._port.flush()
@@ -68,10 +69,15 @@ class SerialPort(Adapter):
68
69
  self._logger.info("Adapter opened !")
69
70
 
70
71
  def close(self):
72
+ super().close()
71
73
  if self._thread is not None and self._thread.is_alive():
72
- os.write(self._stop_event_pipe_write, b'1')
73
- self._thread.join()
74
+ try:
75
+ self._thread.join()
76
+ except RuntimeError:
77
+ # If the thread cannot be joined, then so be it
78
+ pass
74
79
  if hasattr(self, '_port'):
80
+ # Close and the read thread will die by itself
75
81
  self._port.close()
76
82
  self._logger.info("Adapter closed !")
77
83
 
@@ -84,7 +90,7 @@ class SerialPort(Adapter):
84
90
  write_start = time()
85
91
  self._port.write(data)
86
92
  write_duration = time() - write_start
87
- 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)}")
88
94
 
89
95
  def _start_thread(self):
90
96
  """
@@ -92,26 +98,44 @@ class SerialPort(Adapter):
92
98
  """
93
99
  self._logger.debug("Starting read thread...")
94
100
  if self._thread is None or not self._thread.is_alive():
95
- self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue, self._stop_event_pipe))
101
+ self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue, self._thread_stop_read))
96
102
  self._thread.start()
97
103
 
98
- def _read_thread(self, port : serial.Serial , read_queue : TimedQueue, stop_event_pipe):
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
99
109
  while True:
100
- # It looks like using the raw implementation of port.in_waiting and port.read is better, there's no more warnings
101
- # Equivalent of port.in_waiting :
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
104
- if in_waiting == 0:
105
- ready, _, _ = select.select([port.fd, stop_event_pipe], [], [], None)
106
- if stop_event_pipe in ready:
107
- # Stop
110
+ # Check how many bytes are available
111
+ if sys.platform == 'win32':
112
+ ready, _, _ = select.select([stop], [], [], 0)
113
+ if stop in ready:
114
+ # Stop the read thread
108
115
  break
109
- # Else, read as many bytes as possible
110
- fragment = os.read(port.fd, 1000) # simplified version of port.read()
111
- if fragment:
112
- read_queue.put(fragment)
113
-
114
- def read(self, timeout=None, stop_condition=None, return_metrics: bool = False) -> bytes:
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:
115
139
  """
116
140
  Read data from the device
117
141
 
@@ -140,19 +164,19 @@ class SerialPort(Adapter):
140
164
  self.write(data)
141
165
  return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
142
166
 
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
- }
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
+ # }