pyxcp 0.25.4__cp313-cp313-win_amd64.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.
- pyxcp/__init__.py +20 -0
- pyxcp/aml/EtasCANMonitoring.a2l +82 -0
- pyxcp/aml/EtasCANMonitoring.aml +67 -0
- pyxcp/aml/XCP_Common.aml +408 -0
- pyxcp/aml/XCPonCAN.aml +78 -0
- pyxcp/aml/XCPonEth.aml +33 -0
- pyxcp/aml/XCPonFlx.aml +113 -0
- pyxcp/aml/XCPonSxI.aml +66 -0
- pyxcp/aml/XCPonUSB.aml +106 -0
- pyxcp/aml/ifdata_CAN.a2l +20 -0
- pyxcp/aml/ifdata_Eth.a2l +11 -0
- pyxcp/aml/ifdata_Flx.a2l +94 -0
- pyxcp/aml/ifdata_SxI.a2l +13 -0
- pyxcp/aml/ifdata_USB.a2l +81 -0
- pyxcp/asam/__init__.py +0 -0
- pyxcp/asam/types.py +131 -0
- pyxcp/asamkeydll.c +116 -0
- pyxcp/asamkeydll.exe +0 -0
- pyxcp/asamkeydll.sh +2 -0
- pyxcp/checksum.py +732 -0
- pyxcp/cmdline.py +71 -0
- pyxcp/config/__init__.py +1257 -0
- pyxcp/config/legacy.py +120 -0
- pyxcp/constants.py +47 -0
- pyxcp/cpp_ext/__init__.py +0 -0
- pyxcp/cpp_ext/aligned_buffer.hpp +168 -0
- pyxcp/cpp_ext/bin.hpp +105 -0
- pyxcp/cpp_ext/blockmem.hpp +58 -0
- pyxcp/cpp_ext/cpp_ext.cp310-win_amd64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp311-win_amd64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp312-win_amd64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp313-win_amd64.pyd +0 -0
- pyxcp/cpp_ext/daqlist.hpp +374 -0
- pyxcp/cpp_ext/event.hpp +67 -0
- pyxcp/cpp_ext/extension_wrapper.cpp +131 -0
- pyxcp/cpp_ext/framing.hpp +360 -0
- pyxcp/cpp_ext/helper.hpp +280 -0
- pyxcp/cpp_ext/mcobject.hpp +248 -0
- pyxcp/cpp_ext/sxi_framing.hpp +332 -0
- pyxcp/cpp_ext/tsqueue.hpp +46 -0
- pyxcp/daq_stim/__init__.py +291 -0
- pyxcp/daq_stim/optimize/__init__.py +67 -0
- pyxcp/daq_stim/optimize/binpacking.py +41 -0
- pyxcp/daq_stim/scheduler.cpp +62 -0
- pyxcp/daq_stim/scheduler.hpp +75 -0
- pyxcp/daq_stim/stim.cp310-win_amd64.pyd +0 -0
- pyxcp/daq_stim/stim.cp311-win_amd64.pyd +0 -0
- pyxcp/daq_stim/stim.cp312-win_amd64.pyd +0 -0
- pyxcp/daq_stim/stim.cp313-win_amd64.pyd +0 -0
- pyxcp/daq_stim/stim.cpp +13 -0
- pyxcp/daq_stim/stim.hpp +604 -0
- pyxcp/daq_stim/stim_wrapper.cpp +50 -0
- pyxcp/dllif.py +100 -0
- pyxcp/errormatrix.py +878 -0
- pyxcp/examples/conf_can.toml +19 -0
- pyxcp/examples/conf_can_user.toml +16 -0
- pyxcp/examples/conf_can_vector.json +11 -0
- pyxcp/examples/conf_can_vector.toml +11 -0
- pyxcp/examples/conf_eth.toml +9 -0
- pyxcp/examples/conf_nixnet.json +20 -0
- pyxcp/examples/conf_socket_can.toml +12 -0
- pyxcp/examples/run_daq.py +165 -0
- pyxcp/examples/xcp_policy.py +60 -0
- pyxcp/examples/xcp_read_benchmark.py +38 -0
- pyxcp/examples/xcp_skel.py +48 -0
- pyxcp/examples/xcp_unlock.py +36 -0
- pyxcp/examples/xcp_user_supplied_driver.py +43 -0
- pyxcp/examples/xcphello.py +65 -0
- pyxcp/examples/xcphello_recorder.py +107 -0
- pyxcp/master/__init__.py +10 -0
- pyxcp/master/errorhandler.py +677 -0
- pyxcp/master/master.py +2641 -0
- pyxcp/py.typed +0 -0
- pyxcp/recorder/.idea/.gitignore +8 -0
- pyxcp/recorder/.idea/misc.xml +4 -0
- pyxcp/recorder/.idea/modules.xml +8 -0
- pyxcp/recorder/.idea/recorder.iml +6 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +7 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
- pyxcp/recorder/.idea/sonarlint/issuestore/index.pb +7 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
- pyxcp/recorder/.idea/sonarlint/securityhotspotstore/index.pb +7 -0
- pyxcp/recorder/.idea/vcs.xml +10 -0
- pyxcp/recorder/__init__.py +96 -0
- pyxcp/recorder/build_clang.cmd +1 -0
- pyxcp/recorder/build_clang.sh +2 -0
- pyxcp/recorder/build_gcc.cmd +1 -0
- pyxcp/recorder/build_gcc.sh +2 -0
- pyxcp/recorder/build_gcc_arm.sh +2 -0
- pyxcp/recorder/converter/__init__.py +444 -0
- pyxcp/recorder/lz4.c +2829 -0
- pyxcp/recorder/lz4.h +879 -0
- pyxcp/recorder/lz4hc.c +2041 -0
- pyxcp/recorder/lz4hc.h +413 -0
- pyxcp/recorder/mio.hpp +1714 -0
- pyxcp/recorder/reader.hpp +138 -0
- pyxcp/recorder/reco.py +278 -0
- pyxcp/recorder/recorder.rst +0 -0
- pyxcp/recorder/rekorder.cp310-win_amd64.pyd +0 -0
- pyxcp/recorder/rekorder.cp311-win_amd64.pyd +0 -0
- pyxcp/recorder/rekorder.cp312-win_amd64.pyd +0 -0
- pyxcp/recorder/rekorder.cp313-win_amd64.pyd +0 -0
- pyxcp/recorder/rekorder.cpp +59 -0
- pyxcp/recorder/rekorder.hpp +274 -0
- pyxcp/recorder/setup.py +41 -0
- pyxcp/recorder/test_reko.py +34 -0
- pyxcp/recorder/unfolder.hpp +1354 -0
- pyxcp/recorder/wrap.cpp +184 -0
- pyxcp/recorder/writer.hpp +302 -0
- pyxcp/scripts/__init__.py +0 -0
- pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
- pyxcp/scripts/xcp_examples.py +64 -0
- pyxcp/scripts/xcp_fetch_a2l.py +40 -0
- pyxcp/scripts/xcp_id_scanner.py +18 -0
- pyxcp/scripts/xcp_info.py +159 -0
- pyxcp/scripts/xcp_profile.py +26 -0
- pyxcp/scripts/xmraw_converter.py +31 -0
- pyxcp/stim/__init__.py +0 -0
- pyxcp/tests/test_asam_types.py +24 -0
- pyxcp/tests/test_binpacking.py +186 -0
- pyxcp/tests/test_can.py +1324 -0
- pyxcp/tests/test_checksum.py +95 -0
- pyxcp/tests/test_daq.py +193 -0
- pyxcp/tests/test_daq_opt.py +426 -0
- pyxcp/tests/test_frame_padding.py +156 -0
- pyxcp/tests/test_framing.py +262 -0
- pyxcp/tests/test_master.py +2116 -0
- pyxcp/tests/test_transport.py +177 -0
- pyxcp/tests/test_utils.py +30 -0
- pyxcp/timing.py +60 -0
- pyxcp/transport/__init__.py +13 -0
- pyxcp/transport/base.py +484 -0
- pyxcp/transport/base_transport.hpp +0 -0
- pyxcp/transport/can.py +660 -0
- pyxcp/transport/eth.py +254 -0
- pyxcp/transport/hdf5_policy.py +129 -0
- pyxcp/transport/sxi.py +209 -0
- pyxcp/transport/transport_ext.cp310-win_amd64.pyd +0 -0
- pyxcp/transport/transport_ext.cp311-win_amd64.pyd +0 -0
- pyxcp/transport/transport_ext.cp312-win_amd64.pyd +0 -0
- pyxcp/transport/transport_ext.cp313-win_amd64.pyd +0 -0
- pyxcp/transport/transport_ext.hpp +214 -0
- pyxcp/transport/transport_wrapper.cpp +249 -0
- pyxcp/transport/usb_transport.py +229 -0
- pyxcp/types.py +987 -0
- pyxcp/utils/__init__.py +127 -0
- pyxcp/utils/cli.py +78 -0
- pyxcp/vector/__init__.py +0 -0
- pyxcp/vector/map.py +82 -0
- pyxcp-0.25.4.dist-info/METADATA +341 -0
- pyxcp-0.25.4.dist-info/RECORD +157 -0
- pyxcp-0.25.4.dist-info/WHEEL +4 -0
- pyxcp-0.25.4.dist-info/entry_points.txt +9 -0
- pyxcp-0.25.4.dist-info/licenses/LICENSE +165 -0
pyxcp/transport/eth.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import selectors
|
|
3
|
+
import socket
|
|
4
|
+
import struct
|
|
5
|
+
import threading
|
|
6
|
+
from collections import deque
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from pyxcp.transport.base import (
|
|
10
|
+
BaseTransport,
|
|
11
|
+
ChecksumType,
|
|
12
|
+
XcpFramingConfig,
|
|
13
|
+
XcpTransportLayerType,
|
|
14
|
+
)
|
|
15
|
+
from pyxcp.utils import short_sleep
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DEFAULT_XCP_PORT = 5555
|
|
19
|
+
RECV_SIZE = 8196
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def socket_to_str(sock: socket.socket) -> str:
|
|
23
|
+
peer = sock.getpeername()
|
|
24
|
+
local = sock.getsockname()
|
|
25
|
+
AF = {
|
|
26
|
+
socket.AF_INET: "AF_INET",
|
|
27
|
+
socket.AF_INET6: "AF_INET6",
|
|
28
|
+
}
|
|
29
|
+
TYPE = {
|
|
30
|
+
socket.SOCK_DGRAM: "SOCK_DGRAM",
|
|
31
|
+
socket.SOCK_STREAM: "SOCK_STREAM",
|
|
32
|
+
}
|
|
33
|
+
family = AF.get(sock.family, "OTHER")
|
|
34
|
+
typ = TYPE.get(sock.type, "UNKNOWN")
|
|
35
|
+
res = f"XCPonEth - Connected to: {peer[0]}:{peer[1]} local address: {local[0]}:{local[1]} [{family}][{typ}]"
|
|
36
|
+
return res
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Eth(BaseTransport):
|
|
40
|
+
""""""
|
|
41
|
+
|
|
42
|
+
MAX_DATAGRAM_SIZE = 512
|
|
43
|
+
HEADER = struct.Struct("<HH")
|
|
44
|
+
|
|
45
|
+
def __init__(self, config=None, policy=None, transport_layer_interface: Optional[socket.socket] = None) -> None:
|
|
46
|
+
self.load_config(config)
|
|
47
|
+
framing_config = XcpFramingConfig(
|
|
48
|
+
transport_layer_type=XcpTransportLayerType.ETH,
|
|
49
|
+
header_len=2,
|
|
50
|
+
header_ctr=2,
|
|
51
|
+
header_fill=0,
|
|
52
|
+
tail_fill=False,
|
|
53
|
+
tail_cs=ChecksumType.NO_CHECKSUM,
|
|
54
|
+
)
|
|
55
|
+
super().__init__(config, framing_config, policy, transport_layer_interface)
|
|
56
|
+
self.host: str = self.config.host
|
|
57
|
+
self.port: int = self.config.port
|
|
58
|
+
self.protocol: int = self.config.protocol
|
|
59
|
+
self.ipv6: bool = self.config.ipv6
|
|
60
|
+
self.use_tcp_no_delay: bool = self.config.tcp_nodelay
|
|
61
|
+
address_to_bind: str = self.config.bind_to_address
|
|
62
|
+
bind_to_port: int = self.config.bind_to_port
|
|
63
|
+
self._local_address = (address_to_bind, bind_to_port) if address_to_bind else None
|
|
64
|
+
if self.ipv6 and not socket.has_ipv6:
|
|
65
|
+
msg = "XCPonEth - IPv6 not supported by your platform."
|
|
66
|
+
self.logger.critical(msg)
|
|
67
|
+
raise RuntimeError(msg)
|
|
68
|
+
else:
|
|
69
|
+
address_family = socket.AF_INET6 if self.ipv6 else socket.AF_INET
|
|
70
|
+
proto = socket.SOCK_STREAM if self.protocol == "TCP" else socket.SOCK_DGRAM
|
|
71
|
+
if self.host.lower() == "localhost":
|
|
72
|
+
self.host = "::1" if self.ipv6 else "localhost"
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
addrinfo = socket.getaddrinfo(self.host, self.port, address_family, proto)
|
|
76
|
+
(
|
|
77
|
+
self.address_family,
|
|
78
|
+
self.socktype,
|
|
79
|
+
self.proto,
|
|
80
|
+
self.canonname,
|
|
81
|
+
self.sockaddr,
|
|
82
|
+
) = addrinfo[0]
|
|
83
|
+
except BaseException as ex: # noqa: B036
|
|
84
|
+
msg = f"XCPonEth - Failed to resolve address {self.host}:{self.port} ({self.protocol}, ipv6={self.ipv6}): {ex.__class__.__name__}: {ex}"
|
|
85
|
+
self.logger.critical(msg, extra={"transport": "eth", "host": self.host, "port": self.port, "protocol": self.protocol})
|
|
86
|
+
raise Exception(msg) from ex
|
|
87
|
+
self.status: int = 0
|
|
88
|
+
self.sock = socket.socket(self.address_family, self.socktype, self.proto)
|
|
89
|
+
self.selector = selectors.DefaultSelector()
|
|
90
|
+
self.selector.register(self.sock, selectors.EVENT_READ)
|
|
91
|
+
self.use_tcp = self.protocol == "TCP"
|
|
92
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
93
|
+
if self.use_tcp and self.use_tcp_no_delay:
|
|
94
|
+
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
|
95
|
+
if hasattr(socket, "SO_REUSEPORT"):
|
|
96
|
+
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
|
97
|
+
self.sock.settimeout(0.5)
|
|
98
|
+
if self._local_address:
|
|
99
|
+
try:
|
|
100
|
+
self.sock.bind(self._local_address)
|
|
101
|
+
except BaseException as ex: # noqa: B036
|
|
102
|
+
msg = f"XCPonEth - Failed to bind socket to given address {self._local_address}: {ex.__class__.__name__}: {ex}"
|
|
103
|
+
self.logger.critical(
|
|
104
|
+
msg, extra={"transport": "eth", "host": self.host, "port": self.port, "protocol": self.protocol}
|
|
105
|
+
)
|
|
106
|
+
raise Exception(msg) from ex
|
|
107
|
+
self._packet_listener = threading.Thread(
|
|
108
|
+
target=self._packet_listen,
|
|
109
|
+
args=(),
|
|
110
|
+
kwargs={},
|
|
111
|
+
daemon=True,
|
|
112
|
+
)
|
|
113
|
+
self._packets = deque()
|
|
114
|
+
|
|
115
|
+
def connect(self) -> None:
|
|
116
|
+
if self.status == 0:
|
|
117
|
+
self.sock.connect(self.sockaddr)
|
|
118
|
+
self.logger.info(socket_to_str(self.sock))
|
|
119
|
+
self.start_listener()
|
|
120
|
+
self.status = 1 # connected
|
|
121
|
+
|
|
122
|
+
def start_listener(self) -> None:
|
|
123
|
+
super().start_listener()
|
|
124
|
+
if self._packet_listener.is_alive():
|
|
125
|
+
self._packet_listener.join(timeout=2.0)
|
|
126
|
+
self._packet_listener = threading.Thread(target=self._packet_listen, daemon=True)
|
|
127
|
+
self._packet_listener.start()
|
|
128
|
+
|
|
129
|
+
def close(self) -> None:
|
|
130
|
+
"""Close the transport-layer connection and event-loop."""
|
|
131
|
+
self.finish_listener()
|
|
132
|
+
try:
|
|
133
|
+
if self.listener.is_alive():
|
|
134
|
+
self.listener.join(timeout=2.0)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
try:
|
|
138
|
+
if self._packet_listener.is_alive():
|
|
139
|
+
self._packet_listener.join(timeout=2.0)
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
self.close_connection()
|
|
143
|
+
|
|
144
|
+
def _packet_listen(self) -> None:
|
|
145
|
+
use_tcp: bool = self.use_tcp
|
|
146
|
+
EVENT_READ = selectors.EVENT_READ
|
|
147
|
+
close_event_set = self.closeEvent.is_set
|
|
148
|
+
socket_fileno = self.sock.fileno
|
|
149
|
+
select = self.selector.select
|
|
150
|
+
_packets = self._packets
|
|
151
|
+
if use_tcp:
|
|
152
|
+
sock_recv = self.sock.recv
|
|
153
|
+
else:
|
|
154
|
+
sock_recv = self.sock.recvfrom
|
|
155
|
+
while True:
|
|
156
|
+
try:
|
|
157
|
+
if close_event_set() or socket_fileno() == -1:
|
|
158
|
+
return
|
|
159
|
+
sel = select(0.02)
|
|
160
|
+
for _, events in sel:
|
|
161
|
+
if events & EVENT_READ:
|
|
162
|
+
recv_timestamp = self.timestamp.value
|
|
163
|
+
if use_tcp:
|
|
164
|
+
response = sock_recv(RECV_SIZE)
|
|
165
|
+
if not response:
|
|
166
|
+
self.sock.close()
|
|
167
|
+
self.status = 0
|
|
168
|
+
break
|
|
169
|
+
else:
|
|
170
|
+
_packets.append((response, recv_timestamp))
|
|
171
|
+
else:
|
|
172
|
+
response, _ = sock_recv(Eth.MAX_DATAGRAM_SIZE)
|
|
173
|
+
if not response:
|
|
174
|
+
self.sock.close()
|
|
175
|
+
self.status = 0
|
|
176
|
+
break
|
|
177
|
+
else:
|
|
178
|
+
_packets.append((response, recv_timestamp))
|
|
179
|
+
except BaseException: # noqa: B036
|
|
180
|
+
self.status = 0 # disconnected
|
|
181
|
+
break
|
|
182
|
+
|
|
183
|
+
def listen(self) -> None:
|
|
184
|
+
process_response = self.process_response
|
|
185
|
+
popleft = self._packets.popleft
|
|
186
|
+
close_event_set = self.closeEvent.is_set
|
|
187
|
+
socket_fileno = self.sock.fileno
|
|
188
|
+
_packets = self._packets
|
|
189
|
+
length: Optional[int] = None
|
|
190
|
+
counter: int = 0
|
|
191
|
+
data: bytearray = bytearray(b"")
|
|
192
|
+
while True:
|
|
193
|
+
if close_event_set() or socket_fileno() == -1:
|
|
194
|
+
return
|
|
195
|
+
count: int = len(_packets)
|
|
196
|
+
if not count:
|
|
197
|
+
short_sleep()
|
|
198
|
+
continue
|
|
199
|
+
for _ in range(count):
|
|
200
|
+
bts, timestamp = popleft()
|
|
201
|
+
data += bts
|
|
202
|
+
current_size: int = len(data)
|
|
203
|
+
current_position: int = 0
|
|
204
|
+
while True:
|
|
205
|
+
if length is None:
|
|
206
|
+
if current_size >= self.framing.header_size:
|
|
207
|
+
length, counter = self.framing.unpack_header(bytes(data), initial_offset=current_position)
|
|
208
|
+
current_position += self.framing.header_size
|
|
209
|
+
current_size -= self.framing.header_size
|
|
210
|
+
else:
|
|
211
|
+
data = data[current_position:]
|
|
212
|
+
break
|
|
213
|
+
else:
|
|
214
|
+
if current_size >= length:
|
|
215
|
+
response = data[current_position : current_position + length]
|
|
216
|
+
try:
|
|
217
|
+
process_response(response, length, counter, timestamp)
|
|
218
|
+
except BaseException as ex: # Guard listener against unhandled exceptions (e.g., disk full in policy)
|
|
219
|
+
try:
|
|
220
|
+
self.logger.critical(
|
|
221
|
+
f"Listener error in process_response: {ex.__class__.__name__}: {ex}. Stopping listener.",
|
|
222
|
+
extra={"event": "listener_error"},
|
|
223
|
+
)
|
|
224
|
+
except Exception:
|
|
225
|
+
pass
|
|
226
|
+
try:
|
|
227
|
+
# Signal all loops to stop
|
|
228
|
+
if hasattr(self, "closeEvent"):
|
|
229
|
+
self.closeEvent.set()
|
|
230
|
+
except Exception:
|
|
231
|
+
pass
|
|
232
|
+
return
|
|
233
|
+
current_size -= length
|
|
234
|
+
current_position += length
|
|
235
|
+
length = None
|
|
236
|
+
else:
|
|
237
|
+
data = data[current_position:]
|
|
238
|
+
break
|
|
239
|
+
|
|
240
|
+
def send(self, frame) -> None:
|
|
241
|
+
self.pre_send_timestamp = self.timestamp.value
|
|
242
|
+
self.sock.send(frame)
|
|
243
|
+
self.post_send_timestamp = self.timestamp.value
|
|
244
|
+
|
|
245
|
+
def close_connection(self) -> None:
|
|
246
|
+
if not self.invalidSocket:
|
|
247
|
+
# Seems to be problematic /w IPv6
|
|
248
|
+
# if self.status == 1:
|
|
249
|
+
# self.sock.shutdown(socket.SHUT_RDWR)
|
|
250
|
+
self.sock.close()
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def invalidSocket(self) -> bool:
|
|
254
|
+
return not hasattr(self, "sock") or self.sock.fileno() == -1
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Dict
|
|
5
|
+
|
|
6
|
+
import h5py
|
|
7
|
+
import numpy as np
|
|
8
|
+
from pyxcp.daq_stim import DaqOnlinePolicy, DaqList
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
BATCH_SIZE = 4096
|
|
12
|
+
|
|
13
|
+
MAP_TO_NP = {
|
|
14
|
+
"U8": np.uint8,
|
|
15
|
+
"I8": np.int8,
|
|
16
|
+
"U16": np.uint16,
|
|
17
|
+
"I16": np.int16,
|
|
18
|
+
"U32": np.uint32,
|
|
19
|
+
"I32": np.int32,
|
|
20
|
+
"U64": np.uint64,
|
|
21
|
+
"I64": np.int64,
|
|
22
|
+
"F32": np.float32,
|
|
23
|
+
"F64": np.float64,
|
|
24
|
+
"F16": np.float16,
|
|
25
|
+
"BF16": np.float16,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BufferedDataset:
|
|
30
|
+
def __init__(self, dataset: h5py.Dataset):
|
|
31
|
+
self.dataset = dataset
|
|
32
|
+
self.buffer: List[int | float] = []
|
|
33
|
+
|
|
34
|
+
def add_sample(self, sample: int | float):
|
|
35
|
+
self.buffer.append(sample)
|
|
36
|
+
if len(self.buffer) >= BATCH_SIZE:
|
|
37
|
+
self.flush()
|
|
38
|
+
|
|
39
|
+
def flush(self):
|
|
40
|
+
batch = np.array(self.buffer)
|
|
41
|
+
self.dataset.resize((self.dataset.shape[0] + len(batch),))
|
|
42
|
+
self.dataset[-len(batch) :] = batch
|
|
43
|
+
self.buffer.clear()
|
|
44
|
+
self.dataset.flush()
|
|
45
|
+
|
|
46
|
+
def __len__(self):
|
|
47
|
+
return len(self.buffer)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DatasetGroup:
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
ts0_ds: BufferedDataset,
|
|
54
|
+
ts1_ds: BufferedDataset,
|
|
55
|
+
datasets: List[BufferedDataset],
|
|
56
|
+
):
|
|
57
|
+
self.ts0_ds = ts0_ds
|
|
58
|
+
self.ts1_ds = ts1_ds
|
|
59
|
+
self.datasets = datasets
|
|
60
|
+
|
|
61
|
+
def feed(self, ts0: int, ts1: int, *datasets):
|
|
62
|
+
self.ts0_ds.add_sample(ts0)
|
|
63
|
+
self.ts1_ds.add_sample(ts1)
|
|
64
|
+
for dataset, value in zip(self.datasets, datasets):
|
|
65
|
+
dataset.add_sample(value)
|
|
66
|
+
|
|
67
|
+
def finalize(self):
|
|
68
|
+
for dataset in self.datasets:
|
|
69
|
+
dataset.flush()
|
|
70
|
+
self.ts0_ds.flush()
|
|
71
|
+
self.ts1_ds.flush()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def create_timestamp_column(hdf_file: h5py.File, group_name: str, num: int) -> h5py.Dataset:
|
|
75
|
+
return hdf_file.create_dataset(
|
|
76
|
+
f"/{group_name}/timestamp{num}",
|
|
77
|
+
shape=(0,),
|
|
78
|
+
maxshape=(None,),
|
|
79
|
+
dtype=np.uint64,
|
|
80
|
+
chunks=True,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Hdf5OnlinePolicy(DaqOnlinePolicy):
|
|
85
|
+
def __init__(self, file_name: str | Path, daq_lists: List[DaqList]):
|
|
86
|
+
super().__init__(daq_lists=daq_lists)
|
|
87
|
+
path = Path(file_name)
|
|
88
|
+
if path.suffix != ".h5":
|
|
89
|
+
path = path.with_suffix(".h5")
|
|
90
|
+
self.hdf = h5py.File(path, "w", libver="latest")
|
|
91
|
+
|
|
92
|
+
def initialize(self):
|
|
93
|
+
self.log.debug("Hdf5OnlinePolicy::Initialize()")
|
|
94
|
+
self.datasets: Dict[int, DatasetGroup] = {}
|
|
95
|
+
for num, daq_list in enumerate(self.daq_lists):
|
|
96
|
+
if daq_list.stim:
|
|
97
|
+
continue
|
|
98
|
+
self.hdf.create_group(daq_list.name)
|
|
99
|
+
ts0 = BufferedDataset(create_timestamp_column(self.hdf, daq_list.name, 0))
|
|
100
|
+
ts1 = BufferedDataset(create_timestamp_column(self.hdf, daq_list.name, 1))
|
|
101
|
+
meas_map = {m.name: m for m in self.daq_lists[num].measurements}
|
|
102
|
+
dsets = []
|
|
103
|
+
for name, _ in daq_list.headers:
|
|
104
|
+
meas = meas_map[name]
|
|
105
|
+
dataset = self.hdf.create_dataset(
|
|
106
|
+
f"/{daq_list.name}/{meas.name}",
|
|
107
|
+
shape=(0,),
|
|
108
|
+
maxshape=(None,),
|
|
109
|
+
dtype=MAP_TO_NP[meas.data_type],
|
|
110
|
+
chunks=(1024,),
|
|
111
|
+
)
|
|
112
|
+
dsets.append(BufferedDataset(dataset))
|
|
113
|
+
self.datasets[num] = DatasetGroup(ts0_ds=ts0, ts1_ds=ts1, datasets=dsets)
|
|
114
|
+
self.hdf.flush()
|
|
115
|
+
|
|
116
|
+
def finalize(self):
|
|
117
|
+
self.log.debug("Hdf5OnlinePolicy::finalize()")
|
|
118
|
+
if hasattr(self, "datasets"):
|
|
119
|
+
for group in self.datasets.values():
|
|
120
|
+
group.finalize()
|
|
121
|
+
if hasattr(self, "hdf"):
|
|
122
|
+
self.hdf.close()
|
|
123
|
+
|
|
124
|
+
def on_daq_list(self, daq_list: int, timestamp0: int, timestamp1: int, payload: list):
|
|
125
|
+
group = self.datasets.get(daq_list)
|
|
126
|
+
if group is None:
|
|
127
|
+
self.log.warning(f"Received data for unknown DAQ list {daq_list}")
|
|
128
|
+
return
|
|
129
|
+
group.feed(timestamp0, timestamp1, *payload)
|
pyxcp/transport/sxi.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from collections import deque
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
import serial
|
|
7
|
+
|
|
8
|
+
from pyxcp.transport.transport_ext import (
|
|
9
|
+
SxiFrLBCN,
|
|
10
|
+
SxiFrLBC8,
|
|
11
|
+
SxiFrLBC16,
|
|
12
|
+
SxiFrLCBCN,
|
|
13
|
+
SxiFrLCBC8,
|
|
14
|
+
SxiFrLCBC16,
|
|
15
|
+
SxiFrLFBCN,
|
|
16
|
+
SxiFrLFBC8,
|
|
17
|
+
SxiFrLFBC16,
|
|
18
|
+
SxiFrLWCN,
|
|
19
|
+
SxiFrLWC8,
|
|
20
|
+
SxiFrLWC16,
|
|
21
|
+
SxiFrLCWCN,
|
|
22
|
+
SxiFrLCWC8,
|
|
23
|
+
SxiFrLCWC16,
|
|
24
|
+
SxiFrLFWCN,
|
|
25
|
+
SxiFrLFWC8,
|
|
26
|
+
SxiFrLFWC16,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from pyxcp.transport.base import (
|
|
30
|
+
BaseTransport,
|
|
31
|
+
ChecksumType,
|
|
32
|
+
XcpFramingConfig,
|
|
33
|
+
XcpTransportLayerType,
|
|
34
|
+
parse_header_format,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class HeaderValues:
|
|
40
|
+
length: int = 0
|
|
41
|
+
counter: int = 0
|
|
42
|
+
filler: int = 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
RECV_SIZE = 16384
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_receiver_class(header_format: str, checksum_format: str) -> Any:
|
|
49
|
+
COLUMN = {"NO_CHECKSUM": 0, "CHECKSUM_BYTE": 1, "CHECKSUM_WORD": 2}
|
|
50
|
+
FORMATS = {
|
|
51
|
+
"HEADER_LEN_BYTE": (SxiFrLBCN, SxiFrLBC8, SxiFrLBC16),
|
|
52
|
+
"HEADER_LEN_CTR_BYTE": (SxiFrLCBCN, SxiFrLCBC8, SxiFrLCBC16),
|
|
53
|
+
"HEADER_LEN_FILL_BYTE": (SxiFrLFBCN, SxiFrLFBC8, SxiFrLFBC16),
|
|
54
|
+
"HEADER_LEN_WORD": (SxiFrLWCN, SxiFrLWC8, SxiFrLWC16),
|
|
55
|
+
"HEADER_LEN_CTR_WORD": (SxiFrLCWCN, SxiFrLCWC8, SxiFrLCWC16),
|
|
56
|
+
"HEADER_LEN_FILL_WORD": (SxiFrLFWCN, SxiFrLFWC8, SxiFrLFWC16),
|
|
57
|
+
}
|
|
58
|
+
return FORMATS[header_format][COLUMN[checksum_format]]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SxI(BaseTransport):
|
|
62
|
+
""""""
|
|
63
|
+
|
|
64
|
+
def __init__(self, config=None, policy=None, transport_layer_interface: Optional[serial.Serial] = None) -> None:
|
|
65
|
+
self.load_config(config)
|
|
66
|
+
self.port_name = self.config.port
|
|
67
|
+
self.baudrate = self.config.bitrate
|
|
68
|
+
self.bytesize = self.config.bytesize
|
|
69
|
+
self.parity = self.config.parity
|
|
70
|
+
self.stopbits = self.config.stopbits
|
|
71
|
+
self.mode = self.config.mode
|
|
72
|
+
header_len, header_ctr, header_fill = parse_header_format(self.config.header_format)
|
|
73
|
+
tail_cs_map = {
|
|
74
|
+
"NO_CHECKSUM": ChecksumType.NO_CHECKSUM,
|
|
75
|
+
"CHECKSUM_BYTE": ChecksumType.BYTE_CHECKSUM,
|
|
76
|
+
"CHECKSUM_WORD": ChecksumType.WORD_CHECKSUM,
|
|
77
|
+
}
|
|
78
|
+
# self._listener_running = threading.Event()
|
|
79
|
+
tail_cs = tail_cs_map[self.config.tail_format]
|
|
80
|
+
ReceiverKlass = get_receiver_class(self.config.header_format, self.config.tail_format)
|
|
81
|
+
self.receiver = ReceiverKlass(self.frame_dispatcher)
|
|
82
|
+
framing_config = XcpFramingConfig(
|
|
83
|
+
transport_layer_type=XcpTransportLayerType.SXI,
|
|
84
|
+
header_len=header_len,
|
|
85
|
+
header_ctr=header_ctr,
|
|
86
|
+
header_fill=header_fill,
|
|
87
|
+
tail_fill=False,
|
|
88
|
+
tail_cs=tail_cs,
|
|
89
|
+
)
|
|
90
|
+
super().__init__(config, framing_config, policy, transport_layer_interface)
|
|
91
|
+
self.tail_format = self.config.tail_format
|
|
92
|
+
self.esc_sync = self.config.esc_sync
|
|
93
|
+
self.esc_esc = self.config.esc_esc
|
|
94
|
+
self.comm_port: serial.Serial
|
|
95
|
+
|
|
96
|
+
if self.has_user_supplied_interface and transport_layer_interface:
|
|
97
|
+
self.comm_port = transport_layer_interface
|
|
98
|
+
else:
|
|
99
|
+
self.logger.info(f"XCPonSxI - trying to open serial comm_port {self.port_name!r}.")
|
|
100
|
+
try:
|
|
101
|
+
self.comm_port = serial.Serial(
|
|
102
|
+
port=self.port_name,
|
|
103
|
+
baudrate=self.baudrate,
|
|
104
|
+
bytesize=self.bytesize,
|
|
105
|
+
parity=self.parity,
|
|
106
|
+
stopbits=self.stopbits,
|
|
107
|
+
timeout=0.1, # self.timeout,
|
|
108
|
+
write_timeout=self.timeout,
|
|
109
|
+
)
|
|
110
|
+
except serial.SerialException as e:
|
|
111
|
+
self.logger.critical(f"XCPonSxI - {e}")
|
|
112
|
+
raise
|
|
113
|
+
self._condition = threading.Condition()
|
|
114
|
+
self._frames = deque()
|
|
115
|
+
# self._frame_listener = threading.Thread(
|
|
116
|
+
# target=self._frame_listen,
|
|
117
|
+
# args=(),
|
|
118
|
+
# kwargs={},
|
|
119
|
+
# )
|
|
120
|
+
|
|
121
|
+
def __del__(self) -> None:
|
|
122
|
+
self.close_connection()
|
|
123
|
+
|
|
124
|
+
def connect(self) -> None:
|
|
125
|
+
self.logger.info(
|
|
126
|
+
f"XCPonSxI - serial comm_port {self.comm_port.portstr!r} openend "
|
|
127
|
+
f"[{self.baudrate}/{self.bytesize}-{self.parity}-{self.stopbits}] "
|
|
128
|
+
f"mode: {self.config.mode}"
|
|
129
|
+
)
|
|
130
|
+
self.logger.info(f"Framing: {self.config.header_format} {self.config.tail_format}")
|
|
131
|
+
self.start_listener()
|
|
132
|
+
|
|
133
|
+
def output(self, enable) -> None:
|
|
134
|
+
if enable:
|
|
135
|
+
self.comm_port.rts = False
|
|
136
|
+
self.comm_port.dtr = False
|
|
137
|
+
else:
|
|
138
|
+
self.comm_port.rts = True
|
|
139
|
+
self.comm_port.dtr = True
|
|
140
|
+
|
|
141
|
+
def flush(self) -> None:
|
|
142
|
+
self.comm_port.flush()
|
|
143
|
+
|
|
144
|
+
def start_listener(self) -> None:
|
|
145
|
+
super().start_listener()
|
|
146
|
+
if hasattr(self, "_frame_listener") and self._frame_listener.is_alive():
|
|
147
|
+
self._frame_listener.join(timeout=2.0)
|
|
148
|
+
self._frame_listener = threading.Thread(target=self._frame_listen, daemon=True)
|
|
149
|
+
self._frame_listener.start()
|
|
150
|
+
# self._listener_running.wait(2.0)
|
|
151
|
+
|
|
152
|
+
def close(self) -> None:
|
|
153
|
+
"""Close the transport-layer connection and event-loop."""
|
|
154
|
+
self.finish_listener()
|
|
155
|
+
self.closeEvent.set()
|
|
156
|
+
try:
|
|
157
|
+
if self.listener.is_alive():
|
|
158
|
+
self.listener.join(timeout=2.0)
|
|
159
|
+
except Exception:
|
|
160
|
+
pass
|
|
161
|
+
try:
|
|
162
|
+
if hasattr(self, "_frame_listener") and self._frame_listener.is_alive():
|
|
163
|
+
self._frame_listener.join(timeout=2.0)
|
|
164
|
+
except Exception:
|
|
165
|
+
pass
|
|
166
|
+
self.close_connection()
|
|
167
|
+
|
|
168
|
+
def listen(self) -> None:
|
|
169
|
+
while True:
|
|
170
|
+
if self.closeEvent.is_set():
|
|
171
|
+
return
|
|
172
|
+
frame_to_process = None
|
|
173
|
+
with self._condition:
|
|
174
|
+
while not self._frames:
|
|
175
|
+
res = self._condition.wait(1.0)
|
|
176
|
+
if not res:
|
|
177
|
+
break
|
|
178
|
+
if self._frames:
|
|
179
|
+
frame_to_process = self._frames.popleft()
|
|
180
|
+
|
|
181
|
+
if frame_to_process:
|
|
182
|
+
frame, length, counter, timestamp = frame_to_process
|
|
183
|
+
self.process_response(frame, length, counter, timestamp)
|
|
184
|
+
|
|
185
|
+
def _frame_listen(self) -> None:
|
|
186
|
+
# self._listener_running.set()
|
|
187
|
+
while True:
|
|
188
|
+
if self.closeEvent.is_set():
|
|
189
|
+
return
|
|
190
|
+
data = self.comm_port.read(1)
|
|
191
|
+
if data:
|
|
192
|
+
self.receiver.feed_bytes(data)
|
|
193
|
+
data = self.comm_port.read(self.comm_port.in_waiting)
|
|
194
|
+
if data:
|
|
195
|
+
self.receiver.feed_bytes(data)
|
|
196
|
+
|
|
197
|
+
def frame_dispatcher(self, data: bytes, length: int, counter: int) -> None:
|
|
198
|
+
with self._condition:
|
|
199
|
+
self._frames.append((bytes(data), length, counter, self.timestamp.value))
|
|
200
|
+
self._condition.notify()
|
|
201
|
+
|
|
202
|
+
def send(self, frame: bytes) -> None:
|
|
203
|
+
self.pre_send_timestamp = self.timestamp.value
|
|
204
|
+
self.comm_port.write(frame)
|
|
205
|
+
self.post_send_timestamp = self.timestamp.value
|
|
206
|
+
|
|
207
|
+
def close_connection(self) -> None:
|
|
208
|
+
if hasattr(self, "comm_port") and self.comm_port.is_open and not self.has_user_supplied_interface:
|
|
209
|
+
self.comm_port.close()
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|