syndesi 0.2.1__py3-none-any.whl → 0.2.3__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 +6 -0
- syndesi/adapters/adapter.py +63 -26
- syndesi/adapters/ip.py +60 -31
- syndesi/adapters/proxy.py +15 -8
- syndesi/adapters/serialport.py +74 -50
- syndesi/adapters/timeout.py +44 -19
- syndesi/adapters/visa.py +5 -4
- syndesi/cli/__init__.py +0 -0
- syndesi/cli/shell.py +141 -0
- syndesi/cli/syndesi.py +30 -0
- syndesi/protocols/delimited.py +7 -3
- syndesi/protocols/modbus.py +1145 -0
- syndesi/protocols/protocol.py +3 -0
- syndesi/protocols/raw.py +2 -1
- syndesi/protocols/scpi.py +19 -15
- syndesi/proxy/proxy_api.py +39 -10
- syndesi/tools/log.py +9 -9
- {syndesi-0.2.1.dist-info → syndesi-0.2.3.dist-info}/METADATA +1 -1
- {syndesi-0.2.1.dist-info → syndesi-0.2.3.dist-info}/RECORD +23 -19
- {syndesi-0.2.1.dist-info → syndesi-0.2.3.dist-info}/entry_points.txt +1 -1
- {syndesi-0.2.1.dist-info → syndesi-0.2.3.dist-info}/LICENSE +0 -0
- {syndesi-0.2.1.dist-info → syndesi-0.2.3.dist-info}/WHEEL +0 -0
- {syndesi-0.2.1.dist-info → syndesi-0.2.3.dist-info}/top_level.txt +0 -0
syndesi/__init__.py
CHANGED
syndesi/adapters/adapter.py
CHANGED
|
@@ -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=
|
|
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-
|
|
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=
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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=
|
|
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
|
|
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.
|
|
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.
|
|
220
|
-
|
|
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
|
|
235
|
-
raise
|
|
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.
|
|
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.
|
|
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-
|
|
319
|
+
designator = STOP_DESIGNATORS['previous-buffer']
|
|
283
320
|
|
|
284
321
|
read_duration = time() - read_start
|
|
285
|
-
if self.
|
|
286
|
-
self._logger.debug(f'Read [{designator}, {read_duration*1e3:.3f}ms] : {output} , previous read buffer : {self.
|
|
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
|
|
syndesi/adapters/ip.py
CHANGED
|
@@ -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 ..
|
|
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=
|
|
21
|
-
|
|
22
|
-
|
|
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] =
|
|
33
|
-
stop_condition =
|
|
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"
|
|
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
|
|
122
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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:
|
syndesi/adapters/proxy.py
CHANGED
|
@@ -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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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())))
|
syndesi/adapters/serialport.py
CHANGED
|
@@ -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 ..
|
|
16
|
+
#from ..cli import shell
|
|
15
17
|
from ..tools.others import DEFAULT
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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.
|
|
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
|
-
|
|
73
|
-
|
|
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"
|
|
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.
|
|
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
|
|
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
|
-
#
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
+
# }
|