FastLED 3.10.9
Loading...
Searching...
No Matches
AutoResearchLegacyAsyncErrors.h
Go to the documentation of this file.
1#pragma once
2
3#include "FastLED.h"
5#include "fl/channels/data.h"
8#include "fl/stl/chrono.h"
9#include "fl/stl/json.h"
10#include "fl/stl/move.h"
11#include "fl/stl/shared_ptr.h"
12#include "fl/stl/vector.h"
13
14#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES && FL_RMT4_ASYNC // ok platform specific - fault fixtures exercise the actual RMT4 worker and shared hardware ledger.
15#include "platforms/esp/32/drivers/rmt/rmt_4/rmt4_async_driver.h" // ok platform headers; ok platform specific - no generic interface can construct an isolated RMT4 worker for failure tests.
16#include "platforms/esp/32/drivers/rmt/rmt_4/rmt4_peripheral_esp.h" // ok platform headers; ok platform specific - these fixtures use the real peripheral, not a mock SDK.
17#include "platforms/esp/32/drivers/rmt/rmt_4/rmt_memory_manager_4.h" // ok platform headers; ok platform specific - physical block exhaustion must share the actual TX/RX allocator.
18#endif
19
20namespace autoresearch {
21
22#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES && FL_RMT4_ASYNC // ok platform specific - these hardware failure fixtures require classic ESP32's RMT4 worker.
23namespace legacy_async_errors {
24
25#if defined(FL_RMT4_FAULT_TEST) && FL_RMT4_FAULT_TEST == 1 // ok platform specific - dedicated firmware links one-shot SDK wrappers; production has no fault hooks.
26enum class Fault { None, TaskAllocation, MutexAllocation, DriverInstall };
27void armFault(Fault fault) FL_NO_EXCEPT;
28void disarmFault() FL_NO_EXCEPT;
29unsigned consumedFaults() FL_NO_EXCEPT;
30#endif
31
32// Reserve only blocks that were free on entry. Existing TX/RX owners keep
33// their reservations, and every fixture reservation is restored on exit.
34class FreeBlockReservations {
35public:
36 explicit FreeBlockReservations(fl::RmtMemoryManager4 &manager) // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
37 : mManager(manager) {
38 const size_t count = manager.poolSize() / manager.wordsPerBlock(); // ok platform specific - size exhaustion reservations from real RMT4 memory geometry; no neutral allocator exposes it.
39 for (size_t block = 0; block < count && block < 32; ++block) {
40 if (manager.tryReserveBlocks(static_cast<int>(block), 1)) { // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
41 mReserved |= fl::u32(1) << block;
42 ++mCount;
43 }
44 }
45 }
46 ~FreeBlockReservations() {
47 for (unsigned block = 0; block < 32; ++block) {
48 if (mReserved & (fl::u32(1) << block)) {
49 mManager.releaseBlocks(static_cast<int>(block), 1); // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
50 }
51 }
52 }
53 unsigned count() const { return mCount; }
54 fl::u32 mask() const { return mReserved; }
55 FreeBlockReservations(const FreeBlockReservations &) = delete;
56 FreeBlockReservations &operator=(const FreeBlockReservations &) = delete;
57
58private:
59 fl::RmtMemoryManager4 &mManager; // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
60 fl::u32 mReserved = 0;
61 unsigned mCount = 0;
62};
63
64inline fl::ChannelDataPtr makeFrame(size_t leds) {
65 // GPIO4 is an established classic ESP32 AutoResearch output pad.
66 fl::vector_psram<fl::u8> bytes(leds * 3, 0xFF);
67 if (bytes.size() != leds * 3) { return {}; }
71}
72
73inline void passiveWindow(fl::u32 durationUs) {
74 const fl::u32 started = fl::micros();
75 while (fl::micros() - started < durationUs) {
76 // No application polling, yielding or waiting services the driver.
77 }
78}
79
80struct Observation {
81 bool leasedAtSubmission = false;
82 bool releasedAutonomously = false;
83 bool errorReported = false;
84 bool completionSucceeded = false;
85 fl::u32 translatedBytes = 0;
86 fl::u32 workerStackUnusedBytes = 0;
87};
88
89inline Observation submit(fl::Rmt4AsyncDriver &driver, // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
90 const fl::ChannelDataPtr &frame) {
91 Observation result;
92 const fl::u32 before = driver.txDiagnostics().cumulativeTranslatedBytes; // ok platform specific - observe the selected RMT4 worker internals without adding a generic driver diagnostic API.
93 driver.enqueue(frame);
94 result.leasedAtSubmission = frame->isInUse();
95 driver.show();
96 passiveWindow(250000);
97 // Observe release before calling either service/completion API.
98 result.releasedAutonomously = !frame->isInUse();
99 result.errorReported = driver.poll().state ==
101 result.completionSucceeded = driver.waitDone(1000); // ok platform specific - verify isolated RMT4 worker completion after injected failure; no neutral factory creates this worker.
102 result.translatedBytes = driver.txDiagnostics().cumulativeTranslatedBytes - before; // ok platform specific - observe the selected RMT4 worker internals without adding a generic driver diagnostic API.
103 result.workerStackUnusedBytes = driver.workerStackHighWaterBytes(); // ok platform specific - observe the selected RMT4 worker internals without adding a generic driver diagnostic API.
104 return result;
105}
106
107inline void appendObservation(fl::json &result, const Observation &observed) {
108 result.set("leasedAtSubmission", observed.leasedAtSubmission);
109 result.set("releasedWithoutApplicationPolling", observed.releasedAutonomously);
110 result.set("errorReported", observed.errorReported);
111 result.set("completionSucceeded", observed.completionSucceeded);
112 result.set("translatedBytes", static_cast<int>(observed.translatedBytes));
113 result.set("workerStackUnusedBytes", static_cast<int>(observed.workerStackUnusedBytes));
114}
115
116inline bool recoverySucceeded(fl::Rmt4AsyncDriver &driver) { // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
117 auto recovery = makeFrame(1);
118 if (!recovery) { return false; }
119 const auto observed = submit(driver, recovery);
120 return observed.leasedAtSubmission && observed.releasedAutonomously &&
121 !observed.errorReported && observed.completionSucceeded &&
122 (!FL_RMT4_DIAGNOSTICS || observed.translatedBytes == 3); // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
123}
124
125#if defined(FL_RMT4_FAULT_TEST) && FL_RMT4_FAULT_TEST == 1 // ok platform specific - only fault-injection firmware references the linker wrapper control symbols.
126inline fl::json injectedFailureProbe(const fl::string &mode) {
127 fl::json result = fl::json::object();
128 result.set("mode", mode);
129 auto &manager = fl::RmtMemoryManager4::instance(); // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
130 const size_t baseline = manager.allocatedWords(); // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
131 auto frame = makeFrame(1);
132 if (!frame) {
133 result.set("success", false);
134 result.set("error", "Fixture frame allocation failed");
135 return result;
136 }
137 Observation observed;
138 bool recovered = false;
139 unsigned consumed = 0;
140 if (mode == "mutex-allocation") {
141 // Only this constructor runs while the next mutex allocation is armed.
142 auto peripheral = fl::make_shared<fl::Rmt4PeripheralESP>(); // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
143 armFault(Fault::MutexAllocation);
144 {
145 fl::Rmt4AsyncDriver driver(peripheral); // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
146 consumed = consumedFaults();
147 disarmFault();
148 observed = submit(driver, frame);
149 }
150 // A driver missing its construction-time mutex cannot recover in place.
151 fl::Rmt4AsyncDriver replacement(fl::make_shared<fl::Rmt4PeripheralESP>()); // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
152 recovered = recoverySucceeded(replacement);
153 } else {
154 fl::Rmt4AsyncDriver driver(fl::make_shared<fl::Rmt4PeripheralESP>()); // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
155 armFault(mode == "task-allocation" ? Fault::TaskAllocation : Fault::DriverInstall);
156 observed = submit(driver, frame);
157 consumed = consumedFaults();
158 disarmFault();
159 recovered = recoverySucceeded(driver);
160 }
161 const size_t after = manager.allocatedWords(); // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
162 const bool expectedLease = mode == "mutex-allocation" ?
163 !observed.leasedAtSubmission : observed.leasedAtSubmission;
164 appendObservation(result, observed);
165 result.set("injectedFailuresConsumed", static_cast<int>(consumed));
166 result.set("allocatedWordsBefore", static_cast<int>(baseline));
167 result.set("allocatedWordsAfter", static_cast<int>(after));
168 result.set("ledgerRestored", baseline == after);
169 result.set("recovered", recovered);
170 result.set("recoveredOnSameDriver", mode != "mutex-allocation" && recovered);
171 result.set("success", consumed == 1 && expectedLease &&
172 observed.releasedAutonomously && observed.errorReported &&
173 !observed.completionSucceeded && recovered && baseline == after);
174 return result;
175}
176#endif
177
178} // namespace legacy_async_errors
179#endif
180
181// Modes unavailable/setup use the normal firmware. Timeout/recovery requires
182// a dedicated build with FASTLED_RMT4_TRANSMISSION_TIMEOUT_MS=1.
184 fl::json result = fl::json::object();
185#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES && FL_RMT4_ASYNC // ok platform specific - native and other-backend builds return an explicit unsupported result.
186 using namespace legacy_async_errors;
187 const fl::string mode = args["mode"].as_string().value_or("unavailable");
188 result.set("mode", mode);
189 const bool injected = mode == "task-allocation" ||
190 mode == "mutex-allocation" || mode == "install";
191 const bool recoveryMode = mode == "timeout" || mode == "invalid-batch";
192 if (mode != "unavailable" && mode != "setup" && !recoveryMode && !injected) {
193 result.set("success", false);
194 result.set("error", "Unknown legacy asynchronous failure fixture mode");
195 return result;
196 }
197 if (mode == "timeout" &&
198 (FASTLED_RMT4_TRANSMISSION_TIMEOUT_MS != 1 || !FL_RMT4_DIAGNOSTICS)) { // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
199 result.set("success", false);
200 result.set("error", "timeout fixture requires FASTLED_RMT4_TRANSMISSION_TIMEOUT_MS=1 and FL_RMT4_DIAGNOSTICS=1");
201 return result;
202 }
203 if (mode == "invalid-batch" && !FL_RMT4_DIAGNOSTICS) { // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
204 result.set("success", false);
205 result.set("error", "invalid-batch fixture requires FL_RMT4_DIAGNOSTICS=1");
206 return result;
207 }
208 if (!FastLED.wait(1000)) {
209 result.set("success", false);
210 result.set("error", "Existing output did not drain before fixture");
211 return result;
212 }
213 if (injected) {
214#if defined(FL_RMT4_FAULT_TEST) && FL_RMT4_FAULT_TEST == 1 // ok platform specific - injected failures require dedicated wrapper-linked test firmware.
215 return injectedFailureProbe(mode);
216#else
217 result.set("success", false);
218 result.set("error", "Requires FL_RMT4_FAULT_TEST=1 and the documented SDK linker wrappers");
219 return result;
220#endif
221 }
222 auto &manager = fl::RmtMemoryManager4::instance(); // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
223 const size_t baseline = manager.allocatedWords(); // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
224 auto frame = makeFrame(mode == "timeout" ? 4000 : 1);
225 if (!frame) {
226 result.set("success", false);
227 result.set("error", "Fixture frame allocation failed");
228 return result;
229 }
230 Observation observed;
231 bool recovered = false;
232 unsigned reserved = 0;
233 fl::u32 reservationMask = 0;
234 if (mode == "unavailable") {
235 FreeBlockReservations reservations(manager);
236 reserved = reservations.count();
237 reservationMask = reservations.mask();
238 fl::Rmt4AsyncDriver driver(fl::make_shared<fl::Rmt4PeripheralESP>()); // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
239 observed = submit(driver, frame);
240 } else if (mode == "setup") {
241 // A missing real peripheral exercises the existing setup-failure path;
242 // no SDK return value or production behavior is replaced by a mock.
243 fl::Rmt4AsyncDriver driver{fl::shared_ptr<fl::detail::IRMT4Peripheral>()}; // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
244 observed = submit(driver, frame);
245 } else {
246 fl::Rmt4AsyncDriver driver(fl::make_shared<fl::Rmt4PeripheralESP>()); // ok platform specific - construct an isolated RMT4 worker for backend-specific failure recovery, unavailable through the neutral factory.
247 if (mode == "invalid-batch") {
248 // The bridge rejects an unencodable strip this way. A subsequent
249 // valid strip must not erase that failure from the same batch.
250 driver.enqueue(fl::ChannelDataPtr());
251 }
252 observed = submit(driver, frame);
253 recovered = recoverySucceeded(driver);
254 }
255 const size_t after = manager.allocatedWords(); // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
256 bool reservationsRestored = true;
257 for (unsigned block = 0; block < 32; ++block) {
258 if (reservationMask & (fl::u32(1) << block)) {
259 const bool available = manager.tryReserveBlocks(static_cast<int>(block), 1); // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
260 reservationsRestored &= available;
261 if (available) { manager.releaseBlocks(static_cast<int>(block), 1); } // ok platform specific - reserve and audit real RMT4 hardware blocks for exhaustion recovery; no neutral allocator exposes them.
262 }
263 }
264 appendObservation(result, observed);
265 result.set("reservedFreeBlocks", static_cast<int>(reserved));
266 result.set("allocatedWordsBefore", static_cast<int>(baseline));
267 result.set("allocatedWordsAfter", static_cast<int>(after));
268 result.set("ledgerRestored", baseline == after);
269 result.set("fixtureReservationsRestored", reservationsRestored);
270 result.set("recoveryAttempted", recoveryMode);
271 result.set("recoveredOnSameDriver", recoveryMode && recovered);
272 result.set("success", observed.leasedAtSubmission &&
273 observed.releasedAutonomously && observed.errorReported &&
274 !observed.completionSucceeded && baseline == after &&
275 (mode != "timeout" ||
276 (observed.translatedBytes > 0 && observed.translatedBytes < frame->getSize())) &&
277 (mode != "invalid-batch" || observed.translatedBytes == 3) &&
278 reservationsRestored && (!recoveryMode || recovered));
279#else
280 (void)args;
281 result.set("success", false);
282 result.set("error", "Requires asynchronous classic ESP32 RMT4 output");
283#endif
284 return result;
285}
286
287} // namespace autoresearch
CRGB leds[1]
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
Runtime chipset timing configuration for clockless LED drivers.
FastLED chrono implementation - duration types for time measurements.
static ChannelDataPtr create(const ChipsetVariant &chipset, fl::vector_psram< u8 > &&encodedData=fl::vector_psram< u8 >(), ChannelPixelFormat pixelFormat=ChannelPixelFormat::RGB) FL_NO_EXCEPT
Create channel transmission data (modern variant-based API)
Definition data.cpp.hpp:12
static json object() FL_NO_EXCEPT
Definition json.h:864
Channel transmission data - lightweight DTO for driver transmission.
FastLED's Elegant JSON Library: fl::json
Centralized LED chipset timing definitions with nanosecond precision.
fl::json legacyAsyncErrorProbe(const fl::json &args)
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:30
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
Definition shared_ptr.h:423
@ None
Raw magnitudes (no scaling)
Definition equalizer.h:18
constexpr ChipsetTimingConfig makeTimingConfig() FL_NO_EXCEPT
Convert compile-time CHIPSET type to runtime timing config.
fl::u32 micros() FL_NO_EXCEPT
Universal microsecond timer - returns microseconds since system startup.
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
corkscrew_args args
Definition old.h:149
#define FL_NO_EXCEPT
@ ERROR
Driver encountered an error.
Definition driver.h:173