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.
- 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/cpp_ext.cp313-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.cp313-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.cp313-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 +134 -0
- pyxcp-0.23.0.dist-info/WHEEL +4 -0
- 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)
|
pyxcp/master/__init__.py
ADDED
|
@@ -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
|