syndesi 0.2.2__tar.gz → 0.2.4__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.4}/PKG-INFO +2 -3
- {syndesi-0.2.2 → syndesi-0.2.4}/README.md +1 -1
- {syndesi-0.2.2 → syndesi-0.2.4}/setup.py +7 -3
- syndesi-0.2.4/syndesi/__init__.py +4 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/adapter.py +108 -64
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/auto.py +1 -1
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/ip.py +74 -45
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/proxy.py +15 -8
- syndesi-0.2.4/syndesi/adapters/serialport.py +203 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/stop_conditions.py +39 -2
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/timeout.py +95 -63
- syndesi-0.2.4/syndesi/adapters/visa.py +103 -0
- {syndesi-0.2.2/syndesi → syndesi-0.2.4/syndesi/api}/__init__.py +0 -0
- syndesi-0.2.4/syndesi/cli/adapter.py +134 -0
- syndesi-0.2.4/syndesi/cli/command.py +29 -0
- syndesi-0.2.4/syndesi/cli/syndesi.py +45 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/__init__.py +2 -1
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/delimited.py +15 -6
- syndesi-0.2.4/syndesi/protocols/modbus.py +1303 -0
- syndesi-0.2.4/syndesi/protocols/protocol.py +24 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/raw.py +1 -1
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/scpi.py +22 -18
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/proxy/proxy_api.py +39 -10
- syndesi-0.2.4/syndesi/tools/log.py +75 -0
- syndesi-0.2.4/syndesi/version.py +1 -0
- {syndesi-0.2.2 → syndesi-0.2.4/syndesi.egg-info}/PKG-INFO +2 -3
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi.egg-info/SOURCES.txt +6 -3
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi.egg-info/entry_points.txt +1 -1
- syndesi-0.2.2/LICENSE +0 -674
- syndesi-0.2.2/syndesi/adapters/serialport.py +0 -139
- syndesi-0.2.2/syndesi/adapters/visa.py +0 -56
- syndesi-0.2.2/syndesi/protocols/protocol.py +0 -17
- syndesi-0.2.2/syndesi/tools/log.py +0 -107
- syndesi-0.2.2/syndesi/tools/others.py +0 -1
- syndesi-0.2.2/syndesi/tools/shell.py +0 -111
- {syndesi-0.2.2 → syndesi-0.2.4}/setup.cfg +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/ip_server.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/adapters/timed_queue.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/api/api.py +0 -0
- {syndesi-0.2.2/syndesi/api → syndesi-0.2.4/syndesi/cli}/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/protocols/sdp.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/proxy/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/proxy/proxy.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/tools/__init__.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/tools/exceptions.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi/tools/types.py +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/syndesi.egg-info/dependency_links.txt +0 -0
- {syndesi-0.2.2 → syndesi-0.2.4}/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.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Syndesi
|
|
5
5
|
Author: Sebastien Deriaz
|
|
6
6
|
Author-email: sebastien.deriaz1@gmail.com
|
|
@@ -12,7 +12,6 @@ Classifier: Operating System :: Unix
|
|
|
12
12
|
Classifier: Operating System :: MacOS :: MacOS X
|
|
13
13
|
Classifier: Operating System :: Microsoft :: Windows
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
License-File: LICENSE
|
|
16
15
|
|
|
17
16
|
# Syndesi Python Implementation
|
|
18
17
|
|
|
@@ -40,7 +39,7 @@ To instantiate a device, one must import the device and a suitable adapter
|
|
|
40
39
|
|
|
41
40
|
```python
|
|
42
41
|
# 1) Import the device
|
|
43
|
-
from syndesi.drivers.instruments.
|
|
42
|
+
from syndesi.drivers.instruments.multimeters.siglent.SDM3055 import SDM3055
|
|
44
43
|
# 2) Import the adapter
|
|
45
44
|
from syndesi.adapters import IP
|
|
46
45
|
|
|
@@ -24,7 +24,7 @@ To instantiate a device, one must import the device and a suitable adapter
|
|
|
24
24
|
|
|
25
25
|
```python
|
|
26
26
|
# 1) Import the device
|
|
27
|
-
from syndesi.drivers.instruments.
|
|
27
|
+
from syndesi.drivers.instruments.multimeters.siglent.SDM3055 import SDM3055
|
|
28
28
|
# 2) Import the adapter
|
|
29
29
|
from syndesi.adapters import IP
|
|
30
30
|
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
from setuptools import setup, find_packages
|
|
2
2
|
|
|
3
|
-
VERSION = '0.2.2'
|
|
4
3
|
DESCRIPTION = 'Syndesi'
|
|
5
4
|
|
|
5
|
+
__version__ : str
|
|
6
|
+
# Load __version__ from file
|
|
7
|
+
with open('syndesi/version.py', 'r', encoding='utf-8') as f:
|
|
8
|
+
exec(f.read())
|
|
9
|
+
|
|
6
10
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
7
11
|
long_description = fh.read()
|
|
8
12
|
|
|
9
13
|
# Setting up
|
|
10
14
|
setup(
|
|
11
15
|
name="syndesi",
|
|
12
|
-
version=
|
|
16
|
+
version=__version__,
|
|
13
17
|
author="Sebastien Deriaz",
|
|
14
18
|
author_email="sebastien.deriaz1@gmail.com",
|
|
15
19
|
description=DESCRIPTION,
|
|
@@ -17,7 +21,7 @@ setup(
|
|
|
17
21
|
long_description=long_description,
|
|
18
22
|
entry_points = {
|
|
19
23
|
'console_scripts': [
|
|
20
|
-
'syndesi=syndesi.
|
|
24
|
+
'syndesi=syndesi.cli.syndesi:main',
|
|
21
25
|
'syndesi-proxy=syndesi.proxy.proxy:main'],
|
|
22
26
|
},
|
|
23
27
|
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,11 @@ 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
|
-
from ..tools.others import DEFAULT
|
|
33
32
|
|
|
34
|
-
DEFAULT_TIMEOUT = Timeout(response=1, continuation=100e-3, total=None)
|
|
35
33
|
DEFAULT_STOP_CONDITION = None
|
|
36
34
|
|
|
37
35
|
|
|
@@ -48,7 +46,7 @@ STOP_DESIGNATORS = {
|
|
|
48
46
|
Termination : 'ST',
|
|
49
47
|
Length : 'SL'
|
|
50
48
|
},
|
|
51
|
-
'previous-
|
|
49
|
+
'previous-buffer' : 'PB'
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
class Origin(Enum):
|
|
@@ -72,7 +70,7 @@ class Adapter(ABC):
|
|
|
72
70
|
DISCONNECTED = 0
|
|
73
71
|
CONNECTED = 1
|
|
74
72
|
|
|
75
|
-
def __init__(self, alias : str = '', stop_condition : Union[StopCondition, None] =
|
|
73
|
+
def __init__(self, alias : str = '', stop_condition : Union[StopCondition, None] = ..., timeout : Union[float, Timeout] = ...) -> None:
|
|
76
74
|
"""
|
|
77
75
|
Adapter instance
|
|
78
76
|
|
|
@@ -81,37 +79,52 @@ class Adapter(ABC):
|
|
|
81
79
|
alias : str
|
|
82
80
|
The alias is used to identify the class in the logs
|
|
83
81
|
timeout : float or Timeout instance
|
|
84
|
-
Default timeout is Timeout(response=
|
|
82
|
+
Default timeout is Timeout(response=5, continuation=0.2, total=None)
|
|
85
83
|
stop_condition : StopCondition or None
|
|
86
84
|
Default to None
|
|
87
85
|
"""
|
|
88
86
|
super().__init__()
|
|
87
|
+
|
|
89
88
|
self._alias = alias
|
|
90
89
|
|
|
91
|
-
self.
|
|
90
|
+
self.is_default_timeout = timeout is Ellipsis
|
|
91
|
+
if self.is_default_timeout:
|
|
92
|
+
self._timeout = self._default_timeout()
|
|
93
|
+
else:
|
|
94
|
+
self._timeout = timeout_fuse(timeout, self._default_timeout())
|
|
95
|
+
|
|
96
|
+
self._default_stop_condition = stop_condition is Ellipsis
|
|
92
97
|
if self._default_stop_condition:
|
|
93
98
|
self._stop_condition = DEFAULT_STOP_CONDITION
|
|
94
99
|
else:
|
|
95
100
|
self._stop_condition = stop_condition
|
|
101
|
+
|
|
96
102
|
self._read_queue = TimedQueue()
|
|
97
103
|
self._thread : Union[Thread, None] = None
|
|
98
104
|
self._status = self.Status.DISCONNECTED
|
|
99
105
|
self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
|
|
106
|
+
self._thread_stop_read, self._thread_stop_write = socket.socketpair()
|
|
100
107
|
|
|
101
108
|
# Buffer for data that has been pulled from the queue but
|
|
102
109
|
# not used because of termination or length stop condition
|
|
103
|
-
self.
|
|
110
|
+
self._previous_buffer = b''
|
|
104
111
|
|
|
105
|
-
self.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
if not isinstance(self._timeout, Timeout):
|
|
113
|
+
raise ValueError('Timeout must be defined to initialize an Adapter base class')
|
|
114
|
+
|
|
115
|
+
@abstractmethod
|
|
116
|
+
def _default_timeout(self):
|
|
117
|
+
pass
|
|
118
|
+
|
|
119
|
+
def set_timeout(self, timeout : Timeout):
|
|
120
|
+
"""
|
|
121
|
+
Overwrite timeout
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
timeout : Timeout
|
|
126
|
+
"""
|
|
127
|
+
self._timeout = timeout
|
|
115
128
|
|
|
116
129
|
def set_default_timeout(self, default_timeout : Union[Timeout, tuple, float]):
|
|
117
130
|
"""
|
|
@@ -121,10 +134,23 @@ class Adapter(ABC):
|
|
|
121
134
|
----------
|
|
122
135
|
default_timeout : Timeout or tuple or float
|
|
123
136
|
"""
|
|
124
|
-
if self.
|
|
137
|
+
if self.is_default_timeout:
|
|
138
|
+
self._logger.debug(f'Setting default timeout to {default_timeout}')
|
|
125
139
|
self._timeout = default_timeout
|
|
126
140
|
else:
|
|
141
|
+
log = f'Fusing timeouts {self._timeout}+{default_timeout} -> '
|
|
127
142
|
self._timeout = timeout_fuse(self._timeout, default_timeout)
|
|
143
|
+
self._logger.debug(f'{log}{self._timeout}')
|
|
144
|
+
|
|
145
|
+
def set_stop_condition(self, stop_condition):
|
|
146
|
+
"""
|
|
147
|
+
Overwrite the stop-condition
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
stop_condition : StopCondition
|
|
152
|
+
"""
|
|
153
|
+
self._stop_condition = stop_condition
|
|
128
154
|
|
|
129
155
|
def set_default_stop_condition(self, stop_condition):
|
|
130
156
|
"""
|
|
@@ -142,7 +168,17 @@ class Adapter(ABC):
|
|
|
142
168
|
Flush the input buffer
|
|
143
169
|
"""
|
|
144
170
|
self._read_queue.clear()
|
|
145
|
-
self.
|
|
171
|
+
self._previous_buffer = b''
|
|
172
|
+
|
|
173
|
+
def previous_read_buffer_empty(self):
|
|
174
|
+
"""
|
|
175
|
+
Check whether the previous read buffer is empty
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
empty : bool
|
|
180
|
+
"""
|
|
181
|
+
return self._previous_buffer == b''
|
|
146
182
|
|
|
147
183
|
@abstractmethod
|
|
148
184
|
def open(self):
|
|
@@ -151,12 +187,12 @@ class Adapter(ABC):
|
|
|
151
187
|
"""
|
|
152
188
|
pass
|
|
153
189
|
|
|
154
|
-
@abstractmethod
|
|
155
190
|
def close(self):
|
|
156
191
|
"""
|
|
157
192
|
Stop communication with the device
|
|
158
193
|
"""
|
|
159
|
-
|
|
194
|
+
self._logger.debug('Closing adapter and stopping read thread')
|
|
195
|
+
self._thread_stop_write.send(b'1')
|
|
160
196
|
|
|
161
197
|
@abstractmethod
|
|
162
198
|
def write(self, data : Union[bytes, str]):
|
|
@@ -168,12 +204,33 @@ class Adapter(ABC):
|
|
|
168
204
|
data : bytes or str
|
|
169
205
|
"""
|
|
170
206
|
pass
|
|
207
|
+
|
|
208
|
+
@abstractmethod
|
|
209
|
+
def read(self, timeout : Timeout = ..., stop_condition : StopCondition = ..., return_metrics : bool = False) -> bytes:
|
|
210
|
+
pass
|
|
171
211
|
|
|
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
212
|
|
|
213
|
+
@abstractmethod
|
|
214
|
+
def _start_thread(self):
|
|
215
|
+
self._logger.debug("Starting read thread...")
|
|
216
|
+
|
|
217
|
+
def __del__(self):
|
|
218
|
+
self.close()
|
|
175
219
|
|
|
176
|
-
def
|
|
220
|
+
def query(self, data : Union[bytes, str], timeout : Timeout = ..., stop_condition : StopCondition = ..., return_metrics : bool = False) -> bytes:
|
|
221
|
+
"""
|
|
222
|
+
Shortcut function that combines
|
|
223
|
+
- flush_read
|
|
224
|
+
- write
|
|
225
|
+
- read
|
|
226
|
+
"""
|
|
227
|
+
self.flushRead()
|
|
228
|
+
self.write(data)
|
|
229
|
+
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class StreamAdapter(Adapter):
|
|
233
|
+
def read(self, timeout=..., stop_condition=..., return_metrics : bool = False) -> bytes:
|
|
177
234
|
"""
|
|
178
235
|
Read data from the device
|
|
179
236
|
|
|
@@ -189,35 +246,40 @@ class Adapter(ABC):
|
|
|
189
246
|
if self._status == self.Status.DISCONNECTED:
|
|
190
247
|
self.open()
|
|
191
248
|
|
|
192
|
-
#
|
|
193
|
-
if timeout is
|
|
249
|
+
# 29.08.24 Change timeout behavior
|
|
250
|
+
if timeout is ...:
|
|
251
|
+
# Use the class timeout
|
|
194
252
|
timeout = self._timeout
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
253
|
+
else:
|
|
254
|
+
# Fuse it
|
|
255
|
+
timeout = timeout_fuse(timeout, self._timeout)
|
|
198
256
|
|
|
199
|
-
if stop_condition is
|
|
257
|
+
if stop_condition is ...:
|
|
200
258
|
stop_condition = self._stop_condition
|
|
201
259
|
|
|
202
260
|
# If the adapter is closed, open it
|
|
203
261
|
if self._status == self.Status.DISCONNECTED:
|
|
204
262
|
self.open()
|
|
205
263
|
|
|
206
|
-
|
|
207
|
-
self._start_thread()
|
|
208
|
-
|
|
209
|
-
timeout_ms = timeout.initiate_read(len(self._previous_read_buffer) > 0)
|
|
264
|
+
timeout_ms = timeout.initiate_read(len(self._previous_buffer) > 0)
|
|
210
265
|
|
|
211
266
|
if stop_condition is not None:
|
|
212
267
|
stop_condition.initiate_read()
|
|
213
268
|
|
|
269
|
+
|
|
214
270
|
deferred_buffer = b''
|
|
215
271
|
|
|
216
272
|
# Start with the deferred buffer
|
|
217
273
|
# TODO : Check if data could be lost here, like the data is put in the previous_read_buffer and is never
|
|
218
274
|
# read back again because there's no stop condition
|
|
219
|
-
if len(self.
|
|
220
|
-
|
|
275
|
+
if len(self._previous_buffer) > 0:# and stop_condition is not None:
|
|
276
|
+
self._logger.debug(f'Using previous buffer ({self._previous_buffer})')
|
|
277
|
+
if stop_condition is not None:
|
|
278
|
+
stop, output, self._previous_buffer = stop_condition.evaluate(self._previous_buffer)
|
|
279
|
+
else:
|
|
280
|
+
stop = True
|
|
281
|
+
output = self._previous_buffer
|
|
282
|
+
self._previous_buffer = b''
|
|
221
283
|
previous_read_buffer_used = True
|
|
222
284
|
else:
|
|
223
285
|
stop = False
|
|
@@ -231,8 +293,8 @@ class Adapter(ABC):
|
|
|
231
293
|
(timestamp, fragment) = self._read_queue.get(timeout_ms)
|
|
232
294
|
n_fragments += 1
|
|
233
295
|
|
|
234
|
-
if fragment
|
|
235
|
-
raise
|
|
296
|
+
if isinstance(fragment, AdapterDisconnected):
|
|
297
|
+
raise fragment
|
|
236
298
|
|
|
237
299
|
# 1) Evaluate the timeout
|
|
238
300
|
stop, timeout_ms = timeout.evaluate(timestamp)
|
|
@@ -248,10 +310,10 @@ class Adapter(ABC):
|
|
|
248
310
|
output += fragment
|
|
249
311
|
elif data_strategy == Timeout.OnTimeoutStrategy.STORE:
|
|
250
312
|
# Store the data
|
|
251
|
-
self.
|
|
313
|
+
self._previous_buffer = output
|
|
252
314
|
output = b''
|
|
253
315
|
elif data_strategy == Timeout.OnTimeoutStrategy.ERROR:
|
|
254
|
-
raise TimeoutException(origin)
|
|
316
|
+
raise TimeoutException(origin, timeout._stop_source_overtime, timeout._stop_source_limit)
|
|
255
317
|
break
|
|
256
318
|
else:
|
|
257
319
|
origin = None
|
|
@@ -267,7 +329,7 @@ class Adapter(ABC):
|
|
|
267
329
|
stop, kept_fragment, deferred_buffer = stop_condition.evaluate(fragment)
|
|
268
330
|
output += kept_fragment
|
|
269
331
|
if stop:
|
|
270
|
-
self.
|
|
332
|
+
self._previous_buffer = deferred_buffer
|
|
271
333
|
else:
|
|
272
334
|
output += fragment
|
|
273
335
|
if stop:
|
|
@@ -279,11 +341,11 @@ class Adapter(ABC):
|
|
|
279
341
|
else:
|
|
280
342
|
designator = STOP_DESIGNATORS['stop_condition'][type(stop_condition)]
|
|
281
343
|
else:
|
|
282
|
-
designator = STOP_DESIGNATORS['previous-
|
|
344
|
+
designator = STOP_DESIGNATORS['previous-buffer']
|
|
283
345
|
|
|
284
346
|
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.
|
|
347
|
+
if self._previous_buffer:
|
|
348
|
+
self._logger.debug(f'Read [{designator}, {read_duration*1e3:.3f}ms] : {output} , previous read buffer : {self._previous_buffer}')
|
|
287
349
|
else:
|
|
288
350
|
self._logger.debug(f'Read [{designator}, {read_duration*1e3:.3f}ms] : {output}')
|
|
289
351
|
|
|
@@ -300,22 +362,4 @@ class Adapter(ABC):
|
|
|
300
362
|
total_time=timeout.total_time
|
|
301
363
|
)
|
|
302
364
|
else:
|
|
303
|
-
return output
|
|
304
|
-
|
|
305
|
-
@abstractmethod
|
|
306
|
-
def _start_thread(self):
|
|
307
|
-
pass
|
|
308
|
-
|
|
309
|
-
def __del__(self):
|
|
310
|
-
self.close()
|
|
311
|
-
|
|
312
|
-
@abstractmethod
|
|
313
|
-
def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
|
|
314
|
-
"""
|
|
315
|
-
Shortcut function that combines
|
|
316
|
-
- flush_read
|
|
317
|
-
- write
|
|
318
|
-
- read
|
|
319
|
-
"""
|
|
320
|
-
pass
|
|
321
|
-
|
|
365
|
+
return output
|
|
@@ -17,7 +17,7 @@ from typing import Union
|
|
|
17
17
|
import re
|
|
18
18
|
from . import Adapter, IP, SerialPort
|
|
19
19
|
|
|
20
|
-
IP_PATTERN = '([0-9]
|
|
20
|
+
IP_PATTERN = '([0-9]+.[0-9]+.[0-9]+.[0-9]+)(:[0-9]+)*'
|
|
21
21
|
|
|
22
22
|
WINDOWS_SERIAL_PATTERN = '(COM[0-9]+)(:[0-9]+)*'
|
|
23
23
|
LINUX_SERIAL_PATTERN = '(/dev/tty[a-zA-Z0-9]+)(:[0-9]+)*'
|
|
@@ -1,26 +1,19 @@
|
|
|
1
1
|
import socket
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from .adapter import
|
|
3
|
+
from .adapter import StreamAdapter, 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
|
+
import select
|
|
12
14
|
|
|
13
|
-
class IP(
|
|
14
|
-
|
|
15
|
-
DEFAULT_CONTINUATION_TIMEOUT = 1e-3
|
|
16
|
-
DEFAULT_TOTAL_TIMEOUT = 5
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
DEFAULT_TIMEOUT = Timeout(
|
|
20
|
-
response=DEFAULT_RESPONSE_TIMEOUT,
|
|
21
|
-
continuation=DEFAULT_CONTINUATION_TIMEOUT,
|
|
22
|
-
total=DEFAULT_TOTAL_TIMEOUT)
|
|
23
|
-
DEFAULT_BUFFER_SIZE = 1024
|
|
15
|
+
class IP(StreamAdapter):
|
|
16
|
+
_DEFAULT_BUFFER_SIZE = 1024
|
|
24
17
|
class Protocol(Enum):
|
|
25
18
|
TCP = 'TCP'
|
|
26
19
|
UDP = 'UDP'
|
|
@@ -29,10 +22,10 @@ class IP(Adapter):
|
|
|
29
22
|
address : str,
|
|
30
23
|
port : int = None,
|
|
31
24
|
transport : str = 'TCP',
|
|
32
|
-
timeout : Union[Timeout, float] =
|
|
33
|
-
stop_condition =
|
|
25
|
+
timeout : Union[Timeout, float] = ...,
|
|
26
|
+
stop_condition : StopCondition = ...,
|
|
34
27
|
alias : str = '',
|
|
35
|
-
buffer_size : int =
|
|
28
|
+
buffer_size : int = _DEFAULT_BUFFER_SIZE,
|
|
36
29
|
_socket : socket.socket = None):
|
|
37
30
|
"""
|
|
38
31
|
IP stack adapter
|
|
@@ -55,8 +48,9 @@ class IP(Adapter):
|
|
|
55
48
|
Socket buffer size, may be removed in the future
|
|
56
49
|
socket : socket.socket
|
|
57
50
|
Specify a custom socket, this is reserved for server application
|
|
58
|
-
"""
|
|
51
|
+
"""
|
|
59
52
|
super().__init__(alias=alias, timeout=timeout, stop_condition=stop_condition)
|
|
53
|
+
|
|
60
54
|
self._transport = self.Protocol(transport)
|
|
61
55
|
self._is_server = _socket is not None
|
|
62
56
|
|
|
@@ -75,6 +69,21 @@ class IP(Adapter):
|
|
|
75
69
|
self._port = port
|
|
76
70
|
self._buffer_size = buffer_size
|
|
77
71
|
|
|
72
|
+
def __str__(self) -> str:
|
|
73
|
+
return f'IP({self._address}:{self._port})'
|
|
74
|
+
|
|
75
|
+
def __repr__(self) -> str:
|
|
76
|
+
return self.__str__()
|
|
77
|
+
|
|
78
|
+
def _default_timeout(self):
|
|
79
|
+
return Timeout(
|
|
80
|
+
response=5,
|
|
81
|
+
on_response='error',
|
|
82
|
+
continuation=100e-3,
|
|
83
|
+
on_continuation='return',
|
|
84
|
+
total=5,
|
|
85
|
+
on_total='error')
|
|
86
|
+
|
|
78
87
|
def set_default_port(self, port):
|
|
79
88
|
"""
|
|
80
89
|
Sets IP port if no port has been set yet.
|
|
@@ -95,12 +104,22 @@ class IP(Adapter):
|
|
|
95
104
|
if self._port is None:
|
|
96
105
|
raise ValueError(f"Cannot open adapter without specifying a port")
|
|
97
106
|
|
|
98
|
-
self._logger.debug(f"Adapter {self._alias}
|
|
107
|
+
self._logger.debug(f"Adapter {self._alias + ' ' if self._alias != '' else ''}connect to ({self._address}, {self._port})")
|
|
99
108
|
self._socket.connect((self._address, self._port))
|
|
100
109
|
self._status = self.Status.CONNECTED
|
|
110
|
+
if self._thread is None or not self._thread.is_alive():
|
|
111
|
+
self._start_thread()
|
|
112
|
+
|
|
101
113
|
self._logger.info(f"Adapter {self._alias} opened !")
|
|
102
114
|
|
|
103
115
|
def close(self):
|
|
116
|
+
super().close()
|
|
117
|
+
if self._thread is not None and self._thread.is_alive():
|
|
118
|
+
try:
|
|
119
|
+
self._thread.join()
|
|
120
|
+
except RuntimeError:
|
|
121
|
+
# If the thread cannot be joined, then so be it
|
|
122
|
+
pass
|
|
104
123
|
if hasattr(self, '_socket'):
|
|
105
124
|
self._socket.close()
|
|
106
125
|
self._logger.info("Adapter closed !")
|
|
@@ -109,42 +128,52 @@ class IP(Adapter):
|
|
|
109
128
|
def write(self, data : Union[bytes, str]):
|
|
110
129
|
data = to_bytes(data)
|
|
111
130
|
if self._status == self.Status.DISCONNECTED:
|
|
112
|
-
self._logger.info(f"Adapter {self._alias}
|
|
131
|
+
self._logger.info(f"Adapter {self._alias + ' ' if self._alias != '' else ''}is closed, opening...")
|
|
113
132
|
self.open()
|
|
114
133
|
write_start = time()
|
|
115
134
|
self._socket.send(data)
|
|
116
135
|
write_duration = time() - write_start
|
|
117
|
-
self._logger.debug(f"
|
|
136
|
+
self._logger.debug(f"Write [{write_duration*1e3:.3f}ms]: {repr(data)}")
|
|
118
137
|
|
|
119
138
|
def _start_thread(self):
|
|
120
|
-
|
|
121
|
-
self._thread
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def _read_thread(self, socket : socket.socket, read_queue : TimedQueue):
|
|
130
|
-
while True: # TODO : Add stop_pipe ? Maybe it was removed ?
|
|
139
|
+
super()._start_thread()
|
|
140
|
+
if self._thread is None or not self._thread.is_alive():
|
|
141
|
+
self._thread = Thread(target=self._read_thread, daemon=True, args=(self._socket, self._read_queue, self._thread_stop_read))
|
|
142
|
+
self._thread.start()
|
|
143
|
+
|
|
144
|
+
def _read_thread(self, socket : socket.socket, read_queue : TimedQueue, stop : socket.socket):
|
|
145
|
+
# Using select.select works on both Windows and Linux as long as the inputs are all sockets
|
|
146
|
+
while True:
|
|
147
|
+
|
|
131
148
|
try:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
ready, _, _ = select.select([socket, stop], [], [])
|
|
150
|
+
except ValueError:
|
|
151
|
+
# File desctiptor is s negative integer
|
|
152
|
+
read_queue.put(AdapterDisconnected())
|
|
153
|
+
else:
|
|
154
|
+
if stop in ready:
|
|
155
|
+
# Stop the thread
|
|
156
|
+
stop.recv(1)
|
|
157
|
+
break
|
|
158
|
+
elif socket in ready:
|
|
159
|
+
# Read from the socket
|
|
160
|
+
try:
|
|
161
|
+
payload = socket.recv(self._buffer_size)
|
|
162
|
+
except ConnectionRefusedError:
|
|
163
|
+
# TODO : Check if this is the right way of doing it
|
|
164
|
+
read_queue.put(AdapterDisconnected())
|
|
165
|
+
else:
|
|
166
|
+
if len(payload) == self._buffer_size and self._transport == self.Protocol.UDP:
|
|
167
|
+
self._logger.warning("Warning, inbound UDP data may have been lost (max buffer size attained)")
|
|
168
|
+
if payload == b'':
|
|
169
|
+
read_queue.put(AdapterDisconnected())
|
|
170
|
+
break
|
|
171
|
+
else:
|
|
172
|
+
read_queue.put(payload)
|
|
142
173
|
|
|
143
174
|
def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
|
|
144
175
|
if self._is_server:
|
|
145
176
|
raise SystemError("Cannot query on server adapters")
|
|
146
|
-
|
|
147
|
-
self.write(data)
|
|
148
|
-
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
177
|
+
return super().query(data=data, timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
149
178
|
|
|
150
179
|
|
|
@@ -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())))
|