pyxcp 0.23.0__cp312-cp312-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.
- 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.sh +2 -0
- pyxcp/checksum.py +732 -0
- pyxcp/cmdline.py +52 -0
- pyxcp/config/__init__.py +1113 -0
- pyxcp/config/legacy.py +120 -0
- pyxcp/constants.py +47 -0
- pyxcp/cpp_ext/__init__.py +0 -0
- pyxcp/cpp_ext/bin.hpp +104 -0
- pyxcp/cpp_ext/blockmem.hpp +58 -0
- pyxcp/cpp_ext/cpp_ext.cp310-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp311-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/cpp_ext.cp312-win_arm64.pyd +0 -0
- pyxcp/cpp_ext/daqlist.hpp +206 -0
- pyxcp/cpp_ext/event.hpp +67 -0
- pyxcp/cpp_ext/extension_wrapper.cpp +100 -0
- pyxcp/cpp_ext/helper.hpp +280 -0
- pyxcp/cpp_ext/mcobject.hpp +246 -0
- pyxcp/cpp_ext/tsqueue.hpp +46 -0
- pyxcp/daq_stim/__init__.py +232 -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_arm64.pyd +0 -0
- pyxcp/daq_stim/stim.cp311-win_arm64.pyd +0 -0
- pyxcp/daq_stim/stim.cp312-win_arm64.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/conf_sxi.json +9 -0
- pyxcp/examples/conf_sxi.toml +7 -0
- pyxcp/examples/run_daq.py +163 -0
- pyxcp/examples/xcp_policy.py +60 -0
- pyxcp/examples/xcp_read_benchmark.py +38 -0
- pyxcp/examples/xcp_skel.py +49 -0
- pyxcp/examples/xcp_unlock.py +38 -0
- pyxcp/examples/xcp_user_supplied_driver.py +44 -0
- pyxcp/examples/xcphello.py +78 -0
- pyxcp/examples/xcphello_recorder.py +107 -0
- pyxcp/master/__init__.py +9 -0
- pyxcp/master/errorhandler.py +442 -0
- pyxcp/master/master.py +2047 -0
- pyxcp/py.typed +0 -0
- pyxcp/recorder/__init__.py +101 -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 +450 -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 +139 -0
- pyxcp/recorder/reco.py +277 -0
- pyxcp/recorder/recorder.rst +0 -0
- pyxcp/recorder/rekorder.cp310-win_arm64.pyd +0 -0
- pyxcp/recorder/rekorder.cp311-win_arm64.pyd +0 -0
- pyxcp/recorder/rekorder.cp312-win_arm64.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 +1332 -0
- pyxcp/recorder/wrap.cpp +189 -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 +19 -0
- pyxcp/scripts/xcp_info.py +144 -0
- pyxcp/scripts/xcp_profile.py +27 -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_master.py +2006 -0
- pyxcp/tests/test_transport.py +81 -0
- pyxcp/tests/test_utils.py +30 -0
- pyxcp/timing.py +60 -0
- pyxcp/transport/__init__.py +10 -0
- pyxcp/transport/base.py +440 -0
- pyxcp/transport/base_transport.hpp +0 -0
- pyxcp/transport/can.py +556 -0
- pyxcp/transport/eth.py +219 -0
- pyxcp/transport/sxi.py +135 -0
- pyxcp/transport/transport_wrapper.cpp +0 -0
- pyxcp/transport/usb_transport.py +213 -0
- pyxcp/types.py +1000 -0
- pyxcp/utils.py +128 -0
- pyxcp/vector/__init__.py +0 -0
- pyxcp/vector/map.py +82 -0
- pyxcp-0.23.0.dist-info/LICENSE +165 -0
- pyxcp-0.23.0.dist-info/METADATA +107 -0
- pyxcp-0.23.0.dist-info/RECORD +131 -0
- pyxcp-0.23.0.dist-info/WHEEL +4 -0
- 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__}"
|