pyxcp 0.23.4__cp313-cp313-win_arm64.whl → 0.23.7__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 +1 -1
- pyxcp/cmdline.py +1 -1
- pyxcp/config/__init__.py +74 -20
- 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/daq_stim/__init__.py +61 -1
- 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/examples/run_daq.py +1 -1
- pyxcp/master/errorhandler.py +116 -11
- pyxcp/master/master.py +18 -1
- pyxcp/recorder/__init__.py +3 -6
- 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/unfolder.hpp +67 -55
- pyxcp/recorder/wrap.cpp +0 -6
- pyxcp/transport/base.py +144 -6
- pyxcp/transport/can.py +59 -6
- pyxcp/transport/eth.py +23 -5
- pyxcp/utils.py +2 -2
- pyxcp-0.23.7.dist-info/METADATA +339 -0
- {pyxcp-0.23.4.dist-info → pyxcp-0.23.7.dist-info}/RECORD +31 -31
- pyxcp-0.23.4.dist-info/METADATA +0 -219
- {pyxcp-0.23.4.dist-info → pyxcp-0.23.7.dist-info}/LICENSE +0 -0
- {pyxcp-0.23.4.dist-info → pyxcp-0.23.7.dist-info}/WHEEL +0 -0
- {pyxcp-0.23.4.dist-info → pyxcp-0.23.7.dist-info}/entry_points.txt +0 -0
pyxcp/__init__.py
CHANGED
pyxcp/cmdline.py
CHANGED
|
@@ -55,7 +55,7 @@ class ArgumentParser:
|
|
|
55
55
|
self._description = description
|
|
56
56
|
|
|
57
57
|
def run(self, policy=None, transport_layer_interface=None):
|
|
58
|
-
"""Create and configure a master instance.
|
|
58
|
+
"""Create and configure a synchronous master instance.
|
|
59
59
|
|
|
60
60
|
Args:
|
|
61
61
|
policy: Optional policy to use for the master
|
pyxcp/config/__init__.py
CHANGED
|
@@ -913,6 +913,10 @@ class General(Configurable):
|
|
|
913
913
|
connect_retries = Integer(help="Number of CONNECT retries (None for infinite retries).", allow_none=True, default_value=3).tag(
|
|
914
914
|
config=True
|
|
915
915
|
)
|
|
916
|
+
# Structured diagnostics dump options
|
|
917
|
+
diagnostics_on_failure = Bool(True, help="Append a structured diagnostics dump to timeout errors.").tag(config=True)
|
|
918
|
+
diagnostics_last_pdus = Integer(20, help="How many recent PDUs to include in diagnostics dump.").tag(config=True)
|
|
919
|
+
|
|
916
920
|
seed_n_key_dll = Unicode("", allow_none=False, help="Dynamic library used for slave resource unlocking.").tag(config=True)
|
|
917
921
|
seed_n_key_dll_same_bit_width = Bool(False, help="").tag(config=True)
|
|
918
922
|
custom_dll_loader = Unicode(allow_none=True, default_value=None, help="Use an custom seed and key DLL loader.").tag(config=True)
|
|
@@ -1010,6 +1014,11 @@ class PyXCP(Application):
|
|
|
1010
1014
|
config=True
|
|
1011
1015
|
)
|
|
1012
1016
|
|
|
1017
|
+
# Logging options
|
|
1018
|
+
structured_logging = Bool(False, help="Emit one-line JSON logs instead of rich text.").tag(config=True)
|
|
1019
|
+
# Use log_output_format to avoid clashing with traitlets.Application.log_format (a %-style template)
|
|
1020
|
+
log_output_format = Enum(values=["rich", "json"], default_value="rich", help="Select logging output format.").tag(config=True)
|
|
1021
|
+
|
|
1013
1022
|
classes = List([General, Transport, CustomArgs])
|
|
1014
1023
|
|
|
1015
1024
|
subcommands = dict(
|
|
@@ -1026,34 +1035,79 @@ class PyXCP(Application):
|
|
|
1026
1035
|
self.subapp.start()
|
|
1027
1036
|
exit(2)
|
|
1028
1037
|
else:
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
self.
|
|
1038
|
+
# Always read configuration and then set up our logger explicitly to avoid
|
|
1039
|
+
# traitlets.Application default logging using an incompatible 'log_format'.
|
|
1040
|
+
self._read_configuration(self.config_file)
|
|
1041
|
+
try:
|
|
1042
|
+
# Ensure base Application.log_format is a valid %-style template
|
|
1043
|
+
# (Users might set c.PyXCP.log_format = "json" which clashes with traitlets behavior.)
|
|
1044
|
+
self.log_format = "%(message)s" # type: ignore[assignment]
|
|
1045
|
+
except Exception:
|
|
1046
|
+
pass
|
|
1047
|
+
self._setup_logger()
|
|
1036
1048
|
self.log.debug(f"pyxcp version: {self.version}")
|
|
1037
1049
|
|
|
1038
1050
|
def _setup_logger(self):
|
|
1039
1051
|
from pyxcp.types import Command
|
|
1040
1052
|
|
|
1041
1053
|
# Remove any handlers installed by `traitlets`.
|
|
1042
|
-
for hdl in self.log.handlers:
|
|
1054
|
+
for hdl in list(self.log.handlers):
|
|
1043
1055
|
self.log.removeHandler(hdl)
|
|
1044
1056
|
|
|
1045
|
-
# formatter
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
+
# Decide formatter/handler based on config
|
|
1058
|
+
use_json = False
|
|
1059
|
+
try:
|
|
1060
|
+
# Prefer explicit log_output_format; fallback to structured_logging for compatibility
|
|
1061
|
+
use_json = getattr(self, "log_output_format", "rich") == "json" or getattr(self, "structured_logging", False)
|
|
1062
|
+
# Backward-compat: if someone set PyXCP.log_format="json" in config, honor it here too
|
|
1063
|
+
if not use_json:
|
|
1064
|
+
lf = getattr(self, "log_format", None)
|
|
1065
|
+
if isinstance(lf, str) and lf.lower() == "json":
|
|
1066
|
+
use_json = True
|
|
1067
|
+
except Exception:
|
|
1068
|
+
use_json = False
|
|
1069
|
+
|
|
1070
|
+
if use_json:
|
|
1071
|
+
|
|
1072
|
+
class JSONFormatter(logging.Formatter):
|
|
1073
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
1074
|
+
# Build a minimal structured payload
|
|
1075
|
+
payload = {
|
|
1076
|
+
"time": self.formatTime(record, self.datefmt),
|
|
1077
|
+
"level": record.levelname,
|
|
1078
|
+
"logger": record.name,
|
|
1079
|
+
"message": record.getMessage(),
|
|
1080
|
+
}
|
|
1081
|
+
# Include extras if present
|
|
1082
|
+
for key in ("transport", "host", "port", "protocol", "event", "command"):
|
|
1083
|
+
if hasattr(record, key):
|
|
1084
|
+
payload[key] = getattr(record, key)
|
|
1085
|
+
# Exceptions
|
|
1086
|
+
if record.exc_info:
|
|
1087
|
+
payload["exc_type"] = record.exc_info[0].__name__ if record.exc_info[0] else None
|
|
1088
|
+
payload["exc_text"] = self.formatException(record.exc_info)
|
|
1089
|
+
try:
|
|
1090
|
+
import json as _json
|
|
1091
|
+
|
|
1092
|
+
return _json.dumps(payload, ensure_ascii=False)
|
|
1093
|
+
except Exception:
|
|
1094
|
+
return f"{payload}"
|
|
1095
|
+
|
|
1096
|
+
handler = logging.StreamHandler()
|
|
1097
|
+
formatter = JSONFormatter(datefmt=self.log_datefmt)
|
|
1098
|
+
handler.setFormatter(formatter)
|
|
1099
|
+
handler.setLevel(self.log_level)
|
|
1100
|
+
self.log.addHandler(handler)
|
|
1101
|
+
else:
|
|
1102
|
+
keywords = list(Command.__members__.keys()) + ["ARGS", "KWS"] # Syntax highlight XCP commands and other stuff.
|
|
1103
|
+
rich_handler = RichHandler(
|
|
1104
|
+
rich_tracebacks=True,
|
|
1105
|
+
tracebacks_show_locals=True,
|
|
1106
|
+
log_time_format=self.log_datefmt,
|
|
1107
|
+
level=self.log_level,
|
|
1108
|
+
keywords=keywords,
|
|
1109
|
+
)
|
|
1110
|
+
self.log.addHandler(rich_handler)
|
|
1057
1111
|
|
|
1058
1112
|
def initialize(self, argv=None):
|
|
1059
1113
|
from pyxcp import __version__ as pyxcp_version
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
pyxcp/daq_stim/__init__.py
CHANGED
|
@@ -33,6 +33,8 @@ class DaqProcessor:
|
|
|
33
33
|
def __init__(self, daq_lists: List[DaqList]):
|
|
34
34
|
self.daq_lists = daq_lists
|
|
35
35
|
self.log = get_application().log
|
|
36
|
+
# Flag indicating a fatal OS-level error occurred during DAQ (e.g., disk full, out-of-memory)
|
|
37
|
+
self._fatal_os_error: bool = False
|
|
36
38
|
|
|
37
39
|
def setup(self, start_datetime: Optional[CurrentDatetime] = None, write_multiple: bool = True):
|
|
38
40
|
if not self.xcp_master.slaveProperties.supportsDaq:
|
|
@@ -163,6 +165,31 @@ class DaqProcessor:
|
|
|
163
165
|
self.xcp_master.startStopSynch(0x01)
|
|
164
166
|
|
|
165
167
|
def stop(self):
|
|
168
|
+
# If a fatal OS error occurred during acquisition, skip sending stop to the slave to avoid
|
|
169
|
+
# cascading timeouts/unrecoverable errors and shut down transport gracefully instead.
|
|
170
|
+
if getattr(self, "_fatal_os_error", False):
|
|
171
|
+
try:
|
|
172
|
+
self.log.error(
|
|
173
|
+
"DAQ stop skipped due to previous fatal OS error (e.g., disk full or out-of-memory). Closing transport."
|
|
174
|
+
)
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
try:
|
|
178
|
+
# Best-effort: stop listener and close transport so threads finish cleanly.
|
|
179
|
+
if hasattr(self.xcp_master, "transport") and self.xcp_master.transport is not None:
|
|
180
|
+
# Signal listeners to stop
|
|
181
|
+
try:
|
|
182
|
+
if hasattr(self.xcp_master.transport, "closeEvent"):
|
|
183
|
+
self.xcp_master.transport.closeEvent.set()
|
|
184
|
+
except Exception:
|
|
185
|
+
pass
|
|
186
|
+
# Close transport connection
|
|
187
|
+
try:
|
|
188
|
+
self.xcp_master.transport.close()
|
|
189
|
+
except Exception:
|
|
190
|
+
pass
|
|
191
|
+
finally:
|
|
192
|
+
return
|
|
166
193
|
self.xcp_master.startStopSynch(0x00)
|
|
167
194
|
|
|
168
195
|
def first_pids(self):
|
|
@@ -218,7 +245,40 @@ class DaqToCsv(DaqOnlinePolicy):
|
|
|
218
245
|
out_file.write(f"{hdr}\n")
|
|
219
246
|
|
|
220
247
|
def on_daq_list(self, daq_list: int, timestamp0: int, timestamp1: int, payload: list):
|
|
221
|
-
|
|
248
|
+
# Guard against hard OS errors (e.g., disk full) during file writes.
|
|
249
|
+
if getattr(self, "_fatal_os_error", False):
|
|
250
|
+
return
|
|
251
|
+
try:
|
|
252
|
+
self.files[daq_list].write(f"{timestamp0},{timestamp1},{', '.join([str(x) for x in payload])}\n")
|
|
253
|
+
except (OSError, MemoryError) as ex:
|
|
254
|
+
# Mark fatal condition to alter shutdown path and avoid further writes/commands.
|
|
255
|
+
self._fatal_os_error = True
|
|
256
|
+
try:
|
|
257
|
+
self.log.critical(f"DAQ file write failed: {ex.__class__.__name__}: {ex}. Initiating graceful shutdown.")
|
|
258
|
+
except Exception:
|
|
259
|
+
pass
|
|
260
|
+
# Stop listener to prevent more DAQ traffic and avoid thread crashes.
|
|
261
|
+
try:
|
|
262
|
+
if hasattr(self.xcp_master, "transport") and self.xcp_master.transport is not None:
|
|
263
|
+
if hasattr(self.xcp_master.transport, "closeEvent"):
|
|
264
|
+
self.xcp_master.transport.closeEvent.set()
|
|
265
|
+
except Exception:
|
|
266
|
+
pass
|
|
267
|
+
# Best-effort: close any opened files to flush buffers and release resources.
|
|
268
|
+
try:
|
|
269
|
+
for f in getattr(self, "files", {}).values():
|
|
270
|
+
try:
|
|
271
|
+
f.flush()
|
|
272
|
+
except Exception:
|
|
273
|
+
pass
|
|
274
|
+
try:
|
|
275
|
+
f.close()
|
|
276
|
+
except Exception:
|
|
277
|
+
pass
|
|
278
|
+
except Exception:
|
|
279
|
+
pass
|
|
280
|
+
# Do not re-raise; allow the system to continue to a controlled shutdown.
|
|
281
|
+
return
|
|
222
282
|
|
|
223
283
|
def finalize(self):
|
|
224
284
|
self.log.debug("DaqCsv::finalize()")
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
pyxcp/examples/run_daq.py
CHANGED
|
@@ -149,7 +149,7 @@ with ap.run(policy=daq_parser) as x:
|
|
|
149
149
|
print("start DAQ lists.")
|
|
150
150
|
daq_parser.start() # Start DAQ lists.
|
|
151
151
|
|
|
152
|
-
time.sleep(
|
|
152
|
+
time.sleep(2.0 * 60.0 * 60.0) # Run for 15 minutes.
|
|
153
153
|
|
|
154
154
|
print("Stop DAQ....")
|
|
155
155
|
daq_parser.stop() # Stop DAQ lists.
|
pyxcp/master/errorhandler.py
CHANGED
|
@@ -17,6 +17,26 @@ from pyxcp.types import COMMAND_CATEGORIES, XcpError, XcpResponseError, XcpTimeo
|
|
|
17
17
|
|
|
18
18
|
handle_errors = True # enable/disable XCP error-handling.
|
|
19
19
|
|
|
20
|
+
# Thread-local flag to suppress logging for expected XCP negative responses
|
|
21
|
+
import threading
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_thread_flags = threading.local()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def set_suppress_xcp_error_log(value: bool) -> None:
|
|
28
|
+
try:
|
|
29
|
+
_thread_flags.suppress_xcp_error_log = bool(value)
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_suppress_xcp_error_log() -> bool:
|
|
35
|
+
try:
|
|
36
|
+
return bool(getattr(_thread_flags, "suppress_xcp_error_log", False))
|
|
37
|
+
except Exception:
|
|
38
|
+
return False
|
|
39
|
+
|
|
20
40
|
|
|
21
41
|
class SingletonBase:
|
|
22
42
|
_lock = threading.Lock()
|
|
@@ -196,6 +216,35 @@ class Handler:
|
|
|
196
216
|
self._repeater = None
|
|
197
217
|
self.logger = logging.getLogger("PyXCP")
|
|
198
218
|
|
|
219
|
+
def _diagnostics_enabled(self) -> bool:
|
|
220
|
+
try:
|
|
221
|
+
app = getattr(self.instance, "config", None)
|
|
222
|
+
if app is None:
|
|
223
|
+
return True
|
|
224
|
+
general = getattr(app, "general", None)
|
|
225
|
+
if general is None:
|
|
226
|
+
return True
|
|
227
|
+
return bool(getattr(general, "diagnostics_on_failure", True))
|
|
228
|
+
except Exception:
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
def _build_transport_diagnostics(self) -> str:
|
|
232
|
+
try:
|
|
233
|
+
transport = getattr(self.instance, "transport", None)
|
|
234
|
+
if transport is None:
|
|
235
|
+
return ""
|
|
236
|
+
if hasattr(transport, "_build_diagnostics_dump"):
|
|
237
|
+
return transport._build_diagnostics_dump() # type: ignore[attr-defined]
|
|
238
|
+
except Exception:
|
|
239
|
+
pass
|
|
240
|
+
return ""
|
|
241
|
+
|
|
242
|
+
def _append_diag(self, msg: str) -> str:
|
|
243
|
+
if not self._diagnostics_enabled():
|
|
244
|
+
return msg
|
|
245
|
+
diag = self._build_transport_diagnostics()
|
|
246
|
+
return msg + ("\n" + diag if diag else "")
|
|
247
|
+
|
|
199
248
|
def __str__(self):
|
|
200
249
|
return f"Handler(func = {func_name(self.func)} -- {self.arguments} service = {self.service} error_code = {self.error_code})"
|
|
201
250
|
|
|
@@ -268,16 +317,16 @@ class Handler:
|
|
|
268
317
|
if item == Action.NONE:
|
|
269
318
|
pass
|
|
270
319
|
elif item == Action.DISPLAY_ERROR:
|
|
271
|
-
raise SystemExit("Could not proceed due to unhandled error (DISPLAY_ERROR).", self.error_code)
|
|
320
|
+
raise SystemExit(self._append_diag("Could not proceed due to unhandled error (DISPLAY_ERROR)."), self.error_code)
|
|
272
321
|
elif item == Action.RETRY_SYNTAX:
|
|
273
|
-
raise SystemExit("Could not proceed due to unhandled error (RETRY_SYNTAX).", self.error_code)
|
|
322
|
+
raise SystemExit(self._append_diag("Could not proceed due to unhandled error (RETRY_SYNTAX)."), self.error_code)
|
|
274
323
|
elif item == Action.RETRY_PARAM:
|
|
275
|
-
raise SystemExit("Could not proceed due to unhandled error (RETRY_PARAM).", self.error_code)
|
|
324
|
+
raise SystemExit(self._append_diag("Could not proceed due to unhandled error (RETRY_PARAM)."), self.error_code)
|
|
276
325
|
elif item == Action.USE_A2L:
|
|
277
|
-
raise SystemExit("Could not proceed due to unhandled error (USE_A2L).", self.error_code)
|
|
326
|
+
raise SystemExit(self._append_diag("Could not proceed due to unhandled error (USE_A2L)."), self.error_code)
|
|
278
327
|
elif item == Action.USE_ALTERATIVE:
|
|
279
328
|
raise SystemExit(
|
|
280
|
-
"Could not proceed due to unhandled error (USE_ALTERATIVE).", self.error_code
|
|
329
|
+
self._append_diag("Could not proceed due to unhandled error (USE_ALTERATIVE)."), self.error_code
|
|
281
330
|
) # TODO: check alternatives.
|
|
282
331
|
elif item == Action.REPEAT:
|
|
283
332
|
repetitionCount = Repeater.REPEAT
|
|
@@ -286,13 +335,15 @@ class Handler:
|
|
|
286
335
|
elif item == Action.REPEAT_INF_TIMES:
|
|
287
336
|
repetitionCount = Repeater.INFINITE
|
|
288
337
|
elif item == Action.RESTART_SESSION:
|
|
289
|
-
raise SystemExit("Could not proceed due to unhandled error (RESTART_SESSION).", self.error_code)
|
|
338
|
+
raise SystemExit(self._append_diag("Could not proceed due to unhandled error (RESTART_SESSION)."), self.error_code)
|
|
290
339
|
elif item == Action.TERMINATE_SESSION:
|
|
291
|
-
raise SystemExit(
|
|
340
|
+
raise SystemExit(
|
|
341
|
+
self._append_diag("Could not proceed due to unhandled error (TERMINATE_SESSION)."), self.error_code
|
|
342
|
+
)
|
|
292
343
|
elif item == Action.SKIP:
|
|
293
344
|
pass
|
|
294
345
|
elif item == Action.NEW_FLASH_WARE:
|
|
295
|
-
raise SystemExit("Could not proceed due to unhandled error (NEW_FLASH_WARE)", self.error_code)
|
|
346
|
+
raise SystemExit(self._append_diag("Could not proceed due to unhandled error (NEW_FLASH_WARE)"), self.error_code)
|
|
296
347
|
return result_pre_actions, result_actions, Repeater(repetitionCount)
|
|
297
348
|
|
|
298
349
|
|
|
@@ -366,6 +417,48 @@ class Executor(SingletonBase):
|
|
|
366
417
|
# self.logger.critical(f"XcpResponseError [{e.get_error_code()}]")
|
|
367
418
|
self.error_code = e.get_error_code()
|
|
368
419
|
handler.error_code = self.error_code
|
|
420
|
+
try:
|
|
421
|
+
svc = getattr(inst.service, "name", None)
|
|
422
|
+
# Derive a human-friendly error name if available
|
|
423
|
+
try:
|
|
424
|
+
err_name = (
|
|
425
|
+
getattr(XcpError, int(self.error_code)).name
|
|
426
|
+
if hasattr(XcpError, "__members__")
|
|
427
|
+
else str(self.error_code)
|
|
428
|
+
)
|
|
429
|
+
except Exception:
|
|
430
|
+
# Fallbacks: try enum-style .name or string conversion
|
|
431
|
+
err_name = getattr(self.error_code, "name", None) or str(self.error_code)
|
|
432
|
+
try:
|
|
433
|
+
err_code_int = int(self.error_code)
|
|
434
|
+
except Exception:
|
|
435
|
+
err_code_int = self.error_code # best effort
|
|
436
|
+
msg = f"XCP negative response: {err_name} (0x{err_code_int:02X})"
|
|
437
|
+
if svc:
|
|
438
|
+
msg += f" on service {svc}"
|
|
439
|
+
# Suppress noisy ERROR log if requested by caller context
|
|
440
|
+
if is_suppress_xcp_error_log():
|
|
441
|
+
self.logger.debug(
|
|
442
|
+
msg,
|
|
443
|
+
extra={
|
|
444
|
+
"event": "xcp_error_suppressed",
|
|
445
|
+
"service": svc,
|
|
446
|
+
"error_code": err_code_int,
|
|
447
|
+
"error_name": err_name,
|
|
448
|
+
},
|
|
449
|
+
)
|
|
450
|
+
else:
|
|
451
|
+
self.logger.error(
|
|
452
|
+
msg,
|
|
453
|
+
extra={
|
|
454
|
+
"event": "xcp_error",
|
|
455
|
+
"service": svc,
|
|
456
|
+
"error_code": err_code_int,
|
|
457
|
+
"error_name": err_name,
|
|
458
|
+
},
|
|
459
|
+
)
|
|
460
|
+
except Exception:
|
|
461
|
+
pass
|
|
369
462
|
except XcpTimeoutError:
|
|
370
463
|
is_connect = func.__name__ == "connect"
|
|
371
464
|
self.logger.warning(f"XcpTimeoutError -- Service: {func.__name__!r}")
|
|
@@ -405,9 +498,21 @@ class Executor(SingletonBase):
|
|
|
405
498
|
if handler.repeater.repeat():
|
|
406
499
|
continue
|
|
407
500
|
else:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
501
|
+
msg = f"Max. repetition count reached while trying to execute service {handler.func.__name__!r}."
|
|
502
|
+
# Try to append diagnostics from the transport
|
|
503
|
+
try:
|
|
504
|
+
if hasattr(handler, "_append_diag"):
|
|
505
|
+
msg = handler._append_diag(msg)
|
|
506
|
+
except Exception:
|
|
507
|
+
pass
|
|
508
|
+
try:
|
|
509
|
+
self.logger.error(
|
|
510
|
+
"XCP unrecoverable",
|
|
511
|
+
extra={"event": "xcp_unrecoverable", "service": getattr(inst.service, "name", None)},
|
|
512
|
+
)
|
|
513
|
+
except Exception:
|
|
514
|
+
pass
|
|
515
|
+
raise UnrecoverableError(msg)
|
|
411
516
|
finally:
|
|
412
517
|
# cleanup of class variables
|
|
413
518
|
self.previous_error_code = None
|
pyxcp/master/master.py
CHANGED
|
@@ -25,7 +25,13 @@ from pyxcp.constants import (
|
|
|
25
25
|
makeWordUnpacker,
|
|
26
26
|
)
|
|
27
27
|
from pyxcp.daq_stim.stim import DaqEventInfo, Stim
|
|
28
|
-
from pyxcp.master.errorhandler import
|
|
28
|
+
from pyxcp.master.errorhandler import (
|
|
29
|
+
SystemExit,
|
|
30
|
+
disable_error_handling,
|
|
31
|
+
is_suppress_xcp_error_log,
|
|
32
|
+
set_suppress_xcp_error_log,
|
|
33
|
+
wrapped,
|
|
34
|
+
)
|
|
29
35
|
from pyxcp.transport.base import create_transport
|
|
30
36
|
from pyxcp.utils import decode_bytes, delay, short_sleep
|
|
31
37
|
|
|
@@ -1972,6 +1978,9 @@ class Master:
|
|
|
1972
1978
|
is normal for this kind of applications -- or to test for optional commands.
|
|
1973
1979
|
Use carefuly not to hide serious error causes.
|
|
1974
1980
|
"""
|
|
1981
|
+
# Suppress logging of expected XCP negative responses during try_command
|
|
1982
|
+
_prev_suppress = is_suppress_xcp_error_log()
|
|
1983
|
+
set_suppress_xcp_error_log(True)
|
|
1975
1984
|
try:
|
|
1976
1985
|
extra_msg: Optional[str] = kws.get("extra_msg")
|
|
1977
1986
|
if extra_msg:
|
|
@@ -1985,6 +1994,8 @@ class Master:
|
|
|
1985
1994
|
silent = False
|
|
1986
1995
|
res = cmd(*args, **kws)
|
|
1987
1996
|
except SystemExit as e:
|
|
1997
|
+
# restore suppression flag before handling
|
|
1998
|
+
set_suppress_xcp_error_log(_prev_suppress)
|
|
1988
1999
|
# print(f"\tUnexpected error while executing command {cmd.__name__!r}: {e!r}")
|
|
1989
2000
|
if e.error_code == types.XcpError.ERR_CMD_UNKNOWN:
|
|
1990
2001
|
# This is a rather common use-case, so let the user know that there is some functionality missing.
|
|
@@ -2000,6 +2011,12 @@ class Master:
|
|
|
2000
2011
|
return (types.TryCommandResult.OTHER_ERROR, e)
|
|
2001
2012
|
else:
|
|
2002
2013
|
return (types.TryCommandResult.OK, res)
|
|
2014
|
+
finally:
|
|
2015
|
+
# Ensure suppression flag is restored even on success/other exceptions
|
|
2016
|
+
try:
|
|
2017
|
+
set_suppress_xcp_error_log(_prev_suppress)
|
|
2018
|
+
except Exception:
|
|
2019
|
+
pass
|
|
2003
2020
|
|
|
2004
2021
|
|
|
2005
2022
|
def ticks_to_seconds(ticks, resolution):
|
pyxcp/recorder/__init__.py
CHANGED
|
@@ -16,12 +16,9 @@ else:
|
|
|
16
16
|
HAS_PANDAS = True
|
|
17
17
|
|
|
18
18
|
from pyxcp.recorder.rekorder import DaqOnlinePolicy # noqa: F401
|
|
19
|
-
from pyxcp.recorder.rekorder import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
MeasurementParameters, # noqa: F401
|
|
23
|
-
ValueHolder, # noqa: F401
|
|
24
|
-
)
|
|
19
|
+
from pyxcp.recorder.rekorder import DaqRecorderPolicy # noqa: F401
|
|
20
|
+
from pyxcp.recorder.rekorder import Deserializer # noqa: F401
|
|
21
|
+
from pyxcp.recorder.rekorder import MeasurementParameters # noqa: F401
|
|
25
22
|
from pyxcp.recorder.rekorder import XcpLogFileDecoder as _XcpLogFileDecoder
|
|
26
23
|
from pyxcp.recorder.rekorder import _PyXcpLogFileReader, _PyXcpLogFileWriter, data_types
|
|
27
24
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|