pyxcp 0.22.1__cp38-cp38-macosx_13_0_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.

Potentially problematic release.


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

Files changed (126) hide show
  1. CMakeLists.txt +117 -0
  2. pyxcp/__init__.py +20 -0
  3. pyxcp/aml/EtasCANMonitoring.a2l +82 -0
  4. pyxcp/aml/EtasCANMonitoring.aml +67 -0
  5. pyxcp/aml/XCP_Common.aml +408 -0
  6. pyxcp/aml/XCPonCAN.aml +78 -0
  7. pyxcp/aml/XCPonEth.aml +33 -0
  8. pyxcp/aml/XCPonFlx.aml +113 -0
  9. pyxcp/aml/XCPonSxI.aml +66 -0
  10. pyxcp/aml/XCPonUSB.aml +106 -0
  11. pyxcp/aml/ifdata_CAN.a2l +20 -0
  12. pyxcp/aml/ifdata_Eth.a2l +11 -0
  13. pyxcp/aml/ifdata_Flx.a2l +94 -0
  14. pyxcp/aml/ifdata_SxI.a2l +13 -0
  15. pyxcp/aml/ifdata_USB.a2l +81 -0
  16. pyxcp/asam/__init__.py +0 -0
  17. pyxcp/asam/types.py +131 -0
  18. pyxcp/asamkeydll.c +116 -0
  19. pyxcp/asamkeydll.sh +2 -0
  20. pyxcp/checksum.py +722 -0
  21. pyxcp/cmdline.py +52 -0
  22. pyxcp/config/__init__.py +1089 -0
  23. pyxcp/config/legacy.py +120 -0
  24. pyxcp/constants.py +47 -0
  25. pyxcp/cpp_ext/__init__.py +8 -0
  26. pyxcp/cpp_ext/bin.hpp +104 -0
  27. pyxcp/cpp_ext/blockmem.hpp +58 -0
  28. pyxcp/cpp_ext/cpp_ext.cpython-312-darwin.so +0 -0
  29. pyxcp/cpp_ext/daqlist.hpp +197 -0
  30. pyxcp/cpp_ext/event.hpp +67 -0
  31. pyxcp/cpp_ext/extension_wrapper.cpp +94 -0
  32. pyxcp/cpp_ext/helper.hpp +264 -0
  33. pyxcp/cpp_ext/mcobject.hpp +241 -0
  34. pyxcp/cpp_ext/tsqueue.hpp +46 -0
  35. pyxcp/daq_stim/__init__.py +226 -0
  36. pyxcp/daq_stim/optimize/__init__.py +67 -0
  37. pyxcp/daq_stim/optimize/binpacking.py +41 -0
  38. pyxcp/daq_stim/scheduler.cpp +28 -0
  39. pyxcp/daq_stim/scheduler.hpp +75 -0
  40. pyxcp/daq_stim/stim.cpp +13 -0
  41. pyxcp/daq_stim/stim.cpython-312-darwin.so +0 -0
  42. pyxcp/daq_stim/stim.hpp +604 -0
  43. pyxcp/daq_stim/stim_wrapper.cpp +48 -0
  44. pyxcp/dllif.py +95 -0
  45. pyxcp/errormatrix.py +878 -0
  46. pyxcp/examples/conf_can.toml +19 -0
  47. pyxcp/examples/conf_can_user.toml +16 -0
  48. pyxcp/examples/conf_can_vector.json +11 -0
  49. pyxcp/examples/conf_can_vector.toml +11 -0
  50. pyxcp/examples/conf_eth.toml +9 -0
  51. pyxcp/examples/conf_nixnet.json +20 -0
  52. pyxcp/examples/conf_socket_can.toml +12 -0
  53. pyxcp/examples/conf_sxi.json +9 -0
  54. pyxcp/examples/conf_sxi.toml +7 -0
  55. pyxcp/examples/ex_arrow.py +109 -0
  56. pyxcp/examples/ex_mdf.py +124 -0
  57. pyxcp/examples/ex_sqlite.py +128 -0
  58. pyxcp/examples/run_daq.py +146 -0
  59. pyxcp/examples/xcp_policy.py +60 -0
  60. pyxcp/examples/xcp_read_benchmark.py +38 -0
  61. pyxcp/examples/xcp_skel.py +49 -0
  62. pyxcp/examples/xcp_unlock.py +38 -0
  63. pyxcp/examples/xcp_user_supplied_driver.py +54 -0
  64. pyxcp/examples/xcphello.py +79 -0
  65. pyxcp/examples/xcphello_recorder.py +107 -0
  66. pyxcp/master/__init__.py +9 -0
  67. pyxcp/master/errorhandler.py +436 -0
  68. pyxcp/master/master.py +2029 -0
  69. pyxcp/py.typed +0 -0
  70. pyxcp/recorder/__init__.py +102 -0
  71. pyxcp/recorder/build_clang.cmd +1 -0
  72. pyxcp/recorder/build_clang.sh +2 -0
  73. pyxcp/recorder/build_gcc.cmd +1 -0
  74. pyxcp/recorder/build_gcc.sh +2 -0
  75. pyxcp/recorder/build_gcc_arm.sh +2 -0
  76. pyxcp/recorder/converter/__init__.py +37 -0
  77. pyxcp/recorder/lz4.c +2829 -0
  78. pyxcp/recorder/lz4.h +879 -0
  79. pyxcp/recorder/lz4hc.c +2041 -0
  80. pyxcp/recorder/lz4hc.h +413 -0
  81. pyxcp/recorder/mio.hpp +1714 -0
  82. pyxcp/recorder/reader.hpp +139 -0
  83. pyxcp/recorder/reco.py +277 -0
  84. pyxcp/recorder/recorder.rst +0 -0
  85. pyxcp/recorder/rekorder.cpp +59 -0
  86. pyxcp/recorder/rekorder.cpython-312-darwin.so +0 -0
  87. pyxcp/recorder/rekorder.hpp +274 -0
  88. pyxcp/recorder/setup.py +41 -0
  89. pyxcp/recorder/test_reko.py +34 -0
  90. pyxcp/recorder/unfolder.hpp +1249 -0
  91. pyxcp/recorder/wrap.cpp +189 -0
  92. pyxcp/recorder/writer.hpp +302 -0
  93. pyxcp/scripts/__init__.py +0 -0
  94. pyxcp/scripts/pyxcp_probe_can_drivers.py +20 -0
  95. pyxcp/scripts/xcp_fetch_a2l.py +40 -0
  96. pyxcp/scripts/xcp_id_scanner.py +19 -0
  97. pyxcp/scripts/xcp_info.py +109 -0
  98. pyxcp/scripts/xcp_profile.py +27 -0
  99. pyxcp/stim/__init__.py +0 -0
  100. pyxcp/tests/test_asam_types.py +24 -0
  101. pyxcp/tests/test_binpacking.py +184 -0
  102. pyxcp/tests/test_can.py +1324 -0
  103. pyxcp/tests/test_checksum.py +95 -0
  104. pyxcp/tests/test_daq.py +188 -0
  105. pyxcp/tests/test_frame_padding.py +153 -0
  106. pyxcp/tests/test_master.py +2006 -0
  107. pyxcp/tests/test_transport.py +64 -0
  108. pyxcp/tests/test_utils.py +30 -0
  109. pyxcp/timing.py +60 -0
  110. pyxcp/transport/__init__.py +10 -0
  111. pyxcp/transport/base.py +436 -0
  112. pyxcp/transport/base_transport.hpp +0 -0
  113. pyxcp/transport/can.py +443 -0
  114. pyxcp/transport/eth.py +219 -0
  115. pyxcp/transport/sxi.py +133 -0
  116. pyxcp/transport/transport_wrapper.cpp +0 -0
  117. pyxcp/transport/usb_transport.py +213 -0
  118. pyxcp/types.py +993 -0
  119. pyxcp/utils.py +102 -0
  120. pyxcp/vector/__init__.py +0 -0
  121. pyxcp/vector/map.py +82 -0
  122. pyxcp-0.22.1.dist-info/LICENSE +165 -0
  123. pyxcp-0.22.1.dist-info/METADATA +107 -0
  124. pyxcp-0.22.1.dist-info/RECORD +126 -0
  125. pyxcp-0.22.1.dist-info/WHEEL +4 -0
  126. pyxcp-0.22.1.dist-info/entry_points.txt +7 -0
@@ -0,0 +1,64 @@
1
+ import pytest
2
+
3
+ import pyxcp.transport.base as tr
4
+
5
+
6
+ def test_factory_works():
7
+ assert isinstance(tr.create_transport("eth"), tr.BaseTransport)
8
+ assert isinstance(tr.create_transport("sxi"), tr.BaseTransport)
9
+ assert isinstance(
10
+ tr.create_transport(
11
+ "can",
12
+ config={
13
+ "CAN_ID_MASTER": 1,
14
+ "CAN_ID_SLAVE": 2,
15
+ "CAN_DRIVER": "MockCanInterface",
16
+ },
17
+ ),
18
+ tr.BaseTransport,
19
+ )
20
+
21
+
22
+ def test_factory_works_case_insensitive():
23
+ assert isinstance(tr.create_transport("ETH"), tr.BaseTransport)
24
+ assert isinstance(tr.create_transport("SXI"), tr.BaseTransport)
25
+ assert isinstance(
26
+ tr.create_transport(
27
+ "CAN",
28
+ config={
29
+ "CAN_ID_MASTER": 1,
30
+ "CAN_ID_SLAVE": 2,
31
+ "CAN_DRIVER": "MockCanInterface",
32
+ },
33
+ ),
34
+ tr.BaseTransport,
35
+ )
36
+
37
+
38
+ def test_factory_invalid_transport_name_raises():
39
+ with pytest.raises(ValueError):
40
+ tr.create_transport("xCp")
41
+
42
+
43
+ def test_transport_names():
44
+ transports = tr.available_transports()
45
+
46
+ assert "can" in transports
47
+ assert "eth" in transports
48
+ assert "sxi" in transports
49
+
50
+
51
+ def test_transport_names_are_lower_case_only():
52
+ transports = tr.available_transports()
53
+
54
+ assert "CAN" not in transports
55
+ assert "ETH" not in transports
56
+ assert "SXI" not in transports
57
+
58
+
59
+ def test_transport_classes():
60
+ transports = tr.available_transports()
61
+
62
+ assert issubclass(transports.get("can"), tr.BaseTransport)
63
+ assert issubclass(transports.get("eth"), tr.BaseTransport)
64
+ assert issubclass(transports.get("sxi"), tr.BaseTransport)
@@ -0,0 +1,30 @@
1
+ from sys import version_info
2
+
3
+ from pyxcp.utils import PYTHON_VERSION, flatten, getPythonVersion, hexDump, slicer
4
+
5
+
6
+ def test_hexdump(capsys):
7
+ print(hexDump(range(16)), end="")
8
+ captured = capsys.readouterr()
9
+ assert captured.out == "[00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f]"
10
+
11
+
12
+ def test_slicer1():
13
+ res = slicer([1, 2, 3, 4, 5, 6, 7, 8], 4)
14
+ assert res == [[1, 2, 3, 4], [5, 6, 7, 8]]
15
+
16
+
17
+ def test_slicer2():
18
+ res = slicer(["10", "20", "30", "40", "50", "60", "70", "80"], 4, tuple)
19
+ assert res == [("10", "20", "30", "40"), ("50", "60", "70", "80")]
20
+
21
+
22
+ def test_flatten1():
23
+ res = flatten([[1, 2, 3, 4], [5, 6, 7, 8]])
24
+ assert res == [1, 2, 3, 4, 5, 6, 7, 8]
25
+
26
+
27
+ def test_version():
28
+ assert getPythonVersion() == version_info
29
+ assert PYTHON_VERSION == version_info
30
+ assert getPythonVersion() == PYTHON_VERSION
pyxcp/timing.py ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python
2
+ import time
3
+
4
+
5
+ class Timing:
6
+ T_US = 1000 * 1000
7
+ T_MS = 1000
8
+ T_S = 1
9
+
10
+ UNIT_MAP = {
11
+ T_US: "uS",
12
+ T_MS: "mS",
13
+ T_S: "S",
14
+ }
15
+ FMT = "min: {0:2.3f} {4}\nmax: {1:2.3f} {4}\n" "avg: {2:2.3f} {4}\nlast: {3:2.3f} {4}"
16
+
17
+ def __init__(self, unit=T_MS, record=False):
18
+ self.min = None
19
+ self.max = None
20
+ self.avg = None
21
+ self._previous = None
22
+ self.unit = unit
23
+ self._record = record
24
+ self._values = []
25
+
26
+ def start(self):
27
+ self._start = time.perf_counter()
28
+
29
+ def stop(self):
30
+ self._stop = time.perf_counter()
31
+ elapsed = self._stop - self._start
32
+ if self._record:
33
+ self._values.append(elapsed)
34
+ if self._previous:
35
+ self.min = min(self._previous, elapsed)
36
+ self.max = max(self._previous, elapsed)
37
+ self.avg = (self._previous + elapsed) / 2
38
+ else:
39
+ self.min = self.max = self.avg = elapsed
40
+ self._previous = elapsed
41
+
42
+ def __str__(self):
43
+ unitName = Timing.UNIT_MAP.get(self.unit, "??")
44
+ self.min = 0 if self.min is None else self.min
45
+ self.max = 0 if self.max is None else self.max
46
+ self.avg = 0 if self.avg is None else self.avg
47
+ self._previous = 0 if self._previous is None else self._previous
48
+ return Timing.FMT.format(
49
+ self.min * self.unit,
50
+ self.max * self.unit,
51
+ self.avg * self.unit,
52
+ self._previous * self.unit,
53
+ unitName,
54
+ )
55
+
56
+ __repr__ = __str__
57
+
58
+ @property
59
+ def values(self):
60
+ return self._values
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python
2
+ from .base import FrameAcquisitionPolicy # noqa: F401
3
+ from .base import FrameRecorderPolicy # noqa: F401
4
+ from .base import LegacyFrameAcquisitionPolicy # noqa: F401
5
+ from .base import NoOpPolicy # noqa: F401
6
+ from .base import StdoutPolicy # noqa: F401
7
+ from .can import Can # noqa: F401
8
+ from .eth import Eth # noqa: F401
9
+ from .sxi import SxI # noqa: F401
10
+ from .usb_transport import Usb # noqa: F401
@@ -0,0 +1,436 @@
1
+ #!/usr/bin/env python
2
+ import abc
3
+ import threading
4
+ from collections import deque
5
+ from typing import Any, Dict, Optional, Set, Type
6
+
7
+ import pyxcp.types as types
8
+
9
+ from ..cpp_ext import Timestamp, TimestampType
10
+ from ..recorder import XcpLogFileWriter
11
+ from ..timing import Timing
12
+ from ..utils import (
13
+ CurrentDatetime,
14
+ flatten,
15
+ hexDump,
16
+ seconds_to_nanoseconds,
17
+ short_sleep,
18
+ )
19
+
20
+
21
+ class FrameAcquisitionPolicy:
22
+ """
23
+ Base class for all frame acquisition policies.
24
+
25
+ Parameters
26
+ ---------
27
+ filter_out: set or None
28
+ A set of frame types to filter out.
29
+ If None, all frame types are accepted for further processing.
30
+
31
+ Example: (FrameType.REQUEST, FrameType.RESPONSE, FrameType.EVENT, FrameType.SERV)
32
+ ==> care only about DAQ frames.
33
+ """
34
+
35
+ def __init__(self, filter_out: Optional[Set[types.FrameCategory]] = None):
36
+ self._frame_types_to_filter_out = filter_out or set()
37
+
38
+ @property
39
+ def filtered_out(self) -> Set[types.FrameCategory]:
40
+ return self._frame_types_to_filter_out
41
+
42
+ def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: int, payload: bytes) -> None: ... # noqa: E704
43
+
44
+ def finalize(self) -> None:
45
+ """
46
+ Finalize the frame acquisition policy (if required).
47
+ """
48
+ ...
49
+
50
+
51
+ class NoOpPolicy(FrameAcquisitionPolicy):
52
+ """
53
+ No operation / do nothing policy.
54
+ """
55
+
56
+
57
+ class LegacyFrameAcquisitionPolicy(FrameAcquisitionPolicy):
58
+ """Dequeue based frame acquisition policy.
59
+
60
+ Deprecated: Use only for compatibility reasons.
61
+ """
62
+
63
+ def __init__(self, filter_out: Optional[Set[types.FrameCategory]] = None) -> None:
64
+ super().__init__(filter_out)
65
+ self.reqQueue = deque()
66
+ self.resQueue = deque()
67
+ self.daqQueue = deque()
68
+ self.evQueue = deque()
69
+ self.servQueue = deque()
70
+ self.metaQueue = deque()
71
+ self.errorQueue = deque()
72
+ self.stimQueue = deque()
73
+ self.QUEUE_MAP = {
74
+ types.FrameCategory.CMD: self.reqQueue,
75
+ types.FrameCategory.RESPONSE: self.resQueue,
76
+ types.FrameCategory.EVENT: self.evQueue,
77
+ types.FrameCategory.SERV: self.servQueue,
78
+ types.FrameCategory.DAQ: self.daqQueue,
79
+ types.FrameCategory.METADATA: self.metaQueue,
80
+ types.FrameCategory.ERROR: self.errorQueue,
81
+ types.FrameCategory.STIM: self.stimQueue,
82
+ }
83
+
84
+ def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: int, payload: bytes) -> None:
85
+ # print(f"{frame_type.name:8} {counter:6} {timestamp:7.7f} {hexDump(payload)}")
86
+ if frame_type not in self.filtered_out:
87
+ queue = self.QUEUE_MAP.get(frame_type)
88
+ if queue:
89
+ queue.append((counter, timestamp, payload))
90
+
91
+
92
+ class FrameRecorderPolicy(FrameAcquisitionPolicy):
93
+ """Frame acquisition policy that records frames."""
94
+
95
+ def __init__(
96
+ self,
97
+ file_name: str,
98
+ filter_out: Optional[Set[types.FrameCategory]] = None,
99
+ prealloc: int = 10,
100
+ chunk_size: int = 1,
101
+ ) -> None:
102
+ super().__init__(filter_out)
103
+ self.recorder = XcpLogFileWriter(file_name, prealloc=prealloc, chunk_size=chunk_size)
104
+
105
+ def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: int, payload: bytes) -> None:
106
+ if frame_type not in self.filtered_out:
107
+ self.recorder.add_frame(frame_type, counter, timestamp, payload)
108
+
109
+ def finalize(self) -> None:
110
+ self.recorder.finalize()
111
+
112
+
113
+ class StdoutPolicy(FrameAcquisitionPolicy):
114
+ """Frame acquisition policy that prints frames to stdout."""
115
+
116
+ def __init__(self, filter_out: Optional[Set[types.FrameCategory]] = None) -> None:
117
+ super().__init__(filter_out)
118
+
119
+ def feed(self, frame_type: types.FrameCategory, counter: int, timestamp: int, payload: bytes) -> None:
120
+ if frame_type not in self.filtered_out:
121
+ print(f"{frame_type.name:8} {counter:6} {timestamp:8u} {hexDump(payload)}")
122
+
123
+
124
+ class EmptyFrameError(Exception):
125
+ """Raised when an empty frame is received."""
126
+
127
+
128
+ class BaseTransport(metaclass=abc.ABCMeta):
129
+ """Base class for transport-layers (Can, Eth, Sxi).
130
+
131
+ Parameters
132
+ ----------
133
+ config: dict-like
134
+ Parameters like bitrate.
135
+ loglevel: ["INFO", "WARN", "DEBUG", "ERROR", "CRITICAL"]
136
+ Controls the verbosity of log messages.
137
+
138
+ """
139
+
140
+ def __init__(self, config, policy: Optional[FrameAcquisitionPolicy] = None, transport_layer_interface: Optional[Any] = None):
141
+ self.has_user_supplied_interface: bool = transport_layer_interface is not None
142
+ self.transport_layer_interface: Optional[Any] = transport_layer_interface
143
+ self.parent = None
144
+ self.policy: FrameAcquisitionPolicy = policy or LegacyFrameAcquisitionPolicy()
145
+ self.closeEvent: threading.Event = threading.Event()
146
+
147
+ self.command_lock: threading.Lock = threading.Lock()
148
+ self.policy_lock: threading.Lock = threading.Lock()
149
+
150
+ self.logger: Any = config.log
151
+ self._debug: bool = self.logger.level == 10
152
+ if transport_layer_interface:
153
+ self.logger.info(f"Transport - User Supplied Transport-Layer Interface: '{transport_layer_interface!s}'")
154
+ self.counter_send: int = 0
155
+ self.counter_received: int = -1
156
+ self.create_daq_timestamps: bool = config.create_daq_timestamps
157
+ self.timestamp = Timestamp(TimestampType.ABSOLUTE_TS)
158
+ self._start_datetime: CurrentDatetime = CurrentDatetime(self.timestamp.initial_value)
159
+ self.alignment: int = config.alignment
160
+ self.timeout: int = seconds_to_nanoseconds(config.timeout)
161
+ self.timer_restart_event: threading.Event = threading.Event()
162
+ self.timing: Timing = Timing()
163
+ self.resQueue: deque = deque()
164
+ self.listener: threading.Thread = threading.Thread(
165
+ target=self.listen,
166
+ args=(),
167
+ kwargs={},
168
+ )
169
+
170
+ self.first_daq_timestamp: Optional[int] = None
171
+ # self.timestamp_origin = self.timestamp.value
172
+ # self.datetime_origin = datetime.fromtimestamp(self.timestamp_origin)
173
+ self.pre_send_timestamp: int = self.timestamp.value
174
+ self.post_send_timestamp: int = self.timestamp.value
175
+ self.recv_timestamp: int = self.timestamp.value
176
+
177
+ def __del__(self) -> None:
178
+ self.finish_listener()
179
+ self.close_connection()
180
+
181
+ def load_config(self, config) -> None:
182
+ """Load configuration data."""
183
+ class_name: str = self.__class__.__name__.lower()
184
+ self.config: Any = getattr(config, class_name)
185
+
186
+ def close(self) -> None:
187
+ """Close the transport-layer connection and event-loop."""
188
+ self.finish_listener()
189
+ if self.listener.is_alive():
190
+ self.listener.join()
191
+ self.close_connection()
192
+
193
+ @abc.abstractmethod
194
+ def connect(self) -> None:
195
+ pass
196
+
197
+ def get(self):
198
+ """Get an item from a deque considering a timeout condition."""
199
+ start: int = self.timestamp.value
200
+ while not self.resQueue:
201
+ if self.timer_restart_event.is_set():
202
+ start: int = self.timestamp.value
203
+ self.timer_restart_event.clear()
204
+ if self.timestamp.value - start > self.timeout:
205
+ raise EmptyFrameError
206
+ short_sleep()
207
+ item = self.resQueue.popleft()
208
+ # print("Q", item)
209
+ return item
210
+
211
+ @property
212
+ def start_datetime(self) -> int:
213
+ """"""
214
+ return self._start_datetime
215
+
216
+ def start_listener(self):
217
+ if self.listener.is_alive():
218
+ self.finish_listener()
219
+ self.listener.join()
220
+
221
+ self.listener = threading.Thread(target=self.listen)
222
+ self.listener.start()
223
+
224
+ def finish_listener(self):
225
+ if hasattr(self, "closeEvent"):
226
+ self.closeEvent.set()
227
+
228
+ def _request_internal(self, cmd, ignore_timeout=False, *data):
229
+ with self.command_lock:
230
+ frame = self._prepare_request(cmd, *data)
231
+ self.timing.start()
232
+ with self.policy_lock:
233
+ self.policy.feed(types.FrameCategory.CMD, self.counter_send, self.timestamp.value, frame)
234
+ self.send(frame)
235
+ try:
236
+ xcpPDU = self.get()
237
+ except EmptyFrameError:
238
+ if not ignore_timeout:
239
+ MSG = f"Response timed out (timeout={self.timeout}s)"
240
+ with self.policy_lock:
241
+ self.policy.feed(types.FrameCategory.METADATA, self.counter_send, self.timestamp.value, bytes(MSG, "ascii"))
242
+ raise types.XcpTimeoutError(MSG) from None
243
+ else:
244
+ self.timing.stop()
245
+ return
246
+ self.timing.stop()
247
+ pid = types.Response.parse(xcpPDU).type
248
+ if pid == "ERR" and cmd.name != "SYNCH":
249
+ with self.policy_lock:
250
+ self.policy.feed(types.FrameCategory.ERROR, self.counter_received, self.timestamp.value, xcpPDU[1:])
251
+ err = types.XcpError.parse(xcpPDU[1:])
252
+ raise types.XcpResponseError(err)
253
+ return xcpPDU[1:]
254
+
255
+ def request(self, cmd, *data):
256
+ return self._request_internal(cmd, False, *data)
257
+
258
+ def request_optional_response(self, cmd, *data):
259
+ return self._request_internal(cmd, True, *data)
260
+
261
+ def block_request(self, cmd, *data):
262
+ """
263
+ Implements packet transmission for block communication model (e.g. DOWNLOAD block mode)
264
+ All parameters are the same as in request(), but it does not receive response.
265
+ """
266
+
267
+ # check response queue before each block request, so that if the slave device
268
+ # has responded with a negative response (e.g. ACCESS_DENIED or SEQUENCE_ERROR), we can
269
+ # process it.
270
+ if self.resQueue:
271
+ xcpPDU = self.resQueue.popleft()
272
+ pid = types.Response.parse(xcpPDU).type
273
+ if pid == "ERR" and cmd.name != "SYNCH":
274
+ err = types.XcpError.parse(xcpPDU[1:])
275
+ raise types.XcpResponseError(err)
276
+ with self.command_lock:
277
+ if isinstance(*data, list):
278
+ data = data[0] # C++ interfacing.
279
+ frame = self._prepare_request(cmd, *data)
280
+ with self.policy_lock:
281
+ self.policy.feed(
282
+ types.FrameCategory.CMD if int(cmd) >= 0xC0 else types.FrameCategory.STIM,
283
+ self.counter_send,
284
+ self.timestamp.value,
285
+ frame,
286
+ )
287
+ self.send(frame)
288
+
289
+ def _prepare_request(self, cmd, *data):
290
+ """
291
+ Prepares a request to be sent
292
+ """
293
+ if self._debug:
294
+ self.logger.debug(cmd.name)
295
+ self.parent._setService(cmd)
296
+
297
+ cmd_len = cmd.bit_length() // 8 # calculate bytes needed for cmd
298
+ packet = bytes(flatten(cmd.to_bytes(cmd_len, "big"), data))
299
+
300
+ header = self.HEADER.pack(len(packet), self.counter_send)
301
+ self.counter_send = (self.counter_send + 1) & 0xFFFF
302
+
303
+ frame = header + packet
304
+
305
+ remainder = len(frame) % self.alignment
306
+ if remainder:
307
+ frame += b"\0" * (self.alignment - remainder)
308
+
309
+ if self._debug:
310
+ self.logger.debug(f"-> {hexDump(frame)}")
311
+ return frame
312
+
313
+ def block_receive(self, length_required: int) -> bytes:
314
+ """
315
+ Implements packet reception for block communication model
316
+ (e.g. for XCP on CAN)
317
+
318
+ Parameters
319
+ ----------
320
+ length_required: int
321
+ number of bytes to be expected in block response packets
322
+
323
+ Returns
324
+ -------
325
+ bytes
326
+ all payload bytes received in block response packets
327
+
328
+ Raises
329
+ ------
330
+ :class:`pyxcp.types.XcpTimeoutError`
331
+ """
332
+ block_response = b""
333
+ start = self.timestamp.value
334
+ while len(block_response) < length_required:
335
+ if len(self.resQueue):
336
+ partial_response = self.resQueue.popleft()
337
+ block_response += partial_response[1:]
338
+ else:
339
+ if self.timestamp.value - start > self.timeout:
340
+ raise types.XcpTimeoutError("Response timed out [block_receive].") from None
341
+ short_sleep()
342
+ return block_response
343
+
344
+ @abc.abstractmethod
345
+ def send(self, frame):
346
+ pass
347
+
348
+ @abc.abstractmethod
349
+ def close_connection(self):
350
+ """Does the actual connection shutdown.
351
+ Needs to be implemented by any sub-class.
352
+ """
353
+ pass
354
+
355
+ @abc.abstractmethod
356
+ def listen(self):
357
+ pass
358
+
359
+ def process_event_packet(self, packet):
360
+ packet = packet[1:]
361
+ ev_type = packet[0]
362
+ self.logger.debug(f"EVENT-PACKET: {hexDump(packet)}")
363
+ if ev_type == types.Event.EV_CMD_PENDING:
364
+ self.timer_restart_event.set()
365
+
366
+ def process_response(self, response: bytes, length: int, counter: int, recv_timestamp: int) -> None:
367
+ if counter == self.counter_received:
368
+ self.logger.warning(f"Duplicate message counter {counter} received from the XCP slave")
369
+ if self._debug:
370
+ self.logger.debug(f"<- L{length} C{counter} {hexDump(response[:512])}")
371
+ return
372
+ self.counter_received = counter
373
+ pid = response[0]
374
+ if pid >= 0xFC:
375
+ if self._debug:
376
+ self.logger.debug(f"<- L{length} C{counter} {hexDump(response)}")
377
+ if pid >= 0xFE:
378
+ self.resQueue.append(response)
379
+ with self.policy_lock:
380
+ self.policy.feed(types.FrameCategory.RESPONSE, self.counter_received, self.timestamp.value, response)
381
+ self.recv_timestamp = recv_timestamp
382
+ elif pid == 0xFD:
383
+ self.process_event_packet(response)
384
+ with self.policy_lock:
385
+ self.policy.feed(types.FrameCategory.EVENT, self.counter_received, self.timestamp.value, response)
386
+ elif pid == 0xFC:
387
+ with self.policy_lock:
388
+ self.policy.feed(types.FrameCategory.SERV, self.counter_received, self.timestamp.value, response)
389
+ else:
390
+ if self._debug:
391
+ self.logger.debug(f"<- L{length} C{counter} ODT_Data[0:8] {hexDump(response[:8])}")
392
+ if self.first_daq_timestamp is None:
393
+ self.first_daq_timestamp = recv_timestamp
394
+ if self.create_daq_timestamps:
395
+ timestamp = recv_timestamp
396
+ else:
397
+ timestamp = 0
398
+ with self.policy_lock:
399
+ self.policy.feed(types.FrameCategory.DAQ, self.counter_received, timestamp, response)
400
+
401
+ # @abc.abstractproperty
402
+ # @property
403
+ # def transport_layer_interface(self) -> Any:
404
+ # pass
405
+
406
+ # @transport_layer_interface.setter
407
+ # def transport_layer_interface(self, value: Any) -> None:
408
+ # self._transport_layer_interface = value
409
+
410
+
411
+ def create_transport(name: str, *args, **kws) -> BaseTransport:
412
+ """Factory function for transports.
413
+
414
+ Returns
415
+ -------
416
+ :class:`BaseTransport` derived instance.
417
+ """
418
+ name = name.lower()
419
+ transports = available_transports()
420
+ if name in transports:
421
+ transport_class: Type[BaseTransport] = transports[name]
422
+ else:
423
+ raise ValueError(f"{name!r} is an invalid transport -- please choose one of [{' | '.join(transports.keys())}].")
424
+ return transport_class(*args, **kws)
425
+
426
+
427
+ def available_transports() -> Dict[str, Type[BaseTransport]]:
428
+ """List all subclasses of :class:`BaseTransport`.
429
+
430
+ Returns
431
+ -------
432
+ dict
433
+ name: class
434
+ """
435
+ transports = BaseTransport.__subclasses__()
436
+ return {t.__name__.lower(): t for t in transports}
File without changes