pyxcp 0.25.2__cp314-cp314-win_arm64.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.
Files changed (154) hide show
  1. pyxcp/__init__.py +20 -0
  2. pyxcp/aml/EtasCANMonitoring.a2l +82 -0
  3. pyxcp/aml/EtasCANMonitoring.aml +67 -0
  4. pyxcp/aml/XCP_Common.aml +408 -0
  5. pyxcp/aml/XCPonCAN.aml +78 -0
  6. pyxcp/aml/XCPonEth.aml +33 -0
  7. pyxcp/aml/XCPonFlx.aml +113 -0
  8. pyxcp/aml/XCPonSxI.aml +66 -0
  9. pyxcp/aml/XCPonUSB.aml +106 -0
  10. pyxcp/aml/ifdata_CAN.a2l +20 -0
  11. pyxcp/aml/ifdata_Eth.a2l +11 -0
  12. pyxcp/aml/ifdata_Flx.a2l +94 -0
  13. pyxcp/aml/ifdata_SxI.a2l +13 -0
  14. pyxcp/aml/ifdata_USB.a2l +81 -0
  15. pyxcp/asam/__init__.py +0 -0
  16. pyxcp/asam/types.py +131 -0
  17. pyxcp/asamkeydll.c +116 -0
  18. pyxcp/asamkeydll.exe +0 -0
  19. pyxcp/asamkeydll.sh +2 -0
  20. pyxcp/checksum.py +732 -0
  21. pyxcp/cmdline.py +83 -0
  22. pyxcp/config/__init__.py +1257 -0
  23. pyxcp/config/legacy.py +120 -0
  24. pyxcp/constants.py +47 -0
  25. pyxcp/cpp_ext/__init__.py +0 -0
  26. pyxcp/cpp_ext/aligned_buffer.hpp +168 -0
  27. pyxcp/cpp_ext/bin.hpp +105 -0
  28. pyxcp/cpp_ext/blockmem.hpp +58 -0
  29. pyxcp/cpp_ext/cpp_ext.cp310-win_arm64.pyd +0 -0
  30. pyxcp/cpp_ext/cpp_ext.cp311-win_arm64.pyd +0 -0
  31. pyxcp/cpp_ext/cpp_ext.cp312-win_arm64.pyd +0 -0
  32. pyxcp/cpp_ext/cpp_ext.cp313-win_arm64.pyd +0 -0
  33. pyxcp/cpp_ext/cpp_ext.cp314-win_arm64.pyd +0 -0
  34. pyxcp/cpp_ext/daqlist.hpp +374 -0
  35. pyxcp/cpp_ext/event.hpp +67 -0
  36. pyxcp/cpp_ext/extension_wrapper.cpp +131 -0
  37. pyxcp/cpp_ext/framing.hpp +360 -0
  38. pyxcp/cpp_ext/helper.hpp +280 -0
  39. pyxcp/cpp_ext/mcobject.hpp +248 -0
  40. pyxcp/cpp_ext/sxi_framing.hpp +332 -0
  41. pyxcp/cpp_ext/tsqueue.hpp +46 -0
  42. pyxcp/daq_stim/__init__.py +306 -0
  43. pyxcp/daq_stim/optimize/__init__.py +67 -0
  44. pyxcp/daq_stim/optimize/binpacking.py +41 -0
  45. pyxcp/daq_stim/scheduler.cpp +62 -0
  46. pyxcp/daq_stim/scheduler.hpp +75 -0
  47. pyxcp/daq_stim/stim.cp310-win_arm64.pyd +0 -0
  48. pyxcp/daq_stim/stim.cp311-win_arm64.pyd +0 -0
  49. pyxcp/daq_stim/stim.cp312-win_arm64.pyd +0 -0
  50. pyxcp/daq_stim/stim.cp313-win_arm64.pyd +0 -0
  51. pyxcp/daq_stim/stim.cp314-win_arm64.pyd +0 -0
  52. pyxcp/daq_stim/stim.cpp +13 -0
  53. pyxcp/daq_stim/stim.hpp +604 -0
  54. pyxcp/daq_stim/stim_wrapper.cpp +50 -0
  55. pyxcp/dllif.py +100 -0
  56. pyxcp/errormatrix.py +878 -0
  57. pyxcp/examples/conf_can.toml +19 -0
  58. pyxcp/examples/conf_can_user.toml +16 -0
  59. pyxcp/examples/conf_can_vector.json +11 -0
  60. pyxcp/examples/conf_can_vector.toml +11 -0
  61. pyxcp/examples/conf_eth.toml +9 -0
  62. pyxcp/examples/conf_nixnet.json +20 -0
  63. pyxcp/examples/conf_socket_can.toml +12 -0
  64. pyxcp/examples/run_daq.py +165 -0
  65. pyxcp/examples/xcp_policy.py +60 -0
  66. pyxcp/examples/xcp_read_benchmark.py +38 -0
  67. pyxcp/examples/xcp_skel.py +48 -0
  68. pyxcp/examples/xcp_unlock.py +38 -0
  69. pyxcp/examples/xcp_user_supplied_driver.py +43 -0
  70. pyxcp/examples/xcphello.py +79 -0
  71. pyxcp/examples/xcphello_recorder.py +107 -0
  72. pyxcp/master/__init__.py +10 -0
  73. pyxcp/master/errorhandler.py +677 -0
  74. pyxcp/master/master.py +2645 -0
  75. pyxcp/py.typed +0 -0
  76. pyxcp/recorder/.idea/.gitignore +8 -0
  77. pyxcp/recorder/.idea/misc.xml +4 -0
  78. pyxcp/recorder/.idea/modules.xml +8 -0
  79. pyxcp/recorder/.idea/recorder.iml +6 -0
  80. pyxcp/recorder/.idea/sonarlint/issuestore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +7 -0
  81. pyxcp/recorder/.idea/sonarlint/issuestore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
  82. pyxcp/recorder/.idea/sonarlint/issuestore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
  83. pyxcp/recorder/.idea/sonarlint/issuestore/index.pb +7 -0
  84. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +0 -0
  85. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
  86. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
  87. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/index.pb +7 -0
  88. pyxcp/recorder/.idea/vcs.xml +10 -0
  89. pyxcp/recorder/__init__.py +96 -0
  90. pyxcp/recorder/build_clang.cmd +1 -0
  91. pyxcp/recorder/build_clang.sh +2 -0
  92. pyxcp/recorder/build_gcc.cmd +1 -0
  93. pyxcp/recorder/build_gcc.sh +2 -0
  94. pyxcp/recorder/build_gcc_arm.sh +2 -0
  95. pyxcp/recorder/converter/__init__.py +445 -0
  96. pyxcp/recorder/lz4.c +2829 -0
  97. pyxcp/recorder/lz4.h +879 -0
  98. pyxcp/recorder/lz4hc.c +2041 -0
  99. pyxcp/recorder/lz4hc.h +413 -0
  100. pyxcp/recorder/mio.hpp +1714 -0
  101. pyxcp/recorder/reader.hpp +138 -0
  102. pyxcp/recorder/reco.py +278 -0
  103. pyxcp/recorder/recorder.rst +0 -0
  104. pyxcp/recorder/rekorder.cp310-win_arm64.pyd +0 -0
  105. pyxcp/recorder/rekorder.cp311-win_arm64.pyd +0 -0
  106. pyxcp/recorder/rekorder.cp312-win_arm64.pyd +0 -0
  107. pyxcp/recorder/rekorder.cp313-win_arm64.pyd +0 -0
  108. pyxcp/recorder/rekorder.cp314-win_arm64.pyd +0 -0
  109. pyxcp/recorder/rekorder.cpp +59 -0
  110. pyxcp/recorder/rekorder.hpp +274 -0
  111. pyxcp/recorder/setup.py +41 -0
  112. pyxcp/recorder/test_reko.py +34 -0
  113. pyxcp/recorder/unfolder.hpp +1354 -0
  114. pyxcp/recorder/wrap.cpp +184 -0
  115. pyxcp/recorder/writer.hpp +302 -0
  116. pyxcp/scripts/__init__.py +0 -0
  117. pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
  118. pyxcp/scripts/xcp_examples.py +64 -0
  119. pyxcp/scripts/xcp_fetch_a2l.py +40 -0
  120. pyxcp/scripts/xcp_id_scanner.py +18 -0
  121. pyxcp/scripts/xcp_info.py +144 -0
  122. pyxcp/scripts/xcp_profile.py +26 -0
  123. pyxcp/scripts/xmraw_converter.py +31 -0
  124. pyxcp/stim/__init__.py +0 -0
  125. pyxcp/tests/test_asam_types.py +24 -0
  126. pyxcp/tests/test_binpacking.py +186 -0
  127. pyxcp/tests/test_can.py +1324 -0
  128. pyxcp/tests/test_checksum.py +95 -0
  129. pyxcp/tests/test_daq.py +193 -0
  130. pyxcp/tests/test_daq_opt.py +426 -0
  131. pyxcp/tests/test_frame_padding.py +156 -0
  132. pyxcp/tests/test_framing.py +262 -0
  133. pyxcp/tests/test_master.py +2116 -0
  134. pyxcp/tests/test_transport.py +177 -0
  135. pyxcp/tests/test_utils.py +30 -0
  136. pyxcp/timing.py +60 -0
  137. pyxcp/transport/__init__.py +13 -0
  138. pyxcp/transport/base.py +484 -0
  139. pyxcp/transport/base_transport.hpp +0 -0
  140. pyxcp/transport/can.py +660 -0
  141. pyxcp/transport/eth.py +254 -0
  142. pyxcp/transport/sxi.py +209 -0
  143. pyxcp/transport/transport_ext.hpp +214 -0
  144. pyxcp/transport/transport_wrapper.cpp +249 -0
  145. pyxcp/transport/usb_transport.py +229 -0
  146. pyxcp/types.py +987 -0
  147. pyxcp/utils.py +127 -0
  148. pyxcp/vector/__init__.py +0 -0
  149. pyxcp/vector/map.py +82 -0
  150. pyxcp-0.25.2.dist-info/METADATA +341 -0
  151. pyxcp-0.25.2.dist-info/RECORD +154 -0
  152. pyxcp-0.25.2.dist-info/WHEEL +4 -0
  153. pyxcp-0.25.2.dist-info/entry_points.txt +9 -0
  154. 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
+ };