pyxcp 0.23.8__cp313-cp313-macosx_11_0_arm64.whl → 0.25.7__cp313-cp313-macosx_11_0_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 (89) hide show
  1. pyxcp/__init__.py +1 -1
  2. pyxcp/cmdline.py +14 -29
  3. pyxcp/config/__init__.py +1257 -1258
  4. pyxcp/cpp_ext/aligned_buffer.hpp +168 -0
  5. pyxcp/cpp_ext/bin.hpp +7 -6
  6. pyxcp/cpp_ext/cpp_ext.cpython-310-darwin.so +0 -0
  7. pyxcp/cpp_ext/cpp_ext.cpython-311-darwin.so +0 -0
  8. pyxcp/cpp_ext/cpp_ext.cpython-312-darwin.so +0 -0
  9. pyxcp/cpp_ext/cpp_ext.cpython-313-darwin.so +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/helper.hpp +280 -280
  14. pyxcp/cpp_ext/mcobject.hpp +248 -246
  15. pyxcp/cpp_ext/sxi_framing.hpp +332 -0
  16. pyxcp/daq_stim/__init__.py +145 -67
  17. pyxcp/daq_stim/optimize/binpacking.py +2 -2
  18. pyxcp/daq_stim/scheduler.cpp +8 -8
  19. pyxcp/errormatrix.py +2 -2
  20. pyxcp/examples/run_daq.py +5 -4
  21. pyxcp/examples/xcp_policy.py +6 -6
  22. pyxcp/examples/xcp_read_benchmark.py +2 -2
  23. pyxcp/examples/xcp_skel.py +1 -2
  24. pyxcp/examples/xcp_unlock.py +10 -12
  25. pyxcp/examples/xcp_user_supplied_driver.py +1 -2
  26. pyxcp/examples/xcphello.py +2 -15
  27. pyxcp/examples/xcphello_recorder.py +2 -2
  28. pyxcp/master/__init__.py +1 -0
  29. pyxcp/master/errorhandler.py +134 -4
  30. pyxcp/master/master.py +823 -252
  31. pyxcp/recorder/.idea/.gitignore +8 -0
  32. pyxcp/recorder/.idea/misc.xml +4 -0
  33. pyxcp/recorder/.idea/modules.xml +8 -0
  34. pyxcp/recorder/.idea/recorder.iml +6 -0
  35. pyxcp/recorder/.idea/sonarlint/issuestore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +7 -0
  36. pyxcp/recorder/.idea/sonarlint/issuestore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
  37. pyxcp/recorder/.idea/sonarlint/issuestore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
  38. pyxcp/recorder/.idea/sonarlint/issuestore/index.pb +7 -0
  39. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/3/8/3808afc69ac1edb9d760000a2f137335b1b99728 +0 -0
  40. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/9/a/9a2aa4db38d3115ed60da621e012c0efc0172aae +0 -0
  41. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/b/4/b49006702b459496a8e8c94ebe60947108361b91 +0 -0
  42. pyxcp/recorder/.idea/sonarlint/securityhotspotstore/index.pb +7 -0
  43. pyxcp/recorder/.idea/vcs.xml +10 -0
  44. pyxcp/recorder/__init__.py +96 -98
  45. pyxcp/recorder/converter/__init__.py +4 -10
  46. pyxcp/recorder/reader.hpp +138 -139
  47. pyxcp/recorder/reco.py +1 -0
  48. pyxcp/recorder/rekorder.cpython-310-darwin.so +0 -0
  49. pyxcp/recorder/rekorder.cpython-311-darwin.so +0 -0
  50. pyxcp/recorder/rekorder.cpython-312-darwin.so +0 -0
  51. pyxcp/recorder/rekorder.cpython-313-darwin.so +0 -0
  52. pyxcp/recorder/rekorder.hpp +274 -274
  53. pyxcp/recorder/unfolder.hpp +1354 -1319
  54. pyxcp/recorder/wrap.cpp +184 -183
  55. pyxcp/recorder/writer.hpp +302 -302
  56. pyxcp/scripts/xcp_daq_recorder.py +54 -0
  57. pyxcp/scripts/xcp_fetch_a2l.py +2 -2
  58. pyxcp/scripts/xcp_id_scanner.py +1 -2
  59. pyxcp/scripts/xcp_info.py +66 -51
  60. pyxcp/scripts/xcp_profile.py +1 -2
  61. pyxcp/tests/test_daq.py +1 -1
  62. pyxcp/tests/test_framing.py +262 -0
  63. pyxcp/tests/test_master.py +210 -100
  64. pyxcp/tests/test_transport.py +138 -42
  65. pyxcp/timing.py +1 -1
  66. pyxcp/transport/__init__.py +8 -5
  67. pyxcp/transport/base.py +70 -180
  68. pyxcp/transport/can.py +58 -7
  69. pyxcp/transport/eth.py +32 -15
  70. pyxcp/transport/hdf5_policy.py +167 -0
  71. pyxcp/transport/sxi.py +126 -52
  72. pyxcp/transport/transport_ext.cpython-310-darwin.so +0 -0
  73. pyxcp/transport/transport_ext.cpython-311-darwin.so +0 -0
  74. pyxcp/transport/transport_ext.cpython-312-darwin.so +0 -0
  75. pyxcp/transport/transport_ext.cpython-313-darwin.so +0 -0
  76. pyxcp/transport/transport_ext.hpp +214 -0
  77. pyxcp/transport/transport_wrapper.cpp +249 -0
  78. pyxcp/transport/usb_transport.py +47 -31
  79. pyxcp/types.py +0 -13
  80. pyxcp/{utils.py → utils/__init__.py} +1 -2
  81. pyxcp/utils/cli.py +78 -0
  82. {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/METADATA +4 -2
  83. pyxcp-0.25.7.dist-info/RECORD +158 -0
  84. {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/WHEEL +1 -1
  85. pyxcp/examples/conf_sxi.json +0 -9
  86. pyxcp/examples/conf_sxi.toml +0 -7
  87. pyxcp-0.23.8.dist-info/RECORD +0 -135
  88. {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info}/entry_points.txt +0 -0
  89. {pyxcp-0.23.8.dist-info → pyxcp-0.25.7.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,332 @@
1
+ #if !defined (__SXI_FRAMING_HPP)
2
+ #define __SXI_FRAMING_HPP
3
+
4
+ #include <array>
5
+ #include <bit>
6
+ #include <chrono>
7
+ #include <condition_variable>
8
+ #include <cstdint>
9
+ #include <functional>
10
+ #include <memory>
11
+ #include <mutex>
12
+ #include <thread>
13
+ #include <vector>
14
+ #include <iomanip>
15
+ #include <iostream>
16
+
17
+ // Header format options
18
+ enum class SxiHeaderFormat {
19
+ LenByte,
20
+ LenCtrByte,
21
+ LenFillByte,
22
+ LenWord,
23
+ LenCtrWord,
24
+ LenFillWord,
25
+ };
26
+
27
+ // Checksum type options
28
+ enum class SxiChecksumType {
29
+ None,
30
+ Sum8,
31
+ Sum16
32
+ };
33
+
34
+ namespace detail {
35
+ inline uint16_t make_word_le(const uint8_t* p) {
36
+ return p[0] | p[1] << 8;
37
+ }
38
+
39
+ inline void put_word_le(uint8_t* p, uint16_t v) {
40
+ if constexpr (std::endian::native == std::endian::big) {
41
+ p[0] = static_cast<uint8_t>(v & 0xFF);
42
+ p[1] = static_cast<uint8_t>((v >> 8) & 0xFF);
43
+ } else {
44
+ std::memcpy(p, &v, sizeof(v));
45
+ }
46
+ }
47
+ } // namespace detail
48
+
49
+ class RestartableTimer {
50
+ public:
51
+
52
+ /**
53
+ * @brief Constructs the timer.
54
+ * @param timeout The duration after which the timer expires.
55
+ * @param on_timeout The function to call upon timeout.
56
+ */
57
+ RestartableTimer(std::chrono::milliseconds timeout, std::function<void()> on_timeout) :
58
+ m_timeout(timeout), m_on_timeout(std::move(on_timeout)), m_running(false) {
59
+ }
60
+
61
+ ~RestartableTimer() {
62
+ stop();
63
+ if (m_thread.joinable()) {
64
+ m_thread.join();
65
+ }
66
+ }
67
+
68
+ // Disable copy and move semantics
69
+ RestartableTimer(const RestartableTimer&) = delete;
70
+ RestartableTimer& operator=(const RestartableTimer&) = delete;
71
+ RestartableTimer(RestartableTimer&&) = delete;
72
+ RestartableTimer& operator=(RestartableTimer&&) = delete;
73
+
74
+ /**
75
+ * @brief Starts the timer. If already running, it resets the countdown.
76
+ */
77
+ void start() {
78
+ if (m_timeout == std::chrono::milliseconds(0)) {
79
+ return;
80
+ }
81
+ std::unique_lock<std::mutex> lock(m_mutex);
82
+ if (!m_running) {
83
+ m_running = true;
84
+ if (m_thread.joinable()) {
85
+ m_thread.join(); // Ensure previous thread is finished
86
+ }
87
+ m_thread = std::thread(&RestartableTimer::run, this);
88
+ } else {
89
+ // Already running, just signal a reset
90
+ m_cv.notify_one();
91
+ }
92
+ }
93
+
94
+ /**
95
+ * @brief Stops the timer.
96
+ */
97
+ void stop() {
98
+ if (m_timeout == std::chrono::milliseconds(0)) {
99
+ return;
100
+ }
101
+ std::unique_lock<std::mutex> lock(m_mutex);
102
+ if (!m_running) {
103
+ return;
104
+ }
105
+ m_running = false;
106
+ m_cv.notify_one();
107
+ }
108
+
109
+ /**
110
+ * @brief Resets the timer's countdown.
111
+ */
112
+ void reset_timeout() {
113
+ if (m_timeout == std::chrono::milliseconds(0)) {
114
+ return;
115
+ }
116
+ std::unique_lock<std::mutex> lock(m_mutex);
117
+ if (m_running) {
118
+ m_cv.notify_one();
119
+ }
120
+ }
121
+
122
+ private:
123
+
124
+ void run() {
125
+ std::unique_lock<std::mutex> lock(m_mutex);
126
+ while (m_running) {
127
+ // wait_for returns cv_status::timeout if the time expires without a notification
128
+ if (m_cv.wait_for(lock, m_timeout) == std::cv_status::timeout) {
129
+ // Timeout occurred. Check m_running again in case stop() was called
130
+ // while we were waiting for the lock.
131
+ if (m_running) {
132
+ m_on_timeout();
133
+ m_running = false; // Stop the timer thread after firing
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ std::thread m_thread;
140
+ std::mutex m_mutex;
141
+ std::condition_variable m_cv;
142
+ std::chrono::milliseconds m_timeout;
143
+ std::function<void()> m_on_timeout;
144
+ std::atomic<bool> m_running;
145
+ };
146
+
147
+
148
+ template<SxiHeaderFormat Format, SxiChecksumType Checksum>
149
+ class SxiReceiver {
150
+ public:
151
+
152
+ explicit SxiReceiver(
153
+ std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)> dispatch_handler,
154
+ std::chrono::milliseconds /*timeout*/ = std::chrono::milliseconds(0)
155
+ ) :
156
+ dispatch_(std::move(dispatch_handler)) {
157
+ reset();
158
+ }
159
+
160
+ void feed_bytes(const std::string& data) {
161
+ for (const auto& c : data) {
162
+ //feed(static_cast<uint8_t>(c));
163
+ feed(c);
164
+ }
165
+ }
166
+
167
+ void feed(uint8_t octet) {
168
+ if (index_ >= buffer_.size()) {
169
+ reset();
170
+ return;
171
+ }
172
+ buffer_[index_] = octet;
173
+ if (state_ == State::Idle) {
174
+ state_ = State::UntilLength;
175
+ fill_ = 0;
176
+ }
177
+ if (state_ == State::UntilLength) {
178
+ bool header_complete = false;
179
+ if constexpr (Format == SxiHeaderFormat::LenByte) {
180
+ if (index_ == 0) {
181
+ dlc_ = buffer_[0];
182
+ remaining_ = dlc_;
183
+ header_complete = true;
184
+ }
185
+ } else if constexpr (Format == SxiHeaderFormat::LenCtrByte || Format == SxiHeaderFormat::LenFillByte) {
186
+ if (index_ == 1) {
187
+ dlc_ = buffer_[0];
188
+ if constexpr (Format == SxiHeaderFormat::LenCtrByte) {
189
+ ctr_ = buffer_[1];
190
+ }
191
+ remaining_ = dlc_;
192
+ header_complete = true;
193
+ }
194
+ } else if constexpr (Format == SxiHeaderFormat::LenWord) {
195
+ if (index_ == 1) {
196
+ dlc_ = detail::make_word_le(&buffer_[0]);
197
+ remaining_ = dlc_;
198
+ header_complete = true;
199
+ }
200
+ } else if constexpr (Format == SxiHeaderFormat::LenCtrWord || Format == SxiHeaderFormat::LenFillWord) {
201
+ if (index_ == 3) {
202
+ dlc_ = detail::make_word_le(&buffer_[0]);
203
+ if constexpr (Format == SxiHeaderFormat::LenCtrWord) {
204
+ ctr_ = detail::make_word_le(&buffer_[2]);
205
+ }
206
+ remaining_ = dlc_;
207
+ header_complete = true;
208
+ }
209
+ }
210
+ if (header_complete) {
211
+ if constexpr (Checksum == SxiChecksumType::Sum8) {
212
+ remaining_ += 1;
213
+ } else if constexpr (Checksum == SxiChecksumType::Sum16) {
214
+ uint16_t header_size = 0;
215
+ if constexpr (Format == SxiHeaderFormat::LenByte) header_size = 1;
216
+ else if constexpr (Format == SxiHeaderFormat::LenCtrByte || Format == SxiHeaderFormat::LenFillByte) header_size = 2;
217
+ else if constexpr (Format == SxiHeaderFormat::LenWord) header_size = 2;
218
+ else if constexpr (Format == SxiHeaderFormat::LenCtrWord || Format == SxiHeaderFormat::LenFillWord) header_size = 4;
219
+
220
+ fill_ = ((header_size + dlc_) % 2 != 0) ? 1u : 0u;
221
+ remaining_ += (2 + fill_);
222
+ }
223
+ state_ = State::Remaining;
224
+ if (remaining_ != 0) {
225
+ index_++;
226
+ return;
227
+ }
228
+ }
229
+ }
230
+ if (state_ == State::Remaining) {
231
+ if (remaining_ > 0) {
232
+ remaining_--;
233
+ }
234
+ if (remaining_ == 0) {
235
+ uint16_t payload_off = 0;
236
+ if constexpr (Format == SxiHeaderFormat::LenByte) {
237
+ payload_off = 1;
238
+ } else if constexpr (Format == SxiHeaderFormat::LenCtrByte || Format == SxiHeaderFormat::LenFillByte) {
239
+ payload_off = 2;
240
+ } else if constexpr (Format == SxiHeaderFormat::LenWord) {
241
+ payload_off = 2;
242
+ } else if constexpr (Format == SxiHeaderFormat::LenCtrWord || Format == SxiHeaderFormat::LenFillWord) {
243
+ payload_off = 4;
244
+ }
245
+
246
+ // verify checksum
247
+ if constexpr (Checksum == SxiChecksumType::Sum8) {
248
+ uint8_t sum = 0;
249
+ for (uint16_t i = 0; i < (payload_off + dlc_ + fill_); ++i) {
250
+ sum += buffer_[i];
251
+ }
252
+ uint8_t rx = buffer_[payload_off + dlc_];
253
+ if (sum != rx) {
254
+ log_checksum_error(sum, rx, payload_off + dlc_ + 1);
255
+ reset();
256
+ return;
257
+ }
258
+ } else if constexpr (Checksum == SxiChecksumType::Sum16) {
259
+ uint16_t count = (payload_off + dlc_ + fill_);
260
+ uint16_t sum = 0;
261
+
262
+ for (uint16_t idx = 0; idx < count; idx += 2) {
263
+ sum = static_cast<uint16_t>(sum + detail::make_word_le(&buffer_[idx]));
264
+ }
265
+ uint16_t rx = detail::make_word_le(&buffer_[payload_off + dlc_ + fill_]);
266
+ if (sum != rx) {
267
+ log_checksum_error(sum, rx, payload_off + dlc_ + fill_ + 2);
268
+ reset();
269
+ return;
270
+ }
271
+ }
272
+ if (dispatch_) {
273
+ dispatch_({ buffer_.data() + payload_off, buffer_.data() + payload_off + dlc_ }, dlc_, ctr_);
274
+ #if defined(XCP_TL_TEST_HOOKS)
275
+ std::fill(buffer_.begin(), buffer_.end(), 0xcc);
276
+ #endif
277
+ }
278
+ reset();
279
+ return;
280
+ }
281
+ }
282
+ index_++;
283
+ }
284
+
285
+ private:
286
+
287
+ enum class State {
288
+ Idle,
289
+ UntilLength,
290
+ Remaining
291
+ };
292
+
293
+ template<typename T>
294
+ void log_checksum_error(T calculated, T received, uint16_t packet_len) {
295
+ std::cerr << "SXI checksum error: Calculated " << std::hex << "0x" << static_cast<int>(calculated)
296
+ << ", but received " << "0x" << static_cast<int>(received) << "." << std::dec << std::endl;
297
+ std::cerr << "Packet dump (" << packet_len << " bytes):" << std::endl;
298
+ std::cerr << "[";
299
+ std::ios_base::fmtflags flags(std::cerr.flags()); // save flags
300
+ for (uint16_t i = 0; i < packet_len; ++i) {
301
+ std::cerr << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(buffer_[i]) << " ";
302
+ if ((i + 1) % 16 == 0) {
303
+ std::cerr << std::endl;
304
+ }
305
+ }
306
+ std::cerr << "]" << std::endl;
307
+ std::cerr.flags(flags); // restore flags
308
+ }
309
+
310
+ void reset() {
311
+ state_ = State::Idle;
312
+ index_ = 0;
313
+ dlc_ = 0;
314
+ remaining_ = 0;
315
+ ctr_ = 0;
316
+ fill_ = 0;
317
+ #if defined(XCP_TL_TEST_HOOKS)
318
+ std::fill(buffer_.begin(), buffer_.end(), 0xcc);
319
+ #endif
320
+ }
321
+
322
+ std::array<uint8_t, 1024> buffer_{};
323
+ State state_{ State::Idle };
324
+ uint32_t index_{ 0 };
325
+ uint16_t dlc_{ 0 };
326
+ uint16_t ctr_{ 0 };
327
+ uint32_t remaining_{ 0 };
328
+ uint16_t fill_ {0};
329
+ std::function<void(const std::vector<uint8_t>&, uint16_t, uint16_t)> dispatch_;
330
+ };
331
+
332
+ #endif // __SXI_FRAMING_HPP
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env python
2
2
 
3
- # from pprint import pprint
3
+ from contextlib import suppress
4
+ import json
4
5
  from time import time_ns
5
- from typing import Dict, List, Optional, TextIO
6
+ from typing import Any, Dict, List, Optional, TextIO, Tuple, Union
7
+
8
+ from pyxcp.cpp_ext.cpp_ext import DaqList, PredefinedDaqList
6
9
 
7
10
  from pyxcp import types
8
11
  from pyxcp.config import get_application
9
- from pyxcp.cpp_ext.cpp_ext import DaqList
10
12
  from pyxcp.daq_stim.optimize import make_continuous_blocks
11
13
  from pyxcp.daq_stim.optimize.binpacking import first_fit_decreasing
12
14
  from pyxcp.recorder import DaqOnlinePolicy as _DaqOnlinePolicy
@@ -14,7 +16,6 @@ from pyxcp.recorder import DaqRecorderPolicy as _DaqRecorderPolicy
14
16
  from pyxcp.recorder import MeasurementParameters
15
17
  from pyxcp.utils import CurrentDatetime
16
18
 
17
-
18
19
  DAQ_ID_FIELD_SIZE = {
19
20
  "IDF_ABS_ODT_NUMBER": 1,
20
21
  "IDF_REL_ODT_NUMBER_ABS_DAQ_LIST_NUMBER_BYTE": 2,
@@ -29,9 +30,87 @@ DAQ_TIMESTAMP_SIZE = {
29
30
  }
30
31
 
31
32
 
33
+ def load_daq_lists_from_json(file_path: str) -> List[DaqList]:
34
+ """Load and validate DAQ-list from JSON file."""
35
+ with open(file_path, "r", encoding="utf-8") as f:
36
+ config = json.load(f)
37
+
38
+ if not isinstance(config, list):
39
+ raise ValueError("DAQ configuration must be a JSON array (list)")
40
+
41
+ daq_lists: List[DaqList] = []
42
+ for idx, entry in enumerate(config):
43
+ if not isinstance(entry, dict):
44
+ raise TypeError(f"Entry {idx} must be an object/dict")
45
+
46
+ required = {"name", "event_num", "stim", "enable_timestamps", "measurements", "priority", "prescaler"}
47
+ missing = required - set(entry.keys())
48
+ if missing:
49
+ raise ValueError(f"Entry {idx} missing required keys: {missing}")
50
+
51
+ # Basic type conversions / checks
52
+ name = entry["name"]
53
+ if not isinstance(name, str):
54
+ raise TypeError(f"Entry {idx} 'name' must be a string")
55
+
56
+ try:
57
+ event_num = int(entry["event_num"])
58
+ except Exception as e:
59
+ raise TypeError(f"Entry {idx} 'event_num' must be an integer") from e
60
+
61
+ stim = bool(entry["stim"])
62
+ enable_timestamps = bool(entry["enable_timestamps"])
63
+
64
+ try:
65
+ priority = int(entry["priority"])
66
+ prescaler = int(entry["prescaler"])
67
+ except Exception as e:
68
+ raise TypeError(f"Entry {idx} 'priority' and 'prescaler' must be integers") from e
69
+
70
+ measurements_raw = entry["measurements"]
71
+ if not isinstance(measurements_raw, list):
72
+ raise TypeError(f"Entry {idx} 'measurements' must be a list")
73
+
74
+ measurements: List[Tuple[str, int, int, str]] = []
75
+ for m_idx, m in enumerate(measurements_raw):
76
+ if not (isinstance(m, (list, tuple)) and len(m) == 4):
77
+ raise ValueError(f"Entry {idx} measurement {m_idx} must be a 4-element list/tuple")
78
+ m_name, m_addr, m_offset, m_type = m
79
+
80
+ if not isinstance(m_name, str):
81
+ raise TypeError(f"Entry {idx} measurement {m_idx} name must be a string")
82
+ try:
83
+ m_addr = int(m_addr)
84
+ except Exception as e:
85
+ raise TypeError(f"Entry {idx} measurement {m_idx} address must be an integer") from e
86
+ try:
87
+ m_offset = int(m_offset)
88
+ except Exception as e:
89
+ raise TypeError(f"Entry {idx} measurement {m_idx} offset must be an integer") from e
90
+ if not isinstance(m_type, str):
91
+ raise TypeError(f"Entry {idx} measurement {m_idx} type must be a string")
92
+
93
+ measurements.append((m_name, m_addr, m_offset, m_type))
94
+
95
+ daq_kwargs: Dict[str, Any] = {
96
+ "name": name,
97
+ "event_num": event_num,
98
+ "stim": stim,
99
+ "enable_timestamps": enable_timestamps,
100
+ "measurements": measurements,
101
+ "priority": priority,
102
+ "prescaler": prescaler,
103
+ }
104
+
105
+ daq_lists.append(DaqList(**daq_kwargs))
106
+
107
+ return daq_lists
108
+
109
+
32
110
  class DaqProcessor:
33
- def __init__(self, daq_lists: List[DaqList]):
111
+ def __init__(self, daq_lists: List[Union[DaqList, PredefinedDaqList]]):
34
112
  self.daq_lists = daq_lists
113
+ self.is_predefined = [isinstance(d, PredefinedDaqList) for d in daq_lists]
35
114
  self.log = get_application().log
36
115
  # Flag indicating a fatal OS-level error occurred during DAQ (e.g., disk full, out-of-memory)
37
116
  self._fatal_os_error: bool = False
@@ -43,13 +122,14 @@ class DaqProcessor:
43
122
  if start_datetime is None:
44
123
  start_datetime = CurrentDatetime(time_ns())
45
124
  self.start_datetime = start_datetime
46
- # print(self.start_datetime)
47
125
  try:
48
126
  processor = self.daq_info.get("processor")
49
127
  properties = processor.get("properties")
50
128
  resolution = self.daq_info.get("resolution")
51
- if properties["configType"] == "STATIC":
52
- raise TypeError("DAQ configuration is static, cannot proceed.")
129
+ if properties["configType"] == "STATIC" and not all(self.is_predefined):
130
+ raise TypeError(
131
+ "DAQ configuration is static, but in your configuration are only dynamic DAQ lists -- cannot proceed."
132
+ )
53
133
  self.supports_timestampes = properties["timestampSupported"]
54
134
  self.supports_prescaler = properties["prescalerSupported"]
55
135
  self.supports_pid_off = properties["pidOffSupported"]
@@ -72,22 +152,24 @@ class DaqProcessor:
72
152
  max_payload_size = min(max_odt_entry_size, max_dto - header_len)
73
153
  # First ODT may contain timestamp.
74
154
  self.selectable_timestamps = False
155
+ max_payload_size_first = max_payload_size
75
156
  if not self.supports_timestampes:
76
- max_payload_size_first = max_payload_size
77
- # print("NO TIMESTAMP SUPPORT")
157
+ self.log.info("No timestamp support")
78
158
  else:
79
159
  if self.ts_fixed:
80
- # print("Fixed timestamp")
160
+ self.log.debug("Fixed timestamps")
81
161
  max_payload_size_first = max_payload_size - self.ts_size
82
162
  else:
83
- # print("timestamp variable.")
163
+ self.log.debug("Variable timestamps.")
84
164
  self.selectable_timestamps = True
85
-
86
165
  except Exception as e:
87
166
  raise TypeError(f"DAQ_INFO corrupted: {e}") from e
88
167
 
89
168
  # DAQ optimization.
90
- for daq_list in self.daq_lists:
169
+ # For dynamic DaqList instances, compute physical layout; skip for PredefinedDaqList.
170
+ for idx, daq_list in enumerate(self.daq_lists):
171
+ if isinstance(daq_list, PredefinedDaqList):
172
+ continue
91
173
  if self.selectable_timestamps:
92
174
  if daq_list.enable_timestamps:
93
175
  max_payload_size_first = max_payload_size - self.ts_size
@@ -98,34 +180,47 @@ class DaqProcessor:
98
180
  byte_order = 0 if self.xcp_master.slaveProperties.byteOrder == "INTEL" else 1
99
181
  self._first_pids = []
100
182
  daq_count = len(self.daq_lists)
101
- self.xcp_master.freeDaq()
102
-
103
- # Allocate
104
- self.xcp_master.allocDaq(daq_count)
105
- measurement_list = []
106
- for i, daq_list in enumerate(self.daq_lists, self.min_daq):
107
- measurements = daq_list.measurements_opt
108
- measurement_list.append((i, measurements))
109
- odt_count = len(measurements)
110
- self.xcp_master.allocOdt(i, odt_count)
111
- # Iterate again over ODT entries -- we need to respect sequencing requirements.
112
- for i, measurements in measurement_list:
113
- for j, measurement in enumerate(measurements):
114
- entry_count = len(measurement.entries)
115
- self.xcp_master.allocOdtEntry(i, j, entry_count)
116
- # Write DAQs
117
- for i, daq_list in enumerate(self.daq_lists, self.min_daq):
118
- measurements = daq_list.measurements_opt
119
- for j, measurement in enumerate(measurements):
120
- if len(measurement.entries) == 0:
121
- continue # CAN special case: No room for data in first ODT.
122
- self.xcp_master.setDaqPtr(i, j, 0)
123
- for entry in measurement.entries:
124
- self.xcp_master.writeDaq(0xFF, entry.length, entry.ext, entry.address)
183
+
184
+ # Decide whether DAQ allocation must be performed.
185
+ config_static = self.daq_info.get("processor", {}).get("properties", {}).get("configType") == "STATIC"
186
+
187
+ if not config_static:
188
+ # For dynamic configuration, program only dynamic (non-predefined) DAQ lists.
189
+ self.xcp_master.freeDaq()
190
+ # Allocate the number of DAQ lists required.
191
+ self.xcp_master.allocDaq(daq_count)
192
+ measurement_list = []
193
+ for i, daq_list in enumerate(self.daq_lists, self.min_daq):
194
+ if isinstance(daq_list, PredefinedDaqList):
195
+ # Skip allocation for predefined DAQ lists.
196
+ continue
197
+ measurements = daq_list.measurements_opt
198
+ measurement_list.append((i, measurements))
199
+ odt_count = len(measurements)
200
+ self.xcp_master.allocOdt(i, odt_count)
201
+ # Iterate again over ODT entries -- we need to respect sequencing requirements.
202
+ for i, measurements in measurement_list:
203
+ for j, measurement in enumerate(measurements):
204
+ entry_count = len(measurement.entries)
205
+ self.xcp_master.allocOdtEntry(i, j, entry_count)
206
+ # Write DAQs (only for dynamic lists)
207
+ for i, daq_list in enumerate(self.daq_lists, self.min_daq):
208
+ if isinstance(daq_list, PredefinedDaqList):
209
+ continue
210
+ measurements = daq_list.measurements_opt
211
+ for j, measurement in enumerate(measurements):
212
+ if len(measurement.entries) == 0:
213
+ continue # CAN special case: No room for data in first ODT.
214
+ self.xcp_master.setDaqPtr(i, j, 0)
215
+ for entry in measurement.entries:
216
+ self.xcp_master.writeDaq(0xFF, entry.length, entry.ext, entry.address)
217
+ else:
218
+ # STATIC configuration on the slave: skip allocation and programming; lists/ODTs are predefined.
219
+ pass
125
220
 
126
221
  # arm DAQ lists -- this is technically a function on its own.
127
- for i, daq_list in enumerate(self.daq_lists, self.min_daq):
128
- # print(daq_list.name, daq_list.event_num, daq_list.stim)
222
+ first_daq_list = 0 if config_static else self.min_daq
223
+ for i, daq_list in enumerate(self.daq_lists, first_daq_list):
129
224
  mode = 0x00
130
225
  if self.supports_timestampes and (self.ts_fixed or (self.selectable_timestamps and daq_list.enable_timestamps)):
131
226
  mode = 0x10
@@ -143,8 +238,6 @@ class DaqProcessor:
143
238
  )
144
239
  res = self.xcp_master.startStopDaqList(0x02, i)
145
240
  self._first_pids.append(res.firstPid)
146
- if start_datetime:
147
- pass
148
241
  self.measurement_params = MeasurementParameters(
149
242
  byte_order,
150
243
  header_len,
@@ -168,26 +261,21 @@ class DaqProcessor:
168
261
  # If a fatal OS error occurred during acquisition, skip sending stop to the slave to avoid
169
262
  # cascading timeouts/unrecoverable errors and shut down transport gracefully instead.
170
263
  if getattr(self, "_fatal_os_error", False):
171
- try:
264
+ with suppress(Exception):
172
265
  self.log.error(
173
266
  "DAQ stop skipped due to previous fatal OS error (e.g., disk full or out-of-memory). Closing transport."
174
267
  )
175
- except Exception:
176
- pass
177
268
  try:
178
269
  # Best-effort: stop listener and close transport so threads finish cleanly.
179
270
  if hasattr(self.xcp_master, "transport") and self.xcp_master.transport is not None:
180
271
  # Signal listeners to stop
181
- try:
272
+ with suppress(Exception):
182
273
  if hasattr(self.xcp_master.transport, "closeEvent"):
183
274
  self.xcp_master.transport.closeEvent.set()
184
- except Exception:
185
- pass
275
+
186
276
  # Close transport connection
187
- try:
277
+ with suppress(Exception):
188
278
  self.xcp_master.transport.close()
189
- except Exception:
190
- pass
191
279
  finally:
192
280
  return
193
281
  self.xcp_master.startStopSynch(0x00)
@@ -197,7 +285,6 @@ class DaqProcessor:
197
285
 
198
286
 
199
287
  class DaqRecorder(DaqProcessor, _DaqRecorderPolicy):
200
-
201
288
  def __init__(self, daq_lists: List[DaqList], file_name: str, prealloc: int = 200, chunk_size: int = 1):
202
289
  DaqProcessor.__init__(self, daq_lists)
203
290
  _DaqRecorderPolicy.__init__(self)
@@ -253,30 +340,21 @@ class DaqToCsv(DaqOnlinePolicy):
253
340
  except (OSError, MemoryError) as ex:
254
341
  # Mark fatal condition to alter shutdown path and avoid further writes/commands.
255
342
  self._fatal_os_error = True
256
- try:
343
+ with suppress(Exception):
257
344
  self.log.critical(f"DAQ file write failed: {ex.__class__.__name__}: {ex}. Initiating graceful shutdown.")
258
- except Exception:
259
- pass
345
+
260
346
  # Stop listener to prevent more DAQ traffic and avoid thread crashes.
261
- try:
347
+ with suppress(Exception):
262
348
  if hasattr(self.xcp_master, "transport") and self.xcp_master.transport is not None:
263
349
  if hasattr(self.xcp_master.transport, "closeEvent"):
264
350
  self.xcp_master.transport.closeEvent.set()
265
- except Exception:
266
- pass
267
351
  # Best-effort: close any opened files to flush buffers and release resources.
268
- try:
352
+ with suppress(Exception):
269
353
  for f in getattr(self, "files", {}).values():
270
- try:
354
+ with suppress(Exception):
271
355
  f.flush()
272
- except Exception:
273
- pass
274
- try:
356
+ with suppress(Exception):
275
357
  f.close()
276
- except Exception:
277
- pass
278
- except Exception:
279
- pass
280
358
  # Do not re-raise; allow the system to continue to a controlled shutdown.
281
359
  return
282
360
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
- """Bin-packing algorithms.
3
- """
2
+ """Bin-packing algorithms."""
3
+
4
4
  from typing import List, Optional
5
5
 
6
6
  from pyxcp.cpp_ext.cpp_ext import Bin