pyxcp 0.23.3__cp312-cp312-win_arm64.whl → 0.25.6__cp312-cp312-win_arm64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. pyxcp/__init__.py +1 -1
  2. pyxcp/asamkeydll.exe +0 -0
  3. pyxcp/cmdline.py +15 -30
  4. pyxcp/config/__init__.py +73 -20
  5. pyxcp/cpp_ext/aligned_buffer.hpp +168 -0
  6. pyxcp/cpp_ext/bin.hpp +7 -6
  7. pyxcp/cpp_ext/cpp_ext.cp310-win_arm64.pyd +0 -0
  8. pyxcp/cpp_ext/cpp_ext.cp311-win_arm64.pyd +0 -0
  9. pyxcp/cpp_ext/cpp_ext.cp312-win_arm64.pyd +0 -0
  10. pyxcp/cpp_ext/daqlist.hpp +241 -73
  11. pyxcp/cpp_ext/extension_wrapper.cpp +123 -15
  12. pyxcp/cpp_ext/framing.hpp +360 -0
  13. pyxcp/cpp_ext/mcobject.hpp +5 -3
  14. pyxcp/cpp_ext/sxi_framing.hpp +332 -0
  15. pyxcp/daq_stim/__init__.py +182 -45
  16. pyxcp/daq_stim/optimize/binpacking.py +2 -2
  17. pyxcp/daq_stim/scheduler.cpp +8 -8
  18. pyxcp/daq_stim/stim.cp310-win_arm64.pyd +0 -0
  19. pyxcp/daq_stim/stim.cp311-win_arm64.pyd +0 -0
  20. pyxcp/daq_stim/stim.cp312-win_arm64.pyd +0 -0
  21. pyxcp/errormatrix.py +2 -2
  22. pyxcp/examples/run_daq.py +5 -3
  23. pyxcp/examples/xcp_policy.py +6 -6
  24. pyxcp/examples/xcp_read_benchmark.py +2 -2
  25. pyxcp/examples/xcp_skel.py +1 -2
  26. pyxcp/examples/xcp_unlock.py +10 -12
  27. pyxcp/examples/xcp_user_supplied_driver.py +1 -2
  28. pyxcp/examples/xcphello.py +2 -15
  29. pyxcp/examples/xcphello_recorder.py +2 -2
  30. pyxcp/master/__init__.py +1 -0
  31. pyxcp/master/errorhandler.py +248 -13
  32. pyxcp/master/master.py +838 -250
  33. pyxcp/recorder/.idea/.gitignore +8 -0
  34. pyxcp/recorder/.idea/misc.xml +4 -0
  35. pyxcp/recorder/.idea/modules.xml +8 -0
  36. pyxcp/recorder/.idea/recorder.iml +6 -0
  37. pyxcp/recorder/.idea/sonarlint/issuestore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +7 -0
  38. pyxcp/recorder/.idea/sonarlint/issuestore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
  39. pyxcp/recorder/.idea/sonarlint/issuestore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
  40. pyxcp/recorder/.idea/sonarlint/issuestore/index.pb +7 -0
  41. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +0 -0
  42. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
  43. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
  44. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/index.pb +7 -0
  45. pyxcp/recorder/.idea/vcs.xml +10 -0
  46. pyxcp/recorder/__init__.py +5 -10
  47. pyxcp/recorder/converter/__init__.py +4 -10
  48. pyxcp/recorder/reader.hpp +0 -1
  49. pyxcp/recorder/reco.py +1 -0
  50. pyxcp/recorder/rekorder.cp310-win_arm64.pyd +0 -0
  51. pyxcp/recorder/rekorder.cp311-win_arm64.pyd +0 -0
  52. pyxcp/recorder/rekorder.cp312-win_arm64.pyd +0 -0
  53. pyxcp/recorder/unfolder.hpp +129 -107
  54. pyxcp/recorder/wrap.cpp +3 -8
  55. pyxcp/scripts/xcp_fetch_a2l.py +2 -2
  56. pyxcp/scripts/xcp_id_scanner.py +1 -2
  57. pyxcp/scripts/xcp_info.py +66 -51
  58. pyxcp/scripts/xcp_profile.py +1 -2
  59. pyxcp/tests/test_daq.py +1 -1
  60. pyxcp/tests/test_framing.py +262 -0
  61. pyxcp/tests/test_master.py +210 -100
  62. pyxcp/tests/test_transport.py +138 -42
  63. pyxcp/timing.py +1 -1
  64. pyxcp/transport/__init__.py +8 -5
  65. pyxcp/transport/base.py +187 -143
  66. pyxcp/transport/can.py +117 -13
  67. pyxcp/transport/eth.py +55 -20
  68. pyxcp/transport/hdf5_policy.py +167 -0
  69. pyxcp/transport/sxi.py +126 -52
  70. pyxcp/transport/transport_ext.cp310-win_arm64.pyd +0 -0
  71. pyxcp/transport/transport_ext.cp311-win_arm64.pyd +0 -0
  72. pyxcp/transport/transport_ext.cp312-win_arm64.pyd +0 -0
  73. pyxcp/transport/transport_ext.hpp +214 -0
  74. pyxcp/transport/transport_wrapper.cpp +249 -0
  75. pyxcp/transport/usb_transport.py +47 -31
  76. pyxcp/types.py +0 -13
  77. pyxcp/{utils.py → utils/__init__.py} +3 -4
  78. pyxcp/utils/cli.py +78 -0
  79. pyxcp-0.25.6.dist-info/METADATA +341 -0
  80. pyxcp-0.25.6.dist-info/RECORD +153 -0
  81. {pyxcp-0.23.3.dist-info → pyxcp-0.25.6.dist-info}/WHEEL +1 -1
  82. pyxcp/examples/conf_sxi.json +0 -9
  83. pyxcp/examples/conf_sxi.toml +0 -7
  84. pyxcp-0.23.3.dist-info/METADATA +0 -219
  85. pyxcp-0.23.3.dist-info/RECORD +0 -131
  86. {pyxcp-0.23.3.dist-info → pyxcp-0.25.6.dist-info}/entry_points.txt +0 -0
  87. {pyxcp-0.23.3.dist-info → pyxcp-0.25.6.dist-info/licenses}/LICENSE +0 -0
pyxcp/__init__.py CHANGED
@@ -17,4 +17,4 @@ tb_install(show_locals=True, max_frames=3) # Install custom exception handler.
17
17
 
18
18
  # if you update this manually, do not forget to update
19
19
  # .bumpversion.cfg and pyproject.toml.
20
- __version__ = "0.23.3"
20
+ __version__ = "0.25.6"
pyxcp/asamkeydll.exe ADDED
Binary file
pyxcp/cmdline.py CHANGED
@@ -4,9 +4,8 @@ Parse (transport-layer specific) command line parameters
4
4
  and create a XCP master instance.
5
5
  """
6
6
 
7
+ import argparse
7
8
  import warnings
8
- from dataclasses import dataclass
9
- from typing import List
10
9
 
11
10
  from pyxcp.config import ( # noqa: F401
12
11
  create_application,
@@ -14,33 +13,12 @@ from pyxcp.config import ( # noqa: F401
14
13
  reset_application,
15
14
  )
16
15
  from pyxcp.master import Master
16
+ from pyxcp.utils.cli import StrippingParser
17
17
 
18
18
 
19
19
  warnings.simplefilter("always")
20
20
 
21
21
 
22
- @dataclass
23
- class Option:
24
- short_opt: str
25
- long_opt: str = ""
26
- dest: str = ""
27
- help: str = ""
28
- type: str = ""
29
- default: str = ""
30
- action: str = ""
31
-
32
-
33
- class FakeParser:
34
- """Parser that collects arguments for later processing."""
35
-
36
- def __init__(self):
37
- self.options = []
38
-
39
- def add_argument(self, short_opt, long_opt="", dest="", help="", type=None, default=None, action=None):
40
- """Collect argument definitions without issuing warnings."""
41
- self.options.append(Option(short_opt, long_opt, dest, help, type, default, action))
42
-
43
-
44
22
  class ArgumentParser:
45
23
  """Argument parser for pyXCP applications.
46
24
 
@@ -49,13 +27,20 @@ class ArgumentParser:
49
27
  the parsed arguments.
50
28
  """
51
29
 
52
- def __init__(self, callout=None, description=None, *args, **kws):
53
- self._parser = FakeParser()
54
- self._callout = callout
30
+ def __init__(self, user_parser=None, description=None, *args, **kws):
31
+ if isinstance(user_parser, argparse.ArgumentParser):
32
+ self._parser = StrippingParser(user_parser)
33
+ self._callout = None
34
+ else:
35
+ # Create a default parser. user_parser might be a callout function or None.
36
+ parser = argparse.ArgumentParser(description=description)
37
+ self._parser = StrippingParser(parser)
38
+ self._callout = user_parser
55
39
  self._description = description
40
+ self.args = self._parser.parse_and_strip()
56
41
 
57
42
  def run(self, policy=None, transport_layer_interface=None):
58
- """Create and configure a master instance.
43
+ """Create and configure a synchronous master instance.
59
44
 
60
45
  Args:
61
46
  policy: Optional policy to use for the master
@@ -65,7 +50,7 @@ class ArgumentParser:
65
50
  A configured master instance
66
51
  """
67
52
  # Create the application with custom arguments and callout
68
- application = get_application(self.parser.options, self._callout)
53
+ application = get_application(options=[], callout=self._callout)
69
54
 
70
55
  # Create the master instance
71
56
  master = Master(
@@ -81,4 +66,4 @@ class ArgumentParser:
81
66
 
82
67
  @property
83
68
  def parser(self):
84
- return self._parser
69
+ return self._parser.parser
pyxcp/config/__init__.py CHANGED
@@ -913,6 +913,9 @@ 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)
916
919
  seed_n_key_dll = Unicode("", allow_none=False, help="Dynamic library used for slave resource unlocking.").tag(config=True)
917
920
  seed_n_key_dll_same_bit_width = Bool(False, help="").tag(config=True)
918
921
  custom_dll_loader = Unicode(allow_none=True, default_value=None, help="Use an custom seed and key DLL loader.").tag(config=True)
@@ -1010,6 +1013,11 @@ class PyXCP(Application):
1010
1013
  config=True
1011
1014
  )
1012
1015
 
1016
+ # Logging options
1017
+ structured_logging = Bool(False, help="Emit one-line JSON logs instead of rich text.").tag(config=True)
1018
+ # Use log_output_format to avoid clashing with traitlets.Application.log_format (a %-style template)
1019
+ log_output_format = Enum(values=["rich", "json"], default_value="rich", help="Select logging output format.").tag(config=True)
1020
+
1013
1021
  classes = List([General, Transport, CustomArgs])
1014
1022
 
1015
1023
  subcommands = dict(
@@ -1026,34 +1034,79 @@ class PyXCP(Application):
1026
1034
  self.subapp.start()
1027
1035
  exit(2)
1028
1036
  else:
1029
- has_handlers = logging.getLogger().hasHandlers()
1030
- if has_handlers:
1031
- self.log = logging.getLogger()
1032
- self._read_configuration(self.config_file)
1033
- else:
1034
- self._read_configuration(self.config_file)
1035
- self._setup_logger()
1037
+ # Always read configuration and then set up our logger explicitly to avoid
1038
+ # traitlets.Application default logging using an incompatible 'log_format'.
1039
+ self._read_configuration(self.config_file)
1040
+ try:
1041
+ # Ensure base Application.log_format is a valid %-style template
1042
+ # (Users might set c.PyXCP.log_format = "json" which clashes with traitlets behavior.)
1043
+ self.log_format = "%(message)s" # type: ignore[assignment]
1044
+ except Exception:
1045
+ pass
1046
+ self._setup_logger()
1036
1047
  self.log.debug(f"pyxcp version: {self.version}")
1037
1048
 
1038
1049
  def _setup_logger(self):
1039
1050
  from pyxcp.types import Command
1040
1051
 
1041
1052
  # Remove any handlers installed by `traitlets`.
1042
- for hdl in self.log.handlers:
1053
+ for hdl in list(self.log.handlers):
1043
1054
  self.log.removeHandler(hdl)
1044
1055
 
1045
- # formatter = logging.Formatter(fmt=self.log_format, datefmt=self.log_datefmt)
1046
-
1047
- keywords = list(Command.__members__.keys()) + ["ARGS", "KWS"] # Syntax highlight XCP commands and other stuff.
1048
- rich_handler = RichHandler(
1049
- rich_tracebacks=True,
1050
- tracebacks_show_locals=True,
1051
- log_time_format=self.log_datefmt,
1052
- level=self.log_level,
1053
- keywords=keywords,
1054
- )
1055
- # rich_handler.setFormatter(formatter)
1056
- self.log.addHandler(rich_handler)
1056
+ # Decide formatter/handler based on config
1057
+ use_json = False
1058
+ try:
1059
+ # Prefer explicit log_output_format; fallback to structured_logging for compatibility
1060
+ use_json = getattr(self, "log_output_format", "rich") == "json" or getattr(self, "structured_logging", False)
1061
+ # Backward-compat: if someone set PyXCP.log_format="json" in config, honor it here too
1062
+ if not use_json:
1063
+ lf = getattr(self, "log_format", None)
1064
+ if isinstance(lf, str) and lf.lower() == "json":
1065
+ use_json = True
1066
+ except Exception:
1067
+ use_json = False
1068
+
1069
+ if use_json:
1070
+
1071
+ class JSONFormatter(logging.Formatter):
1072
+ def format(self, record: logging.LogRecord) -> str:
1073
+ # Build a minimal structured payload
1074
+ payload = {
1075
+ "time": self.formatTime(record, self.datefmt),
1076
+ "level": record.levelname,
1077
+ "logger": record.name,
1078
+ "message": record.getMessage(),
1079
+ }
1080
+ # Include extras if present
1081
+ for key in ("transport", "host", "port", "protocol", "event", "command"):
1082
+ if hasattr(record, key):
1083
+ payload[key] = getattr(record, key)
1084
+ # Exceptions
1085
+ if record.exc_info:
1086
+ payload["exc_type"] = record.exc_info[0].__name__ if record.exc_info[0] else None
1087
+ payload["exc_text"] = self.formatException(record.exc_info)
1088
+ try:
1089
+ import json as _json
1090
+
1091
+ return _json.dumps(payload, ensure_ascii=False)
1092
+ except Exception:
1093
+ return f"{payload}"
1094
+
1095
+ handler = logging.StreamHandler()
1096
+ formatter = JSONFormatter(datefmt=self.log_datefmt)
1097
+ handler.setFormatter(formatter)
1098
+ handler.setLevel(self.log_level)
1099
+ self.log.addHandler(handler)
1100
+ else:
1101
+ keywords = list(Command.__members__.keys()) + ["ARGS", "KWS"] # Syntax highlight XCP commands and other stuff.
1102
+ rich_handler = RichHandler(
1103
+ rich_tracebacks=True,
1104
+ tracebacks_show_locals=True,
1105
+ log_time_format=self.log_datefmt,
1106
+ level=self.log_level,
1107
+ keywords=keywords,
1108
+ )
1109
+ self.log.addHandler(rich_handler)
1057
1110
 
1058
1111
  def initialize(self, argv=None):
1059
1112
  from pyxcp import __version__ as pyxcp_version
@@ -0,0 +1,168 @@
1
+
2
+ #if !defined(__ALIGNED_BUFFER_HPP)
3
+ #define __ALIGNED_BUFFER_HPP
4
+
5
+ #include <variant>
6
+
7
+ #include <cstdint>
8
+ #include <cstdlib>
9
+ #include <vector>
10
+ #include <string_view>
11
+ #include <stdexcept>
12
+ #include <algorithm>
13
+ #include <cstring>
14
+
15
+ #include <pybind11/pybind11.h>
16
+
17
+ #if (defined(_WIN32) || defined(_WIN64)) && defined(_MSC_VER)
18
+ #include <malloc.h>
19
+ #endif
20
+
21
+ namespace py = pybind11;
22
+
23
+ class AlignedBuffer {
24
+
25
+ public:
26
+
27
+ AlignedBuffer(size_t size = 0xffff) : m_size(size), m_current_pos(0) {
28
+ m_buffer = nullptr;
29
+ // Create naturally aligned buffer.
30
+ constexpr std::size_t align = alignof(int);
31
+ // aligned_alloc requires size to be a multiple of alignment.
32
+ const std::size_t aligned_size = ((m_size + align - 1) / align) * align;
33
+ #if (defined(_WIN32) || defined(_WIN64)) && defined(_MSC_VER)
34
+ m_buffer = static_cast<uint8_t*>(::_aligned_malloc(aligned_size, align));
35
+ #else
36
+ m_buffer = static_cast<uint8_t*>(::aligned_alloc(align, aligned_size));
37
+ #endif
38
+ }
39
+
40
+ AlignedBuffer(const AlignedBuffer& other) = delete;
41
+ AlignedBuffer& operator=(const AlignedBuffer& other) = delete;
42
+ AlignedBuffer(AlignedBuffer&& other) = delete;
43
+ AlignedBuffer& operator=(AlignedBuffer&& other) = delete;
44
+
45
+ ~AlignedBuffer() {
46
+ if (m_buffer) {
47
+ #if (defined(_WIN32) || defined(_WIN64)) && defined(_MSC_VER)
48
+ ::_aligned_free(m_buffer);
49
+ #else
50
+ ::free(m_buffer);
51
+ #endif
52
+ m_buffer = nullptr;
53
+ }
54
+ }
55
+
56
+ void reset() noexcept {
57
+ m_current_pos = 0;
58
+ }
59
+
60
+ std::size_t size() const noexcept {
61
+ return m_current_pos;
62
+ }
63
+
64
+ // Get an element by index
65
+ uint8_t get(size_t index) const {
66
+ if (index >= size()) throw std::out_of_range("Index out of range");
67
+ return m_buffer[index];
68
+ }
69
+
70
+ void append(uint8_t value) {
71
+ if ((m_current_pos + 1) > m_size) {
72
+ throw std::overflow_error("Buffer overflow");
73
+ }
74
+ m_buffer[m_current_pos] = value;
75
+ m_current_pos++;
76
+ }
77
+
78
+ // Set an element by index
79
+ void set(size_t index, uint8_t value) {
80
+ if (index >= size()) throw std::out_of_range("Index out of range");
81
+ m_buffer[index] = value;
82
+ }
83
+
84
+ static std::string_view bytes_as_string_view(const py::bytes& data) {
85
+ char* buf = nullptr;
86
+ Py_ssize_t len = 0;
87
+ if (PyBytes_AsStringAndSize(data.ptr(), &buf, &len) != 0 || buf == nullptr || len < 0) {
88
+ return std::string_view{};
89
+ }
90
+ return std::string_view(buf, static_cast<std::size_t>(len));
91
+ }
92
+
93
+
94
+ void extend(const py::bytes& values) {
95
+ auto data_view = bytes_as_string_view(values);
96
+
97
+ if ((data_view.size() + m_current_pos) > m_size) {
98
+ throw std::invalid_argument("Values vector is too large");
99
+ }
100
+ if (!data_view.empty()) {
101
+ std::memcpy(m_buffer + m_current_pos, data_view.data(), data_view.size());
102
+ m_current_pos += data_view.size();
103
+ }
104
+ }
105
+
106
+ void extend(const std::vector<std::uint8_t>& values) {
107
+ if ((values.size() + m_current_pos) > m_size) {
108
+ throw std::invalid_argument("Values vector is too large");
109
+ }
110
+ if (!values.empty()) {
111
+ std::memcpy(m_buffer + m_current_pos, values.data(), values.size());
112
+ m_current_pos += values.size();
113
+ }
114
+ }
115
+
116
+ std::variant<uint8_t, py::bytes> get_item(py::object index) const {
117
+ if (py::isinstance<py::slice>(index)) {
118
+ py::slice slice = index.cast<py::slice>();
119
+ size_t start, stop, step, length;
120
+ if (!slice.compute(size(), &start, &stop, &step, &length)) {
121
+ throw py::error_already_set();
122
+ }
123
+ return slice(start, stop, step);
124
+ } else if (py::isinstance<py::int_>(index)) {
125
+ Py_ssize_t idx = index.cast<Py_ssize_t>();
126
+ if (idx < 0) {
127
+ idx += static_cast<Py_ssize_t>(size());
128
+ }
129
+ if (idx < 0 || static_cast<std::size_t>(idx) >= size()) {
130
+ throw std::out_of_range("Index out of range");
131
+ }
132
+ return get(static_cast<std::size_t>(idx));
133
+ } else {
134
+ throw py::type_error("Invalid index type");
135
+ }
136
+ }
137
+
138
+ py::bytes slice(size_t start, size_t stop, size_t step) const {
139
+ if (step == 0) {
140
+ throw std::invalid_argument("Step cannot be zero");
141
+ }
142
+ // Clamp indices to valid range
143
+ start = std::max(size_t(0), std::min(start, size_t(size())));
144
+ stop = std::max(size_t(0), std::min(stop, size_t(size())));
145
+
146
+ if (start >= stop) {
147
+ return py::bytes("");
148
+ }
149
+ if (step == 1) {
150
+ return py::bytes(reinterpret_cast<const char*>(m_buffer) + start, stop - start);
151
+ }
152
+ // General step handling (build result with stride)
153
+ std::string out;
154
+ out.reserve((stop - start + step - 1) / step);
155
+ for (size_t i = start; i < stop; i += step) {
156
+ out.push_back(static_cast<char>(m_buffer[i]));
157
+ }
158
+ return py::bytes(out);
159
+ }
160
+
161
+ private:
162
+ size_t m_size;
163
+ size_t m_current_pos;
164
+ uint8_t * m_buffer;
165
+ };
166
+
167
+
168
+ #endif // __ALIGNED_BUFFER_HPP
pyxcp/cpp_ext/bin.hpp CHANGED
@@ -79,17 +79,18 @@ std::string bin_entries_to_string(const std::vector<McObject>& entries);
79
79
 
80
80
  std::string to_string(const Bin& obj) {
81
81
  std::stringstream ss;
82
-
83
- ss << "Bin(residual_capacity=" << obj.get_residual_capacity() << ", entries=[" << bin_entries_to_string(obj.get_entries())
84
- << "])";
82
+ ss << "Bin(size=" << obj.get_size() << ", residual_capacity=" << obj.get_residual_capacity() << ", entries=["
83
+ << bin_entries_to_string(obj.get_entries()) << "])";
85
84
  return ss.str();
86
85
  }
87
86
 
88
87
  std::string bin_entries_to_string(const std::vector<McObject>& entries) {
89
88
  std::stringstream ss;
90
-
91
- for (const auto& entry : entries) {
92
- ss << to_string(entry) << ",\n ";
89
+ for (std::size_t i = 0; i < entries.size(); ++i) {
90
+ ss << to_string(entries[i]);
91
+ if (i + 1 < entries.size()) {
92
+ ss << ", ";
93
+ }
93
94
  }
94
95
  return ss.str();
95
96
  }
Binary file
Binary file
Binary file