syndesi 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {syndesi-0.2.2/syndesi.egg-info → syndesi-0.2.3}/PKG-INFO +1 -1
- {syndesi-0.2.2 → syndesi-0.2.3}/setup.py +2 -2
- syndesi-0.2.3/syndesi/__init__.py +6 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/adapter.py +63 -26
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/ip.py +60 -31
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/proxy.py +15 -8
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/serialport.py +75 -32
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/timeout.py +44 -19
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/visa.py +5 -4
- {syndesi-0.2.2/syndesi → syndesi-0.2.3/syndesi/api}/__init__.py +0 -0
- {syndesi-0.2.2/syndesi/tools → syndesi-0.2.3/syndesi/cli}/shell.py +44 -14
- syndesi-0.2.3/syndesi/cli/syndesi.py +30 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/delimited.py +7 -3
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/protocol.py +3 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/raw.py +2 -1
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/scpi.py +19 -15
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/proxy/proxy_api.py +39 -10
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/log.py +9 -9
- {syndesi-0.2.2 → syndesi-0.2.3/syndesi.egg-info}/PKG-INFO +1 -1
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi.egg-info/SOURCES.txt +3 -1
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi.egg-info/entry_points.txt +1 -1
- {syndesi-0.2.2 → syndesi-0.2.3}/LICENSE +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/README.md +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/setup.cfg +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/auto.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/ip_server.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/stop_conditions.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/adapters/timed_queue.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/api/api.py +0 -0
- {syndesi-0.2.2/syndesi/api → syndesi-0.2.3/syndesi/cli}/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/protocols/sdp.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/proxy/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/proxy/proxy.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/exceptions.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/others.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi/tools/types.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi.egg-info/dependency_links.txt +0 -0
- {syndesi-0.2.2 → syndesi-0.2.3}/syndesi.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from setuptools import setup, find_packages
|
|
2
2
|
|
|
3
|
-
VERSION = '0.2.
|
|
3
|
+
VERSION = '0.2.3'
|
|
4
4
|
DESCRIPTION = 'Syndesi'
|
|
5
5
|
|
|
6
6
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
@@ -17,7 +17,7 @@ setup(
|
|
|
17
17
|
long_description=long_description,
|
|
18
18
|
entry_points = {
|
|
19
19
|
'console_scripts': [
|
|
20
|
-
'syndesi=syndesi.
|
|
20
|
+
'syndesi=syndesi.cli.syndesi:main',
|
|
21
21
|
'syndesi-proxy=syndesi.proxy.proxy:main'],
|
|
22
22
|
},
|
|
23
23
|
packages=find_packages(),
|
|
@@ -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
|
|
|
@@ -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:
|
|
@@ -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())))
|
|
@@ -4,17 +4,28 @@ from threading import Thread
|
|
|
4
4
|
from typing import Union
|
|
5
5
|
import select
|
|
6
6
|
import argparse
|
|
7
|
+
import socket
|
|
8
|
+
import sys
|
|
7
9
|
#from collections.abc import Sequence
|
|
8
10
|
|
|
9
|
-
from .adapter import Adapter
|
|
11
|
+
from .adapter import Adapter, AdapterDisconnected
|
|
10
12
|
from ..tools.types import to_bytes
|
|
11
13
|
from .stop_conditions import *
|
|
12
14
|
from .timeout import Timeout
|
|
13
15
|
from .timed_queue import TimedQueue
|
|
14
|
-
from ..
|
|
16
|
+
#from ..cli import shell
|
|
15
17
|
from ..tools.others import DEFAULT
|
|
16
18
|
|
|
17
|
-
DEFAULT_TIMEOUT = Timeout(
|
|
19
|
+
DEFAULT_TIMEOUT = Timeout(
|
|
20
|
+
response=1,
|
|
21
|
+
on_response='error',
|
|
22
|
+
continuation=200e-3,
|
|
23
|
+
on_continuation='return',
|
|
24
|
+
total=None,
|
|
25
|
+
on_total='error')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
18
29
|
|
|
19
30
|
class SerialPort(Adapter):
|
|
20
31
|
def __init__(self,
|
|
@@ -36,7 +47,9 @@ class SerialPort(Adapter):
|
|
|
36
47
|
|
|
37
48
|
super().__init__(timeout=timeout, stop_condition=stop_condition)
|
|
38
49
|
self._logger.info(f"Setting up SerialPort adapter timeout:{timeout}, stop_condition:{stop_condition}")
|
|
39
|
-
self.
|
|
50
|
+
self._port_name = port
|
|
51
|
+
self._baudrate = baudrate
|
|
52
|
+
self._port = serial.Serial(port=self._port_name, baudrate=self._baudrate)
|
|
40
53
|
if self._port.isOpen():
|
|
41
54
|
self._status = self.Status.CONNECTED
|
|
42
55
|
else:
|
|
@@ -56,6 +69,13 @@ class SerialPort(Adapter):
|
|
|
56
69
|
self._logger.info("Adapter opened !")
|
|
57
70
|
|
|
58
71
|
def close(self):
|
|
72
|
+
super().close()
|
|
73
|
+
if self._thread is not None and self._thread.is_alive():
|
|
74
|
+
try:
|
|
75
|
+
self._thread.join()
|
|
76
|
+
except RuntimeError:
|
|
77
|
+
# If the thread cannot be joined, then so be it
|
|
78
|
+
pass
|
|
59
79
|
if hasattr(self, '_port'):
|
|
60
80
|
# Close and the read thread will die by itself
|
|
61
81
|
self._port.close()
|
|
@@ -70,7 +90,7 @@ class SerialPort(Adapter):
|
|
|
70
90
|
write_start = time()
|
|
71
91
|
self._port.write(data)
|
|
72
92
|
write_duration = time() - write_start
|
|
73
|
-
self._logger.debug(f"
|
|
93
|
+
self._logger.debug(f"Write [{write_duration*1e3:.3f}ms]: {repr(data)}")
|
|
74
94
|
|
|
75
95
|
def _start_thread(self):
|
|
76
96
|
"""
|
|
@@ -78,21 +98,44 @@ class SerialPort(Adapter):
|
|
|
78
98
|
"""
|
|
79
99
|
self._logger.debug("Starting read thread...")
|
|
80
100
|
if self._thread is None or not self._thread.is_alive():
|
|
81
|
-
self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue))
|
|
101
|
+
self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue, self._thread_stop_read))
|
|
82
102
|
self._thread.start()
|
|
83
103
|
|
|
84
|
-
def _read_thread(self, port : serial.Serial ,
|
|
85
|
-
#
|
|
104
|
+
def _read_thread(self, port : serial.Serial,read_queue : TimedQueue, stop : socket.socket):
|
|
105
|
+
# On linux, it is possivle to use the select.select for both serial port and stop socketpair.
|
|
106
|
+
# On Windows, this is not possible. so the port timeout is used instead.
|
|
107
|
+
if sys.platform == 'win32':
|
|
108
|
+
port.timeout = 0.1
|
|
86
109
|
while True:
|
|
87
110
|
# Check how many bytes are available
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
111
|
+
if sys.platform == 'win32':
|
|
112
|
+
ready, _, _ = select.select([stop], [], [], 0)
|
|
113
|
+
if stop in ready:
|
|
114
|
+
# Stop the read thread
|
|
115
|
+
break
|
|
116
|
+
else:
|
|
117
|
+
# Read data from the serialport with a timeout, if the timeout occurs, read again.
|
|
118
|
+
# This is to avoid having a crazy fast loop
|
|
119
|
+
data = port.read()
|
|
120
|
+
if len(data) > 0:
|
|
121
|
+
read_queue.put(fragment)
|
|
122
|
+
else:
|
|
123
|
+
ready, _, _ = select.select([self._port.fd, stop], [], [])
|
|
124
|
+
if stop in ready:
|
|
125
|
+
# Stop the read thread
|
|
126
|
+
break
|
|
127
|
+
elif self._port.fd in ready:
|
|
128
|
+
try:
|
|
129
|
+
in_waiting = self._port.in_waiting
|
|
130
|
+
except OSError:
|
|
131
|
+
# Input/output error, the port was disconnected
|
|
132
|
+
read_queue.put(AdapterDisconnected)
|
|
133
|
+
else:
|
|
134
|
+
fragment = port.read(in_waiting)
|
|
135
|
+
if fragment:
|
|
136
|
+
read_queue.put(fragment)
|
|
137
|
+
|
|
138
|
+
def read(self, timeout=DEFAULT, stop_condition=DEFAULT, return_metrics: bool = False) -> bytes:
|
|
96
139
|
"""
|
|
97
140
|
Read data from the device
|
|
98
141
|
|
|
@@ -121,19 +164,19 @@ class SerialPort(Adapter):
|
|
|
121
164
|
self.write(data)
|
|
122
165
|
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
123
166
|
|
|
124
|
-
def shell_parse(inp: str):
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
167
|
+
# def shell_parse(inp: str):
|
|
168
|
+
# parser = argparse.ArgumentParser(
|
|
169
|
+
# prog='',
|
|
170
|
+
# description='Serial port shell parser',
|
|
171
|
+
# epilog='')
|
|
172
|
+
# # Parse subcommand
|
|
173
|
+
# parser.add_argument('--' + shell.Arguments.PORT.value, type=str)
|
|
174
|
+
# parser.add_argument('--' + shell.Arguments.BAUDRATE.value, type=int)
|
|
175
|
+
# parser.add_argument('--' + shell.Arguments.ENABLE_RTS_CTS.value, action='store_true')
|
|
176
|
+
# args = parser.parse_args(inp.split())
|
|
177
|
+
|
|
178
|
+
# return {
|
|
179
|
+
# 'port' : getattr(args, shell.Arguments.PORT.value),
|
|
180
|
+
# 'baudrate' : getattr(args, shell.Arguments.BAUDRATE.value),
|
|
181
|
+
# 'rts_cts' : bool(getattr(args, shell.Arguments.ENABLE_RTS_CTS.value))
|
|
182
|
+
# }
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from typing import Union, Tuple
|
|
7
7
|
from time import time
|
|
8
|
-
#from ..tools.others import is_default_argument
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
class Timeout():
|
|
@@ -16,9 +15,9 @@ class Timeout():
|
|
|
16
15
|
ERROR = 'error' # If a timeout is reached, raise an error
|
|
17
16
|
|
|
18
17
|
class TimeoutType(Enum):
|
|
19
|
-
RESPONSE =
|
|
20
|
-
CONTINUATION =
|
|
21
|
-
TOTAL =
|
|
18
|
+
RESPONSE = 'response'
|
|
19
|
+
CONTINUATION = 'continuation'
|
|
20
|
+
TOTAL = 'total'
|
|
22
21
|
|
|
23
22
|
class _State(Enum):
|
|
24
23
|
WAIT_FOR_RESPONSE = 0
|
|
@@ -26,12 +25,12 @@ class Timeout():
|
|
|
26
25
|
|
|
27
26
|
DEFAULT_CONTINUATION = 5e-3
|
|
28
27
|
DEFAULT_TOTAL = None
|
|
29
|
-
DEFAULT_ON_RESPONSE = OnTimeoutStrategy.
|
|
28
|
+
DEFAULT_ON_RESPONSE = OnTimeoutStrategy.ERROR
|
|
30
29
|
DEFAULT_ON_CONTINUATION = OnTimeoutStrategy.RETURN
|
|
31
30
|
DEFAULT_ON_TOTAL = OnTimeoutStrategy.RETURN
|
|
32
31
|
|
|
33
32
|
def __init__(self,
|
|
34
|
-
response,
|
|
33
|
+
response=None,
|
|
35
34
|
continuation=None,
|
|
36
35
|
total=None,
|
|
37
36
|
on_response=None,
|
|
@@ -151,6 +150,8 @@ class Timeout():
|
|
|
151
150
|
def evaluate(self, timestamp : float) -> Tuple[bool, Union[float, None]]:
|
|
152
151
|
stop = False
|
|
153
152
|
self._data_strategy = None
|
|
153
|
+
self._stop_source_overtime = '###' # When a timeout occurs, store the value that exceeded its value here
|
|
154
|
+
self._stop_source_limit = '###' # And store the limit value here
|
|
154
155
|
|
|
155
156
|
# First check if the timestamp is None, that would mean the timeout was reached in the queue
|
|
156
157
|
if timestamp is None:
|
|
@@ -158,15 +159,17 @@ class Timeout():
|
|
|
158
159
|
match self._queue_timeout_type:
|
|
159
160
|
case self.TimeoutType.RESPONSE:
|
|
160
161
|
self._data_strategy = self._on_response
|
|
161
|
-
self.
|
|
162
|
+
self._stop_source_limit = self._response
|
|
162
163
|
case self.TimeoutType.CONTINUATION:
|
|
163
164
|
self._data_strategy = self._on_continuation
|
|
164
|
-
self.
|
|
165
|
+
self._stop_source_limit = self._continuation
|
|
165
166
|
case self.TimeoutType.TOTAL:
|
|
166
|
-
self.total_time = self._output_timeout # This is a test
|
|
167
167
|
self._data_strategy = self._on_total
|
|
168
|
+
self._stop_source_limit = self._total
|
|
169
|
+
self._stop_source_overtime = None # We do not have the exceed time, but None will be printed as '---'
|
|
168
170
|
self._last_data_strategy_origin = self._queue_timeout_type
|
|
169
|
-
stop = True
|
|
171
|
+
stop = True
|
|
172
|
+
|
|
170
173
|
else:
|
|
171
174
|
# Check total
|
|
172
175
|
if self._total is not None:
|
|
@@ -175,23 +178,27 @@ class Timeout():
|
|
|
175
178
|
stop = True
|
|
176
179
|
self._data_strategy = self._on_total
|
|
177
180
|
self._last_data_strategy_origin = self.TimeoutType.TOTAL
|
|
181
|
+
self._stop_source_overtime = self.total_time
|
|
182
|
+
self._stop_source_limit = self._total
|
|
178
183
|
# Check continuation
|
|
179
|
-
|
|
180
|
-
if self._continuation is not None and self._state == self._State.CONTINUATION and self._last_timestamp is not None:
|
|
184
|
+
elif self._continuation is not None and self._state == self._State.CONTINUATION and self._last_timestamp is not None:
|
|
181
185
|
continuation_time = timestamp - self._last_timestamp
|
|
182
186
|
self.continuation_times.append(continuation_time)
|
|
183
187
|
if continuation_time >= self._continuation:
|
|
184
188
|
stop = True
|
|
185
189
|
self._data_strategy = self._on_continuation
|
|
186
190
|
self._last_data_strategy_origin = self.TimeoutType.CONTINUATION
|
|
191
|
+
self._stop_source_overtime = continuation_time
|
|
192
|
+
self._stop_source_limit = self._continuation
|
|
187
193
|
# Check response time
|
|
188
|
-
|
|
189
|
-
if self._response is not None and self._state == self._State.WAIT_FOR_RESPONSE:
|
|
194
|
+
elif self._response is not None and self._state == self._State.WAIT_FOR_RESPONSE:
|
|
190
195
|
self.response_time = timestamp - self._start_time
|
|
191
196
|
if self.response_time >= self._response:
|
|
192
197
|
stop = True
|
|
193
198
|
self._data_strategy = self._on_response
|
|
194
199
|
self._last_data_strategy_origin = self.TimeoutType.RESPONSE
|
|
200
|
+
self._stop_source_overtime = self.response_time
|
|
201
|
+
self._stop_source_limit = self._response
|
|
195
202
|
|
|
196
203
|
self._output_timeout = None
|
|
197
204
|
# If we continue
|
|
@@ -236,9 +243,9 @@ class Timeout():
|
|
|
236
243
|
return self._data_strategy, self._last_data_strategy_origin
|
|
237
244
|
|
|
238
245
|
def __str__(self) -> str:
|
|
239
|
-
response = f'r:{self._response:.3f}ms/{self._on_response},' if self._response is not None else ''
|
|
240
|
-
continuation = f'c:{self._continuation:.3f}ms/{self._on_continuation},' if self._continuation is not None else ''
|
|
241
|
-
total = f't:{self._total:.3f}ms/{self._on_total}' if self._total is not None else ''
|
|
246
|
+
response = f'r:{self._response*1e3:.3f}ms/{self._on_response.value},' if self._response is not None else ''
|
|
247
|
+
continuation = f'c:{self._continuation*1e3:.3f}ms/{self._on_continuation.value},' if self._continuation is not None else ''
|
|
248
|
+
total = f't:{self._total*1e3:.3f}ms/{self._on_total.value}' if self._total is not None else ''
|
|
242
249
|
return f'Timeout({response}{continuation}{total})'
|
|
243
250
|
|
|
244
251
|
def __repr__(self) -> str:
|
|
@@ -246,10 +253,28 @@ class Timeout():
|
|
|
246
253
|
|
|
247
254
|
|
|
248
255
|
class TimeoutException(Exception):
|
|
249
|
-
def __init__(self, type : Timeout.TimeoutType) -> None:
|
|
256
|
+
def __init__(self, type : Timeout.TimeoutType, value : float, limit : float) -> None:
|
|
250
257
|
super().__init__()
|
|
251
258
|
self._type = type
|
|
259
|
+
self._value = value
|
|
260
|
+
self._limit = limit
|
|
261
|
+
|
|
262
|
+
def __str__(self) -> str:
|
|
263
|
+
try:
|
|
264
|
+
value_string = f'{self._value*1e3:.3f}ms'
|
|
265
|
+
except (ValueError, TypeError):
|
|
266
|
+
value_string = 'not received'
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
limit_string = f'{self._limit*1e3:.3f}ms'
|
|
270
|
+
except (ValueError, TypeError):
|
|
271
|
+
limit_string = 'not received'
|
|
272
|
+
|
|
252
273
|
|
|
274
|
+
return f'{self._type.value} : {value_string} / {limit_string}'
|
|
275
|
+
|
|
276
|
+
def __repr__(self) -> str:
|
|
277
|
+
return self.__str__()
|
|
253
278
|
|
|
254
279
|
def timeout_fuse(high_priority, low_priority):
|
|
255
280
|
"""
|
|
@@ -292,6 +317,6 @@ def timeout_fuse(high_priority, low_priority):
|
|
|
292
317
|
L = getattr(low, attr)
|
|
293
318
|
# Use low priority if the default value is used in high priority
|
|
294
319
|
new_attr[attr.removeprefix('_')] = L if high._defaults[attr] else H
|
|
295
|
-
|
|
320
|
+
|
|
296
321
|
return Timeout(**new_attr)
|
|
297
322
|
|
|
@@ -6,17 +6,18 @@ from ..tools.types import to_bytes
|
|
|
6
6
|
from typing import Union
|
|
7
7
|
|
|
8
8
|
class VISA(Adapter):
|
|
9
|
-
def __init__(self,
|
|
9
|
+
def __init__(self, resource : str):
|
|
10
10
|
"""
|
|
11
11
|
USB VISA stack adapter
|
|
12
12
|
|
|
13
13
|
Parameters
|
|
14
14
|
----------
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
resource : str
|
|
16
|
+
resource address string
|
|
17
17
|
"""
|
|
18
|
+
self._resource = resource
|
|
18
19
|
self._rm = ResourceManager()
|
|
19
|
-
self._inst = self._rm.open_resource(
|
|
20
|
+
self._inst = self._rm.open_resource(self._resource)
|
|
20
21
|
self._inst.write_termination = ''
|
|
21
22
|
self._inst.read_termination = ''
|
|
22
23
|
|
|
File without changes
|
|
@@ -3,22 +3,45 @@
|
|
|
3
3
|
# 30.04.2024
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from cmd import Cmd
|
|
6
|
-
from
|
|
6
|
+
from ..adapters import *
|
|
7
|
+
from ..protocols import Delimited
|
|
8
|
+
from ..tools.log import set_log_file, set_log_level, LoggerAlias
|
|
7
9
|
import argparse
|
|
10
|
+
import logging
|
|
8
11
|
import shlex
|
|
9
12
|
import sys
|
|
10
13
|
import os
|
|
11
|
-
from colorist import ColorRGB
|
|
14
|
+
#from colorist import ColorRGB
|
|
12
15
|
|
|
13
16
|
VERSION = 0.1
|
|
14
17
|
|
|
15
18
|
class ShellPrompt(Cmd):
|
|
19
|
+
_logger = logging.getLogger(LoggerAlias.CLI.value)
|
|
16
20
|
__hiden_methods = ('do_EOF','do_clear','do_cls')
|
|
17
|
-
PROMPT_COLOR = ColorRGB(28, 90, 145)
|
|
21
|
+
#PROMPT_COLOR = ColorRGB(28, 90, 145)
|
|
18
22
|
|
|
19
|
-
prompt = f'{PROMPT_COLOR}❯ {PROMPT_COLOR.OFF}'
|
|
23
|
+
#prompt = f'{PROMPT_COLOR}❯ {PROMPT_COLOR.OFF}'
|
|
24
|
+
prompt = f'❯ '
|
|
20
25
|
intro = "Welcome to the Syndesi Shell! Type ? to list commands"
|
|
21
26
|
|
|
27
|
+
common_parser = argparse.ArgumentParser(add_help=False)
|
|
28
|
+
common_parser.add_argument('-v', '--verbose', help='Print logging informations', action='store_true', default=False)
|
|
29
|
+
common_parser.add_argument('-d', '--debug', help='Print debug informations', action='store_true', default=False)
|
|
30
|
+
common_parser.add_argument('--log-file', type=str, default='')
|
|
31
|
+
|
|
32
|
+
def _parse_common_args(self, args):
|
|
33
|
+
common_args, other_args = self.common_parser.parse_known_args(args)
|
|
34
|
+
if common_args.debug:
|
|
35
|
+
set_log_level('DEBUG')
|
|
36
|
+
elif common_args.verbose:
|
|
37
|
+
set_log_level('INFO')
|
|
38
|
+
|
|
39
|
+
if common_args.log_file != '': # TODO : test this
|
|
40
|
+
set_log_file(common_args.log_file)
|
|
41
|
+
|
|
42
|
+
return other_args
|
|
43
|
+
|
|
44
|
+
|
|
22
45
|
def get_names(self):
|
|
23
46
|
return [n for n in dir(self.__class__) if n not in self.__hiden_methods]
|
|
24
47
|
|
|
@@ -31,25 +54,32 @@ class ShellPrompt(Cmd):
|
|
|
31
54
|
self._adapter = SerialPort(**SerialPort.shell_parse(inp))
|
|
32
55
|
|
|
33
56
|
def do_ip(self, inp):
|
|
34
|
-
"""Open IP adapter
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
57
|
+
"""Open IP adapter with delimited protocol (\\n at the end of each line by default)"""
|
|
58
|
+
arguments = shlex.split(inp)
|
|
59
|
+
other_arguments = self._parse_common_args(arguments)
|
|
60
|
+
|
|
61
|
+
parser = argparse.ArgumentParser(
|
|
62
|
+
prog='ip'
|
|
63
|
+
)
|
|
64
|
+
parser.add_argument('ip', type=str)
|
|
40
65
|
parser.add_argument('-p', '--port', type=int, required=True)
|
|
41
|
-
parser.add_argument('--ip', type=str, required=True)
|
|
42
66
|
parser.add_argument('-t', '--transport', type=str, choices=['UDP', 'TCP'], default='TCP', required=False)
|
|
67
|
+
parser.add_argument('-e', '--end', help='Newline character, \\n by default, on unix systems, use -e \'\\\\n\' to circumvent backslash substitution', default='\n', required=False)
|
|
43
68
|
try:
|
|
44
|
-
args = parser.parse_args(
|
|
69
|
+
args = parser.parse_args(other_arguments)
|
|
45
70
|
except SystemExit as e:
|
|
46
71
|
pass
|
|
47
72
|
else:
|
|
48
|
-
|
|
73
|
+
end_string : str = args.end
|
|
74
|
+
termination = end_string.encode('utf-8').decode('unicode_escape')
|
|
75
|
+
self._logger.debug(f'Opening Delimited IP with termination : {repr(termination)}')
|
|
76
|
+
self._adapter = Delimited(IP(address=args.ip, port=args.port, transport=args.transport), termination=termination)
|
|
77
|
+
self._adapter._adapter.open()
|
|
78
|
+
print(f"Successfully opened IP adapter at {args.ip}:{args.port} ({repr(termination)} termination)")
|
|
49
79
|
|
|
50
80
|
def default(self, inp):
|
|
51
81
|
if hasattr(self, '_adapter'):
|
|
52
|
-
cmd = inp
|
|
82
|
+
cmd = inp
|
|
53
83
|
output = self._adapter.query(cmd)
|
|
54
84
|
print(output)
|
|
55
85
|
else:
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
# Syndesi CLI
|
|
4
|
+
import argparse
|
|
5
|
+
from enum import Enum
|
|
6
|
+
from syndesi.cli.shell import ShellPrompt
|
|
7
|
+
|
|
8
|
+
class SubCommands(Enum):
|
|
9
|
+
SHELL = 'shell'
|
|
10
|
+
|
|
11
|
+
def main():
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
prog='syndesi',
|
|
14
|
+
description='Syndesi command line interface',
|
|
15
|
+
epilog='')
|
|
16
|
+
# Parse subcommand
|
|
17
|
+
parser.add_argument('subcommand', choices=[SubCommands.SHELL.value])
|
|
18
|
+
|
|
19
|
+
args, extra_args = parser.parse_known_args()
|
|
20
|
+
|
|
21
|
+
if args.subcommand == SubCommands.SHELL.value:
|
|
22
|
+
p = ShellPrompt()
|
|
23
|
+
if len(extra_args):
|
|
24
|
+
p.cmdqueue.append(' '.join(extra_args))
|
|
25
|
+
p.cmdloop()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
if __name__ == '__main__':
|
|
30
|
+
main()
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from .protocol import Protocol
|
|
2
2
|
from ..adapters import Adapter, Timeout, Termination
|
|
3
3
|
from ..tools.types import assert_byte_instance, assert_byte_instance
|
|
4
|
+
from ..tools.others import DEFAULT
|
|
4
5
|
from time import time
|
|
5
6
|
import warnings
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class Delimited(Protocol):
|
|
9
|
-
def __init__(self, adapter : Adapter, termination='\n', format_response=True, encoding : str = 'utf-8', timeout : Timeout =
|
|
10
|
+
def __init__(self, adapter : Adapter, termination='\n', format_response=True, encoding : str = 'utf-8', timeout : Timeout = DEFAULT) -> None:
|
|
10
11
|
"""
|
|
11
12
|
Protocol with delimiter, like LF, CR, etc... '\\n' is used by default
|
|
12
13
|
|
|
@@ -65,7 +66,7 @@ class Delimited(Protocol):
|
|
|
65
66
|
self.write(command)
|
|
66
67
|
return self.read()
|
|
67
68
|
|
|
68
|
-
def read(self, timeout : Timeout =
|
|
69
|
+
def read(self, timeout : Timeout = DEFAULT, decode : str = True) -> str:
|
|
69
70
|
"""
|
|
70
71
|
Reads command and formats it as a str
|
|
71
72
|
|
|
@@ -79,7 +80,10 @@ class Delimited(Protocol):
|
|
|
79
80
|
# Send up to the termination
|
|
80
81
|
data = self._adapter.read(timeout=timeout)
|
|
81
82
|
if decode:
|
|
82
|
-
|
|
83
|
+
try:
|
|
84
|
+
data = data.decode(self._encoding)
|
|
85
|
+
except UnicodeDecodeError as e:
|
|
86
|
+
raise ValueError(f'Failed to decode {data} to {self._encoding} ({e})')
|
|
83
87
|
if self._response_formatting:
|
|
84
88
|
# Only send the fragment (no termination)
|
|
85
89
|
return data
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from ..adapters import Adapter, Timeout
|
|
2
2
|
from .protocol import Protocol
|
|
3
|
+
from ..tools.others import DEFAULT
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
# Raw protocols provide the user with the binary data directly,
|
|
6
7
|
# without converting it to string first
|
|
7
8
|
|
|
8
9
|
class Raw(Protocol):
|
|
9
|
-
def __init__(self, adapter: Adapter, timeout : Timeout =
|
|
10
|
+
def __init__(self, adapter: Adapter, timeout : Timeout = DEFAULT) -> None:
|
|
10
11
|
"""
|
|
11
12
|
Raw device, no presentation and application layers
|
|
12
13
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from ..adapters import Adapter, IP, Timeout, Termination, StopCondition
|
|
2
2
|
from .protocol import Protocol
|
|
3
3
|
from ..tools.types import is_byte_instance
|
|
4
|
-
#from ..tools.others import is_default_argument
|
|
5
4
|
from ..tools.others import DEFAULT
|
|
6
5
|
|
|
6
|
+
DEFAULT_TIMEOUT = Timeout(response=10, continuation=0.5, total=None, on_response='error', on_continuation='error')
|
|
7
|
+
|
|
7
8
|
class SCPI(Protocol):
|
|
8
9
|
DEFAULT_PORT = 5025
|
|
9
|
-
def __init__(self, adapter: Adapter, send_termination = '\n', receive_termination = None, timeout : Timeout =
|
|
10
|
+
def __init__(self, adapter: Adapter, send_termination = '\n', receive_termination = None, timeout : Timeout = DEFAULT, encoding : str = 'utf-8') -> None:
|
|
10
11
|
"""
|
|
11
12
|
SDP (Syndesi Device Protocol) compatible device
|
|
12
13
|
|
|
@@ -20,22 +21,25 @@ class SCPI(Protocol):
|
|
|
20
21
|
timeout : Timeout/float/tuple
|
|
21
22
|
Set device timeout
|
|
22
23
|
"""
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
self._encoding = encoding
|
|
25
|
+
# Set the default timeout
|
|
26
|
+
if timeout == DEFAULT:
|
|
27
|
+
timeout = DEFAULT_TIMEOUT
|
|
28
|
+
|
|
25
29
|
if receive_termination is None:
|
|
26
30
|
self._receive_termination = send_termination
|
|
27
31
|
else:
|
|
28
32
|
self._receive_termination = receive_termination
|
|
29
|
-
|
|
30
33
|
self._send_termination = send_termination
|
|
31
|
-
|
|
32
|
-
if
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
# Configure the adapter for stop-condition mode (timeouts will raise errors)
|
|
35
|
+
if not adapter._default_stop_condition:
|
|
36
|
+
raise ValueError('No stop-conditions can be set for an adapter used by SCPI protocol')
|
|
37
|
+
adapter.set_stop_condition(Termination(self._receive_termination.encode(self._encoding)))
|
|
38
|
+
adapter.set_timeout(timeout)
|
|
39
|
+
if isinstance(adapter, IP):
|
|
40
|
+
adapter.set_default_port(self.DEFAULT_PORT)
|
|
41
|
+
# Give the adapter to the Protocol base class
|
|
42
|
+
super().__init__(adapter=adapter, timeout=timeout)
|
|
39
43
|
|
|
40
44
|
def _to_bytes(self, command):
|
|
41
45
|
if isinstance(command, str):
|
|
@@ -65,12 +69,12 @@ class SCPI(Protocol):
|
|
|
65
69
|
payload = self._to_bytes(self._formatCommand(command))
|
|
66
70
|
self._adapter.write(payload)
|
|
67
71
|
|
|
68
|
-
def query(self, command : str, timeout : Timeout =
|
|
72
|
+
def query(self, command : str, timeout : Timeout = DEFAULT, stop_condition : StopCondition = DEFAULT, return_metrics : bool = False) -> str:
|
|
69
73
|
self._adapter.flushRead()
|
|
70
74
|
self.write(command)
|
|
71
75
|
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
72
76
|
|
|
73
|
-
def read(self, timeout : Timeout =
|
|
77
|
+
def read(self, timeout : Timeout = DEFAULT, stop_condition : StopCondition = None, return_metrics : bool = False) -> str:
|
|
74
78
|
output = self._from_bytes(self._adapter.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics))
|
|
75
79
|
return self._unformatCommand(output)
|
|
76
80
|
|
|
@@ -4,21 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
+
from ..adapters.stop_conditions import Length, Termination, StopCondition
|
|
8
|
+
from ..adapters.timeout import Timeout
|
|
7
9
|
|
|
8
10
|
from ..api.api import APICall, ACTION_ATTRIBUTE, register_api, APIItem
|
|
9
11
|
|
|
10
12
|
class ProxyException(Exception):
|
|
11
13
|
pass
|
|
12
14
|
|
|
13
|
-
# IP specific
|
|
14
|
-
@dataclass
|
|
15
|
-
class IPInstanciate(APICall):
|
|
16
|
-
action = 'ip_adapter_inst'
|
|
17
|
-
address : str
|
|
18
|
-
port : int
|
|
19
|
-
transport : str
|
|
20
|
-
buffer_size : int
|
|
21
|
-
|
|
22
15
|
@dataclass
|
|
23
16
|
class TimeoutAPI(APIItem):
|
|
24
17
|
name = 'timeout'
|
|
@@ -29,10 +22,22 @@ class TimeoutAPI(APIItem):
|
|
|
29
22
|
on_continuation : str
|
|
30
23
|
on_total : str
|
|
31
24
|
|
|
25
|
+
def timeout_to_api(timeout : Timeout) -> TimeoutAPI:
|
|
26
|
+
if timeout is None:
|
|
27
|
+
return None
|
|
28
|
+
else:
|
|
29
|
+
return TimeoutAPI(
|
|
30
|
+
response=timeout._response,
|
|
31
|
+
continuation=timeout._continuation,
|
|
32
|
+
total=timeout._total,
|
|
33
|
+
on_response=timeout._on_response,
|
|
34
|
+
on_continuation=timeout._on_continuation,
|
|
35
|
+
on_total=timeout._on_total
|
|
36
|
+
)
|
|
37
|
+
|
|
32
38
|
@dataclass
|
|
33
39
|
class StopConditionAPI(APIItem):
|
|
34
40
|
pass
|
|
35
|
-
|
|
36
41
|
@dataclass
|
|
37
42
|
class TerminationAPI(StopConditionAPI):
|
|
38
43
|
name = 'termination'
|
|
@@ -43,6 +48,23 @@ class LengthAPI(StopConditionAPI):
|
|
|
43
48
|
name = 'length'
|
|
44
49
|
length : int
|
|
45
50
|
|
|
51
|
+
def stop_condition_to_api(stop_condition : StopCondition):
|
|
52
|
+
if stop_condition is None:
|
|
53
|
+
return None
|
|
54
|
+
elif isinstance(stop_condition, Length):
|
|
55
|
+
return LengthAPI(length=stop_condition._N)
|
|
56
|
+
elif isinstance(stop_condition, Termination):
|
|
57
|
+
return TerminationAPI(sequence=stop_condition._termination)
|
|
58
|
+
# IP specific
|
|
59
|
+
@dataclass
|
|
60
|
+
class IPInstanciate(APICall):
|
|
61
|
+
action = 'ip_adapter_inst'
|
|
62
|
+
address : str
|
|
63
|
+
port : int
|
|
64
|
+
transport : str
|
|
65
|
+
buffer_size : int
|
|
66
|
+
timeout : TimeoutAPI
|
|
67
|
+
|
|
46
68
|
# Serial specific
|
|
47
69
|
@dataclass
|
|
48
70
|
class SerialPortInstanciate(APICall):
|
|
@@ -53,6 +75,13 @@ class SerialPortInstanciate(APICall):
|
|
|
53
75
|
stop_condition : StopConditionAPI
|
|
54
76
|
rts_cts : bool
|
|
55
77
|
|
|
78
|
+
# VISA specific
|
|
79
|
+
@dataclass
|
|
80
|
+
class VisaInstanciate(APICall):
|
|
81
|
+
action = 'visa_instanciate'
|
|
82
|
+
resource : str
|
|
83
|
+
|
|
84
|
+
|
|
56
85
|
# Adapters common
|
|
57
86
|
@dataclass
|
|
58
87
|
class AdapterOpen(APICall):
|
|
@@ -9,9 +9,10 @@ import logging
|
|
|
9
9
|
from typing import List, Union
|
|
10
10
|
|
|
11
11
|
class LoggerAlias(Enum):
|
|
12
|
-
ADAPTER = 'adapter'
|
|
13
|
-
PROTOCOL = 'protocol'
|
|
14
|
-
PROXY_SERVER = 'proxy_server'
|
|
12
|
+
ADAPTER = 'syndesi.adapter'
|
|
13
|
+
PROTOCOL = 'syndesi.protocol'
|
|
14
|
+
PROXY_SERVER = 'syndesi.proxy_server'
|
|
15
|
+
CLI = 'syndesi.cli'
|
|
15
16
|
|
|
16
17
|
default_formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s:%(message)s')
|
|
17
18
|
|
|
@@ -53,21 +54,19 @@ def set_log_file(file : str, level : Union[str, int], loggers : Union[List[Logge
|
|
|
53
54
|
file_handler.setFormatter(default_formatter)
|
|
54
55
|
# 3) Add to the designated loggers
|
|
55
56
|
for l in LoggerAlias:
|
|
56
|
-
if
|
|
57
|
+
if loggers == 'all' or l.value in loggers:
|
|
57
58
|
logger = logging.getLogger(l.value)
|
|
58
59
|
logger.addHandler(file_handler)
|
|
59
60
|
logger.setLevel(level)
|
|
60
61
|
else:
|
|
61
62
|
file_handler = None
|
|
62
63
|
|
|
63
|
-
def
|
|
64
|
+
def set_log_level(level : Union[str, int], loggers : Union[List[LoggerAlias], str] = 'all'):
|
|
64
65
|
"""
|
|
65
|
-
Set stdout/stderr
|
|
66
|
+
Set log level, everything below or equal to the given level will be outputed to stdout/stderr.
|
|
66
67
|
|
|
67
68
|
Parameters
|
|
68
69
|
----------
|
|
69
|
-
enabled : bool
|
|
70
|
-
enable / disable log to stdout / stderr
|
|
71
70
|
level : str or logging level
|
|
72
71
|
info : 'INFO' or logging.INFO
|
|
73
72
|
critical : 'CRITICAL' or logging.CRITICAL
|
|
@@ -75,6 +74,7 @@ def set_log_stream(enabled : bool, level : Union[str, int], loggers : Union[List
|
|
|
75
74
|
warning : 'WARNING' or logging.WARNING
|
|
76
75
|
info : 'INFO' or logging.INFO
|
|
77
76
|
debug : 'DEBUG' or logging.DEBUG
|
|
77
|
+
None will disable logging
|
|
78
78
|
loggers : list of LoggerAlias or 'all'
|
|
79
79
|
Which loggers to save to the file, 'all' by default
|
|
80
80
|
"""
|
|
@@ -93,7 +93,7 @@ def set_log_stream(enabled : bool, level : Union[str, int], loggers : Union[List
|
|
|
93
93
|
if isinstance(h, logging.StreamHandler):
|
|
94
94
|
logger.removeHandler(h)
|
|
95
95
|
|
|
96
|
-
if
|
|
96
|
+
if level is not None:
|
|
97
97
|
# 2) Create the new stream handler
|
|
98
98
|
stream_handler = logging.StreamHandler()
|
|
99
99
|
stream_handler.setFormatter(default_formatter)
|
|
@@ -20,6 +20,9 @@ syndesi/adapters/timeout.py
|
|
|
20
20
|
syndesi/adapters/visa.py
|
|
21
21
|
syndesi/api/__init__.py
|
|
22
22
|
syndesi/api/api.py
|
|
23
|
+
syndesi/cli/__init__.py
|
|
24
|
+
syndesi/cli/shell.py
|
|
25
|
+
syndesi/cli/syndesi.py
|
|
23
26
|
syndesi/protocols/__init__.py
|
|
24
27
|
syndesi/protocols/delimited.py
|
|
25
28
|
syndesi/protocols/protocol.py
|
|
@@ -33,5 +36,4 @@ syndesi/tools/__init__.py
|
|
|
33
36
|
syndesi/tools/exceptions.py
|
|
34
37
|
syndesi/tools/log.py
|
|
35
38
|
syndesi/tools/others.py
|
|
36
|
-
syndesi/tools/shell.py
|
|
37
39
|
syndesi/tools/types.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|