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
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env python
2
+ """Very basic hello-world example.
3
+ """
4
+ from pprint import pprint
5
+
6
+ from pyxcp.cmdline import ArgumentParser
7
+ from pyxcp.recorder import XcpLogFileReader
8
+ from pyxcp.transport import FrameRecorderPolicy
9
+ from pyxcp.utils import decode_bytes
10
+
11
+
12
+ daq_info = False
13
+
14
+
15
+ def callout(master, args):
16
+ global daq_info
17
+ if args.daq_info:
18
+ daq_info = True
19
+
20
+
21
+ ap = ArgumentParser(description="pyXCP hello world.", callout=callout)
22
+ ap.parser.add_argument(
23
+ "-d",
24
+ "--daq-info",
25
+ dest="daq_info",
26
+ help="Display DAQ-info",
27
+ default=False,
28
+ action="store_true",
29
+ )
30
+
31
+ RECORDER_FILE_NAME = "xcphello"
32
+
33
+ recorder_policy = FrameRecorderPolicy(RECORDER_FILE_NAME) # Create frame recorder.
34
+
35
+ with ap.run(recorder_policy) as x: # parameter policy is new.
36
+ x.connect()
37
+ if x.slaveProperties.optionalCommMode:
38
+ x.getCommModeInfo()
39
+ identifier = x.identifier(0x01)
40
+ print("\nSlave Properties:")
41
+ print("=================")
42
+ print(f"ID: {identifier!r}")
43
+ pprint(x.slaveProperties)
44
+ cps = x.getCurrentProtectionStatus()
45
+ print("\nProtection Status")
46
+ print("=================")
47
+ for k, v in cps.items():
48
+ print(f" {k:6s}: {v}")
49
+ if daq_info:
50
+ dqp = x.getDaqProcessorInfo()
51
+ print("\nDAQ Processor Info:")
52
+ print("===================")
53
+ print(dqp)
54
+ print("\nDAQ Events:")
55
+ print("===========")
56
+ for idx in range(dqp.maxEventChannel):
57
+ evt = x.getDaqEventInfo(idx)
58
+ length = evt.eventChannelNameLength
59
+ name = decode_bytes(x.pull(length))
60
+ dq = "DAQ" if evt.daqEventProperties.daq else ""
61
+ st = "STIM" if evt.daqEventProperties.stim else ""
62
+ dq_st = dq + " " + st
63
+ print(f" [{idx:04}] {name:r}")
64
+ print(f" dir: {dq_st}")
65
+ print(f" packed: {evt.daqEventProperties.packed}")
66
+ PFX_CONS = "CONSISTENCY_"
67
+ print(f" consistency: {evt.daqEventProperties.consistency.strip(PFX_CONS)}")
68
+ print(f" max. DAQ lists: {evt.maxDaqList}")
69
+ PFX_TU = "EVENT_CHANNEL_TIME_UNIT_"
70
+ print(f" unit: {evt.eventChannelTimeUnit.strip(PFX_TU)}")
71
+ print(f" cycle: {evt.eventChannelTimeCycle or 'SPORADIC'}")
72
+ print(f" priority {evt.eventChannelPriority}")
73
+
74
+ dqr = x.getDaqResolutionInfo()
75
+ print("\nDAQ Resolution Info:")
76
+ print("====================")
77
+ print(dqr)
78
+ for idx in range(dqp.maxDaq):
79
+ print(f"\nDAQ List Info #{idx}")
80
+ print("=================")
81
+ print(f"{x.getDaqListInfo(idx)}")
82
+ x.disconnect()
83
+ print("After recording...")
84
+ ##
85
+ ## Now read and dump recorded frames.
86
+ ##
87
+ reader = XcpLogFileReader(RECORDER_FILE_NAME)
88
+
89
+ hdr = reader.get_header() # Get file information.
90
+ print("\n\n")
91
+ print("=" * 82)
92
+ pprint(hdr)
93
+ print("=" * 82)
94
+
95
+ print("\nIterating over frames")
96
+ print("=" * 82)
97
+ for _frame in reader:
98
+ print(_frame)
99
+ print("---")
100
+
101
+ reader.reset_iter() # Non-standard method to restart iteration.
102
+
103
+ df = reader.as_dataframe() # Return recordings as Pandas DataFrame.
104
+ print("\nRecording as Pandas DataFrame")
105
+ print("=" * 82)
106
+ print(df.head(24))
107
+ print("=" * 82)
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env python
2
+ """Lowlevel API reflecting available XCP services
3
+
4
+ .. note:: For technical reasons the API is split into two parts;
5
+ common methods and a Python version specific part.
6
+
7
+ .. [1] XCP Specification, Part 2 - Protocol Layer Specification
8
+ """
9
+ from .master import Master # noqa: F401
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env python
2
+ """Implements error-handling according to XCP spec.
3
+ """
4
+ import functools
5
+ import logging
6
+ import threading
7
+ import time
8
+ import types
9
+ from collections import namedtuple
10
+ from typing import Generic, List, Optional, TypeVar
11
+
12
+ import can
13
+
14
+ from pyxcp.errormatrix import ERROR_MATRIX, Action, PreAction
15
+ from pyxcp.types import COMMAND_CATEGORIES, XcpError, XcpResponseError, XcpTimeoutError
16
+
17
+
18
+ handle_errors = True # enable/disable XCP error-handling.
19
+
20
+
21
+ class SingletonBase:
22
+ _lock = threading.Lock()
23
+
24
+ def __new__(cls, *args, **kws):
25
+ # Double-Checked Locking
26
+ if not hasattr(cls, "_instance"):
27
+ try:
28
+ cls._lock.acquire()
29
+ if not hasattr(cls, "_instance"):
30
+ cls._instance = super().__new__(cls)
31
+ finally:
32
+ cls._lock.release()
33
+ return cls._instance
34
+
35
+
36
+ Function = namedtuple("Function", "fun arguments") # store: var | load: var
37
+
38
+
39
+ class InternalError(Exception):
40
+ """Indicates an internal error, like invalid service."""
41
+
42
+
43
+ class SystemExit(Exception):
44
+ """"""
45
+
46
+ def __init__(self, msg: str, error_code: int = None, *args, **kws):
47
+ super().__init__(*args, **kws)
48
+ self.error_code = error_code
49
+ self.msg = msg
50
+
51
+ def __str__(self):
52
+ return f"SystemExit(error_code={self.error_code}, message={self.msg!r})"
53
+
54
+ __repr__ = __str__
55
+
56
+
57
+ class UnrecoverableError(Exception):
58
+ """"""
59
+
60
+
61
+ def func_name(func):
62
+ return func.__qualname__ if func is not None else None
63
+
64
+
65
+ def getErrorHandler(service):
66
+ """"""
67
+ return ERROR_MATRIX.get(service)
68
+
69
+
70
+ def getTimeoutHandler(service):
71
+ """"""
72
+ handler = getErrorHandler(service)
73
+ if handler is None:
74
+ raise InternalError("Invalid Service")
75
+ return handler.get(XcpError.ERR_TIMEOUT)
76
+
77
+
78
+ def getActions(service, error_code):
79
+ """"""
80
+ error_str = str(error_code)
81
+ if error_code == XcpError.ERR_TIMEOUT:
82
+ preActions, actions = getTimeoutHandler(service)
83
+ else:
84
+ eh = getErrorHandler(service)
85
+ if eh is None:
86
+ raise InternalError(f"Invalid Service 0x{service:02x}")
87
+ # print(f"Try to handle error -- Service: {service.name} Error-Code: {error_code}")
88
+ handler = eh.get(error_str)
89
+ if handler is None:
90
+ raise SystemExit(f"Service {service.name!r} has no handler for {error_code}.", error_code=error_code)
91
+ preActions, actions = handler
92
+ return preActions, actions
93
+
94
+
95
+ def actionIter(actions):
96
+ """Iterate over action from :file:`errormatrix.py`"""
97
+ if isinstance(actions, (tuple, list)):
98
+ yield from actions
99
+ else:
100
+ yield actions
101
+
102
+
103
+ class Arguments:
104
+ """Container for positional and keyword arguments.
105
+
106
+ Parameters
107
+ ----------
108
+ args: tuple
109
+ Positional arguments
110
+ kwargs: dict
111
+ Keyword arguments.
112
+ """
113
+
114
+ def __init__(self, args=None, kwargs=None):
115
+ if args is None:
116
+ self.args = ()
117
+ else:
118
+ if not hasattr(args, "__iter__"):
119
+ self.args = (args,)
120
+ else:
121
+ self.args = tuple(args)
122
+ self.kwargs = kwargs or {}
123
+
124
+ def __str__(self) -> str:
125
+ res = f"{self.__class__.__name__}(ARGS = {self.args}, KWS = {self.kwargs})"
126
+ return res
127
+
128
+ def __eq__(self, other) -> bool:
129
+ return (self.args == other.args if other is not None else False) and (
130
+ self.kwargs == other.kwargs if other is not None else False
131
+ )
132
+
133
+ __repr__ = __str__
134
+
135
+
136
+ class Repeater:
137
+ """A required action of some XCP errorhandler is repetition.
138
+
139
+ Parameters
140
+ ----------
141
+ initial_value: int
142
+ The actual values are predetermined by XCP:
143
+ - REPEAT (one time)
144
+ - REPEAT_2_TIMES (two times)
145
+ - REPEAT_INF_TIMES ("forever")
146
+ """
147
+
148
+ REPEAT = 1
149
+ REPEAT_2_TIMES = 2
150
+ INFINITE = -1
151
+
152
+ def __init__(self, initial_value: int):
153
+ self._counter = initial_value
154
+ # print("\tREPEATER ctor", hex(id(self)))
155
+
156
+ def repeat(self):
157
+ """Check if repetition is required.
158
+
159
+ Returns
160
+ -------
161
+ bool
162
+ """
163
+ # print("\t\tCOUNTER:", hex(id(self)), self._counter)
164
+ if self._counter == Repeater.INFINITE:
165
+ return True
166
+ elif self._counter > 0:
167
+ self._counter -= 1
168
+ return True
169
+ else:
170
+ return False
171
+
172
+
173
+ def display_error():
174
+ """Display error information.
175
+
176
+ TODO: callback.
177
+
178
+ """
179
+
180
+
181
+ class Handler:
182
+ """"""
183
+
184
+ def __init__(self, instance, func, arguments, error_code=None):
185
+ self.instance = instance
186
+ if hasattr(func, "__closure__") and func.__closure__:
187
+ self.func = func.__closure__[0].cell_contents # Use original, undecorated function to prevent
188
+ # nasty recursion problems.
189
+ else:
190
+ self.func = func
191
+ self.arguments = arguments
192
+ self.service = self.instance.service
193
+ self._error_code: int = 0
194
+ if error_code is not None:
195
+ self._error_code = error_code
196
+ self._repeater = None
197
+ self.logger = logging.getLogger("PyXCP")
198
+
199
+ def __str__(self):
200
+ return f"Handler(func = {func_name(self.func)} -- {self.arguments} service = {self.service} error_code = {self.error_code})"
201
+
202
+ def __eq__(self, other):
203
+ if other is None:
204
+ return False
205
+ return (self.instance == other.instance) and (self.func == other.func) and (self.arguments == other.arguments)
206
+
207
+ @property
208
+ def error_code(self) -> int:
209
+ return self._error_code
210
+
211
+ @error_code.setter
212
+ def error_code(self, value: int) -> None:
213
+ self._error_code = value
214
+
215
+ @property
216
+ def repeater(self):
217
+ # print("\tGet repeater", hex(id(self._repeater)), self._repeater is None)
218
+ return self._repeater
219
+
220
+ @repeater.setter
221
+ def repeater(self, value):
222
+ # print("\tSet repeater", hex(id(value)))
223
+ self._repeater = value
224
+
225
+ def execute(self):
226
+ self.logger.debug(f"Execute({func_name(self.func)} -- {self.arguments})")
227
+ if isinstance(self.func, types.MethodType):
228
+ return self.func(*self.arguments.args, **self.arguments.kwargs)
229
+ else:
230
+ return self.func(self.instance, *self.arguments.args, **self.arguments.kwargs)
231
+
232
+ def actions(self, preActions, actions):
233
+ """Preprocess errorhandling pre-actions and actions."""
234
+ result_pre_actions = []
235
+ result_actions = []
236
+ repetitionCount = 0
237
+ for item in actionIter(preActions):
238
+ if item == PreAction.NONE:
239
+ pass
240
+ elif item == PreAction.WAIT_T7:
241
+ time.sleep(0.02) # Completely arbitrary for now.
242
+ elif item == PreAction.SYNCH:
243
+ fn = Function(self.instance.synch, Arguments())
244
+ result_pre_actions.append(fn)
245
+ elif item == PreAction.GET_SEED_UNLOCK:
246
+ raise NotImplementedError("Pre-action GET_SEED_UNLOCK")
247
+ elif item == PreAction.SET_MTA:
248
+ fn = Function(self.instance.setMta, Arguments(self.instance.mta))
249
+ result_pre_actions.append(fn)
250
+ elif item == PreAction.SET_DAQ_PTR:
251
+ fn = Function(self.instance.setDaqPtr, Arguments(self.instance.currentDaqPtr))
252
+ elif item == PreAction.START_STOP_X:
253
+ raise NotImplementedError("Pre-action START_STOP_X")
254
+ elif item == PreAction.REINIT_DAQ:
255
+ raise NotImplementedError("Pre-action REINIT_DAQ")
256
+ elif item == PreAction.DISPLAY_ERROR:
257
+ pass
258
+ elif item == PreAction.DOWNLOAD:
259
+ raise NotImplementedError("Pre-action DOWNLOAD")
260
+ elif item == PreAction.PROGRAM:
261
+ raise NotImplementedError("Pre-action PROGRAM")
262
+ elif item == PreAction.UPLOAD:
263
+ raise NotImplementedError("Pre-action UPLOAD")
264
+ elif item == PreAction.UNLOCK_SLAVE:
265
+ resource = COMMAND_CATEGORIES.get(self.instance.service) # noqa: F841
266
+ raise NotImplementedError("Pre-action UNLOCK_SLAVE")
267
+ for item in actionIter(actions):
268
+ if item == Action.NONE:
269
+ pass
270
+ elif item == Action.DISPLAY_ERROR:
271
+ raise SystemExit("Could not proceed due to unhandled error (DISPLAY_ERROR).", self.error_code)
272
+ elif item == Action.RETRY_SYNTAX:
273
+ raise SystemExit("Could not proceed due to unhandled error (RETRY_SYNTAX).", self.error_code)
274
+ elif item == Action.RETRY_PARAM:
275
+ raise SystemExit("Could not proceed due to unhandled error (RETRY_PARAM).", self.error_code)
276
+ elif item == Action.USE_A2L:
277
+ raise SystemExit("Could not proceed due to unhandled error (USE_A2L).", self.error_code)
278
+ elif item == Action.USE_ALTERATIVE:
279
+ raise SystemExit(
280
+ "Could not proceed due to unhandled error (USE_ALTERATIVE).", self.error_code
281
+ ) # TODO: check alternatives.
282
+ elif item == Action.REPEAT:
283
+ repetitionCount = Repeater.REPEAT
284
+ elif item == Action.REPEAT_2_TIMES:
285
+ repetitionCount = Repeater.REPEAT_2_TIMES
286
+ elif item == Action.REPEAT_INF_TIMES:
287
+ repetitionCount = Repeater.INFINITE
288
+ elif item == Action.RESTART_SESSION:
289
+ raise SystemExit("Could not proceed due to unhandled error (RESTART_SESSION).", self.error_code)
290
+ elif item == Action.TERMINATE_SESSION:
291
+ raise SystemExit("Could not proceed due to unhandled error (TERMINATE_SESSION).", self.error_code)
292
+ elif item == Action.SKIP:
293
+ pass
294
+ elif item == Action.NEW_FLASH_WARE:
295
+ raise SystemExit("Could not proceed due to unhandled error (NEW_FLASH_WARE)", self.error_code)
296
+ return result_pre_actions, result_actions, Repeater(repetitionCount)
297
+
298
+
299
+ T = TypeVar("T")
300
+
301
+
302
+ class HandlerStack(Generic[T]):
303
+ """"""
304
+
305
+ def __init__(self) -> None:
306
+ self._stack: List[T] = []
307
+
308
+ def push(self, value: T):
309
+ if value != self.tos():
310
+ self._stack.append(value)
311
+
312
+ def pop(self) -> None:
313
+ if len(self) > 0:
314
+ self._stack.pop()
315
+
316
+ def tos(self) -> Optional[T]:
317
+ if len(self) > 0:
318
+ return self._stack[-1]
319
+ else:
320
+ return None
321
+ # raise ValueError("empty stack.")
322
+
323
+ def empty(self) -> bool:
324
+ return self._stack == []
325
+
326
+ def __len__(self) -> int:
327
+ return len(self._stack)
328
+
329
+ def __repr__(self) -> str:
330
+ result = []
331
+ for idx in range(len(self)):
332
+ result.append(str(self[idx]))
333
+ return "\n".join(result)
334
+
335
+ def __getitem__(self, ndx: int) -> T:
336
+ return self._stack[ndx]
337
+
338
+ __str__ = __repr__
339
+
340
+
341
+ class Executor(SingletonBase):
342
+ """"""
343
+
344
+ def __init__(self):
345
+ self.handlerStack = HandlerStack()
346
+ self.repeater = None
347
+ self.logger = logging.getLogger("PyXCP")
348
+ self.previous_error_code = None
349
+ self.error_code = None
350
+ self.func = None
351
+ self.arguments = None
352
+
353
+ def __call__(self, inst, func, arguments):
354
+ self.inst = inst
355
+ self.func = func
356
+ self.arguments = arguments
357
+ handler = Handler(inst, func, arguments)
358
+ self.handlerStack.push(handler)
359
+ connect_retries = inst.config.connect_retries
360
+ try:
361
+ while True:
362
+ try:
363
+ handler = self.handlerStack.tos()
364
+ res = handler.execute()
365
+ except XcpResponseError as e:
366
+ # self.logger.critical(f"XcpResponseError [{e.get_error_code()}]")
367
+ self.error_code = e.get_error_code()
368
+ handler.error_code = self.error_code
369
+ except XcpTimeoutError:
370
+ is_connect = func.__name__ == "connect"
371
+ self.logger.warning(f"XcpTimeoutError -- Service: {func.__name__!r}")
372
+ self.error_code = XcpError.ERR_TIMEOUT
373
+ handler.error_code = self.error_code
374
+ if is_connect and connect_retries is not None:
375
+ if connect_retries == 0:
376
+ raise XcpTimeoutError("Maximum CONNECT retries reached.")
377
+ connect_retries -= 1
378
+ except TimeoutError:
379
+ raise
380
+ except can.CanError:
381
+ # self.logger.critical(f"Exception raised by Python CAN [{str(e)}]")
382
+ raise
383
+ except Exception:
384
+ # self.logger.critical(f"Exception [{str(e)}]")
385
+ raise
386
+ else:
387
+ self.error_code = None
388
+ self.handlerStack.pop()
389
+ if self.handlerStack.empty():
390
+ return res
391
+
392
+ if self.error_code == XcpError.ERR_CMD_SYNCH:
393
+ # Don't care about SYNCH for now...
394
+ self.inst.logger.info("SYNCH received.")
395
+ continue
396
+
397
+ if self.error_code is not None:
398
+ preActions, actions, repeater = handler.actions(*getActions(inst.service, self.error_code))
399
+ if handler.repeater is None:
400
+ handler.repeater = repeater
401
+ for f, a in reversed(preActions):
402
+ self.handlerStack.push(Handler(inst, f, a, self.error_code))
403
+ self.previous_error_code = self.error_code
404
+ if handler.repeater:
405
+ if handler.repeater.repeat():
406
+ continue
407
+ else:
408
+ raise UnrecoverableError(
409
+ f"Max. repetition count reached while trying to execute service {handler.func.__name__!r}."
410
+ )
411
+ finally:
412
+ # cleanup of class variables
413
+ self.previous_error_code = None
414
+ while not self.handlerStack.empty():
415
+ self.handlerStack.pop()
416
+ self.error_code = None
417
+ self.func = None
418
+ self.arguments = None
419
+
420
+
421
+ def disable_error_handling(value: bool):
422
+ """Disable XCP error-handling (mainly for performance reasons)."""
423
+
424
+ global handle_errors
425
+ handle_errors = not bool(value)
426
+
427
+
428
+ def wrapped(func):
429
+ """This decorator is XCP error-handling enabled."""
430
+
431
+ @functools.wraps(func)
432
+ def inner(*args, **kwargs):
433
+ if handle_errors:
434
+ inst = args[0] # First parameter is 'self'.
435
+ arguments = Arguments(args[1:], kwargs)
436
+ executor = Executor()
437
+ res = executor(inst, func, arguments)
438
+ else:
439
+ res = func(*args, **kwargs)
440
+ return res
441
+
442
+ return inner