pyxcp 0.25.2__cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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 +0 -0
- pyxcp/asamkeydll.c +116 -0
- pyxcp/asamkeydll.sh +2 -0
- pyxcp/checksum.py +732 -0
- pyxcp/cmdline.py +83 -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.cpython-310-x86_64-linux-gnu.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-311-x86_64-linux-gnu.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-312-x86_64-linux-gnu.so +0 -0
- pyxcp/cpp_ext/cpp_ext.cpython-313-x86_64-linux-gnu.so +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 +306 -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.cpp +13 -0
- pyxcp/daq_stim/stim.cpython-310-x86_64-linux-gnu.so +0 -0
- pyxcp/daq_stim/stim.cpython-311-x86_64-linux-gnu.so +0 -0
- pyxcp/daq_stim/stim.cpython-312-x86_64-linux-gnu.so +0 -0
- pyxcp/daq_stim/stim.cpython-313-x86_64-linux-gnu.so +0 -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 +38 -0
- pyxcp/examples/xcp_user_supplied_driver.py +43 -0
- pyxcp/examples/xcphello.py +79 -0
- pyxcp/examples/xcphello_recorder.py +107 -0
- pyxcp/master/__init__.py +10 -0
- pyxcp/master/errorhandler.py +677 -0
- pyxcp/master/master.py +2645 -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 +445 -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.cpp +59 -0
- pyxcp/recorder/rekorder.cpython-310-x86_64-linux-gnu.so +0 -0
- pyxcp/recorder/rekorder.cpython-311-x86_64-linux-gnu.so +0 -0
- pyxcp/recorder/rekorder.cpython-312-x86_64-linux-gnu.so +0 -0
- pyxcp/recorder/rekorder.cpython-313-x86_64-linux-gnu.so +0 -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 +144 -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/sxi.py +209 -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.py +127 -0
- pyxcp/vector/__init__.py +0 -0
- pyxcp/vector/map.py +82 -0
- pyxcp-0.25.2.dist-info/METADATA +341 -0
- pyxcp-0.25.2.dist-info/RECORD +151 -0
- pyxcp-0.25.2.dist-info/WHEEL +6 -0
- pyxcp-0.25.2.dist-info/entry_points.txt +9 -0
- pyxcp-0.25.2.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
|
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()
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
|
|
2
|
+
#include <cstdint>
|
|
3
|
+
|
|
4
|
+
#include <bit>
|
|
5
|
+
#include <optional>
|
|
6
|
+
#include <iostream>
|
|
7
|
+
#include <map>
|
|
8
|
+
#include <set>
|
|
9
|
+
#include <tuple>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
#include "rekorder.hpp"
|
|
13
|
+
|
|
14
|
+
const std::map<FrameCategory, std::string> FrameCategoryName {
|
|
15
|
+
{FrameCategory::META, "METADATA"},
|
|
16
|
+
{FrameCategory::CMD, "CMD"},
|
|
17
|
+
{FrameCategory::RES, "RESPONSE"},
|
|
18
|
+
{FrameCategory::ERR, "ERROR"},
|
|
19
|
+
{FrameCategory::EV, "EVENT"},
|
|
20
|
+
{FrameCategory::SERV, "SERV"},
|
|
21
|
+
{FrameCategory::DAQ, "DAQ"},
|
|
22
|
+
{FrameCategory::STIM, "STIM"},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
Base class for all frame acquisition policies.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
---------
|
|
30
|
+
filter_out: set or None
|
|
31
|
+
A set of frame types to filter out.
|
|
32
|
+
If None, all frame types are accepted for further processing.
|
|
33
|
+
|
|
34
|
+
Example: (FrameType.REQUEST, FrameType.RESPONSE, FrameType.EVENT, FrameType.SERV)
|
|
35
|
+
==> care only about DAQ frames.
|
|
36
|
+
*/
|
|
37
|
+
class FrameAcquisitionPolicy {
|
|
38
|
+
public:
|
|
39
|
+
|
|
40
|
+
using payload_t = std::string;
|
|
41
|
+
using filter_t = std::set<FrameCategory>;
|
|
42
|
+
using frame_t = std::tuple<std::uint32_t, std::uint64_t, const payload_t>;
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
FrameAcquisitionPolicy(const std::optional<filter_t>& filter_out) {
|
|
46
|
+
if (!filter_out) {
|
|
47
|
+
m_filter_out = filter_t{};
|
|
48
|
+
} else {
|
|
49
|
+
m_filter_out = filter_out;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
std::optional<filter_t> get_filtered_out() const {
|
|
54
|
+
return m_filter_out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
FrameAcquisitionPolicy(const FrameAcquisitionPolicy&) = delete;
|
|
58
|
+
FrameAcquisitionPolicy(FrameAcquisitionPolicy&&) = delete;
|
|
59
|
+
FrameAcquisitionPolicy() = delete;
|
|
60
|
+
|
|
61
|
+
virtual ~FrameAcquisitionPolicy() {}
|
|
62
|
+
|
|
63
|
+
virtual void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) = 0;
|
|
64
|
+
|
|
65
|
+
virtual void finalize() = 0;
|
|
66
|
+
|
|
67
|
+
protected:
|
|
68
|
+
|
|
69
|
+
std::optional<filter_t> m_filter_out;
|
|
70
|
+
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
No operation / do nothing policy.
|
|
76
|
+
*/
|
|
77
|
+
class NoOpPolicy : public FrameAcquisitionPolicy {
|
|
78
|
+
public:
|
|
79
|
+
|
|
80
|
+
NoOpPolicy(const std::optional<filter_t>& filter_out) : FrameAcquisitionPolicy(filter_out) {}
|
|
81
|
+
|
|
82
|
+
void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) override {}
|
|
83
|
+
|
|
84
|
+
void finalize() override {}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
Dequeue based frame acquisition policy.
|
|
90
|
+
|
|
91
|
+
Deprecated: Use only for compatibility reasons.
|
|
92
|
+
*/
|
|
93
|
+
class LegacyFrameAcquisitionPolicy : public FrameAcquisitionPolicy {
|
|
94
|
+
public:
|
|
95
|
+
|
|
96
|
+
using deque_t = TsQueue<frame_t>;
|
|
97
|
+
|
|
98
|
+
LegacyFrameAcquisitionPolicy(const std::optional<filter_t>& filter_out) : FrameAcquisitionPolicy(filter_out) {
|
|
99
|
+
|
|
100
|
+
m_queue_map[FrameCategory::CMD] = std::make_shared<deque_t>();
|
|
101
|
+
m_queue_map[FrameCategory::RES] = std::make_shared<deque_t>();
|
|
102
|
+
m_queue_map[FrameCategory::EV] = std::make_shared<deque_t>();
|
|
103
|
+
m_queue_map[FrameCategory::SERV] = std::make_shared<deque_t>();
|
|
104
|
+
m_queue_map[FrameCategory::DAQ] = std::make_shared<deque_t>();
|
|
105
|
+
m_queue_map[FrameCategory::META] = std::make_shared<deque_t>();
|
|
106
|
+
m_queue_map[FrameCategory::ERR] = std::make_shared<deque_t>();
|
|
107
|
+
m_queue_map[FrameCategory::STIM] = std::make_shared<deque_t>();
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
LegacyFrameAcquisitionPolicy() = delete;
|
|
112
|
+
LegacyFrameAcquisitionPolicy(const LegacyFrameAcquisitionPolicy&) = delete;
|
|
113
|
+
LegacyFrameAcquisitionPolicy(LegacyFrameAcquisitionPolicy&&) = delete;
|
|
114
|
+
|
|
115
|
+
void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) override {
|
|
116
|
+
if (m_filter_out && (!(*m_filter_out).contains(frame_category))) {
|
|
117
|
+
m_queue_map[frame_category]->put({counter, timestamp, payload});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
std::shared_ptr<deque_t> get_req_queue() {
|
|
122
|
+
return m_queue_map.at(FrameCategory::CMD);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
std::shared_ptr<deque_t> get_res_queue() {
|
|
126
|
+
return m_queue_map.at(FrameCategory::RES);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
std::shared_ptr<deque_t> get_daq_queue() {
|
|
130
|
+
return m_queue_map.at(FrameCategory::DAQ);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
std::shared_ptr<deque_t> get_ev_queue() {
|
|
134
|
+
return m_queue_map.at(FrameCategory::EV);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
std::shared_ptr<deque_t> get_serv_queue() {
|
|
138
|
+
return m_queue_map.at(FrameCategory::SERV);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
std::shared_ptr<deque_t> get_meta_queue() {
|
|
142
|
+
return m_queue_map.at(FrameCategory::META);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
std::shared_ptr<deque_t> get_error_queue() {
|
|
146
|
+
return m_queue_map.at(FrameCategory::ERR);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
std::shared_ptr<deque_t> get_stim_queue() {
|
|
150
|
+
return m_queue_map.at(FrameCategory::STIM);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
void finalize() override {}
|
|
154
|
+
|
|
155
|
+
private:
|
|
156
|
+
|
|
157
|
+
std::map<FrameCategory, std::shared_ptr<deque_t>> m_queue_map{};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
std::string hex_bytes(std::string_view payload) {
|
|
161
|
+
std::stringstream ss;
|
|
162
|
+
|
|
163
|
+
for (auto ch: payload) {
|
|
164
|
+
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(static_cast<unsigned char>(ch)) << " ";
|
|
165
|
+
}
|
|
166
|
+
return ss.str();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/*
|
|
170
|
+
Frame acquisition policy that prints frames to stdout.
|
|
171
|
+
*/
|
|
172
|
+
class StdoutPolicy : public FrameAcquisitionPolicy {
|
|
173
|
+
public:
|
|
174
|
+
StdoutPolicy(const std::optional<filter_t>& filter_out) : FrameAcquisitionPolicy(filter_out) {}
|
|
175
|
+
|
|
176
|
+
StdoutPolicy() = delete;
|
|
177
|
+
StdoutPolicy(const StdoutPolicy&) = delete;
|
|
178
|
+
StdoutPolicy(StdoutPolicy&&) = delete;
|
|
179
|
+
|
|
180
|
+
void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) override {
|
|
181
|
+
if (m_filter_out && (!(*m_filter_out).contains(frame_category))) {
|
|
182
|
+
std::cout << std::left << std::setw(8) << FrameCategoryName.at(frame_category) << " " << std::right <<
|
|
183
|
+
std::setw(6) << counter << " " << std::setw(8) << timestamp << " [ " << std::left << hex_bytes(payload) << "]" << std::endl;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
void finalize() override { }
|
|
188
|
+
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
class FrameRecorderPolicy : public FrameAcquisitionPolicy {
|
|
192
|
+
public:
|
|
193
|
+
|
|
194
|
+
FrameRecorderPolicy(const std::string& file_name, const std::optional<filter_t>& filter_out, uint32_t prealloc = 10UL, uint32_t chunk_size = 1) : FrameAcquisitionPolicy(filter_out) {
|
|
195
|
+
m_writer = std::make_unique<XcpLogFileWriter>(file_name, prealloc, chunk_size);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
FrameRecorderPolicy() = delete;
|
|
199
|
+
FrameRecorderPolicy(const FrameRecorderPolicy&) = delete;
|
|
200
|
+
FrameRecorderPolicy(FrameRecorderPolicy&&) = delete;
|
|
201
|
+
|
|
202
|
+
void feed(FrameCategory frame_category, std::uint32_t counter, std::uint64_t timestamp, const payload_t& payload) override {
|
|
203
|
+
if (m_filter_out && (!(*m_filter_out).contains(frame_category))) {
|
|
204
|
+
m_writer->add_frame(static_cast<std::uint8_t>(frame_category), counter, timestamp, payload.size(), std::bit_cast<const char *>(payload.data()));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
void finalize() override {
|
|
209
|
+
m_writer->finalize();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private:
|
|
213
|
+
std::unique_ptr<XcpLogFileWriter> m_writer;
|
|
214
|
+
};
|