syndesi 0.4.2__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- syndesi/__init__.py +22 -2
- syndesi/adapters/adapter.py +332 -489
- syndesi/adapters/adapter_worker.py +820 -0
- syndesi/adapters/auto.py +58 -25
- syndesi/adapters/descriptors.py +38 -0
- syndesi/adapters/ip.py +203 -71
- syndesi/adapters/serialport.py +154 -25
- syndesi/adapters/stop_conditions.py +354 -0
- syndesi/adapters/timeout.py +58 -21
- syndesi/adapters/visa.py +236 -11
- syndesi/cli/console.py +51 -16
- syndesi/cli/shell.py +95 -47
- syndesi/cli/terminal_tools.py +8 -8
- syndesi/component.py +315 -0
- syndesi/protocols/delimited.py +92 -107
- syndesi/protocols/modbus.py +2368 -868
- syndesi/protocols/protocol.py +186 -33
- syndesi/protocols/raw.py +45 -62
- syndesi/protocols/scpi.py +65 -102
- syndesi/remote/remote.py +188 -0
- syndesi/scripts/syndesi.py +12 -2
- syndesi/tools/errors.py +49 -31
- syndesi/tools/log_settings.py +21 -8
- syndesi/tools/{log.py → logmanager.py} +24 -13
- syndesi/tools/types.py +9 -7
- syndesi/version.py +5 -1
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/METADATA +1 -1
- syndesi-0.5.0.dist-info/RECORD +41 -0
- syndesi/adapters/backend/__init__.py +0 -0
- syndesi/adapters/backend/adapter_backend.py +0 -438
- syndesi/adapters/backend/adapter_manager.py +0 -48
- syndesi/adapters/backend/adapter_session.py +0 -346
- syndesi/adapters/backend/backend.py +0 -438
- syndesi/adapters/backend/backend_status.py +0 -0
- syndesi/adapters/backend/backend_tools.py +0 -66
- syndesi/adapters/backend/descriptors.py +0 -153
- syndesi/adapters/backend/ip_backend.py +0 -149
- syndesi/adapters/backend/serialport_backend.py +0 -241
- syndesi/adapters/backend/stop_condition_backend.py +0 -219
- syndesi/adapters/backend/timed_queue.py +0 -39
- syndesi/adapters/backend/timeout.py +0 -252
- syndesi/adapters/backend/visa_backend.py +0 -197
- syndesi/adapters/ip_server.py +0 -102
- syndesi/adapters/stop_condition.py +0 -90
- syndesi/cli/backend_console.py +0 -96
- syndesi/cli/backend_status.py +0 -274
- syndesi/cli/backend_wrapper.py +0 -61
- syndesi/scripts/syndesi_backend.py +0 -37
- syndesi/tools/backend_api.py +0 -175
- syndesi/tools/backend_logger.py +0 -64
- syndesi/tools/exceptions.py +0 -16
- syndesi/tools/internal.py +0 -0
- syndesi-0.4.2.dist-info/RECORD +0 -60
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/WHEEL +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/entry_points.txt +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {syndesi-0.4.2.dist-info → syndesi-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# File : timeout.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# The timeout backend manages timing when reading from a device, it both
|
|
6
|
-
# records how much time each action took and stops communication if it
|
|
7
|
-
# exceeds set criteria
|
|
8
|
-
|
|
9
|
-
# from enum import Enum
|
|
10
|
-
# from typing import Union, Tuple
|
|
11
|
-
# from time import time
|
|
12
|
-
# import json
|
|
13
|
-
|
|
14
|
-
# class TimeoutAction(Enum):
|
|
15
|
-
# DISCARD = "discard" # If a timeout is reached, data is discarded
|
|
16
|
-
# RETURN = "return" # If a timeout is reached, data is returned (timeout acts as a stop condition)
|
|
17
|
-
# STORE = "store" # If a timeout is reached, data is stored and returned on the next read() call
|
|
18
|
-
# ERROR = "error" # If a timeout is reached, raise an error
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# class TimeoutType(Enum):
|
|
22
|
-
# RESPONSE = "response"
|
|
23
|
-
|
|
24
|
-
# class JsonKey(Enum):
|
|
25
|
-
# RESPONSE = "r"
|
|
26
|
-
# ACTION = "a"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# class TimeoutBackend:
|
|
30
|
-
# class _State(Enum):
|
|
31
|
-
# WAIT_FOR_RESPONSE = 0
|
|
32
|
-
# CONTINUATION = 1
|
|
33
|
-
|
|
34
|
-
# def __init__(
|
|
35
|
-
# self, response, continuation, total, on_response, on_continuation, on_total
|
|
36
|
-
# ) -> None:
|
|
37
|
-
# """
|
|
38
|
-
# A class to manage timeouts
|
|
39
|
-
|
|
40
|
-
# Timeouts are split in three categories :
|
|
41
|
-
# - response timeout : the "standard" timeout, (i.e the time it takes for
|
|
42
|
-
# a device to start transmitting data)
|
|
43
|
-
# - continuation timeout : the time between reception of
|
|
44
|
-
# data units (bytes or blocks of data)
|
|
45
|
-
# - total timeout : maximum time from start to end of transmission.
|
|
46
|
-
# This timeout can stop a communication mid-way. It is used to
|
|
47
|
-
# prevent a host from getting stuck reading a constantly streaming device
|
|
48
|
-
|
|
49
|
-
# Each timeout is specified in seconds
|
|
50
|
-
|
|
51
|
-
# Actions
|
|
52
|
-
# - discard : Discard all of data obtained during the read() call if the specified timeout if reached and return empty data
|
|
53
|
-
# - return : Return all of the data read up to this point when the specified timeout is reached
|
|
54
|
-
# - store : Store all of the data read up to this point in a buffer. Data will be available at the next read() call
|
|
55
|
-
# - error : Produce an error
|
|
56
|
-
|
|
57
|
-
# Parameters
|
|
58
|
-
# ----------
|
|
59
|
-
# response : float
|
|
60
|
-
# continuation : float
|
|
61
|
-
# total : float
|
|
62
|
-
# on_response : str
|
|
63
|
-
# Action on response timeout (see Actions)
|
|
64
|
-
# on_continuation : str
|
|
65
|
-
# Action on continuation timeout (see Actions)
|
|
66
|
-
# on_total : str
|
|
67
|
-
# Action on total timeout (see Actions)
|
|
68
|
-
# """
|
|
69
|
-
# super().__init__()
|
|
70
|
-
# # It is possible to pass a tuple to set response/continuation/total, parse this first if it is the case
|
|
71
|
-
# if isinstance(response, (tuple, list)):
|
|
72
|
-
# if len(response) >= 3:
|
|
73
|
-
# total = response[2]
|
|
74
|
-
# if len(response) >= 2:
|
|
75
|
-
# continuation = response[1]
|
|
76
|
-
# response = response[0]
|
|
77
|
-
|
|
78
|
-
# # Timeout values (response, continuation and total)
|
|
79
|
-
# self._response = response
|
|
80
|
-
# self._continuation = continuation
|
|
81
|
-
# self._total = total
|
|
82
|
-
# self._on_response = (
|
|
83
|
-
# TimeoutAction(on_response) if on_response is not None else None
|
|
84
|
-
# )
|
|
85
|
-
# self._on_continuation = (
|
|
86
|
-
# TimeoutAction(on_continuation) if on_continuation is not None else None
|
|
87
|
-
# )
|
|
88
|
-
# self._on_total = TimeoutAction(on_total) if on_total is not None else None
|
|
89
|
-
|
|
90
|
-
# # State machine flags
|
|
91
|
-
# self._state = self._State.WAIT_FOR_RESPONSE
|
|
92
|
-
# self._queue_timeout_type = TimeoutType.RESPONSE
|
|
93
|
-
# self._last_data_action_origin = TimeoutType.RESPONSE
|
|
94
|
-
|
|
95
|
-
# def initiate_read(self, deferred_buffer: bool = False) -> Union[float, None]:
|
|
96
|
-
# """
|
|
97
|
-
# Initiate a read sequence.
|
|
98
|
-
|
|
99
|
-
# The maximum time that should be spent in the next byte read
|
|
100
|
-
# is returned
|
|
101
|
-
|
|
102
|
-
# Returns
|
|
103
|
-
# -------
|
|
104
|
-
# stop : bool
|
|
105
|
-
# Timeout is reached
|
|
106
|
-
# keep : bool
|
|
107
|
-
# True if data read up to this point should be kept
|
|
108
|
-
# False if data should be discarded
|
|
109
|
-
# timeout : float or None
|
|
110
|
-
# None is there's no timeout
|
|
111
|
-
# """
|
|
112
|
-
# self._start_time = time()
|
|
113
|
-
# if deferred_buffer:
|
|
114
|
-
# self._state = self._State.CONTINUATION
|
|
115
|
-
# self._data_action = self._on_continuation
|
|
116
|
-
# self._queue_timeout_type = TimeoutType.CONTINUATION
|
|
117
|
-
# else:
|
|
118
|
-
# self._state = self._State.WAIT_FOR_RESPONSE
|
|
119
|
-
# self._data_action = self._on_response
|
|
120
|
-
# self._queue_timeout_type = TimeoutType.RESPONSE
|
|
121
|
-
# self._last_timestamp = None
|
|
122
|
-
|
|
123
|
-
# self.response_time = None
|
|
124
|
-
# self.continuation_times = []
|
|
125
|
-
# self.total_time = None
|
|
126
|
-
|
|
127
|
-
# self._output_timeout = None
|
|
128
|
-
|
|
129
|
-
# return self._response
|
|
130
|
-
|
|
131
|
-
# def evaluate(self, timestamp: float) -> Tuple[bool, Union[float, None]]:
|
|
132
|
-
# stop = False
|
|
133
|
-
|
|
134
|
-
# # self._check_uninitialization()
|
|
135
|
-
|
|
136
|
-
# self._data_action = None
|
|
137
|
-
# self._stop_source_value = (
|
|
138
|
-
# "###" # When a timeout occurs, store the value that exceeded its value here
|
|
139
|
-
# )
|
|
140
|
-
# self._stop_source_limit = "###" # And store the limit value here
|
|
141
|
-
|
|
142
|
-
# # First check if the timestamp is None, that would mean the timeout was reached in the queue
|
|
143
|
-
# if timestamp is None:
|
|
144
|
-
# # Set the data action according to the timeout that was given for the queue before
|
|
145
|
-
# match self._queue_timeout_type:
|
|
146
|
-
# case TimeoutType.RESPONSE:
|
|
147
|
-
# self._data_action = self._on_response
|
|
148
|
-
# self._stop_source_limit = self._response
|
|
149
|
-
# case TimeoutType.CONTINUATION:
|
|
150
|
-
# self._data_action = self._on_continuation
|
|
151
|
-
# self._stop_source_limit = self._continuation
|
|
152
|
-
# case TimeoutType.TOTAL:
|
|
153
|
-
# self._data_action = self._on_total
|
|
154
|
-
# self._stop_source_limit = self._total
|
|
155
|
-
# self._stop_source_value = None # We do not have the exceed time, but None will be printed as '---'
|
|
156
|
-
# self._last_data_action_origin = self._queue_timeout_type
|
|
157
|
-
# stop = True
|
|
158
|
-
|
|
159
|
-
# else:
|
|
160
|
-
# # Check total
|
|
161
|
-
# if self._total is not None:
|
|
162
|
-
# self.total_time = timestamp - self._start_time
|
|
163
|
-
# if self.total_time >= self._total:
|
|
164
|
-
# stop = True
|
|
165
|
-
# self._data_action = self._on_total
|
|
166
|
-
# self._last_data_action_origin = TimeoutType.TOTAL
|
|
167
|
-
# self._stop_source_value = self.total_time
|
|
168
|
-
# self._stop_source_limit = self._total
|
|
169
|
-
# # Check continuation
|
|
170
|
-
# elif (
|
|
171
|
-
# self._continuation is not None
|
|
172
|
-
# and self._state == self._State.CONTINUATION
|
|
173
|
-
# and self._last_timestamp is not None
|
|
174
|
-
# ):
|
|
175
|
-
# continuation_time = timestamp - self._last_timestamp
|
|
176
|
-
# self.continuation_times.append(continuation_time)
|
|
177
|
-
# if continuation_time >= self._continuation:
|
|
178
|
-
# stop = True
|
|
179
|
-
# self._data_action = self._on_continuation
|
|
180
|
-
# self._last_data_action_origin = TimeoutType.CONTINUATION
|
|
181
|
-
# self._stop_source_value = continuation_time
|
|
182
|
-
# self._stop_source_limit = self._continuation
|
|
183
|
-
# # Check response time
|
|
184
|
-
# elif (
|
|
185
|
-
# self._response is not None
|
|
186
|
-
# and self._state == self._State.WAIT_FOR_RESPONSE
|
|
187
|
-
# ):
|
|
188
|
-
# self.response_time = timestamp - self._start_time
|
|
189
|
-
# if self.response_time >= self._response:
|
|
190
|
-
# stop = True
|
|
191
|
-
# self._data_action = self._on_response
|
|
192
|
-
# self._last_data_action_origin = TimeoutType.RESPONSE
|
|
193
|
-
# self._stop_source_value = self.response_time
|
|
194
|
-
# self._stop_source_limit = self._response
|
|
195
|
-
|
|
196
|
-
# self._output_timeout = None
|
|
197
|
-
# # If we continue
|
|
198
|
-
# if not stop:
|
|
199
|
-
# # Update the state
|
|
200
|
-
# if self._state == self._State.WAIT_FOR_RESPONSE:
|
|
201
|
-
# self._state = self._State.CONTINUATION
|
|
202
|
-
# self._last_timestamp = timestamp
|
|
203
|
-
# # No timeouts were reached, return the next one
|
|
204
|
-
# # Return the timeout (state is always CONTINUATION at this stage)
|
|
205
|
-
# # Take the smallest between continuation and total
|
|
206
|
-
# if self._total is not None and self._continuation is not None:
|
|
207
|
-
# c = self._continuation
|
|
208
|
-
# t = self._start_time + self._total
|
|
209
|
-
# if c < t:
|
|
210
|
-
# self._output_timeout = c
|
|
211
|
-
# self._queue_timeout_type = TimeoutType.CONTINUATION
|
|
212
|
-
# else:
|
|
213
|
-
# self._output_timeout = t
|
|
214
|
-
# self._queue_timeout_type = TimeoutType.TOTAL
|
|
215
|
-
# elif self._total is not None:
|
|
216
|
-
# self._output_timeout = time() - (self._start_time + self._total)
|
|
217
|
-
# self._queue_timeout_type = TimeoutType.TOTAL
|
|
218
|
-
# elif self._continuation is not None:
|
|
219
|
-
# self._output_timeout = self._continuation
|
|
220
|
-
# self._queue_timeout_type = TimeoutType.CONTINUATION
|
|
221
|
-
|
|
222
|
-
# return stop, self._output_timeout
|
|
223
|
-
|
|
224
|
-
# def dataAction(self):
|
|
225
|
-
# """
|
|
226
|
-
# Return the data action (discard, return, store or error)
|
|
227
|
-
# and the timeout origin (response, continuation or total)
|
|
228
|
-
|
|
229
|
-
# Returns
|
|
230
|
-
# -------
|
|
231
|
-
# data_action : Timeout.DataAction
|
|
232
|
-
# origin : Timeout.TimeoutType
|
|
233
|
-
|
|
234
|
-
# """
|
|
235
|
-
# return self._data_action, self._last_data_action_origin
|
|
236
|
-
|
|
237
|
-
# def __str__(self) -> str:
|
|
238
|
-
# def _format(value, action):
|
|
239
|
-
# if value is None:
|
|
240
|
-
# return "None"
|
|
241
|
-
# elif value is Ellipsis:
|
|
242
|
-
# return "not set"
|
|
243
|
-
# else:
|
|
244
|
-
# return f'{value*1e3:.3f}ms/{action.value if isinstance(action, Enum) else "not set"}'
|
|
245
|
-
|
|
246
|
-
# response = "r:" + _format(self._response, self._on_response)
|
|
247
|
-
# continuation = "c:" + _format(self._continuation, self._on_continuation)
|
|
248
|
-
# total = "t:" + _format(self._total, self._on_total)
|
|
249
|
-
# return f"Timeout({response},{continuation},{total})"
|
|
250
|
-
|
|
251
|
-
# def __repr__(self) -> str:
|
|
252
|
-
# return self.__str__()
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# File : visa.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# The VISA backend communicates using pyvisa
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import queue
|
|
10
|
-
import socket
|
|
11
|
-
import threading
|
|
12
|
-
import time
|
|
13
|
-
from types import ModuleType
|
|
14
|
-
from typing import TYPE_CHECKING
|
|
15
|
-
|
|
16
|
-
from ...tools.backend_api import AdapterBackendStatus, Fragment
|
|
17
|
-
from .adapter_backend import (
|
|
18
|
-
AdapterBackend,
|
|
19
|
-
AdapterDisconnected,
|
|
20
|
-
AdapterSignal,
|
|
21
|
-
HasFileno,
|
|
22
|
-
)
|
|
23
|
-
from .descriptors import VisaDescriptor
|
|
24
|
-
|
|
25
|
-
# --- Typing-only imports so mypy knows pyvisa symbols without requiring it at runtime
|
|
26
|
-
if TYPE_CHECKING:
|
|
27
|
-
import pyvisa # type: ignore
|
|
28
|
-
from pyvisa.resources import Resource # type: ignore
|
|
29
|
-
|
|
30
|
-
# --- Runtime optional import
|
|
31
|
-
try:
|
|
32
|
-
import pyvisa as _pyvisa_runtime
|
|
33
|
-
except Exception:
|
|
34
|
-
_pyvisa_runtime = None
|
|
35
|
-
|
|
36
|
-
pyvisa: ModuleType | None = _pyvisa_runtime # type: ignore
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
class VisaBackend(AdapterBackend):
|
|
40
|
-
def __init__(self, descriptor: VisaDescriptor):
|
|
41
|
-
"""
|
|
42
|
-
USB VISA stack adapter
|
|
43
|
-
"""
|
|
44
|
-
super().__init__(descriptor=descriptor)
|
|
45
|
-
self.descriptor: VisaDescriptor
|
|
46
|
-
|
|
47
|
-
if pyvisa is None:
|
|
48
|
-
raise ImportError(
|
|
49
|
-
"Missing optional dependency 'pyvisa'. Install with:\n"
|
|
50
|
-
" python -m pip install pyvisa"
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
# Safe: guarded above, so mypy knows pyvisa is a module here
|
|
54
|
-
self._rm = pyvisa.ResourceManager()
|
|
55
|
-
self._inst: Resource | None = None # annotation only; no runtime import needed
|
|
56
|
-
|
|
57
|
-
# We need a socket pair because VISA doesn't expose a selectable fileno/socket
|
|
58
|
-
# So we create a thread to read data and push that to the socket
|
|
59
|
-
self._notify_recv, self._notify_send = socket.socketpair()
|
|
60
|
-
self._notify_recv.setblocking(False)
|
|
61
|
-
self._notify_send.setblocking(False)
|
|
62
|
-
|
|
63
|
-
self._stop_lock = threading.Lock()
|
|
64
|
-
self.stop = False
|
|
65
|
-
|
|
66
|
-
self._fragment_lock = threading.Lock()
|
|
67
|
-
self._fragment = Fragment(b"", None)
|
|
68
|
-
self._event_queue: queue.Queue[AdapterSignal] = queue.Queue()
|
|
69
|
-
|
|
70
|
-
@classmethod
|
|
71
|
-
def list_devices(cls: type[VisaBackend]) -> list[str]:
|
|
72
|
-
"""
|
|
73
|
-
Returns a list of available VISA devices
|
|
74
|
-
"""
|
|
75
|
-
if pyvisa is None:
|
|
76
|
-
raise ImportError(
|
|
77
|
-
"Missing optional dependency 'pyvisa'. Install with:\n"
|
|
78
|
-
" python -m pip install pyvisa"
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
rm = pyvisa.ResourceManager()
|
|
82
|
-
available_resources: list[str] = []
|
|
83
|
-
for device in rm.list_resources():
|
|
84
|
-
try:
|
|
85
|
-
d = rm.open_resource(device)
|
|
86
|
-
d.close()
|
|
87
|
-
available_resources.append(device)
|
|
88
|
-
except pyvisa.VisaIOError:
|
|
89
|
-
# Device cannot be opened; skip it
|
|
90
|
-
pass
|
|
91
|
-
|
|
92
|
-
return available_resources
|
|
93
|
-
|
|
94
|
-
def flush_read(self) -> bool:
|
|
95
|
-
super().flush_read()
|
|
96
|
-
while not self._event_queue.empty():
|
|
97
|
-
self._event_queue.get()
|
|
98
|
-
return True
|
|
99
|
-
|
|
100
|
-
def open(self) -> bool:
|
|
101
|
-
output = False
|
|
102
|
-
if self._inst is None:
|
|
103
|
-
# NOTE: self._rm is always defined in __init__ when pyvisa is present
|
|
104
|
-
self._inst = self._rm.open_resource(self.descriptor.descriptor)
|
|
105
|
-
|
|
106
|
-
if self._status == AdapterBackendStatus.DISCONNECTED:
|
|
107
|
-
# These attributes exist on pyvisa resources
|
|
108
|
-
self._inst.write_termination = ""
|
|
109
|
-
self._inst.read_termination = None
|
|
110
|
-
|
|
111
|
-
self._inst_lock = threading.Lock()
|
|
112
|
-
self._status = AdapterBackendStatus.CONNECTED
|
|
113
|
-
|
|
114
|
-
if self._thread is None:
|
|
115
|
-
self._thread = threading.Thread(
|
|
116
|
-
target=self._internal_thread,
|
|
117
|
-
args=(self._inst, self._event_queue),
|
|
118
|
-
daemon=True,
|
|
119
|
-
)
|
|
120
|
-
self._thread.start()
|
|
121
|
-
output = True
|
|
122
|
-
|
|
123
|
-
return output
|
|
124
|
-
|
|
125
|
-
def close(self) -> bool:
|
|
126
|
-
super().close()
|
|
127
|
-
with self._inst_lock:
|
|
128
|
-
if self._inst is not None:
|
|
129
|
-
self._inst.close()
|
|
130
|
-
self._status = AdapterBackendStatus.DISCONNECTED
|
|
131
|
-
with self._stop_lock:
|
|
132
|
-
self.stop = True
|
|
133
|
-
return True
|
|
134
|
-
|
|
135
|
-
def write(self, data: bytes) -> bool:
|
|
136
|
-
super().write(data)
|
|
137
|
-
with self._inst_lock:
|
|
138
|
-
if self._inst is not None:
|
|
139
|
-
self._inst.write_raw(data)
|
|
140
|
-
return True
|
|
141
|
-
|
|
142
|
-
def _socket_read(self) -> Fragment:
|
|
143
|
-
self._notify_recv.recv(1)
|
|
144
|
-
if not self._event_queue.empty():
|
|
145
|
-
event = self._event_queue.get()
|
|
146
|
-
if isinstance(event, AdapterDisconnected):
|
|
147
|
-
return Fragment(b"", None)
|
|
148
|
-
|
|
149
|
-
with self._fragment_lock:
|
|
150
|
-
output = self._fragment
|
|
151
|
-
self._fragment = Fragment(b"", None)
|
|
152
|
-
return output
|
|
153
|
-
|
|
154
|
-
def _internal_thread(
|
|
155
|
-
self,
|
|
156
|
-
inst: Resource,
|
|
157
|
-
event_queue: queue.Queue[AdapterSignal],
|
|
158
|
-
) -> None:
|
|
159
|
-
assert pyvisa is not None # for type-narrowing inside this method
|
|
160
|
-
timeout = 2000
|
|
161
|
-
while True:
|
|
162
|
-
payload = b""
|
|
163
|
-
with self._fragment_lock:
|
|
164
|
-
self._fragment = Fragment(b"", None)
|
|
165
|
-
try:
|
|
166
|
-
inst.timeout = timeout
|
|
167
|
-
except pyvisa.InvalidSession:
|
|
168
|
-
pass
|
|
169
|
-
try:
|
|
170
|
-
while True:
|
|
171
|
-
# Read up to an error
|
|
172
|
-
payload += inst.read_bytes(1)
|
|
173
|
-
inst.timeout = 0
|
|
174
|
-
except pyvisa.VisaIOError:
|
|
175
|
-
# Timeout
|
|
176
|
-
if payload:
|
|
177
|
-
with self._fragment_lock:
|
|
178
|
-
if self._fragment.timestamp is None:
|
|
179
|
-
self._fragment.timestamp = time.time()
|
|
180
|
-
self._fragment.data += payload
|
|
181
|
-
# Tell the session that there's data (write to a virtual socket)
|
|
182
|
-
self._notify_send.send(b"1")
|
|
183
|
-
except (TypeError, pyvisa.InvalidSession, BrokenPipeError):
|
|
184
|
-
event_queue.put(AdapterDisconnected())
|
|
185
|
-
self._notify_send.send(b"1")
|
|
186
|
-
with self._stop_lock:
|
|
187
|
-
if self.stop:
|
|
188
|
-
break
|
|
189
|
-
|
|
190
|
-
def selectable(self) -> HasFileno | None:
|
|
191
|
-
return self._notify_recv
|
|
192
|
-
|
|
193
|
-
def is_opened(self) -> bool:
|
|
194
|
-
if self._inst is None:
|
|
195
|
-
return False
|
|
196
|
-
else:
|
|
197
|
-
return self._status == AdapterBackendStatus.CONNECTED
|
syndesi/adapters/ip_server.py
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# File : ip_server.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
#
|
|
5
|
-
# IP server, WIP
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# NOTE : The adapter is only meant to work with a single client, thus
|
|
9
|
-
# a IPServer class is made to handle the socket and generate IP adapters
|
|
10
|
-
|
|
11
|
-
# class IPServer:
|
|
12
|
-
# DEFAULT_BUFFER_SIZE = 1024
|
|
13
|
-
# class Protocol(Enum):
|
|
14
|
-
# TCP = 'TCP'
|
|
15
|
-
# UDP = 'UDP'
|
|
16
|
-
|
|
17
|
-
# def __init__(self,
|
|
18
|
-
# port : int = None,
|
|
19
|
-
# transport : str = 'TCP',
|
|
20
|
-
# address : str = None,
|
|
21
|
-
# max_clients : int = 5,
|
|
22
|
-
# stop_condition = None,
|
|
23
|
-
# alias : str = '',
|
|
24
|
-
# buffer_size : int = DEFAULT_BUFFER_SIZE):
|
|
25
|
-
|
|
26
|
-
# """
|
|
27
|
-
# IP server adapter
|
|
28
|
-
|
|
29
|
-
# Parameters
|
|
30
|
-
# ----------
|
|
31
|
-
# port : int
|
|
32
|
-
# IP port. If None, the default port set with set_default_port will be used
|
|
33
|
-
# transport : str
|
|
34
|
-
# 'TCP' or 'UDP'
|
|
35
|
-
# address : str
|
|
36
|
-
# Custom socket ip, None by default
|
|
37
|
-
# max_clients : int
|
|
38
|
-
# Maximum number of clients, 5 by default
|
|
39
|
-
# stop_condition : StopCondition
|
|
40
|
-
# Specify a read stop condition (None by default)
|
|
41
|
-
# alias : str
|
|
42
|
-
# Specify an alias for this adapter, '' by default
|
|
43
|
-
# buffer_size : int
|
|
44
|
-
# Socket buffer size, may be removed in the future
|
|
45
|
-
# """
|
|
46
|
-
# self._alias = alias
|
|
47
|
-
# self._stop_condition = stop_condition
|
|
48
|
-
# self._logger = logging.getLogger(LoggerAlias.ADAPTER.value)
|
|
49
|
-
# self._transport = self.Protocol(transport)
|
|
50
|
-
# if self._transport == self.Protocol.TCP:
|
|
51
|
-
# self._logger.info("Setting up TCP IP server adapter")
|
|
52
|
-
# self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
53
|
-
# elif self._transport == self.Protocol.UDP:
|
|
54
|
-
# self._logger.info("Setting up UDP IP server adapter")
|
|
55
|
-
# self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
56
|
-
# else:
|
|
57
|
-
# raise ValueError("Invalid protocol")
|
|
58
|
-
|
|
59
|
-
# self._address = socket.gethostname() if address is None else address
|
|
60
|
-
# self._port = port
|
|
61
|
-
# self._max_clients = max_clients
|
|
62
|
-
# self._opened = False
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
# def set_default_port(self, port):
|
|
66
|
-
# """
|
|
67
|
-
# Sets IP port if no port has been set yet.
|
|
68
|
-
|
|
69
|
-
# This way, the user can leave the port empty
|
|
70
|
-
# and the driver/protocol can specify it later
|
|
71
|
-
|
|
72
|
-
# Parameters
|
|
73
|
-
# ----------
|
|
74
|
-
# port : int
|
|
75
|
-
# """
|
|
76
|
-
# if self._port is None:
|
|
77
|
-
# self._port = port
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
# def open(self):
|
|
81
|
-
# if self._port is None:
|
|
82
|
-
# raise ValueError(f"Cannot open adapter without specifying a port")
|
|
83
|
-
# self._logger.info(f"Listening to incoming connections on {self._address}:{self._port}")
|
|
84
|
-
# self._socket.bind((self._address, self._port))
|
|
85
|
-
# self._socket.listen(self._max_clients)
|
|
86
|
-
# self._opened = True
|
|
87
|
-
|
|
88
|
-
# def close(self):
|
|
89
|
-
# if hasattr(self, '_socket'):
|
|
90
|
-
# self._socket.close()
|
|
91
|
-
# self._logger.info("Adapter closed !")
|
|
92
|
-
# self._opened = False
|
|
93
|
-
|
|
94
|
-
# def get_client(self, stop_condition : StopConditionBackend = None, timeout : Timeout = None) -> IP:
|
|
95
|
-
# """
|
|
96
|
-
# Wait for a client to connect to the server and return the corresponding adapter
|
|
97
|
-
# """
|
|
98
|
-
# if not self._opened:
|
|
99
|
-
# raise RuntimeError("open() must be called before getting client")
|
|
100
|
-
# client_socket, address = self._socket.accept()
|
|
101
|
-
# default_timeout = Timeout(response=None, continuation=IP.DEFAULT_CONTINUATION_TIMEOUT, total=None)
|
|
102
|
-
# return IP(_socket=client_socket, address=address, stop_condition=stop_condition, timeout=timeout_fuse(timeout, default_timeout))
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# File : stop_condition.py
|
|
2
|
-
# Author : Sébastien Deriaz
|
|
3
|
-
# License : GPL
|
|
4
|
-
|
|
5
|
-
from abc import abstractmethod
|
|
6
|
-
from enum import Enum
|
|
7
|
-
|
|
8
|
-
class StopConditionType(Enum):
|
|
9
|
-
TERMINATION = "termination"
|
|
10
|
-
LENGTH = "length"
|
|
11
|
-
TIMEOUT = "timeout"
|
|
12
|
-
|
|
13
|
-
class StopCondition:
|
|
14
|
-
@abstractmethod
|
|
15
|
-
def type(self) -> StopConditionType:
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
def __init__(self) -> None:
|
|
19
|
-
"""
|
|
20
|
-
A condition to stop reading from a device
|
|
21
|
-
|
|
22
|
-
Cannot be used on its own
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class Termination(StopCondition):
|
|
27
|
-
def __init__(self, sequence: bytes | str) -> None:
|
|
28
|
-
"""
|
|
29
|
-
Stop reading once the desired sequence is detected
|
|
30
|
-
|
|
31
|
-
Parameters
|
|
32
|
-
----------
|
|
33
|
-
sequence : bytes
|
|
34
|
-
"""
|
|
35
|
-
self.sequence: bytes
|
|
36
|
-
if isinstance(sequence, str):
|
|
37
|
-
self.sequence = sequence.encode("utf-8")
|
|
38
|
-
elif isinstance(sequence, bytes):
|
|
39
|
-
self.sequence = sequence
|
|
40
|
-
else:
|
|
41
|
-
raise ValueError(f"Invalid termination sequence type : {type(sequence)}")
|
|
42
|
-
|
|
43
|
-
def __repr__(self) -> str:
|
|
44
|
-
return self.__str__()
|
|
45
|
-
|
|
46
|
-
def __str__(self) -> str:
|
|
47
|
-
return f"Termination({repr(self.sequence)})"
|
|
48
|
-
|
|
49
|
-
def type(self) -> StopConditionType:
|
|
50
|
-
return StopConditionType.TERMINATION
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class Length(StopCondition):
|
|
54
|
-
def __init__(self, N: int) -> None:
|
|
55
|
-
"""
|
|
56
|
-
Stop condition when the desired number of bytes is reached or passed
|
|
57
|
-
|
|
58
|
-
Parameters
|
|
59
|
-
----------
|
|
60
|
-
N : int
|
|
61
|
-
Number of bytes
|
|
62
|
-
"""
|
|
63
|
-
self.N = N
|
|
64
|
-
|
|
65
|
-
def __repr__(self) -> str:
|
|
66
|
-
return self.__str__()
|
|
67
|
-
|
|
68
|
-
def __str__(self) -> str:
|
|
69
|
-
return f"Length({self.N})"
|
|
70
|
-
|
|
71
|
-
def type(self) -> StopConditionType:
|
|
72
|
-
return StopConditionType.LENGTH
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class Continuation(StopCondition):
|
|
76
|
-
def __init__(self, time: float) -> None:
|
|
77
|
-
super().__init__()
|
|
78
|
-
self.continuation = time
|
|
79
|
-
|
|
80
|
-
def type(self) -> StopConditionType:
|
|
81
|
-
return StopConditionType.TIMEOUT
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class Total(StopCondition):
|
|
85
|
-
def __init__(self, time: float) -> None:
|
|
86
|
-
super().__init__()
|
|
87
|
-
self.total = time
|
|
88
|
-
|
|
89
|
-
def type(self) -> StopConditionType:
|
|
90
|
-
return StopConditionType.TIMEOUT
|