14#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES && FL_RMT4_ASYNC
15#include "platforms/esp/32/drivers/rmt/rmt_4/rmt4_async_driver.h"
16#include "platforms/esp/32/drivers/rmt/rmt_4/rmt4_peripheral_esp.h"
17#include "platforms/esp/32/drivers/rmt/rmt_4/rmt_memory_manager_4.h"
22#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES && FL_RMT4_ASYNC
23namespace legacy_async_errors {
25#if defined(FL_RMT4_FAULT_TEST) && FL_RMT4_FAULT_TEST == 1
26enum class Fault {
None, TaskAllocation, MutexAllocation, DriverInstall };
34class FreeBlockReservations {
36 explicit FreeBlockReservations(fl::RmtMemoryManager4 &manager)
38 const size_t count = manager.poolSize() / manager.wordsPerBlock();
39 for (
size_t block = 0; block < count && block < 32; ++block) {
40 if (manager.tryReserveBlocks(
static_cast<int>(block), 1)) {
41 mReserved |= fl::u32(1) << block;
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);
53 unsigned count()
const {
return mCount; }
54 fl::u32 mask()
const {
return mReserved; }
55 FreeBlockReservations(
const FreeBlockReservations &) =
delete;
56 FreeBlockReservations &operator=(
const FreeBlockReservations &) =
delete;
59 fl::RmtMemoryManager4 &mManager;
60 fl::u32 mReserved = 0;
64inline fl::ChannelDataPtr makeFrame(
size_t leds) {
66 fl::vector_psram<fl::u8> bytes(
leds * 3, 0xFF);
67 if (bytes.size() !=
leds * 3) {
return {}; }
73inline void passiveWindow(fl::u32 durationUs) {
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;
89inline Observation submit(fl::Rmt4AsyncDriver &driver,
90 const fl::ChannelDataPtr &frame) {
92 const fl::u32 before = driver.txDiagnostics().cumulativeTranslatedBytes;
93 driver.enqueue(frame);
94 result.leasedAtSubmission = frame->isInUse();
96 passiveWindow(250000);
98 result.releasedAutonomously = !frame->isInUse();
99 result.errorReported = driver.poll().state ==
101 result.completionSucceeded = driver.waitDone(1000);
102 result.translatedBytes = driver.txDiagnostics().cumulativeTranslatedBytes - before;
103 result.workerStackUnusedBytes = driver.workerStackHighWaterBytes();
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));
116inline bool recoverySucceeded(fl::Rmt4AsyncDriver &driver) {
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);
125#if defined(FL_RMT4_FAULT_TEST) && FL_RMT4_FAULT_TEST == 1
126inline fl::json injectedFailureProbe(
const fl::string &mode) {
129 auto &manager = fl::RmtMemoryManager4::instance();
130 const size_t baseline = manager.allocatedWords();
131 auto frame = makeFrame(1);
133 result.set(
"success",
false);
134 result.set(
"error",
"Fixture frame allocation failed");
137 Observation observed;
138 bool recovered =
false;
139 unsigned consumed = 0;
140 if (mode ==
"mutex-allocation") {
143 armFault(Fault::MutexAllocation);
145 fl::Rmt4AsyncDriver driver(peripheral);
146 consumed = consumedFaults();
148 observed = submit(driver, frame);
152 recovered = recoverySucceeded(replacement);
155 armFault(mode ==
"task-allocation" ? Fault::TaskAllocation : Fault::DriverInstall);
156 observed = submit(driver, frame);
157 consumed = consumedFaults();
159 recovered = recoverySucceeded(driver);
161 const size_t after = manager.allocatedWords();
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);
185#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES && FL_RMT4_ASYNC
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");
197 if (mode ==
"timeout" &&
198 (FASTLED_RMT4_TRANSMISSION_TIMEOUT_MS != 1 || !FL_RMT4_DIAGNOSTICS)) {
199 result.set(
"success",
false);
200 result.set(
"error",
"timeout fixture requires FASTLED_RMT4_TRANSMISSION_TIMEOUT_MS=1 and FL_RMT4_DIAGNOSTICS=1");
203 if (mode ==
"invalid-batch" && !FL_RMT4_DIAGNOSTICS) {
204 result.set(
"success",
false);
205 result.set(
"error",
"invalid-batch fixture requires FL_RMT4_DIAGNOSTICS=1");
209 result.set(
"success",
false);
210 result.set(
"error",
"Existing output did not drain before fixture");
214#if defined(FL_RMT4_FAULT_TEST) && FL_RMT4_FAULT_TEST == 1
215 return injectedFailureProbe(mode);
217 result.set(
"success",
false);
218 result.set(
"error",
"Requires FL_RMT4_FAULT_TEST=1 and the documented SDK linker wrappers");
222 auto &manager = fl::RmtMemoryManager4::instance();
223 const size_t baseline = manager.allocatedWords();
224 auto frame = makeFrame(mode ==
"timeout" ? 4000 : 1);
226 result.set(
"success",
false);
227 result.set(
"error",
"Fixture frame allocation failed");
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();
239 observed = submit(driver, frame);
240 }
else if (mode ==
"setup") {
244 observed = submit(driver, frame);
247 if (mode ==
"invalid-batch") {
250 driver.enqueue(fl::ChannelDataPtr());
252 observed = submit(driver, frame);
253 recovered = recoverySucceeded(driver);
255 const size_t after = manager.allocatedWords();
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);
260 reservationsRestored &= available;
261 if (available) { manager.releaseBlocks(
static_cast<int>(block), 1); }
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));
281 result.set(
"success",
false);
282 result.set(
"error",
"Requires asynchronous classic ESP32 RMT4 output");
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)
static json object() FL_NO_EXCEPT
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
shared_ptr< T > make_shared(Args &&... args) FL_NO_EXCEPT
@ None
Raw magnitudes (no scaling)
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)
@ ERROR
Driver encountered an error.