syndesi 0.1.6__tar.gz → 0.2.0__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.1.6/syndesi.egg-info → syndesi-0.2.0}/PKG-INFO +1 -1
- {syndesi-0.1.6 → syndesi-0.2.0}/setup.py +6 -2
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/__init__.py +2 -1
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/adapter.py +47 -30
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/ip.py +52 -26
- syndesi-0.2.0/syndesi/adapters/ip_server.py +108 -0
- syndesi-0.2.0/syndesi/adapters/proxy.py +92 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/serialport.py +27 -3
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/stop_conditions.py +7 -1
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/timeout.py +6 -5
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/protocols/scpi.py +3 -2
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/tools/log.py +1 -0
- syndesi-0.2.0/syndesi/tools/others.py +1 -0
- syndesi-0.2.0/syndesi/tools/shell.py +111 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/tools/types.py +1 -1
- {syndesi-0.1.6 → syndesi-0.2.0/syndesi.egg-info}/PKG-INFO +1 -1
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi.egg-info/SOURCES.txt +4 -2
- syndesi-0.2.0/syndesi.egg-info/entry_points.txt +3 -0
- syndesi-0.1.6/bin/syndesi +0 -57
- syndesi-0.1.6/syndesi/adapters/remote.py +0 -18
- syndesi-0.1.6/syndesi/tools/others.py +0 -10
- {syndesi-0.1.6 → syndesi-0.2.0}/LICENSE +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/README.md +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/setup.cfg +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/__init__.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/auto.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/timed_queue.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/adapters/visa.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/protocols/__init__.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/protocols/delimited.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/protocols/protocol.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/protocols/raw.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/protocols/sdp.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/tools/__init__.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi/tools/exceptions.py +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/syndesi.egg-info/dependency_links.txt +0 -0
- {syndesi-0.1.6 → syndesi-0.2.0}/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.
|
|
3
|
+
VERSION = '0.2.0'
|
|
4
4
|
DESCRIPTION = 'Syndesi'
|
|
5
5
|
|
|
6
6
|
with open("README.md", "r", encoding="utf-8") as fh:
|
|
@@ -15,7 +15,11 @@ setup(
|
|
|
15
15
|
description=DESCRIPTION,
|
|
16
16
|
long_description_content_type="text/markdown",
|
|
17
17
|
long_description=long_description,
|
|
18
|
-
|
|
18
|
+
entry_points = {
|
|
19
|
+
'console_scripts': [
|
|
20
|
+
'syndesi=syndesi.shell.syndesi:main',
|
|
21
|
+
'syndesi-proxy=syndesi.proxy.proxy:main'],
|
|
22
|
+
},
|
|
19
23
|
packages=find_packages(),
|
|
20
24
|
install_requires=[''],
|
|
21
25
|
keywords=['python', 'syndesi', 'interface', 'ethernet'],
|
|
@@ -2,6 +2,7 @@ from .adapter import Adapter
|
|
|
2
2
|
from .ip import IP
|
|
3
3
|
from .serialport import SerialPort
|
|
4
4
|
from .visa import VISA
|
|
5
|
+
from .proxy import Proxy
|
|
5
6
|
|
|
6
7
|
from .timeout import Timeout
|
|
7
|
-
from .stop_conditions import Termination, Length, StopCondition
|
|
8
|
+
from .stop_conditions import Termination, Length, StopCondition
|
|
@@ -29,10 +29,14 @@ from ..tools.log import LoggerAlias
|
|
|
29
29
|
import logging
|
|
30
30
|
from time import time
|
|
31
31
|
from dataclasses import dataclass
|
|
32
|
-
from ..tools.others import
|
|
32
|
+
from ..tools.others import DEFAULT
|
|
33
33
|
|
|
34
|
-
DEFAULT_TIMEOUT =
|
|
35
|
-
DEFAULT_STOP_CONDITION =
|
|
34
|
+
DEFAULT_TIMEOUT = Timeout(response=1, continuation=100e-3, total=None)
|
|
35
|
+
DEFAULT_STOP_CONDITION = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class AdapterDisconnected(Exception):
|
|
39
|
+
pass
|
|
36
40
|
|
|
37
41
|
STOP_DESIGNATORS = {
|
|
38
42
|
'timeout' : {
|
|
@@ -68,10 +72,7 @@ class Adapter(ABC):
|
|
|
68
72
|
DISCONNECTED = 0
|
|
69
73
|
CONNECTED = 1
|
|
70
74
|
|
|
71
|
-
def __init__(self,
|
|
72
|
-
alias : str = '',
|
|
73
|
-
timeout : Union[float, Timeout] = DEFAULT_TIMEOUT,
|
|
74
|
-
stop_condition : Union[StopCondition, None] = DEFAULT_STOP_CONDITION):
|
|
75
|
+
def __init__(self, alias : str = '', stop_condition : Union[StopCondition, None] = DEFAULT, timeout : Union[float, Timeout] = DEFAULT) -> None:
|
|
75
76
|
"""
|
|
76
77
|
Adapter instance
|
|
77
78
|
|
|
@@ -84,24 +85,33 @@ class Adapter(ABC):
|
|
|
84
85
|
stop_condition : StopCondition or None
|
|
85
86
|
Default to None
|
|
86
87
|
"""
|
|
88
|
+
super().__init__()
|
|
89
|
+
self._alias = alias
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
self._timeout = timeout
|
|
91
|
+
self._default_stop_condition = stop_condition == DEFAULT
|
|
92
|
+
if self._default_stop_condition:
|
|
93
|
+
self._stop_condition = DEFAULT_STOP_CONDITION
|
|
92
94
|
else:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
self._stop_condition = stop_condition
|
|
95
|
+
self._stop_condition = stop_condition
|
|
96
96
|
self._read_queue = TimedQueue()
|
|
97
97
|
self._thread : Union[Thread, None] = None
|
|
98
98
|
self._status = self.Status.DISCONNECTED
|
|
99
|
+
self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
|
|
100
|
+
|
|
99
101
|
# Buffer for data that has been pulled from the queue but
|
|
100
102
|
# not used because of termination or length stop condition
|
|
101
103
|
self._previous_read_buffer = b''
|
|
102
104
|
|
|
103
|
-
self.
|
|
104
|
-
self.
|
|
105
|
+
self._default_timeout = timeout == DEFAULT
|
|
106
|
+
if self._default_timeout:
|
|
107
|
+
self._timeout = DEFAULT_TIMEOUT
|
|
108
|
+
else:
|
|
109
|
+
if is_number(timeout):
|
|
110
|
+
self._timeout = Timeout(response=timeout, continuation=100e-3)
|
|
111
|
+
elif isinstance(timeout, Timeout):
|
|
112
|
+
self._timeout = timeout
|
|
113
|
+
else:
|
|
114
|
+
raise ValueError(f"Invalid timeout type : {type(timeout)}")
|
|
105
115
|
|
|
106
116
|
def set_default_timeout(self, default_timeout : Union[Timeout, tuple, float]):
|
|
107
117
|
"""
|
|
@@ -111,7 +121,10 @@ class Adapter(ABC):
|
|
|
111
121
|
----------
|
|
112
122
|
default_timeout : Timeout or tuple or float
|
|
113
123
|
"""
|
|
114
|
-
|
|
124
|
+
if self._default_timeout:
|
|
125
|
+
self._timeout = default_timeout
|
|
126
|
+
else:
|
|
127
|
+
self._timeout = timeout_fuse(self._timeout, default_timeout)
|
|
115
128
|
|
|
116
129
|
def set_default_stop_condition(self, stop_condition):
|
|
117
130
|
"""
|
|
@@ -121,9 +134,8 @@ class Adapter(ABC):
|
|
|
121
134
|
----------
|
|
122
135
|
stop_condition : StopCondition
|
|
123
136
|
"""
|
|
124
|
-
if
|
|
137
|
+
if self._default_stop_condition:
|
|
125
138
|
self._stop_condition = stop_condition
|
|
126
|
-
|
|
127
139
|
|
|
128
140
|
def flushRead(self):
|
|
129
141
|
"""
|
|
@@ -145,7 +157,7 @@ class Adapter(ABC):
|
|
|
145
157
|
Stop communication with the device
|
|
146
158
|
"""
|
|
147
159
|
pass
|
|
148
|
-
|
|
160
|
+
|
|
149
161
|
@abstractmethod
|
|
150
162
|
def write(self, data : Union[bytes, str]):
|
|
151
163
|
"""
|
|
@@ -156,14 +168,11 @@ class Adapter(ABC):
|
|
|
156
168
|
data : bytes or str
|
|
157
169
|
"""
|
|
158
170
|
pass
|
|
159
|
-
|
|
160
|
-
@abstractmethod
|
|
161
|
-
def _start_thread(self):
|
|
162
|
-
"""
|
|
163
|
-
Initiate the read thread
|
|
164
|
-
"""
|
|
165
|
-
pass
|
|
166
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
|
+
|
|
175
|
+
|
|
167
176
|
def read(self, timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
|
|
168
177
|
"""
|
|
169
178
|
Read data from the device
|
|
@@ -222,6 +231,9 @@ class Adapter(ABC):
|
|
|
222
231
|
(timestamp, fragment) = self._read_queue.get(timeout_ms)
|
|
223
232
|
n_fragments += 1
|
|
224
233
|
|
|
234
|
+
if fragment == b'':
|
|
235
|
+
raise AdapterDisconnected()
|
|
236
|
+
|
|
225
237
|
# 1) Evaluate the timeout
|
|
226
238
|
stop, timeout_ms = timeout.evaluate(timestamp)
|
|
227
239
|
if stop:
|
|
@@ -290,6 +302,13 @@ class Adapter(ABC):
|
|
|
290
302
|
else:
|
|
291
303
|
return output
|
|
292
304
|
|
|
305
|
+
@abstractmethod
|
|
306
|
+
def _start_thread(self):
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
def __del__(self):
|
|
310
|
+
self.close()
|
|
311
|
+
|
|
293
312
|
@abstractmethod
|
|
294
313
|
def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False) -> bytes:
|
|
295
314
|
"""
|
|
@@ -299,6 +318,4 @@ class Adapter(ABC):
|
|
|
299
318
|
- read
|
|
300
319
|
"""
|
|
301
320
|
pass
|
|
302
|
-
|
|
303
|
-
def __del__(self):
|
|
304
|
-
self.close()
|
|
321
|
+
|
|
@@ -7,22 +7,24 @@ from threading import Thread
|
|
|
7
7
|
from .timed_queue import TimedQueue
|
|
8
8
|
from typing import Union
|
|
9
9
|
from time import time
|
|
10
|
+
import argparse
|
|
11
|
+
from ..tools import shell
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
DEFAULT_BUFFER_SIZE = 1024
|
|
13
|
+
class IP(Adapter):
|
|
14
|
+
DEFAULT_RESPONSE_TIMEOUT = 1
|
|
15
|
+
DEFAULT_CONTINUATION_TIMEOUT = 1e-3
|
|
16
|
+
DEFAULT_TOTAL_TIMEOUT = 5
|
|
16
17
|
|
|
17
|
-
DEFAULT_TIMEOUT = Timeout(
|
|
18
|
-
response=DEFAULT_RESPONSE_TIMEOUT,
|
|
19
|
-
continuation=DEFAULT_CONTINUATION_TIMEOUT,
|
|
20
|
-
total=DEFAULT_TOTAL_TIMEOUT)
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
DEFAULT_TIMEOUT = Timeout(
|
|
20
|
+
response=DEFAULT_RESPONSE_TIMEOUT,
|
|
21
|
+
continuation=DEFAULT_CONTINUATION_TIMEOUT,
|
|
22
|
+
total=DEFAULT_TOTAL_TIMEOUT)
|
|
23
|
+
DEFAULT_BUFFER_SIZE = 1024
|
|
23
24
|
class Protocol(Enum):
|
|
24
25
|
TCP = 'TCP'
|
|
25
26
|
UDP = 'UDP'
|
|
27
|
+
|
|
26
28
|
def __init__(self,
|
|
27
29
|
address : str,
|
|
28
30
|
port : int = None,
|
|
@@ -30,7 +32,8 @@ class IP(Adapter):
|
|
|
30
32
|
timeout : Union[Timeout, float] = DEFAULT_TIMEOUT,
|
|
31
33
|
stop_condition = None,
|
|
32
34
|
alias : str = '',
|
|
33
|
-
buffer_size : int = DEFAULT_BUFFER_SIZE
|
|
35
|
+
buffer_size : int = DEFAULT_BUFFER_SIZE,
|
|
36
|
+
_socket : socket.socket = None):
|
|
34
37
|
"""
|
|
35
38
|
IP stack adapter
|
|
36
39
|
|
|
@@ -50,17 +53,24 @@ class IP(Adapter):
|
|
|
50
53
|
Specify an alias for this adapter, '' by default
|
|
51
54
|
buffer_size : int
|
|
52
55
|
Socket buffer size, may be removed in the future
|
|
56
|
+
socket : socket.socket
|
|
57
|
+
Specify a custom socket, this is reserved for server application
|
|
53
58
|
"""
|
|
54
59
|
super().__init__(alias=alias, timeout=timeout, stop_condition=stop_condition)
|
|
55
60
|
self._transport = self.Protocol(transport)
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
self._is_server = _socket is not None
|
|
62
|
+
|
|
63
|
+
self._logger.info(f"Setting up {self._transport.value} IP adapter ({'server' if self._is_server else 'client'})")
|
|
64
|
+
|
|
65
|
+
if self._is_server:
|
|
66
|
+
# Server
|
|
67
|
+
self._socket = _socket
|
|
68
|
+
self._status = self.Status.CONNECTED
|
|
69
|
+
elif self._transport == self.Protocol.TCP:
|
|
58
70
|
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
59
71
|
elif self._transport == self.Protocol.UDP:
|
|
60
|
-
self._logger.info("Setting up UDP IP adapter")
|
|
61
72
|
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
62
|
-
|
|
63
|
-
raise ValueError("Invalid protocol")
|
|
73
|
+
|
|
64
74
|
self._address = address
|
|
65
75
|
self._port = port
|
|
66
76
|
self._buffer_size = buffer_size
|
|
@@ -80,45 +90,61 @@ class IP(Adapter):
|
|
|
80
90
|
self._port = port
|
|
81
91
|
|
|
82
92
|
def open(self):
|
|
93
|
+
if self._is_server:
|
|
94
|
+
raise SystemError("Cannot open server socket. It must be passed already opened")
|
|
83
95
|
if self._port is None:
|
|
84
96
|
raise ValueError(f"Cannot open adapter without specifying a port")
|
|
97
|
+
|
|
98
|
+
self._logger.debug(f"Adapter {self._alias} connect to ({self._address}, {self._port})")
|
|
85
99
|
self._socket.connect((self._address, self._port))
|
|
86
100
|
self._status = self.Status.CONNECTED
|
|
87
|
-
self._logger.info("Adapter opened !")
|
|
101
|
+
self._logger.info(f"Adapter {self._alias} opened !")
|
|
88
102
|
|
|
89
103
|
def close(self):
|
|
90
104
|
if hasattr(self, '_socket'):
|
|
91
105
|
self._socket.close()
|
|
92
106
|
self._logger.info("Adapter closed !")
|
|
107
|
+
self._status = self.Status.DISCONNECTED
|
|
93
108
|
|
|
94
109
|
def write(self, data : Union[bytes, str]):
|
|
95
110
|
data = to_bytes(data)
|
|
96
111
|
if self._status == self.Status.DISCONNECTED:
|
|
97
|
-
self._logger.info("Adapter is closed, opening...")
|
|
112
|
+
self._logger.info(f"Adapter {self._alias} is closed, opening...")
|
|
98
113
|
self.open()
|
|
99
114
|
write_start = time()
|
|
100
115
|
self._socket.send(data)
|
|
101
116
|
write_duration = time() - write_start
|
|
102
117
|
self._logger.debug(f"Written [{write_duration*1e3:.3f}ms]: {repr(data)}")
|
|
103
118
|
|
|
119
|
+
def _start_thread(self):
|
|
120
|
+
self._logger.debug("Starting read thread...")
|
|
121
|
+
self._thread = Thread(target=self._read_thread, daemon=True, args=(self._socket, self._read_queue))
|
|
122
|
+
self._thread.start()
|
|
123
|
+
|
|
124
|
+
# EXPERIMENTAL
|
|
125
|
+
def read_thread_alive(self):
|
|
126
|
+
return self._thread.is_alive()
|
|
127
|
+
|
|
128
|
+
|
|
104
129
|
def _read_thread(self, socket : socket.socket, read_queue : TimedQueue):
|
|
105
|
-
while True:
|
|
130
|
+
while True: # TODO : Add stop_pipe ? Maybe it was removed ?
|
|
106
131
|
try:
|
|
107
132
|
payload = socket.recv(self._buffer_size)
|
|
108
133
|
if len(payload) == self._buffer_size and self._transport == self.Protocol.UDP:
|
|
109
134
|
self._logger.warning("Warning, inbound UDP data may have been lost (max buffer size attained)")
|
|
110
135
|
except OSError:
|
|
111
136
|
break
|
|
112
|
-
|
|
137
|
+
# If payload is empty, it means the socket has been disconnected
|
|
138
|
+
if payload == b'':
|
|
139
|
+
read_queue.put(payload)
|
|
113
140
|
break
|
|
114
141
|
read_queue.put(payload)
|
|
115
142
|
|
|
116
|
-
def _start_thread(self):
|
|
117
|
-
self._logger.debug("Starting read thread...")
|
|
118
|
-
self._thread = Thread(target=self._read_thread, daemon=True, args=(self._socket, self._read_queue))
|
|
119
|
-
self._thread.start()
|
|
120
|
-
|
|
121
143
|
def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
|
|
144
|
+
if self._is_server:
|
|
145
|
+
raise SystemError("Cannot query on server adapters")
|
|
122
146
|
self.flushRead()
|
|
123
147
|
self.write(data)
|
|
124
|
-
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
148
|
+
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
149
|
+
|
|
150
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# ip_server.py
|
|
2
|
+
# Sébastien Deriaz
|
|
3
|
+
# 31.05.2024
|
|
4
|
+
|
|
5
|
+
import socket
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from . import IP, StopCondition, Timeout
|
|
8
|
+
from time import time
|
|
9
|
+
from ..tools.types import to_bytes
|
|
10
|
+
from .timeout import timeout_fuse
|
|
11
|
+
import logging
|
|
12
|
+
from ..tools.log import LoggerAlias
|
|
13
|
+
|
|
14
|
+
# NOTE : The adapter is only meant to work with a single client, thus
|
|
15
|
+
# a IPServer class is made to handle the socket and generate IP adapters
|
|
16
|
+
|
|
17
|
+
class IPServer:
|
|
18
|
+
DEFAULT_BUFFER_SIZE = 1024
|
|
19
|
+
class Protocol(Enum):
|
|
20
|
+
TCP = 'TCP'
|
|
21
|
+
UDP = 'UDP'
|
|
22
|
+
|
|
23
|
+
def __init__(self,
|
|
24
|
+
port : int = None,
|
|
25
|
+
transport : str = 'TCP',
|
|
26
|
+
address : str = None,
|
|
27
|
+
max_clients : int = 5,
|
|
28
|
+
stop_condition = None,
|
|
29
|
+
alias : str = '',
|
|
30
|
+
buffer_size : int = DEFAULT_BUFFER_SIZE):
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
IP server adapter
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
port : int
|
|
38
|
+
IP port. If None, the default port set with set_default_port will be used
|
|
39
|
+
transport : str
|
|
40
|
+
'TCP' or 'UDP'
|
|
41
|
+
address : str
|
|
42
|
+
Custom socket ip, None by default
|
|
43
|
+
max_clients : int
|
|
44
|
+
Maximum number of clients, 5 by default
|
|
45
|
+
stop_condition : StopCondition
|
|
46
|
+
Specify a read stop condition (None by default)
|
|
47
|
+
alias : str
|
|
48
|
+
Specify an alias for this adapter, '' by default
|
|
49
|
+
buffer_size : int
|
|
50
|
+
Socket buffer size, may be removed in the future
|
|
51
|
+
"""
|
|
52
|
+
self._alias = alias
|
|
53
|
+
self._stop_condition = stop_condition
|
|
54
|
+
self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
|
|
55
|
+
self._transport = self.Protocol(transport)
|
|
56
|
+
if self._transport == self.Protocol.TCP:
|
|
57
|
+
self._logger.info("Setting up TCP IP server adapter")
|
|
58
|
+
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
59
|
+
elif self._transport == self.Protocol.UDP:
|
|
60
|
+
self._logger.info("Setting up UDP IP server adapter")
|
|
61
|
+
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError("Invalid protocol")
|
|
64
|
+
|
|
65
|
+
self._address = socket.gethostname() if address is None else address
|
|
66
|
+
self._port = port
|
|
67
|
+
self._max_clients = max_clients
|
|
68
|
+
self._opened = False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def set_default_port(self, port):
|
|
72
|
+
"""
|
|
73
|
+
Sets IP port if no port has been set yet.
|
|
74
|
+
|
|
75
|
+
This way, the user can leave the port empty
|
|
76
|
+
and the driver/protocol can specify it later
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
port : int
|
|
81
|
+
"""
|
|
82
|
+
if self._port is None:
|
|
83
|
+
self._port = port
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def open(self):
|
|
87
|
+
if self._port is None:
|
|
88
|
+
raise ValueError(f"Cannot open adapter without specifying a port")
|
|
89
|
+
self._logger.info(f"Listening to incoming connections on {self._address}:{self._port}")
|
|
90
|
+
self._socket.bind((self._address, self._port))
|
|
91
|
+
self._socket.listen(self._max_clients)
|
|
92
|
+
self._opened = True
|
|
93
|
+
|
|
94
|
+
def close(self):
|
|
95
|
+
if hasattr(self, '_socket'):
|
|
96
|
+
self._socket.close()
|
|
97
|
+
self._logger.info("Adapter closed !")
|
|
98
|
+
self._opened = False
|
|
99
|
+
|
|
100
|
+
def get_client(self, stop_condition : StopCondition = None, timeout : Timeout = None) -> IP:
|
|
101
|
+
"""
|
|
102
|
+
Wait for a client to connect to the server and return the corresponding adapter
|
|
103
|
+
"""
|
|
104
|
+
if not self._opened:
|
|
105
|
+
raise RuntimeError("open() must be called before getting client")
|
|
106
|
+
client_socket, address = self._socket.accept()
|
|
107
|
+
default_timeout = Timeout(response=None, continuation=IP.DEFAULT_CONTINUATION_TIMEOUT, total=None)
|
|
108
|
+
return IP(_socket=client_socket, address=address, stop_condition=stop_condition, timeout=timeout_fuse(timeout, default_timeout))
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# proxy.py
|
|
2
|
+
# Sébastien Deriaz
|
|
3
|
+
# 09.04.2024
|
|
4
|
+
#
|
|
5
|
+
# The proxy adapter allows for commands to be issued on a different device
|
|
6
|
+
# The goal is to istanciate a class as such :
|
|
7
|
+
#
|
|
8
|
+
# Only adapter :
|
|
9
|
+
# # The proxy computer is accessed with 192.168.1.1
|
|
10
|
+
# # The device (connected to the remote computer) is accessed with 192.168.2.1
|
|
11
|
+
# my_adapter = Proxy('192.168.1.1', IP('192.168.2.1'))
|
|
12
|
+
#
|
|
13
|
+
# Protocol :
|
|
14
|
+
# my_protocol = SCPI(Proxy('192.168.1.1', Serial('/dev/ttyUSB0')))
|
|
15
|
+
#
|
|
16
|
+
#
|
|
17
|
+
# Driver :
|
|
18
|
+
# my_device = Driver(Proxy('192.168.1.1', VISA('...')))
|
|
19
|
+
|
|
20
|
+
from enum import Enum
|
|
21
|
+
from typing import Union
|
|
22
|
+
|
|
23
|
+
from .adapter import Adapter
|
|
24
|
+
from .. import IP, SerialPort, VISA
|
|
25
|
+
from ..proxy.proxy_api import *
|
|
26
|
+
from ..api.api import parse
|
|
27
|
+
|
|
28
|
+
DEFAULT_PORT = 2608
|
|
29
|
+
|
|
30
|
+
class Proxy(Adapter):
|
|
31
|
+
def __init__(self, proxy_adapter : Adapter, remote_adapter : Adapter):
|
|
32
|
+
"""
|
|
33
|
+
Proxy adapter
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
proxy_adapter : Adapter
|
|
38
|
+
Adapter to connect to the proxy server
|
|
39
|
+
remote_adapter : Adapter
|
|
40
|
+
Adapter to instanciate onto the proxy server
|
|
41
|
+
"""
|
|
42
|
+
super().__init__()
|
|
43
|
+
|
|
44
|
+
self._proxy = proxy_adapter
|
|
45
|
+
self._remote = remote_adapter
|
|
46
|
+
|
|
47
|
+
if isinstance(proxy_adapter, IP):
|
|
48
|
+
proxy_adapter.set_default_port(DEFAULT_PORT)
|
|
49
|
+
|
|
50
|
+
if isinstance(self._remote, IP):
|
|
51
|
+
self._proxy.query(IPInstanciate(
|
|
52
|
+
address=self._remote._address,
|
|
53
|
+
port=self._remote._port,
|
|
54
|
+
transport=self._remote._transport,
|
|
55
|
+
buffer_size=self._remote._buffer_size
|
|
56
|
+
).encode())
|
|
57
|
+
elif isinstance(self._remote, SerialPort):
|
|
58
|
+
self._proxy.query()
|
|
59
|
+
|
|
60
|
+
def check(self, status : ReturnStatus):
|
|
61
|
+
if not status.success:
|
|
62
|
+
# There is an error
|
|
63
|
+
raise ProxyException(status.error_message)
|
|
64
|
+
|
|
65
|
+
def open(self):
|
|
66
|
+
if isinstance(self._remote, IP):
|
|
67
|
+
self._proxy.query(AdapterOpen().encode())
|
|
68
|
+
|
|
69
|
+
def close(self):
|
|
70
|
+
self._proxy.query(AdapterClose().encode())
|
|
71
|
+
|
|
72
|
+
def write(self, data : Union[bytes, str]):
|
|
73
|
+
self._proxy.query(AdapterWrite(data).encode())
|
|
74
|
+
|
|
75
|
+
def read(self, timeout=None, stop_condition=None, return_metrics : bool = False):
|
|
76
|
+
output : AdapterReadReturn
|
|
77
|
+
output = parse(self._proxy.query(AdapterRead().encode()))
|
|
78
|
+
if isinstance(output, AdapterReadReturn):
|
|
79
|
+
return output.data
|
|
80
|
+
elif isinstance(output, ReturnStatus):
|
|
81
|
+
raise ProxyException(output.error_message)
|
|
82
|
+
else:
|
|
83
|
+
raise RuntimeError(f"Invalid return : {type(output)}")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
|
|
87
|
+
self.check(parse(self._proxy.query(AdapterFlushRead().encode())))
|
|
88
|
+
self.check(parse(self._proxy.query(AdapterWrite(data).encode())))
|
|
89
|
+
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
90
|
+
|
|
91
|
+
def _start_thread(self):
|
|
92
|
+
pass
|
|
@@ -7,6 +7,9 @@ from .timed_queue import TimedQueue
|
|
|
7
7
|
from threading import Thread
|
|
8
8
|
from typing import Union
|
|
9
9
|
import select
|
|
10
|
+
import argparse
|
|
11
|
+
from ..tools import shell
|
|
12
|
+
from collections.abc import Sequence
|
|
10
13
|
|
|
11
14
|
# From pyserial - serialposix.py
|
|
12
15
|
import fcntl
|
|
@@ -60,10 +63,11 @@ class SerialPort(Adapter):
|
|
|
60
63
|
self._logger.info("Adapter opened !")
|
|
61
64
|
|
|
62
65
|
def close(self):
|
|
63
|
-
if self._thread.is_alive():
|
|
66
|
+
if self._thread is not None and self._thread.is_alive():
|
|
64
67
|
os.write(self._stop_event_pipe_write, b'1')
|
|
65
68
|
self._thread.join()
|
|
66
|
-
self
|
|
69
|
+
if hasattr(self, '_port'):
|
|
70
|
+
self._port.close()
|
|
67
71
|
self._logger.info("Adapter closed !")
|
|
68
72
|
|
|
69
73
|
def write(self, data : bytes):
|
|
@@ -78,6 +82,9 @@ class SerialPort(Adapter):
|
|
|
78
82
|
self._logger.debug(f"Written [{write_duration*1e3:.3f}ms]: {repr(data)}")
|
|
79
83
|
|
|
80
84
|
def _start_thread(self):
|
|
85
|
+
"""
|
|
86
|
+
Start the read thread
|
|
87
|
+
"""
|
|
81
88
|
self._logger.debug("Starting read thread...")
|
|
82
89
|
if self._thread is None or not self._thread.is_alive():
|
|
83
90
|
self._thread = Thread(target=self._read_thread, daemon=True, args=(self._port, self._read_queue, self._stop_event_pipe))
|
|
@@ -125,4 +132,21 @@ class SerialPort(Adapter):
|
|
|
125
132
|
def query(self, data : Union[bytes, str], timeout=None, stop_condition=None, return_metrics : bool = False):
|
|
126
133
|
self.flushRead()
|
|
127
134
|
self.write(data)
|
|
128
|
-
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
135
|
+
return self.read(timeout=timeout, stop_condition=stop_condition, return_metrics=return_metrics)
|
|
136
|
+
|
|
137
|
+
def shell_parse(inp: str):
|
|
138
|
+
parser = argparse.ArgumentParser(
|
|
139
|
+
prog='',
|
|
140
|
+
description='Serial port shell parser',
|
|
141
|
+
epilog='')
|
|
142
|
+
# Parse subcommand
|
|
143
|
+
parser.add_argument('--' + shell.Arguments.PORT.value, type=str)
|
|
144
|
+
parser.add_argument('--' + shell.Arguments.BAUDRATE.value, type=int)
|
|
145
|
+
parser.add_argument('--' + shell.Arguments.ENABLE_RTS_CTS.value, action='store_true')
|
|
146
|
+
args = parser.parse_args(inp.split())
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
'port' : getattr(args, shell.Arguments.PORT.value),
|
|
150
|
+
'baudrate' : getattr(args, shell.Arguments.BAUDRATE.value),
|
|
151
|
+
'rts_cts' : bool(getattr(args, shell.Arguments.ENABLE_RTS_CTS.value))
|
|
152
|
+
}
|
|
@@ -60,7 +60,13 @@ class Termination(StopCondition):
|
|
|
60
60
|
sequence : bytes
|
|
61
61
|
"""
|
|
62
62
|
super().__init__()
|
|
63
|
-
|
|
63
|
+
if isinstance(sequence, str):
|
|
64
|
+
self._termination = sequence.encode('utf-8')
|
|
65
|
+
elif isinstance(sequence, bytes):
|
|
66
|
+
self._termination = sequence
|
|
67
|
+
else:
|
|
68
|
+
raise ValueError(f"Invalid termination sequence type : {type(sequence)}")
|
|
69
|
+
|
|
64
70
|
#self._fragment_store = b''
|
|
65
71
|
#self._sequence_index = 0
|
|
66
72
|
|
|
@@ -5,7 +5,7 @@
|
|
|
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
|
|
8
|
+
#from ..tools.others import is_default_argument
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Timeout():
|
|
@@ -264,10 +264,11 @@ def timeout_fuse(high_priority, low_priority):
|
|
|
264
264
|
low = low_priority if isinstance(low_priority, Timeout) else Timeout(low_priority)
|
|
265
265
|
|
|
266
266
|
# 3) If one is the default, take the other
|
|
267
|
-
if is_default_argument(high):
|
|
268
|
-
|
|
269
|
-
if is_default_argument(low):
|
|
270
|
-
|
|
267
|
+
# if is_default_argument(high):
|
|
268
|
+
# return low
|
|
269
|
+
# if is_default_argument(low):
|
|
270
|
+
# return high
|
|
271
|
+
# 05.06.2024 : Removed because is_default_argument is obsolete, use DEFAULT is necessary
|
|
271
272
|
|
|
272
273
|
new_attr = {}
|
|
273
274
|
# 4) Select with parameter to keep based on where it has been set
|
|
@@ -1,7 +1,8 @@
|
|
|
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
|
|
4
|
+
#from ..tools.others import is_default_argument
|
|
5
|
+
from ..tools.others import DEFAULT
|
|
5
6
|
|
|
6
7
|
class SCPI(Protocol):
|
|
7
8
|
DEFAULT_PORT = 5025
|
|
@@ -32,7 +33,7 @@ class SCPI(Protocol):
|
|
|
32
33
|
self._adapter.set_default_port(self.DEFAULT_PORT)
|
|
33
34
|
|
|
34
35
|
self._adapter.set_default_timeout(timeout)
|
|
35
|
-
if
|
|
36
|
+
if self._adapter._stop_condition != DEFAULT:
|
|
36
37
|
raise ValueError('A conflicting stop-condition has been set for this adapter')
|
|
37
38
|
self._adapter._stop_condition = Termination(self._receive_termination.encode(encoding=encoding))
|
|
38
39
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DEFAULT = 'default'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# shell.py
|
|
2
|
+
# Sébastien Deriaz
|
|
3
|
+
# 30.04.2024
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from cmd import Cmd
|
|
6
|
+
from syndesi.adapters import *
|
|
7
|
+
import argparse
|
|
8
|
+
import shlex
|
|
9
|
+
import sys
|
|
10
|
+
import os
|
|
11
|
+
from colorist import ColorRGB
|
|
12
|
+
|
|
13
|
+
VERSION = 0.1
|
|
14
|
+
|
|
15
|
+
class ShellPrompt(Cmd):
|
|
16
|
+
__hiden_methods = ('do_EOF','do_clear','do_cls')
|
|
17
|
+
PROMPT_COLOR = ColorRGB(28, 90, 145)
|
|
18
|
+
|
|
19
|
+
prompt = f'{PROMPT_COLOR}❯ {PROMPT_COLOR.OFF}'
|
|
20
|
+
intro = "Welcome to the Syndesi Shell! Type ? to list commands"
|
|
21
|
+
|
|
22
|
+
def get_names(self):
|
|
23
|
+
return [n for n in dir(self.__class__) if n not in self.__hiden_methods]
|
|
24
|
+
|
|
25
|
+
def do_exit(self, inp):
|
|
26
|
+
"""Exit"""
|
|
27
|
+
return True
|
|
28
|
+
|
|
29
|
+
def do_serial(self, inp):
|
|
30
|
+
"""Open serial adapter"""
|
|
31
|
+
self._adapter = SerialPort(**SerialPort.shell_parse(inp))
|
|
32
|
+
|
|
33
|
+
def do_ip(self, inp):
|
|
34
|
+
"""Open IP adapter
|
|
35
|
+
-p / --port : port number
|
|
36
|
+
--ip : ip address
|
|
37
|
+
-t / --transport : TCP or UDP
|
|
38
|
+
"""
|
|
39
|
+
parser = argparse.ArgumentParser()
|
|
40
|
+
parser.add_argument('-p', '--port', type=int, required=True)
|
|
41
|
+
parser.add_argument('--ip', type=str, required=True)
|
|
42
|
+
parser.add_argument('-t', '--transport', type=str, choices=['UDP', 'TCP'], default='TCP', required=False)
|
|
43
|
+
try:
|
|
44
|
+
args = parser.parse_args(shlex.split(inp))
|
|
45
|
+
except SystemExit as e:
|
|
46
|
+
pass
|
|
47
|
+
else:
|
|
48
|
+
self._adapter = IP(address=args.ip, port=args.port, transport=args.transport)
|
|
49
|
+
|
|
50
|
+
def default(self, inp):
|
|
51
|
+
if hasattr(self, '_adapter'):
|
|
52
|
+
cmd = inp + '\n'
|
|
53
|
+
output = self._adapter.query(cmd)
|
|
54
|
+
print(output)
|
|
55
|
+
else:
|
|
56
|
+
print(f"Unknown command : {inp}, type ? to list commands")
|
|
57
|
+
|
|
58
|
+
def do_clear(self, _):
|
|
59
|
+
if sys.platform == "linux" or sys.platform == "linux2" or sys.platform == "darwin":
|
|
60
|
+
# linux
|
|
61
|
+
os.system('clear')
|
|
62
|
+
elif sys.platform == "win32":
|
|
63
|
+
# Windows
|
|
64
|
+
os.system('cls')
|
|
65
|
+
|
|
66
|
+
def do_cls(self, _):
|
|
67
|
+
self.do_clear()
|
|
68
|
+
|
|
69
|
+
def do_help(self, arg: str) -> bool | None:
|
|
70
|
+
if arg:
|
|
71
|
+
# Use Cmd's help
|
|
72
|
+
super().do_help(arg)
|
|
73
|
+
else:
|
|
74
|
+
# Otherwise, print a custom help
|
|
75
|
+
names = self.get_names()
|
|
76
|
+
|
|
77
|
+
cmds_doc = []
|
|
78
|
+
cmds_undoc = []
|
|
79
|
+
docs = []
|
|
80
|
+
topics = set()
|
|
81
|
+
for name in names:
|
|
82
|
+
if name[:5] == 'help_':
|
|
83
|
+
topics.add(name[5:])
|
|
84
|
+
names.sort()
|
|
85
|
+
# There can be duplicates if routines overridden
|
|
86
|
+
prevname = ''
|
|
87
|
+
for name in names:
|
|
88
|
+
if name[:3] == 'do_':
|
|
89
|
+
if name == prevname:
|
|
90
|
+
continue
|
|
91
|
+
prevname = name
|
|
92
|
+
cmd=name[3:]
|
|
93
|
+
if cmd in topics:
|
|
94
|
+
#cmds_doc.append(cmd)
|
|
95
|
+
topics.remove(cmd)
|
|
96
|
+
elif getattr(self, name).__doc__:
|
|
97
|
+
cmds_doc.append(cmd)
|
|
98
|
+
docs.append(getattr(self, name).__doc__)
|
|
99
|
+
else:
|
|
100
|
+
cmds_undoc.append(cmd)
|
|
101
|
+
|
|
102
|
+
print(f"Syndesi shell V{VERSION}")
|
|
103
|
+
print("Available commands :")
|
|
104
|
+
max_width = max([len(cmd) for cmd in cmds_doc])
|
|
105
|
+
for cmd, doc in zip(cmds_doc, docs):
|
|
106
|
+
print(f" {cmd:<{max_width+2}} : {doc}")
|
|
107
|
+
|
|
108
|
+
#self.print_topics(self.misc_header, sorted(topics),15,80)
|
|
109
|
+
#self.print_topics(self.undoc_header, cmds_undoc, 15,80)
|
|
110
|
+
|
|
111
|
+
do_EOF = do_exit # Allow CTRL+d to exit
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
3
|
setup.py
|
|
4
|
-
bin/syndesi
|
|
5
4
|
syndesi/__init__.py
|
|
6
5
|
syndesi.egg-info/PKG-INFO
|
|
7
6
|
syndesi.egg-info/SOURCES.txt
|
|
8
7
|
syndesi.egg-info/dependency_links.txt
|
|
8
|
+
syndesi.egg-info/entry_points.txt
|
|
9
9
|
syndesi.egg-info/top_level.txt
|
|
10
10
|
syndesi/adapters/__init__.py
|
|
11
11
|
syndesi/adapters/adapter.py
|
|
12
12
|
syndesi/adapters/auto.py
|
|
13
13
|
syndesi/adapters/ip.py
|
|
14
|
-
syndesi/adapters/
|
|
14
|
+
syndesi/adapters/ip_server.py
|
|
15
|
+
syndesi/adapters/proxy.py
|
|
15
16
|
syndesi/adapters/serialport.py
|
|
16
17
|
syndesi/adapters/stop_conditions.py
|
|
17
18
|
syndesi/adapters/timed_queue.py
|
|
@@ -27,4 +28,5 @@ syndesi/tools/__init__.py
|
|
|
27
28
|
syndesi/tools/exceptions.py
|
|
28
29
|
syndesi/tools/log.py
|
|
29
30
|
syndesi/tools/others.py
|
|
31
|
+
syndesi/tools/shell.py
|
|
30
32
|
syndesi/tools/types.py
|
syndesi-0.1.6/bin/syndesi
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
|
|
3
|
-
# Syndesi CLI
|
|
4
|
-
import argparse
|
|
5
|
-
from cmd import Cmd
|
|
6
|
-
from enum import Enum
|
|
7
|
-
|
|
8
|
-
class MyPrompt(Cmd):
|
|
9
|
-
prompt = '❯ '
|
|
10
|
-
intro = "Welcome! Type ? to list commands"
|
|
11
|
-
|
|
12
|
-
def do_exit(self, inp):
|
|
13
|
-
print("Bye !")
|
|
14
|
-
return True
|
|
15
|
-
|
|
16
|
-
def do_connect(self, inp):
|
|
17
|
-
print("Device")
|
|
18
|
-
|
|
19
|
-
def default(self, inp):
|
|
20
|
-
print(f"Entered : {inp}")
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
do_EOF = do_exit # Allow CTRL+d to exit
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class SubCommands(Enum):
|
|
27
|
-
SHELL = 'shell'
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def connect():
|
|
33
|
-
print("Entering connect subcommand...")
|
|
34
|
-
|
|
35
|
-
p = MyPrompt()
|
|
36
|
-
p.cmdloop()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def main():
|
|
42
|
-
parser = argparse.ArgumentParser(
|
|
43
|
-
prog='syndesi',
|
|
44
|
-
description='Syndesi command line interface',
|
|
45
|
-
epilog='')
|
|
46
|
-
# Parse subcommand
|
|
47
|
-
parser.add_argument('subcommand', choices=[SubCommands.SHELL.value])
|
|
48
|
-
|
|
49
|
-
args = parser.parse_args()
|
|
50
|
-
|
|
51
|
-
if args.subcommand == SubCommands.SHELL.value:
|
|
52
|
-
connect()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if __name__ == '__main__':
|
|
57
|
-
main()
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# remote.py
|
|
2
|
-
# Sébastien Deriaz
|
|
3
|
-
# 09.04.2024
|
|
4
|
-
#
|
|
5
|
-
# The remote adapter allows for commands to be issued on a different device through TCP
|
|
6
|
-
# The goal is to istanciate a class as such :
|
|
7
|
-
#
|
|
8
|
-
# Only adapter :
|
|
9
|
-
# # The remote computer is accessed with 192.168.1.1
|
|
10
|
-
# # The device (connected to the remote computer) is accessed with 192.168.2.1
|
|
11
|
-
# my_adapter = Remote('192.168.1.1', IP('192.168.2.1'))
|
|
12
|
-
#
|
|
13
|
-
# Protocol :
|
|
14
|
-
# my_protocol = SCPI(Remote('192.168.1.1', Serial('/dev/ttyUSB0')))
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# Driver :
|
|
18
|
-
# my_device = Driver(Remote('192.168.1.1', VISA('...')))
|
|
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
|