syndesi 0.5.0__py3-none-any.whl → 0.5.1__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/adapters/adapter.py +32 -24
- syndesi/adapters/adapter_worker.py +28 -10
- syndesi/adapters/ip.py +7 -13
- syndesi/adapters/serialport.py +70 -12
- syndesi/adapters/stop_conditions.py +9 -16
- syndesi/adapters/timeout.py +1 -3
- syndesi/adapters/tracehub.py +229 -0
- syndesi/adapters/visa.py +96 -75
- syndesi/cli/shell.py +6 -5
- syndesi/component.py +2 -2
- syndesi/protocols/delimited.py +16 -28
- syndesi/protocols/modbus.py +0 -3
- syndesi/protocols/protocol.py +19 -5
- syndesi/protocols/raw.py +13 -8
- syndesi/scripts/syndesi_trace.py +711 -0
- syndesi/version.py +1 -1
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/METADATA +3 -3
- syndesi-0.5.1.dist-info/RECORD +43 -0
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/WHEEL +1 -1
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/entry_points.txt +1 -1
- syndesi-0.5.0.dist-info/RECORD +0 -41
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {syndesi-0.5.0.dist-info → syndesi-0.5.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# File : tracehub.py
|
|
2
|
+
# Author : Sébastien Deriaz
|
|
3
|
+
# License : GPL
|
|
4
|
+
"""
|
|
5
|
+
Trace module
|
|
6
|
+
|
|
7
|
+
A single global instance of TraceHub allows all the syndesi modules and workers
|
|
8
|
+
to emit trace events
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import socket
|
|
15
|
+
import struct
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import asdict, dataclass, field
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from syndesi.adapters.stop_conditions import StopConditionType
|
|
21
|
+
|
|
22
|
+
STOP_CONDITION_INDICATOR = {
|
|
23
|
+
StopConditionType.CONTINUATION : "Cont",
|
|
24
|
+
StopConditionType.FRAGMENT : "Frag",
|
|
25
|
+
StopConditionType.LENGTH : "Len",
|
|
26
|
+
StopConditionType.TERMINATION : "Term",
|
|
27
|
+
StopConditionType.TOTAL : "Tot",
|
|
28
|
+
StopConditionType.TIMEOUT : "Time"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class TraceEvent:
|
|
34
|
+
"""
|
|
35
|
+
Base trace event
|
|
36
|
+
"""
|
|
37
|
+
descriptor : str
|
|
38
|
+
timestamp : float
|
|
39
|
+
t : str = field(default="", init=False)
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class OpenEvent(TraceEvent):
|
|
43
|
+
"""
|
|
44
|
+
Adapter open trace event
|
|
45
|
+
"""
|
|
46
|
+
t : str = field(default="open", init=False)
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class FragmentEvent(TraceEvent):
|
|
50
|
+
"""
|
|
51
|
+
Fragment received trace event
|
|
52
|
+
"""
|
|
53
|
+
data : str
|
|
54
|
+
length : int
|
|
55
|
+
t : str = field(default="fragment", init=False)
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class CloseEvent(TraceEvent):
|
|
59
|
+
"""
|
|
60
|
+
Adapter close trace event
|
|
61
|
+
"""
|
|
62
|
+
t : str = field(default="close", init=False)
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class ReadEvent(TraceEvent):
|
|
66
|
+
"""
|
|
67
|
+
Adapter read trace event
|
|
68
|
+
"""
|
|
69
|
+
data : str
|
|
70
|
+
t : str = field(default="read", init=False)
|
|
71
|
+
length : int
|
|
72
|
+
stop_condition_indicator : str
|
|
73
|
+
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class WriteEvent(TraceEvent):
|
|
76
|
+
"""
|
|
77
|
+
Adapter write trace event
|
|
78
|
+
"""
|
|
79
|
+
data : str
|
|
80
|
+
length : int
|
|
81
|
+
t : str = field(default="write", init=False)
|
|
82
|
+
|
|
83
|
+
EVENTS : list[type[TraceEvent]] = [FragmentEvent, OpenEvent, CloseEvent, ReadEvent, WriteEvent]
|
|
84
|
+
|
|
85
|
+
EVENTS_MAP : dict[str, type[TraceEvent]]= {
|
|
86
|
+
e.t : e for e in EVENTS
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
DEFAULT_MULTICAST_GROUP = "239.255.42.99"
|
|
90
|
+
DEFAULT_MULTICAST_PORT = 12000
|
|
91
|
+
|
|
92
|
+
def json_to_trace_event(payload : dict[str, Any]) -> TraceEvent:
|
|
93
|
+
"""
|
|
94
|
+
Convert json data to TraceEvent
|
|
95
|
+
"""
|
|
96
|
+
payload_type = payload.get("t", None)
|
|
97
|
+
if payload_type in EVENTS_MAP:
|
|
98
|
+
arguments = payload.copy()
|
|
99
|
+
arguments.pop("t")
|
|
100
|
+
return EVENTS_MAP[payload_type](**arguments)
|
|
101
|
+
|
|
102
|
+
raise ValueError(f"Could not parse payload : {payload}")
|
|
103
|
+
|
|
104
|
+
class _TraceHub:
|
|
105
|
+
TTL = 1
|
|
106
|
+
LOOPBACK = True
|
|
107
|
+
TRUNCATE_LENGTH = 50
|
|
108
|
+
|
|
109
|
+
_udp_addr = (DEFAULT_MULTICAST_GROUP, DEFAULT_MULTICAST_PORT)
|
|
110
|
+
|
|
111
|
+
TRUNCATION_TERMINATION = "..."
|
|
112
|
+
# CHARACTERS_REPLACEMENT = {
|
|
113
|
+
# '\n' : '\\n',
|
|
114
|
+
# '\r' : '\\r',
|
|
115
|
+
# '\t' : '\t',
|
|
116
|
+
|
|
117
|
+
# }
|
|
118
|
+
|
|
119
|
+
def __init__(self) -> None:
|
|
120
|
+
#self._udp_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
|
|
121
|
+
#self._udp_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 1)
|
|
122
|
+
|
|
123
|
+
self._udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
124
|
+
self._udp_sock.setblocking(False)
|
|
125
|
+
self._udp_sock.setsockopt(
|
|
126
|
+
socket.IPPROTO_IP,
|
|
127
|
+
socket.IP_MULTICAST_IF,
|
|
128
|
+
socket.inet_aton("127.0.0.1")
|
|
129
|
+
)
|
|
130
|
+
self._udp_sock.setsockopt(
|
|
131
|
+
socket.IPPROTO_IP,
|
|
132
|
+
socket.IP_MULTICAST_TTL,
|
|
133
|
+
struct.pack("b", self.TTL)
|
|
134
|
+
)
|
|
135
|
+
self._udp_sock.setsockopt(
|
|
136
|
+
socket.IPPROTO_IP,
|
|
137
|
+
socket.IP_MULTICAST_LOOP,
|
|
138
|
+
struct.pack("b", 1 if self.LOOPBACK else 0)
|
|
139
|
+
)
|
|
140
|
+
self._udp_dropped = 0
|
|
141
|
+
|
|
142
|
+
def emit_open(self, descriptor : str) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Emit an open trace event
|
|
145
|
+
"""
|
|
146
|
+
self._emit_event(OpenEvent(descriptor, time.time()))
|
|
147
|
+
|
|
148
|
+
def emit_close(self, descriptor : str) -> None:
|
|
149
|
+
"""
|
|
150
|
+
Emit a close trace event
|
|
151
|
+
"""
|
|
152
|
+
self._emit_event(CloseEvent(descriptor, time.time()))
|
|
153
|
+
|
|
154
|
+
def _format_data(self, data : bytes) -> str:
|
|
155
|
+
if len(data) > 4*self.TRUNCATE_LENGTH:
|
|
156
|
+
# Pre-truncate to avoid working with super long data
|
|
157
|
+
data = data[:4*self.TRUNCATE_LENGTH]
|
|
158
|
+
|
|
159
|
+
str_data = repr(data)[2:-1]
|
|
160
|
+
|
|
161
|
+
truncated_str = str_data[:self.TRUNCATE_LENGTH]
|
|
162
|
+
|
|
163
|
+
if len(str_data) != len(truncated_str):
|
|
164
|
+
return truncated_str[:-len(self.TRUNCATION_TERMINATION)] + self.TRUNCATION_TERMINATION
|
|
165
|
+
return truncated_str
|
|
166
|
+
|
|
167
|
+
def emit_fragment(self, descriptor : str, data : bytes) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Emit a fragment trace event
|
|
170
|
+
"""
|
|
171
|
+
self._emit_event(FragmentEvent(
|
|
172
|
+
descriptor,
|
|
173
|
+
time.time(),
|
|
174
|
+
self._format_data(data),
|
|
175
|
+
len(data)
|
|
176
|
+
))
|
|
177
|
+
|
|
178
|
+
def emit_write(self, descriptor : str, data : bytes) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Emit a write trace event
|
|
181
|
+
"""
|
|
182
|
+
self._emit_event(WriteEvent(
|
|
183
|
+
descriptor,
|
|
184
|
+
time.time(),
|
|
185
|
+
self._format_data(data),
|
|
186
|
+
len(data)
|
|
187
|
+
))
|
|
188
|
+
|
|
189
|
+
def emit_read(
|
|
190
|
+
self,
|
|
191
|
+
descriptor : str,
|
|
192
|
+
data : bytes,
|
|
193
|
+
stop_condition_type : StopConditionType
|
|
194
|
+
) -> None:
|
|
195
|
+
"""
|
|
196
|
+
Emit a read trace event
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
indicator = STOP_CONDITION_INDICATOR[stop_condition_type]
|
|
200
|
+
|
|
201
|
+
self._emit_event(ReadEvent(
|
|
202
|
+
descriptor,
|
|
203
|
+
time.time(),
|
|
204
|
+
self._format_data(data),
|
|
205
|
+
len(data),
|
|
206
|
+
indicator
|
|
207
|
+
))
|
|
208
|
+
|
|
209
|
+
def _emit_event(self, ev: TraceEvent) -> None:
|
|
210
|
+
d = asdict(ev)
|
|
211
|
+
# meta = d.setdefault("meta", {})
|
|
212
|
+
# if isinstance(meta, dict):
|
|
213
|
+
# meta.setdefault("pid", os.getpid())
|
|
214
|
+
|
|
215
|
+
payload = json.dumps(d, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
|
|
216
|
+
|
|
217
|
+
# if len(payload) > self._udp_max:
|
|
218
|
+
# if isinstance(meta, dict):
|
|
219
|
+
# meta["truncated"] = True
|
|
220
|
+
# payload = payload[: self._udp_max]
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
self._udp_sock.sendto(payload, self._udp_addr)
|
|
224
|
+
except (BlockingIOError, InterruptedError, OSError):
|
|
225
|
+
# drop rather than blocking I/O paths
|
|
226
|
+
self._udp_dropped += 1
|
|
227
|
+
|
|
228
|
+
# Public singleton
|
|
229
|
+
tracehub = _TraceHub()
|
syndesi/adapters/visa.py
CHANGED
|
@@ -15,9 +15,12 @@ from collections.abc import Callable
|
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
from enum import Enum
|
|
17
17
|
from types import EllipsisType
|
|
18
|
+
from typing import cast
|
|
19
|
+
|
|
20
|
+
import pyvisa
|
|
21
|
+
from pyvisa.resources import MessageBasedResource
|
|
18
22
|
|
|
19
23
|
from syndesi.adapters.adapter_worker import (
|
|
20
|
-
AdapterDisconnectedEvent,
|
|
21
24
|
AdapterEvent,
|
|
22
25
|
HasFileno,
|
|
23
26
|
)
|
|
@@ -28,13 +31,20 @@ from syndesi.tools.errors import AdapterReadError
|
|
|
28
31
|
from .adapter import Adapter
|
|
29
32
|
from .timeout import Timeout
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
|
|
35
|
+
class QueueEvent:
|
|
36
|
+
"""VISA adapter queue event"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DisconnectedEvent(QueueEvent):
|
|
40
|
+
"""VISA queue disconnected event"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class FragmentEvent(QueueEvent):
|
|
45
|
+
"""VISA queue new fragment event"""
|
|
46
|
+
|
|
47
|
+
fragment: Fragment
|
|
38
48
|
|
|
39
49
|
|
|
40
50
|
@dataclass
|
|
@@ -100,6 +110,8 @@ class Visa(Adapter):
|
|
|
100
110
|
It uses pyvisa under the hood
|
|
101
111
|
"""
|
|
102
112
|
|
|
113
|
+
THREAD_STOP_DELAY = 0.2
|
|
114
|
+
|
|
103
115
|
def __init__(
|
|
104
116
|
self,
|
|
105
117
|
descriptor: str,
|
|
@@ -109,21 +121,12 @@ class Visa(Adapter):
|
|
|
109
121
|
timeout: None | float | Timeout | EllipsisType = ...,
|
|
110
122
|
encoding: str = "utf-8",
|
|
111
123
|
event_callback: Callable[[AdapterEvent], None] | None = None,
|
|
124
|
+
auto_open: bool = False,
|
|
112
125
|
) -> None:
|
|
113
|
-
super().__init__(
|
|
114
|
-
descriptor=VisaDescriptor.from_string(descriptor),
|
|
115
|
-
alias=alias,
|
|
116
|
-
stop_conditions=stop_conditions,
|
|
117
|
-
timeout=timeout,
|
|
118
|
-
encoding=encoding,
|
|
119
|
-
event_callback=event_callback,
|
|
120
|
-
)
|
|
121
126
|
|
|
122
127
|
self._worker_descriptor: VisaDescriptor
|
|
123
128
|
self._descriptor: VisaDescriptor
|
|
124
129
|
|
|
125
|
-
self._logger.info("Setting up VISA IP adapter")
|
|
126
|
-
|
|
127
130
|
if pyvisa is None:
|
|
128
131
|
raise ImportError(
|
|
129
132
|
"Missing optional dependency 'pyvisa'. Install with:\n"
|
|
@@ -131,7 +134,9 @@ class Visa(Adapter):
|
|
|
131
134
|
)
|
|
132
135
|
|
|
133
136
|
self._rm = pyvisa.ResourceManager()
|
|
134
|
-
self._inst:
|
|
137
|
+
self._inst: MessageBasedResource | None = (
|
|
138
|
+
None # annotation only; no runtime import needed
|
|
139
|
+
)
|
|
135
140
|
|
|
136
141
|
# We need a socket pair because VISA doesn't expose a selectable fileno/socket
|
|
137
142
|
# So we create a thread to read data and push that to the socket
|
|
@@ -142,18 +147,19 @@ class Visa(Adapter):
|
|
|
142
147
|
self._stop_lock = threading.Lock()
|
|
143
148
|
self.stop = False
|
|
144
149
|
|
|
145
|
-
self.
|
|
146
|
-
self._fragment: Fragment | None = None
|
|
147
|
-
self._event_queue: queue.Queue[AdapterEvent] = queue.Queue()
|
|
150
|
+
self._event_queue: queue.Queue[QueueEvent] = queue.Queue()
|
|
148
151
|
|
|
149
|
-
self._thread
|
|
150
|
-
target=self._internal_thread,
|
|
151
|
-
args=(self._inst, self._event_queue),
|
|
152
|
-
daemon=True,
|
|
153
|
-
)
|
|
154
|
-
self._thread.start()
|
|
152
|
+
self._thread: threading.Thread | None = None
|
|
155
153
|
|
|
156
|
-
|
|
154
|
+
super().__init__(
|
|
155
|
+
descriptor=VisaDescriptor.from_string(descriptor),
|
|
156
|
+
alias=alias,
|
|
157
|
+
stop_conditions=stop_conditions,
|
|
158
|
+
timeout=timeout,
|
|
159
|
+
encoding=encoding,
|
|
160
|
+
event_callback=event_callback,
|
|
161
|
+
auto_open=auto_open,
|
|
162
|
+
)
|
|
157
163
|
|
|
158
164
|
def _default_timeout(self) -> Timeout:
|
|
159
165
|
return Timeout(response=5, action="error")
|
|
@@ -186,83 +192,98 @@ class Visa(Adapter):
|
|
|
186
192
|
return available_resources
|
|
187
193
|
|
|
188
194
|
def _worker_close(self) -> None:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
super()._worker_close()
|
|
196
|
+
# with self._inst_lock:
|
|
197
|
+
# Stop the thread
|
|
198
|
+
if self._thread is not None:
|
|
193
199
|
with self._stop_lock:
|
|
194
200
|
self.stop = True
|
|
201
|
+
self._thread.join(timeout=self.THREAD_STOP_DELAY)
|
|
202
|
+
|
|
203
|
+
# if self._inst is not None:
|
|
204
|
+
# self._inst.close()
|
|
205
|
+
self._opened = False
|
|
195
206
|
|
|
196
207
|
def _worker_write(self, data: bytes) -> None:
|
|
208
|
+
super()._worker_write(data)
|
|
197
209
|
# TODO : Add try around write
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
210
|
+
# TODO : We assume that the instance is thread safe because
|
|
211
|
+
# it would slow things down to have a lock because the internal thread
|
|
212
|
+
# would release it every cycle (50ms)
|
|
213
|
+
|
|
214
|
+
# with self._inst_lock:
|
|
215
|
+
if self._inst is not None:
|
|
216
|
+
self._inst.write_raw(data)
|
|
201
217
|
|
|
202
218
|
def _worker_read(self, fragment_timestamp: float) -> Fragment:
|
|
203
219
|
self._notify_recv.recv(1)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
output = self._fragment
|
|
214
|
-
self._fragment = None
|
|
215
|
-
return output
|
|
220
|
+
event = self._event_queue.get(block=False, timeout=None)
|
|
221
|
+
|
|
222
|
+
if isinstance(event, DisconnectedEvent):
|
|
223
|
+
# Signal that the adapter disconnected
|
|
224
|
+
return Fragment(b"", fragment_timestamp)
|
|
225
|
+
if isinstance(event, FragmentEvent):
|
|
226
|
+
return event.fragment
|
|
227
|
+
|
|
228
|
+
raise AdapterReadError("Invalid queue event")
|
|
216
229
|
|
|
217
230
|
def _worker_open(self) -> None:
|
|
231
|
+
super()._worker_open()
|
|
218
232
|
self._worker_check_descriptor()
|
|
219
233
|
|
|
220
|
-
if self.
|
|
221
|
-
|
|
222
|
-
self._inst = self._rm.open_resource(self._worker_descriptor.descriptor)
|
|
234
|
+
if self._thread is not None:
|
|
235
|
+
self.close()
|
|
223
236
|
|
|
224
|
-
if
|
|
225
|
-
|
|
226
|
-
self._inst.write_termination = ""
|
|
227
|
-
self._inst.read_termination = None
|
|
228
|
-
self._opened = True
|
|
237
|
+
# if self._inst is None:
|
|
238
|
+
# NOTE: self._rm is always defined in __init__ when pyvisa is present
|
|
229
239
|
|
|
230
|
-
|
|
240
|
+
self._inst = cast(
|
|
241
|
+
MessageBasedResource,
|
|
242
|
+
self._rm.open_resource(self._worker_descriptor.descriptor),
|
|
243
|
+
)
|
|
244
|
+
self._inst.write_termination = ""
|
|
245
|
+
self._inst.read_termination = None
|
|
231
246
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
247
|
+
self._opened = True
|
|
248
|
+
|
|
249
|
+
self._thread = threading.Thread(
|
|
250
|
+
target=self._internal_thread,
|
|
251
|
+
args=(self._inst,),
|
|
252
|
+
daemon=True,
|
|
253
|
+
)
|
|
254
|
+
self._thread.start()
|
|
255
|
+
|
|
256
|
+
def _internal_thread(self, instance: MessageBasedResource) -> None:
|
|
257
|
+
timeout = 50e-3
|
|
238
258
|
while True:
|
|
239
259
|
payload = b""
|
|
240
|
-
|
|
241
|
-
self._fragment = None # Fragment(b"", None)
|
|
260
|
+
fragment: Fragment | None = None
|
|
242
261
|
try:
|
|
243
|
-
|
|
262
|
+
instance.timeout = timeout
|
|
244
263
|
except pyvisa.InvalidSession:
|
|
245
|
-
|
|
264
|
+
return
|
|
246
265
|
try:
|
|
247
266
|
while True:
|
|
248
267
|
# Read up to an error
|
|
249
|
-
payload +=
|
|
250
|
-
|
|
268
|
+
payload += instance.read_bytes(1) # TODO : Maybe test with read_raw
|
|
269
|
+
instance.timeout = 0
|
|
251
270
|
except pyvisa.VisaIOError:
|
|
252
271
|
# Timeout
|
|
253
272
|
if payload:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
self._fragment.data += payload
|
|
273
|
+
if fragment is None:
|
|
274
|
+
fragment = Fragment(payload, time.time())
|
|
275
|
+
else:
|
|
276
|
+
fragment.data += payload
|
|
259
277
|
# Tell the session that there's data (write to a virtual socket)
|
|
278
|
+
self._event_queue.put(FragmentEvent(fragment))
|
|
260
279
|
self._notify_send.send(b"1")
|
|
261
280
|
except (TypeError, pyvisa.InvalidSession, BrokenPipeError):
|
|
262
|
-
|
|
281
|
+
self._event_queue.put(DisconnectedEvent())
|
|
263
282
|
self._notify_send.send(b"1")
|
|
283
|
+
|
|
264
284
|
with self._stop_lock:
|
|
265
285
|
if self.stop:
|
|
286
|
+
instance.close()
|
|
266
287
|
break
|
|
267
288
|
|
|
268
289
|
def _selectable(self) -> HasFileno | None:
|
syndesi/cli/shell.py
CHANGED
|
@@ -128,10 +128,9 @@ class AdapterShell:
|
|
|
128
128
|
self._parser.add_argument(
|
|
129
129
|
"-t",
|
|
130
130
|
"--timeout",
|
|
131
|
-
nargs="+",
|
|
132
131
|
type=float,
|
|
133
132
|
required=False,
|
|
134
|
-
default=
|
|
133
|
+
default=2.0,
|
|
135
134
|
help="Adapter timeout (response)",
|
|
136
135
|
)
|
|
137
136
|
self._parser.add_argument(
|
|
@@ -202,7 +201,9 @@ class AdapterShell:
|
|
|
202
201
|
auto_open=False,
|
|
203
202
|
)
|
|
204
203
|
elif kind == AdapterType.VISA:
|
|
205
|
-
self.adapter = Visa(
|
|
204
|
+
self.adapter = Visa(
|
|
205
|
+
descriptor=args.descriptor, timeout=timeout, auto_open=False
|
|
206
|
+
)
|
|
206
207
|
|
|
207
208
|
self.adapter.set_default_timeout(Timeout(action="return_empty"))
|
|
208
209
|
|
|
@@ -245,7 +246,8 @@ class AdapterShell:
|
|
|
245
246
|
else:
|
|
246
247
|
self.shell.run()
|
|
247
248
|
self.shell.print(
|
|
248
|
-
f"Opened adapter {self.adapter.
|
|
249
|
+
f"Opened adapter {self.adapter.get_descriptor()}",
|
|
250
|
+
style=Shell.Style.NOTE,
|
|
249
251
|
)
|
|
250
252
|
|
|
251
253
|
def on_command(self, command: str) -> None:
|
|
@@ -276,5 +278,4 @@ class AdapterShell:
|
|
|
276
278
|
)
|
|
277
279
|
elif isinstance(event, ProtocolFrameEvent):
|
|
278
280
|
data = event.frame.get_payload()
|
|
279
|
-
# TODO : Catch data from delimited with formatting
|
|
280
281
|
self.shell.print(data)
|
syndesi/component.py
CHANGED
|
@@ -57,7 +57,7 @@ class Frame(Generic[T]):
|
|
|
57
57
|
"""
|
|
58
58
|
|
|
59
59
|
stop_timestamp: float | None
|
|
60
|
-
stop_condition_type: StopConditionType
|
|
60
|
+
stop_condition_type: StopConditionType
|
|
61
61
|
previous_read_buffer_used: bool
|
|
62
62
|
response_delay: float | None
|
|
63
63
|
|
|
@@ -133,7 +133,7 @@ class Component(ABC, Generic[T]):
|
|
|
133
133
|
"""
|
|
134
134
|
|
|
135
135
|
def __init__(self, logger_alias: LoggerAlias) -> None:
|
|
136
|
-
super().__init__()
|
|
136
|
+
#super().__init__()
|
|
137
137
|
self._logger = logging.getLogger(logger_alias.value)
|
|
138
138
|
|
|
139
139
|
# ==== open ====
|
syndesi/protocols/delimited.py
CHANGED
|
@@ -9,23 +9,11 @@ command-like formats with specified delimiters (like \\n, \\r, \\r\\n, etc...)
|
|
|
9
9
|
from collections.abc import Callable
|
|
10
10
|
from types import EllipsisType
|
|
11
11
|
|
|
12
|
-
from syndesi.adapters.adapter_worker import (
|
|
13
|
-
AdapterDisconnectedEvent,
|
|
14
|
-
AdapterEvent,
|
|
15
|
-
AdapterFrameEvent,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
12
|
from ..adapters.adapter import Adapter
|
|
19
13
|
from ..adapters.stop_conditions import StopCondition, Termination
|
|
20
14
|
from ..adapters.timeout import Timeout
|
|
21
15
|
from ..component import AdapterFrame, ReadScope
|
|
22
|
-
from .protocol import
|
|
23
|
-
Protocol,
|
|
24
|
-
ProtocolDisconnectedEvent,
|
|
25
|
-
ProtocolEvent,
|
|
26
|
-
ProtocolFrame,
|
|
27
|
-
ProtocolFrameEvent,
|
|
28
|
-
)
|
|
16
|
+
from .protocol import Protocol, ProtocolEvent, ProtocolFrame
|
|
29
17
|
|
|
30
18
|
|
|
31
19
|
class DelimitedFrame(ProtocolFrame[str]):
|
|
@@ -70,7 +58,10 @@ class Delimited(Protocol[str]):
|
|
|
70
58
|
event_callback: Callable[[ProtocolEvent], None] | None = None,
|
|
71
59
|
receive_termination: str | None = None,
|
|
72
60
|
) -> None:
|
|
73
|
-
|
|
61
|
+
self._encoding = encoding
|
|
62
|
+
if isinstance(termination, bytes):
|
|
63
|
+
termination = termination.decode(self._encoding)
|
|
64
|
+
elif not isinstance(termination, str):
|
|
74
65
|
raise ValueError(
|
|
75
66
|
f"end argument must be of type str or bytes, not {type(termination)}"
|
|
76
67
|
)
|
|
@@ -79,7 +70,6 @@ class Delimited(Protocol[str]):
|
|
|
79
70
|
else:
|
|
80
71
|
self._receive_termination = receive_termination
|
|
81
72
|
self._termination = termination
|
|
82
|
-
self._encoding = encoding
|
|
83
73
|
self._response_formatting = format_response
|
|
84
74
|
|
|
85
75
|
adapter.set_stop_conditions(
|
|
@@ -89,8 +79,6 @@ class Delimited(Protocol[str]):
|
|
|
89
79
|
|
|
90
80
|
self._adapter.set_event_callback(self._on_event)
|
|
91
81
|
|
|
92
|
-
# TODO : Disable encoding/decoding when encoding==None
|
|
93
|
-
|
|
94
82
|
def __str__(self) -> str:
|
|
95
83
|
if self._receive_termination == self._termination:
|
|
96
84
|
return f"Delimited({self._adapter},{repr(self._termination)})"
|
|
@@ -153,16 +141,16 @@ class Delimited(Protocol[str]):
|
|
|
153
141
|
)
|
|
154
142
|
return frame.get_payload()
|
|
155
143
|
|
|
156
|
-
def _on_event(self, event: AdapterEvent) -> None:
|
|
144
|
+
# def _on_event(self, event: AdapterEvent) -> None:
|
|
157
145
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
146
|
+
# if self._event_callback is not None:
|
|
147
|
+
# output_event: ProtocolEvent | None = None
|
|
148
|
+
# if isinstance(event, AdapterDisconnectedEvent):
|
|
149
|
+
# output_event = ProtocolDisconnectedEvent()
|
|
150
|
+
# if isinstance(event, AdapterFrameEvent):
|
|
151
|
+
# output_event = ProtocolFrameEvent(
|
|
152
|
+
# frame=self._adapter_to_protocol(event.frame)
|
|
153
|
+
# )
|
|
166
154
|
|
|
167
|
-
|
|
168
|
-
|
|
155
|
+
# if output_event is not None:
|
|
156
|
+
# self._event_callback(output_event)
|
syndesi/protocols/modbus.py
CHANGED
|
@@ -43,7 +43,6 @@ from math import ceil
|
|
|
43
43
|
from types import EllipsisType
|
|
44
44
|
from typing import cast
|
|
45
45
|
|
|
46
|
-
from syndesi.adapters.adapter_worker import AdapterEvent
|
|
47
46
|
from syndesi.component import AdapterFrame
|
|
48
47
|
|
|
49
48
|
from ..adapters.adapter import Adapter
|
|
@@ -1422,8 +1421,6 @@ class Modbus(Protocol[ModbusSDU]):
|
|
|
1422
1421
|
def _default_timeout(self) -> Timeout | None:
|
|
1423
1422
|
return Timeout(response=1, action="error")
|
|
1424
1423
|
|
|
1425
|
-
def _on_event(self, event: AdapterEvent) -> None: ...
|
|
1426
|
-
|
|
1427
1424
|
def _protocol_to_adapter(self, protocol_payload: ModbusSDU) -> bytes:
|
|
1428
1425
|
if isinstance(protocol_payload, SerialLineOnlySDU):
|
|
1429
1426
|
if self._modbus_type == ModbusType.TCP:
|