pyxcp 0.23.0__cp313-cp313-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.

Potentially problematic release.


This version of pyxcp might be problematic. Click here for more details.

Files changed (134) 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.sh +2 -0
  19. pyxcp/checksum.py +732 -0
  20. pyxcp/cmdline.py +52 -0
  21. pyxcp/config/__init__.py +1113 -0
  22. pyxcp/config/legacy.py +120 -0
  23. pyxcp/constants.py +47 -0
  24. pyxcp/cpp_ext/__init__.py +0 -0
  25. pyxcp/cpp_ext/bin.hpp +104 -0
  26. pyxcp/cpp_ext/blockmem.hpp +58 -0
  27. pyxcp/cpp_ext/cpp_ext.cp310-win_arm64.pyd +0 -0
  28. pyxcp/cpp_ext/cpp_ext.cp311-win_arm64.pyd +0 -0
  29. pyxcp/cpp_ext/cpp_ext.cp312-win_arm64.pyd +0 -0
  30. pyxcp/cpp_ext/cpp_ext.cp313-win_arm64.pyd +0 -0
  31. pyxcp/cpp_ext/daqlist.hpp +206 -0
  32. pyxcp/cpp_ext/event.hpp +67 -0
  33. pyxcp/cpp_ext/extension_wrapper.cpp +100 -0
  34. pyxcp/cpp_ext/helper.hpp +280 -0
  35. pyxcp/cpp_ext/mcobject.hpp +246 -0
  36. pyxcp/cpp_ext/tsqueue.hpp +46 -0
  37. pyxcp/daq_stim/__init__.py +232 -0
  38. pyxcp/daq_stim/optimize/__init__.py +67 -0
  39. pyxcp/daq_stim/optimize/binpacking.py +41 -0
  40. pyxcp/daq_stim/scheduler.cpp +62 -0
  41. pyxcp/daq_stim/scheduler.hpp +75 -0
  42. pyxcp/daq_stim/stim.cp310-win_arm64.pyd +0 -0
  43. pyxcp/daq_stim/stim.cp311-win_arm64.pyd +0 -0
  44. pyxcp/daq_stim/stim.cp312-win_arm64.pyd +0 -0
  45. pyxcp/daq_stim/stim.cp313-win_arm64.pyd +0 -0
  46. pyxcp/daq_stim/stim.cpp +13 -0
  47. pyxcp/daq_stim/stim.hpp +604 -0
  48. pyxcp/daq_stim/stim_wrapper.cpp +50 -0
  49. pyxcp/dllif.py +100 -0
  50. pyxcp/errormatrix.py +878 -0
  51. pyxcp/examples/conf_can.toml +19 -0
  52. pyxcp/examples/conf_can_user.toml +16 -0
  53. pyxcp/examples/conf_can_vector.json +11 -0
  54. pyxcp/examples/conf_can_vector.toml +11 -0
  55. pyxcp/examples/conf_eth.toml +9 -0
  56. pyxcp/examples/conf_nixnet.json +20 -0
  57. pyxcp/examples/conf_socket_can.toml +12 -0
  58. pyxcp/examples/conf_sxi.json +9 -0
  59. pyxcp/examples/conf_sxi.toml +7 -0
  60. pyxcp/examples/run_daq.py +163 -0
  61. pyxcp/examples/xcp_policy.py +60 -0
  62. pyxcp/examples/xcp_read_benchmark.py +38 -0
  63. pyxcp/examples/xcp_skel.py +49 -0
  64. pyxcp/examples/xcp_unlock.py +38 -0
  65. pyxcp/examples/xcp_user_supplied_driver.py +44 -0
  66. pyxcp/examples/xcphello.py +78 -0
  67. pyxcp/examples/xcphello_recorder.py +107 -0
  68. pyxcp/master/__init__.py +9 -0
  69. pyxcp/master/errorhandler.py +442 -0
  70. pyxcp/master/master.py +2047 -0
  71. pyxcp/py.typed +0 -0
  72. pyxcp/recorder/__init__.py +101 -0
  73. pyxcp/recorder/build_clang.cmd +1 -0
  74. pyxcp/recorder/build_clang.sh +2 -0
  75. pyxcp/recorder/build_gcc.cmd +1 -0
  76. pyxcp/recorder/build_gcc.sh +2 -0
  77. pyxcp/recorder/build_gcc_arm.sh +2 -0
  78. pyxcp/recorder/converter/__init__.py +450 -0
  79. pyxcp/recorder/lz4.c +2829 -0
  80. pyxcp/recorder/lz4.h +879 -0
  81. pyxcp/recorder/lz4hc.c +2041 -0
  82. pyxcp/recorder/lz4hc.h +413 -0
  83. pyxcp/recorder/mio.hpp +1714 -0
  84. pyxcp/recorder/reader.hpp +139 -0
  85. pyxcp/recorder/reco.py +277 -0
  86. pyxcp/recorder/recorder.rst +0 -0
  87. pyxcp/recorder/rekorder.cp310-win_arm64.pyd +0 -0
  88. pyxcp/recorder/rekorder.cp311-win_arm64.pyd +0 -0
  89. pyxcp/recorder/rekorder.cp312-win_arm64.pyd +0 -0
  90. pyxcp/recorder/rekorder.cp313-win_arm64.pyd +0 -0
  91. pyxcp/recorder/rekorder.cpp +59 -0
  92. pyxcp/recorder/rekorder.hpp +274 -0
  93. pyxcp/recorder/setup.py +41 -0
  94. pyxcp/recorder/test_reko.py +34 -0
  95. pyxcp/recorder/unfolder.hpp +1332 -0
  96. pyxcp/recorder/wrap.cpp +189 -0
  97. pyxcp/recorder/writer.hpp +302 -0
  98. pyxcp/scripts/__init__.py +0 -0
  99. pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
  100. pyxcp/scripts/xcp_examples.py +64 -0
  101. pyxcp/scripts/xcp_fetch_a2l.py +40 -0
  102. pyxcp/scripts/xcp_id_scanner.py +19 -0
  103. pyxcp/scripts/xcp_info.py +144 -0
  104. pyxcp/scripts/xcp_profile.py +27 -0
  105. pyxcp/scripts/xmraw_converter.py +31 -0
  106. pyxcp/stim/__init__.py +0 -0
  107. pyxcp/tests/test_asam_types.py +24 -0
  108. pyxcp/tests/test_binpacking.py +186 -0
  109. pyxcp/tests/test_can.py +1324 -0
  110. pyxcp/tests/test_checksum.py +95 -0
  111. pyxcp/tests/test_daq.py +193 -0
  112. pyxcp/tests/test_daq_opt.py +426 -0
  113. pyxcp/tests/test_frame_padding.py +156 -0
  114. pyxcp/tests/test_master.py +2006 -0
  115. pyxcp/tests/test_transport.py +81 -0
  116. pyxcp/tests/test_utils.py +30 -0
  117. pyxcp/timing.py +60 -0
  118. pyxcp/transport/__init__.py +10 -0
  119. pyxcp/transport/base.py +440 -0
  120. pyxcp/transport/base_transport.hpp +0 -0
  121. pyxcp/transport/can.py +556 -0
  122. pyxcp/transport/eth.py +219 -0
  123. pyxcp/transport/sxi.py +135 -0
  124. pyxcp/transport/transport_wrapper.cpp +0 -0
  125. pyxcp/transport/usb_transport.py +213 -0
  126. pyxcp/types.py +1000 -0
  127. pyxcp/utils.py +128 -0
  128. pyxcp/vector/__init__.py +0 -0
  129. pyxcp/vector/map.py +82 -0
  130. pyxcp-0.23.0.dist-info/LICENSE +165 -0
  131. pyxcp-0.23.0.dist-info/METADATA +107 -0
  132. pyxcp-0.23.0.dist-info/RECORD +134 -0
  133. pyxcp-0.23.0.dist-info/WHEEL +4 -0
  134. pyxcp-0.23.0.dist-info/entry_points.txt +9 -0
pyxcp/transport/can.py ADDED
@@ -0,0 +1,556 @@
1
+ #!/usr/bin/env python
2
+ """ """
3
+
4
+ import functools
5
+ import operator
6
+ from abc import ABC, abstractmethod
7
+ from bisect import bisect_left
8
+ from enum import IntEnum
9
+ from typing import Any, Dict, List, Optional, Union
10
+
11
+ from can import (
12
+ BusState,
13
+ CanError,
14
+ CanInitializationError,
15
+ Message,
16
+ detect_available_configs,
17
+ )
18
+ from can.bus import BusABC
19
+ from can.interface import _get_class_for_interface
20
+ from rich.console import Console
21
+
22
+ from pyxcp.config import CAN_INTERFACE_MAP
23
+ from pyxcp.transport.base import BaseTransport
24
+
25
+ from ..utils import seconds_to_nanoseconds
26
+
27
+
28
+ console = Console()
29
+
30
+ CAN_EXTENDED_ID = 0x80000000
31
+ MAX_11_BIT_IDENTIFIER = (1 << 11) - 1
32
+ MAX_29_BIT_IDENTIFIER = (1 << 29) - 1
33
+ MAX_DLC_CLASSIC = 8
34
+ CAN_FD_DLCS = (12, 16, 20, 24, 32, 48, 64) # Discrete CAN-FD DLCs in case DLC > 8.
35
+
36
+
37
+ class FilterState(IntEnum):
38
+ REJECT_ALL = 0
39
+ ACCEPT_ALL = 1
40
+ FILTERING = 2
41
+
42
+
43
+ class SoftwareFilter:
44
+ """Additional CAN filters in software."""
45
+
46
+ def __init__(self) -> None:
47
+ self.filters = None
48
+ self.reject_all()
49
+
50
+ def set_filters(self, filters: List[Dict]) -> None:
51
+ self.filters = filters
52
+ self.filtering()
53
+
54
+ def reject_all(self) -> None:
55
+ self.filter_state = FilterState.REJECT_ALL
56
+
57
+ def accept_all(self) -> None:
58
+ self.filter_state = FilterState.ACCEPT_ALL
59
+
60
+ def filtering(self) -> None:
61
+ self.filter_state = FilterState.FILTERING
62
+
63
+ @property
64
+ def state(self) -> FilterState:
65
+ return self.filter_state
66
+
67
+ def accept(self, msg: Message) -> bool:
68
+ """
69
+ Based on: https://github.com/hardbyte/python-can/blob/bc248e8aaf96280a574c06e8e7d2778a67f091e3/can/bus.py#L430
70
+ """
71
+ if self.filter_state == FilterState.REJECT_ALL:
72
+ return False
73
+ elif self.filter_state == FilterState.ACCEPT_ALL or self.filters is None:
74
+ return True
75
+ for filter in self.filters:
76
+ if "extended" in filter:
77
+ if filter["extended"] != msg.is_extended_id:
78
+ continue
79
+ can_id = filter["can_id"]
80
+ can_mask = filter["can_mask"]
81
+ if (can_id ^ msg.arbitration_id) & can_mask == 0:
82
+ return True
83
+ return False
84
+
85
+
86
+ class IdentifierOutOfRangeError(Exception):
87
+ """Signals an identifier greater then :obj:`MAX_11_BIT_IDENTIFIER` or :obj:`MAX_29_BIT_IDENTIFIER`."""
88
+
89
+ pass
90
+
91
+
92
+ def is_extended_identifier(identifier: int) -> bool:
93
+ """Check for extendend CAN identifier.
94
+
95
+ Parameters
96
+ ----------
97
+ identifier: int
98
+
99
+ Returns
100
+ -------
101
+ bool
102
+ """
103
+ return (identifier & CAN_EXTENDED_ID) == CAN_EXTENDED_ID
104
+
105
+
106
+ def stripIdentifier(identifier: int) -> int:
107
+ """Get raw CAN identifier (remove :obj:`CAN_EXTENDED_ID` bit if present).
108
+
109
+ Parameters
110
+ ----------
111
+ identifier: int
112
+
113
+ Returns
114
+ -------
115
+ int
116
+ """
117
+ return identifier & (~0xE0000000)
118
+
119
+
120
+ def samplePointToTsegs(tqs: int, samplePoint: float) -> tuple:
121
+ """Calculate TSEG1 and TSEG2 from time-quantas and sample-point.
122
+
123
+ Parameters
124
+ ----------
125
+ tqs: int
126
+ Number of time-quantas
127
+ samplePoint: float or int
128
+ Sample-point as a percentage value.
129
+
130
+ Returns
131
+ -------
132
+ tuple (TSEG1, TSEG2)
133
+ """
134
+ factor = samplePoint / 100.0
135
+ tseg1 = int(tqs * factor)
136
+ tseg2 = tqs - tseg1
137
+ return (tseg1, tseg2)
138
+
139
+
140
+ def pad_frame(frame: bytes, pad_frame: bool, padding_value: int) -> bytes:
141
+ """Pad frame to next discrete DLC value (CAN-FD) or on request (CAN-Classic).
142
+
143
+ References:
144
+ -----------
145
+ ISO/DIS 15765 - 4; 8.2 Data length Code (DLC)
146
+ AUTOSAR CP Release 4.3.0, Specification of CAN Transport Layer; 7.3.8 N-PDU padding
147
+ AUTOSAR CP Release 4.3.0, Specification of CAN Driver; [SWS_CAN_00502], [ECUC_Can_00485]
148
+ AUTOSAR CP Release 4.3.0, Requirements on CAN; [SRS_Can_01073], [SRS_Can_01086], [SRS_Can_01160]
149
+ """
150
+ frame_len = len(frame)
151
+ if frame_len <= MAX_DLC_CLASSIC:
152
+ actual_len = MAX_DLC_CLASSIC if pad_frame else frame_len
153
+ else:
154
+ actual_len = CAN_FD_DLCS[bisect_left(CAN_FD_DLCS, frame_len)]
155
+ # append fill bytes up to MAX_DLC resp. next discrete FD DLC.
156
+ if len(frame) < actual_len:
157
+ frame += bytes([padding_value]) * (actual_len - len(frame))
158
+ return frame
159
+
160
+
161
+ class Identifier:
162
+ """Convenience class for XCP formatted CAN identifiers.
163
+
164
+ Parameters:
165
+ -----------
166
+ raw_id: int
167
+ Bit 32 set (i.e. 0x80000000) signals an extended (29-bit) identifier.
168
+
169
+ Raises
170
+ ------
171
+ :class:`IdentifierOutOfRangeError`
172
+ """
173
+
174
+ def __init__(self, raw_id: int):
175
+ self._raw_id = raw_id
176
+ self._id = stripIdentifier(raw_id)
177
+ self._is_extended = is_extended_identifier(raw_id)
178
+ if self._is_extended:
179
+ if self._id > MAX_29_BIT_IDENTIFIER:
180
+ raise IdentifierOutOfRangeError(f"29-bit identifier {self._id!r} is out of range")
181
+ else:
182
+ if self._id > MAX_11_BIT_IDENTIFIER:
183
+ raise IdentifierOutOfRangeError(f"11-bit identifier {self._id!r} is out of range")
184
+
185
+ @property
186
+ def id(self) -> int:
187
+ """
188
+ Returns
189
+ -------
190
+ int
191
+ Identifier as seen on bus.
192
+ """
193
+ return self._id
194
+
195
+ @property
196
+ def raw_id(self) -> int:
197
+ """
198
+ Returns
199
+ -------
200
+ int
201
+ Raw XCP formatted identifier.
202
+ """
203
+ return self._raw_id
204
+
205
+ @property
206
+ def is_extended(self) -> bool:
207
+ """
208
+ Returns
209
+ -------
210
+ bool
211
+ - True - 29-bit identifier.
212
+ - False - 11-bit identifier.
213
+ """
214
+ return self._is_extended
215
+
216
+ @property
217
+ def type_str(self) -> str:
218
+ """
219
+
220
+ Returns
221
+ -------
222
+ str
223
+ - "S" - 11-bit identifier.
224
+ - "E" - 29-bit identifier.
225
+ """
226
+ return "E" if self.is_extended else "S"
227
+
228
+ @staticmethod
229
+ def make_identifier(identifier: int, extended: bool) -> "Identifier":
230
+ """Factory method.
231
+
232
+ Parameters
233
+ ----------
234
+ identifier: int
235
+ Identifier as seen on bus.
236
+
237
+ extended: bool
238
+ bool
239
+ - True - 29-bit identifier.
240
+ - False - 11-bit identifier.
241
+ Returns
242
+ -------
243
+ :class:`Identifier`
244
+
245
+ Raises
246
+ ------
247
+ :class:`IdentifierOutOfRangeError`
248
+ """
249
+ return Identifier(identifier if not extended else (identifier | CAN_EXTENDED_ID))
250
+
251
+ def create_filter_from_id(self) -> Dict:
252
+ """Create a single CAN filter entry.
253
+ s. https://python-can.readthedocs.io/en/stable/bus.html#filtering
254
+ """
255
+ return {
256
+ "can_id": self.id,
257
+ "can_mask": MAX_29_BIT_IDENTIFIER if self.is_extended else MAX_11_BIT_IDENTIFIER,
258
+ "extended": self.is_extended,
259
+ }
260
+
261
+ def __eq__(self, other) -> bool:
262
+ return (self.id == other.id) and (self.is_extended == other.is_extended)
263
+
264
+ def __str__(self) -> str:
265
+ return f"Identifier(id = 0x{self.id:08x}, is_extended = {self.is_extended})"
266
+
267
+ def __repr__(self) -> str:
268
+ return f"Identifier(0x{self.raw_id:08x})"
269
+
270
+
271
+ class Frame:
272
+ """"""
273
+
274
+ def __init__(self, id_: Identifier, dlc: int, data: bytes, timestamp: int) -> None:
275
+ self.id: Identifier = id_
276
+ self.dlc: int = dlc
277
+ self.data: bytes = data
278
+ self.timestamp: int = timestamp
279
+
280
+ def __repr__(self) -> str:
281
+ return f"Frame(id = 0x{self.id:08x}, dlc = {self.dlc}, data = {self.data}, timestamp = {self.timestamp})"
282
+
283
+ __str__ = __repr__
284
+
285
+
286
+ class PythonCanWrapper:
287
+ """Wrapper around python-can - github.com/hardbyte/python-can"""
288
+
289
+ def __init__(self, parent, interface_name: str, timeout: int, **parameters) -> None:
290
+ self.parent = parent
291
+ self.interface_name: str = interface_name
292
+ self.timeout: int = timeout
293
+ self.parameters = parameters
294
+ if not self.parent.has_user_supplied_interface:
295
+ self.can_interface_class = _get_class_for_interface(self.interface_name)
296
+ else:
297
+ self.can_interface_class = None
298
+ self.can_interface: BusABC
299
+ self.connected: bool = False
300
+ self.software_filter = SoftwareFilter()
301
+ self.saved_filters = []
302
+
303
+ def connect(self) -> None:
304
+ if self.connected:
305
+ return
306
+ can_filters = []
307
+ can_filters.append(self.parent.can_id_slave.create_filter_from_id()) # Primary CAN filter.
308
+ if self.parent.daq_identifier:
309
+ # Add filters for DAQ identifiers.
310
+ for daq_id in self.parent.daq_identifier:
311
+ can_filters.append(daq_id.create_filter_from_id())
312
+ if self.parent.has_user_supplied_interface:
313
+ self.saved_filters = self.parent.transport_layer_interface.filters
314
+ if self.saved_filters:
315
+ merged_filters = can_filters[::]
316
+ for fltr in self.saved_filters:
317
+ if fltr not in merged_filters:
318
+ merged_filters.append(fltr)
319
+ self.can_interface = self.parent.transport_layer_interface
320
+ self.can_interface.set_filters(merged_filters)
321
+ self.software_filter.set_filters(can_filters) # Filter unwanted traffic.
322
+ else:
323
+ self.can_interface = self.can_interface_class(interface=self.interface_name, can_filters=can_filters, **self.parameters)
324
+ self.software_filter.accept_all()
325
+ self.parent.logger.info(f"XCPonCAN - Using Interface: '{self.can_interface!s}'")
326
+ self.parent.logger.info(f"XCPonCAN - Filters used: {self.can_interface.filters}")
327
+ self.parent.logger.info(f"XCPonCAN - State: {self.can_interface.state!s}")
328
+ self.connected = True
329
+
330
+ def close(self) -> None:
331
+ if self.connected and not self.parent.has_user_supplied_interface:
332
+ self.can_interface.shutdown()
333
+ if self.saved_filters:
334
+ self.can_interface.set_filters(self.saved_filters)
335
+ self.connected = False
336
+
337
+ def transmit(self, payload: bytes) -> None:
338
+ frame = Message(
339
+ arbitration_id=self.parent.can_id_master.id,
340
+ is_extended_id=True if self.parent.can_id_master.is_extended else False,
341
+ is_fd=self.parent.fd,
342
+ data=payload,
343
+ )
344
+ self.can_interface.send(frame)
345
+
346
+ def read(self) -> Optional[Frame]:
347
+ if not self.connected:
348
+ return None
349
+ try:
350
+ frame = self.can_interface.recv(self.timeout)
351
+ except CanError:
352
+ return None
353
+ else:
354
+ if frame is None or not len(frame.data):
355
+ return None # Timeout condition.
356
+ if not self.software_filter.accept(frame):
357
+ return None # Filter out unwanted traffic.
358
+ extended = frame.is_extended_id
359
+ identifier = Identifier.make_identifier(frame.arbitration_id, extended)
360
+ return Frame(
361
+ id_=identifier,
362
+ dlc=frame.dlc,
363
+ data=frame.data,
364
+ timestamp=seconds_to_nanoseconds(frame.timestamp),
365
+ )
366
+
367
+ def get_timestamp_resolution(self) -> int:
368
+ return 10 * 1000
369
+
370
+
371
+ class EmptyHeader:
372
+ """There is no header for XCP on CAN"""
373
+
374
+ def pack(self, *args, **kwargs):
375
+ return b""
376
+
377
+
378
+ class Can(BaseTransport):
379
+ """"""
380
+
381
+ MAX_DATAGRAM_SIZE = 7
382
+ HEADER = EmptyHeader()
383
+ HEADER_SIZE = 0
384
+
385
+ def __init__(self, config, policy=None, transport_layer_interface: Optional[BusABC] = None):
386
+ super().__init__(config, policy, transport_layer_interface)
387
+ self.load_config(config)
388
+ self.useDefaultListener = self.config.use_default_listener
389
+ self.can_id_master = Identifier(self.config.can_id_master)
390
+ self.can_id_slave = Identifier(self.config.can_id_slave)
391
+
392
+ # Regarding CAN-FD s. AUTOSAR CP Release 4.3.0, Requirements on CAN; [SRS_Can_01160] Padding of bytes due to discrete CAN FD DLC]:
393
+ # "... If a PDU does not exactly match these configurable sizes the unused bytes shall be padded."
394
+ #
395
+ self.fd = self.config.fd
396
+ self.daq_identifier = []
397
+ if self.config.daq_identifier:
398
+ for daq_id in self.config.daq_identifier:
399
+ self.daq_identifier.append(Identifier(daq_id))
400
+ self.max_dlc_required = self.config.max_dlc_required
401
+ self.padding_value = self.config.padding_value
402
+ if transport_layer_interface is None:
403
+ self.interface_name = self.config.interface
404
+ self.interface_configuration = detect_available_configs(interfaces=[self.interface_name])
405
+ parameters = self.get_interface_parameters()
406
+ else:
407
+ self.interface_name = "custom"
408
+ # print("TRY GET PARAMs", self.get_interface_parameters())
409
+ parameters = {}
410
+ self.can_interface = PythonCanWrapper(self, self.interface_name, config.timeout, **parameters)
411
+ self.logger.info(f"XCPonCAN - Interface-Type: {self.interface_name!r} Parameters: {list(parameters.items())}")
412
+ self.logger.info(
413
+ f"XCPonCAN - Master-ID (Tx): 0x{self.can_id_master.id:08X}{self.can_id_master.type_str} -- "
414
+ f"Slave-ID (Rx): 0x{self.can_id_slave.id:08X}{self.can_id_slave.type_str}"
415
+ )
416
+
417
+ def get_interface_parameters(self) -> Dict[str, Any]:
418
+ result = dict(channel=self.config.channel)
419
+
420
+ can_interface_config_class = CAN_INTERFACE_MAP[self.interface_name]
421
+
422
+ # Optional base class parameters.
423
+ optional_parameters = [(p, p.removeprefix("has_")) for p in can_interface_config_class.OPTIONAL_BASE_PARAMS]
424
+ for o, n in optional_parameters:
425
+ opt = getattr(can_interface_config_class, o)
426
+ value = getattr(self.config, n)
427
+ if opt:
428
+ if value is not None:
429
+ result[n] = value
430
+ elif value is not None:
431
+ self.logger.warning(f"XCPonCAN - {self.interface_name!r} has no support for parameter {n!r}.")
432
+ # Parameter names that need to be mapped.
433
+ for base_name, name in can_interface_config_class.CAN_PARAM_MAP.items():
434
+ value = getattr(self.config, base_name)
435
+ if name is not None and value is not None:
436
+ result[name] = value
437
+ # Interface specific parameters.
438
+ cxx = getattr(self.config, self.interface_name)
439
+ for name in can_interface_config_class.class_own_traits().keys():
440
+ value = getattr(cxx, name)
441
+ if value is not None:
442
+ result[name] = value
443
+ return result
444
+
445
+ def data_received(self, payload: bytes, recv_timestamp: int):
446
+ self.process_response(
447
+ payload,
448
+ len(payload),
449
+ counter=(self.counter_received + 1) & 0xFFFF,
450
+ recv_timestamp=recv_timestamp,
451
+ )
452
+
453
+ def listen(self):
454
+ while True:
455
+ if self.closeEvent.is_set():
456
+ return
457
+ frame = self.can_interface.read()
458
+ if frame:
459
+ self.data_received(frame.data, frame.timestamp)
460
+
461
+ def connect(self):
462
+ if self.useDefaultListener:
463
+ self.start_listener()
464
+ try:
465
+ self.can_interface.connect()
466
+ except CanInitializationError:
467
+ console.print("[red]\nThere may be a problem with the configuration of your CAN-interface.\n")
468
+ console.print(f"[grey]Current configuration of interface {self.interface_name!r}:")
469
+ console.print(self.interface_configuration)
470
+ raise
471
+ self.status = 1 # connected
472
+
473
+ def send(self, frame: bytes) -> None:
474
+ # send the request
475
+ self.pre_send_timestamp = self.timestamp.value
476
+ self.can_interface.transmit(payload=pad_frame(frame, self.max_dlc_required, self.padding_value))
477
+ self.post_send_timestamp = self.timestamp.value
478
+
479
+ def close_connection(self):
480
+ if hasattr(self, "can_interface"):
481
+ self.can_interface.close()
482
+
483
+ def close(self):
484
+ self.finish_listener()
485
+ self.close_connection()
486
+
487
+
488
+ def set_DLC(length: int):
489
+ """Return DLC value according to CAN-FD.
490
+
491
+ :param length: Length value to be mapped to a valid CAN-FD DLC.
492
+ ( 0 <= length <= 64)
493
+ """
494
+
495
+ if length < 0:
496
+ raise ValueError("Non-negative length value required.")
497
+ elif length <= MAX_DLC_CLASSIC:
498
+ return length
499
+ elif length <= 64:
500
+ for dlc in CAN_FD_DLCS:
501
+ if length <= dlc:
502
+ return dlc
503
+ else:
504
+ raise ValueError("DLC could be at most 64.")
505
+
506
+
507
+ def calculate_filter(ids: list):
508
+ """
509
+ :param ids: An iterable (usually list or tuple) containing CAN identifiers.
510
+
511
+ :return: Calculated filter and mask.
512
+ :rtype: tuple (int, int)
513
+ """
514
+ any_extended_ids = any(is_extended_identifier(i) for i in ids)
515
+ raw_ids = [stripIdentifier(i) for i in ids]
516
+ cfilter = functools.reduce(operator.and_, raw_ids)
517
+ cmask = functools.reduce(operator.or_, raw_ids) ^ cfilter
518
+ cmask ^= 0x1FFFFFFF if any_extended_ids else 0x7FF
519
+ return (cfilter, cmask)
520
+
521
+
522
+ class CanInterfaceBase(ABC):
523
+ """
524
+ Base class for custom CAN interfaces.
525
+ This is basically a subset of python-CANs `BusABC`.
526
+ """
527
+
528
+ @abstractmethod
529
+ def set_filters(self, filters: Optional[List[Dict[str, Union[int, bool]]]] = None) -> None:
530
+ """Apply filtering to all messages received by this Bus.
531
+
532
+ filters:
533
+ A list of dictionaries, each containing a 'can_id', 'can_mask', and 'extended' field, e.g.:
534
+ [{"can_id": 0x11, "can_mask": 0x21, "extended": False}]
535
+ """
536
+
537
+ @abstractmethod
538
+ def recv(self, timeout: Optional[float] = None) -> Optional[Message]:
539
+ """Block waiting for a message from the Bus."""
540
+
541
+ @abstractmethod
542
+ def send(self, msg: Message) -> None:
543
+ """Transmit a message to the CAN bus."""
544
+
545
+ @property
546
+ @abstractmethod
547
+ def filters(self) -> Optional[List[Dict[str, Union[int, bool]]]]:
548
+ """Modify the filters of this bus."""
549
+
550
+ @property
551
+ @abstractmethod
552
+ def state(self) -> BusState:
553
+ """Return the current state of the hardware."""
554
+
555
+ def __repr__(self):
556
+ return f"{self.__class__.__name__}"