FastLED 3.10.9
Loading...
Searching...
No Matches
AutoResearchLegacyAsync.h
Go to the documentation of this file.
1#pragma once
2
3#include "FastLED.h"
6#include "fl/stl/json.h"
7#include "fl/stl/vector.h"
8#include "fl/stl/chrono.h"
11#include <Arduino.h> // digitalRead observes the actual output pad.
12
13#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
14#include "platforms/esp/32/drivers/rmt/rmt_4/bus_traits.h" // ok platform headers; ok platform specific - RMT4 worker stack has no generic driver diagnostic API.
15#endif
16
17namespace autoresearch {
18
20 fl::json result = fl::json::object();
21 result.set("success", false);
22 result.set("error", "Cannot allocate caller pixels for hardware fixture");
23 return result;
24}
25
30 bool restore;
31 explicit LegacyAsyncManagerScope(bool legacyOnly)
32 : manager(fl::ChannelManager::registry()),
33 restore(legacyOnly && fl::EngineEvents::hasListener(&manager)) {
35 }
39};
40
41inline unsigned sampleLegacyAsyncPad(int pin, fl::u32 durationUs) {
42 unsigned samples = 0;
43 const fl::u32 started = fl::micros();
44 while (fl::micros() - started < durationUs) {
45 samples += digitalRead(pin) ? 1u : 0u;
46 }
47 return samples;
48}
49
50#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
51// These probes inspect the existing RMT4 bridge's real retained frame. Other
52// backends do not expose that ownership observation and must not fake it.
53inline bool legacyFrameMatches(const fl::weak_ptr<fl::ChannelData> &observer,
54 int count, bool white) {
55 auto frame = observer.lock();
56 if (!frame || frame->getSize() != static_cast<size_t>(count) * 3) {
57 return false;
58 }
59 const auto &bytes = frame->getData();
60 for (size_t i = 0; i < bytes.size(); ++i) {
61 const fl::u8 expected = white || i % 3 == 2 ? 255 : 0;
62 if (bytes[i] != expected) { return false; }
63 }
64 return true;
65}
66
67inline bool legacyFrameLeased(const fl::weak_ptr<fl::ChannelData> &observer) {
68 auto frame = observer.lock();
69 return frame && frame->isInUse();
70}
71
72inline fl::json legacyAsyncControllerProbe(const fl::string &scenario, int pin,
73 int count, const fl::shared_ptr<fl::IChannelDriver> &driver) {
74 fl::json result = fl::json::object();
75 fl::vector<CRGB> pixels(static_cast<size_t>(count), CRGB::White);
76 if (pixels.size() != static_cast<size_t>(count)) { return legacyPixelAllocationFailure(); }
77 fl::weak_ptr<fl::ChannelData> observer;
78 bool disabledSkipped = true;
79 bool firstLease = false;
80 bool firstSnapshot = false;
81 bool secondSnapshot = true;
82 bool retainedAfterRemoval = false;
83 fl::u32 secondShowUs = 0;
84 {
85 LegacyClocklessProxy proxy(pin, pixels.data(), count);
86 if (!proxy.valid()) {
87 result.set("success", false);
88 result.set("error", "Unsupported legacy pin");
89 return result;
90 }
91 observer = proxy.frameData();
92 if (scenario == "enable") {
93 // Prime the pad to a known idle-low output before testing disable.
94 FastLED.show();
95 FastLED.wait();
96 proxy.setEnabled(false);
97 FastLED.show();
98 disabledSkipped = !legacyFrameLeased(observer) &&
99 sampleLegacyAsyncPad(pin, 2000) == 0;
100 proxy.setEnabled(true);
101 }
102 if (scenario == "standalone") {
103 proxy.showLeds();
104 } else {
105 FastLED.show();
106 }
107 firstLease = legacyFrameLeased(observer);
108 fill_solid(pixels.data(), count, CRGB::Blue);
109 firstSnapshot = legacyFrameMatches(observer, count, true);
110 if (scenario == "backpressure") {
111 // Change both the caller buffer and length while the old encoded
112 // snapshot is leased, then immediately reuse the same controller.
113 fl::vector<CRGB> replacement(static_cast<size_t>(count + 1), CRGB::Blue);
114 if (replacement.size() != static_cast<size_t>(count + 1)) { return legacyPixelAllocationFailure(); }
115 proxy.setLeds(fl::span<CRGB>(replacement));
116 pixels.clear();
117 const fl::u32 started = fl::micros();
118 FastLED.show();
119 secondShowUs = fl::micros() - started;
120 secondSnapshot = legacyFrameMatches(observer, count + 1, false) &&
121 legacyFrameLeased(observer);
122 }
123 } // Controller and (for backpressure) replacement source are destroyed.
124 pixels.clear();
125 pixels.shrink_to_fit();
126 retainedAfterRemoval = legacyFrameLeased(observer);
127 const unsigned samples = sampleLegacyAsyncPad(pin, 250000);
128 const bool autonomous = observer.expired() &&
129 driver->poll().state == fl::IChannelDriver::DriverState::READY;
130 const bool waited = FastLED.wait(1000);
131 result.set("scenario", scenario);
132 result.set("disabledControllerSkipped", disabledSkipped);
133 result.set("firstFrameLeasedAtReturn", firstLease);
134 result.set("firstSnapshotSurvivedSourceMutation", firstSnapshot);
135 result.set("reconfiguredSnapshotCorrect", secondSnapshot);
136 result.set("secondShowDurationUs", static_cast<int>(secondShowUs));
137 result.set("retainedAfterControllerRemoval", retainedAfterRemoval);
138 result.set("padHighSamplesAfterRemoval", static_cast<int>(samples));
139 result.set("completedWithoutApplicationPolling", autonomous);
140 result.set("waitSucceeded", waited);
141 result.set("success", disabledSkipped && firstLease && firstSnapshot &&
142 secondSnapshot && retainedAfterRemoval && samples > 0 && autonomous && waited);
143 return result;
144}
145
146inline fl::json legacyAsyncReplacementProbe(int pin, int count,
147 const fl::shared_ptr<fl::IChannelDriver> &driver) {
148 fl::json result = fl::json::object();
149 fl::weak_ptr<fl::ChannelData> oldFrame;
150 fl::weak_ptr<fl::ChannelData> nextFrame;
151 fl::u32 resetUs = 0;
152 {
153 fl::vector<CRGB> oldPixels(static_cast<size_t>(count), CRGB::White);
154 if (oldPixels.size() != static_cast<size_t>(count)) { return legacyPixelAllocationFailure(); }
155 LegacyClocklessProxy oldController(pin, oldPixels.data(), count);
156 oldFrame = oldController.frameData();
157 FastLED.show();
158 auto snapshot = oldFrame.lock();
159 if (snapshot) { resetUs = snapshot->getTiming().reset_us; }
160 }
161 bool bothLeased = false;
162 bool snapshots = false;
163 {
164 fl::vector<CRGB> nextPixels(static_cast<size_t>(count), CRGB::Blue);
165 if (nextPixels.size() != static_cast<size_t>(count)) { return legacyPixelAllocationFailure(); }
166 LegacyClocklessProxy nextController(pin, nextPixels.data(), count);
167 nextFrame = nextController.frameData();
168 FastLED.show();
169 bothLeased = legacyFrameLeased(oldFrame) && legacyFrameLeased(nextFrame);
170 snapshots = legacyFrameMatches(oldFrame, count, true) &&
171 legacyFrameMatches(nextFrame, count, false);
172 }
173 unsigned oldHigh = 0;
174 unsigned nextHigh = 0;
175 fl::u32 lastOldHigh = 0;
176 fl::u32 firstNextHigh = 0;
177 bool oldReleased = false;
178 const fl::u32 started = fl::micros();
179 while (fl::micros() - started < static_cast<fl::u32>(count) * 60 + 100000) {
180 const fl::u32 elapsed = fl::micros() - started;
181 oldReleased |= oldFrame.expired();
182 if (digitalRead(pin)) {
183 if (oldReleased) {
184 if (!nextHigh) { firstNextHigh = elapsed; }
185 ++nextHigh;
186 } else {
187 ++oldHigh;
188 lastOldHigh = elapsed;
189 }
190 }
191 }
192 const bool released = oldFrame.expired() && nextFrame.expired();
193 const bool autonomous = driver->poll().state == fl::IChannelDriver::DriverState::READY;
194 const bool waited = FastLED.wait(1000);
195 const fl::u32 observedGap = nextHigh && oldHigh ? firstNextHigh - lastOldHigh : 0;
196 result.set("scenario", "replacement");
197 result.set("samePinFramesLeasedTogether", bothLeased);
198 result.set("bothSnapshotsCorrectAfterSourceRemoval", snapshots);
199 result.set("oldFramePadHighSamples", static_cast<int>(oldHigh));
200 result.set("replacementPadHighSamples", static_cast<int>(nextHigh));
201 result.set("sampledGapUs", static_cast<int>(observedGap));
202 result.set("requiredResetUs", static_cast<int>(resetUs));
203 result.set("sampledGapMeetsReset", observedGap >= resetUs);
204 result.set("releasedWithoutApplicationPolling", released);
205 result.set("completedWithoutApplicationPolling", autonomous);
206 result.set("waitSucceeded", waited);
207 // Pad polling is a forward-progress observation, not waveform decoding.
208 result.set("success", bothLeased && snapshots && oldHigh && nextHigh &&
209 observedGap >= resetUs && released && autonomous && waited);
210 return result;
211}
212
213inline fl::json legacyAsyncMixedProbe(int pin, int count,
214 const fl::shared_ptr<fl::IChannelDriver> &driver) {
215 fl::json result = fl::json::object();
216 const int modernPin = pin == 5 ? 4 : 5;
217 fl::vector<CRGB> legacyPixels(static_cast<size_t>(count), CRGB::White);
218 fl::vector<CRGB> modernPixels(static_cast<size_t>(count), CRGB::White);
219 if (legacyPixels.size() != static_cast<size_t>(count) ||
220 modernPixels.size() != static_cast<size_t>(count)) { return legacyPixelAllocationFailure(); }
221 fl::ChannelOptions options;
222 options.mBus = fl::Bus::RMT;
223 const fl::ChannelConfig config(modernPin,
225 fl::span<CRGB>(modernPixels), RGB, options);
226 auto modern = FastLED.add(config);
227 const bool modernCreated = modern != nullptr;
228 bool modernEncoded = false;
229 const int event = FastLED.channelEvents().onChannelDataEncoded.add(
230 [&](const fl::IChannel &, const fl::ChannelData &data) {
231 if (data.getPin() != modernPin) { return; }
232 modernEncoded = data.getSize() == static_cast<size_t>(count) * 3;
233 for (size_t i = 0; modernEncoded && i < data.getSize(); ++i) {
234 modernEncoded = data.getData()[i] == 255;
235 }
236 });
237 fl::weak_ptr<fl::ChannelData> legacyFrame;
238 bool legacyLeased = false;
239 {
240 LegacyClocklessProxy legacy(pin, legacyPixels.data(), count);
241 legacyFrame = legacy.frameData();
242 FastLED.show();
243 legacyLeased = legacyFrameLeased(legacyFrame) &&
244 legacyFrameMatches(legacyFrame, count, true);
245 fill_solid(legacyPixels.data(), count, CRGB::Blue);
246 fill_solid(modernPixels.data(), count, CRGB::Blue);
247 }
249 unsigned legacyHigh = 0;
250 unsigned modernHigh = 0;
251 const fl::u32 started = fl::micros();
252 while (fl::micros() - started < 250000) {
253 legacyHigh += digitalRead(pin) ? 1u : 0u;
254 modernHigh += digitalRead(modernPin) ? 1u : 0u;
255 }
256 const bool autonomous = driver->poll().state == fl::IChannelDriver::DriverState::READY;
257 const bool waited = FastLED.wait(1000);
258 FastLED.remove(modern);
259 modern.reset();
260 result.set("scenario", "mixed");
261 result.set("modernPin", modernPin);
262 result.set("modernChannelCreated", modernCreated);
263 result.set("modernEncodedSnapshotCorrect", modernEncoded);
264 result.set("legacyFrameLeasedAtReturn", legacyLeased);
265 result.set("legacyPadHighSamples", static_cast<int>(legacyHigh));
266 result.set("modernPadHighSamples", static_cast<int>(modernHigh));
267 result.set("legacyLeaseReleased", legacyFrame.expired());
268 result.set("completedWithoutApplicationPolling", autonomous);
269 result.set("waitSucceeded", waited);
270 result.set("success", modernCreated && modernEncoded && legacyLeased && legacyHigh && modernHigh &&
271 legacyFrame.expired() && autonomous && waited);
272 return result;
273}
274#endif
275
276#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
277inline void setLegacyTxDiagnostics(fl::json &result,
278 const fl::Rmt4TxDiagnostics &diagnostics) { // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
279 result.set("translatedBytes", static_cast<int>(diagnostics.translatedBytes)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
280 result.set("cumulativeTranslatedBytes", static_cast<int64_t>(diagnostics.cumulativeTranslatedBytes)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
281 result.set("latchChecks", static_cast<int>(diagnostics.latchChecks)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
282 result.set("latchViolations", static_cast<int>(diagnostics.latchViolations)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
283 result.set("minimumLatchHeldUs", static_cast<int>(diagnostics.minLatchHeldUs)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
284 result.set("maximumRequiredResetUs", static_cast<int>(diagnostics.maxRequiredResetUs)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
285 result.set("lateAborts", static_cast<int>(diagnostics.lateAborts)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
286 result.set("maxRefillDeltaCycles", static_cast<int64_t>(diagnostics.maxDeltaCycles)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
287 result.set("abortDeltaCycles", static_cast<int64_t>(diagnostics.abortDeltaCycles)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
288 result.set("allowedDeltaCycles", static_cast<int64_t>(diagnostics.allowedDeltaCycles)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
289 result.set("preloadCycles", static_cast<int64_t>(diagnostics.preloadCycles)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
290 result.set("firstIsrCycles", static_cast<int64_t>(diagnostics.firstIsrCycles)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
291 result.set("postStartCycles", static_cast<int64_t>(diagnostics.postStartCycles)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
292 result.set("postStartUs", static_cast<int64_t>(diagnostics.postStartUs)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
293 result.set("firstIsrUs", static_cast<int64_t>(diagnostics.firstIsrUs)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
294 result.set("maxRefillDeltaUs", static_cast<int>(diagnostics.maxDeltaUs)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
295 result.set("abortDeltaUs", static_cast<int>(diagnostics.abortDeltaUs)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
296 result.set("allowedDeltaUs", static_cast<int>(diagnostics.allowedDeltaUs)); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
297 result.set("preloadCore", diagnostics.preloadCore); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
298 result.set("isrCore", diagnostics.isrCore); // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
299}
300
301inline fl::Rmt4TxDiagnostics legacyTxDiagnostics(int channel = -1) { // ok platform specific - RMT4 ISR timing counters have no platform-neutral diagnostic API.
302 auto &selected = static_cast<fl::detail::Rmt4LegacyDriver &>(fl::BusTraits<fl::Bus::RMT>::instance()); // ok platform specific - observe the selected RMT4 worker internals without adding a generic driver diagnostic API.
303 return selected.txDiagnostics(channel); // ok platform specific - observe the selected RMT4 worker internals without adding a generic driver diagnostic API.
304}
305
306inline void addLegacyTxDiagnostics(fl::json &result) {
307 result.set("translatorDiagnosticsEnabled", FL_RMT4_DIAGNOSTICS != 0); // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
308 setLegacyTxDiagnostics(result, legacyTxDiagnostics());
309#if FL_RMT4_DIAGNOSTICS // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
310 fl::json channels = fl::json::array();
311 for (int channel = 0; channel < FASTLED_RMT_MAX_CHANNELS; ++channel) { // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
312 fl::json entry = fl::json::object();
313 entry.set("channel", channel);
314 setLegacyTxDiagnostics(entry, legacyTxDiagnostics(channel));
315 channels.push_back(entry);
316 }
317 result.set("translatorChannels", channels);
318#endif
319}
320#endif
321
322#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
323// Five strips exceed the four classic ESP32 TX slots with two blocks each.
324// Weak observations do not keep buffers alive after controller destruction.
325inline fl::json legacyAsyncOverflowProbe(int count,
326 fl::shared_ptr<fl::IChannelDriver> driver) {
327 const int pins[] = {2, 4, 5, 18, 19};
328 fl::weak_ptr<fl::ChannelData> frames[5];
329 unsigned highSamples[5] = {};
330 unsigned encodedSizes[5] = {};
331 unsigned encodedCapacities[5] = {};
332 unsigned leasedAtReturn = 0;
333 unsigned retainedAfterRemoval = 0;
334 bool snapshotsIntact = true;
335 bool returnedActive = false;
336 const auto before = legacyTxDiagnostics();
337 {
338 fl::vector<CRGB> pixels[5];
339 fl::unique_ptr<LegacyClocklessProxy> proxies[5];
340 for (unsigned i = 0; i < 5; ++i) {
341 pixels[i].resize(static_cast<size_t>(count));
342 if (pixels[i].size() != static_cast<size_t>(count)) { return legacyPixelAllocationFailure(); }
343 fill_solid(pixels[i].data(), count, CRGB::White);
345 pins[i], pixels[i].data(), count);
346 if (!proxies[i]->valid()) {
347 fl::json failure = fl::json::object();
348 failure.set("success", false);
349 failure.set("error", "Unsupported overflow probe pin");
350 return failure;
351 }
352 frames[i] = proxies[i]->frameData();
353 }
354 FastLED.show();
355 const auto state = driver->poll().state;
356 returnedActive = state == fl::IChannelDriver::DriverState::BUSY ||
358 for (unsigned i = 0; i < 5; ++i) {
359 fill_solid(pixels[i].data(), count, CRGB::Blue);
360 auto frame = frames[i].lock();
361 if (frame) {
362 encodedSizes[i] = frame->getSize();
363 encodedCapacities[i] = frame->getData().capacity();
364 }
365 if (frame && frame->isInUse()) { ++leasedAtReturn; }
366 snapshotsIntact &= legacyFrameMatches(frames[i], count, true);
367 frame.reset();
368 proxies[i].reset();
369 auto retained = frames[i].lock();
370 if (retained && retained->isInUse()) { ++retainedAfterRemoval; }
371 }
372 } // Caller pixel arrays and every controller are gone while TX is active.
373 const fl::u32 start = fl::micros();
374 const fl::u32 observeUs = static_cast<fl::u32>(count) * 60 + 50000;
375 while (fl::micros() - start < observeUs) {
376 for (unsigned i = 0; i < 5; ++i) {
377 highSamples[i] += digitalRead(pins[i]) ? 1u : 0u;
378 }
379 }
380 // No poll/show/wait/yield above: the worker must finish the overflow strip.
381 const bool completed = driver->poll().state == fl::IChannelDriver::DriverState::READY;
382 unsigned released = 0;
383 unsigned observedPins = 0;
384 fl::json samples = fl::json::array();
385 fl::json sizes = fl::json::array();
386 fl::json capacities = fl::json::array();
387 for (unsigned i = 0; i < 5; ++i) {
388 if (frames[i].expired()) { ++released; }
389 if (highSamples[i]) { ++observedPins; }
390 samples.push_back(static_cast<int>(highSamples[i]));
391 sizes.push_back(static_cast<int>(encodedSizes[i]));
392 capacities.push_back(static_cast<int>(encodedCapacities[i]));
393 }
394 const bool drained = FastLED.wait(1000);
395 const auto after = legacyTxDiagnostics();
396 const fl::u32 translatedInBatch = after.cumulativeTranslatedBytes - before.cumulativeTranslatedBytes;
397 const fl::u32 latchChecks = after.latchChecks - before.latchChecks;
398 const fl::u32 latchViolations = after.latchViolations - before.latchViolations;
399 fl::json result = fl::json::object();
400 result.set("returnedActive", returnedActive);
401 result.set("leasedFramesAtReturn", static_cast<int>(leasedAtReturn));
402 result.set("retainedFramesAfterControllerRemoval", static_cast<int>(retainedAfterRemoval));
403 result.set("encodedSnapshotsUnaffectedBySourceMutation", snapshotsIntact);
404 result.set("padHighSamples", samples);
405 result.set("encodedFrameSizes", sizes);
406 result.set("encodedBufferCapacities", capacities);
407 result.set("releasedFramesWithoutApplicationPolling", static_cast<int>(released));
408 result.set("completedWithoutApplicationPolling", completed);
409 result.set("waitSucceeded", drained);
410 result.set("batchTranslatedBytes", static_cast<int>(translatedInBatch));
411 result.set("batchLatchChecks", static_cast<int>(latchChecks));
412 result.set("batchLatchViolations", static_cast<int>(latchViolations));
413 addLegacyTxDiagnostics(result);
414 result.set("success", returnedActive && leasedAtReturn == 5 &&
415 retainedAfterRemoval == 5 && snapshotsIntact && observedPins == 5 &&
416 released == 5 && completed && drained &&
417 (!FL_RMT4_DIAGNOSTICS || (translatedInBatch == static_cast<fl::u32>(count) * 15 && // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
418 latchChecks == 5 && latchViolations == 0)));
419 return result;
420}
421#endif
422
427 int pin = 4;
428 int count = 2000;
429 if (args.is_object()) {
430 if (args["pin"].is_int()) { pin = static_cast<int>(args["pin"].as_int().value()); }
431 if (args["numLeds"].is_int()) { count = static_cast<int>(args["numLeds"].as_int().value()); }
432 }
433 fl::json result = fl::json::object();
434 if (count < 100 || count > 4096) {
435 result.set("success", false);
436 result.set("error", "numLeds must be in [100,4096]");
437 return result;
438 }
439 const char* name = legacyClocklessBoundDriverName();
440 auto driver = name ? fl::channelManager().findDriverByName(name) : nullptr;
441 if (!driver) {
442 result.set("success", false);
443 result.set("error", "Run the regular driver test first to register the backend");
444 return result;
445 }
447 FastLED.setBrightness(255);
448 const fl::string scenario = args.is_object()
449 ? args["scenario"].as_string().value_or("") : fl::string();
450 const bool legacyOnly = scenario == "replacement" ||
451 (args.is_object() && args["legacyOnly"].as_bool().value_or(false));
452 if (scenario == "mixed" && legacyOnly) {
453 result.set("success", false);
454 result.set("error", "Mixed Channel API coverage requires the manager listener");
455 return result;
456 }
457 LegacyAsyncManagerScope managerScope(legacyOnly);
458#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
459 if (scenario == "standalone" || scenario == "enable" || scenario == "backpressure") {
460 if (count < 1000) { count = 1000; }
461 result = legacyAsyncControllerProbe(scenario, pin, count, driver);
462 result.set("legacyOnly", legacyOnly);
463 return result;
464 }
465 if (scenario == "replacement") {
466 if (count < 1000) { count = 1000; }
467 result = legacyAsyncReplacementProbe(pin, count, driver);
468 result.set("legacyOnly", legacyOnly);
469 return result;
470 }
471 if (scenario == "mixed") {
472 if (count < 1000) { count = 1000; }
473 return legacyAsyncMixedProbe(pin, count, driver);
474 }
475 if (args.is_object() && args["overflow"].is_bool() &&
476 args["overflow"].as_bool().value()) {
477 if (count < 1000) { count = 1000; }
478 return legacyAsyncOverflowProbe(count, driver);
479 }
480#endif
482 leds.resize(static_cast<size_t>(count));
483 if (leds.size() != static_cast<size_t>(count)) { return legacyPixelAllocationFailure(); }
484 fill_solid(leds.data(), count, CRGB::White);
485 LegacyClocklessProxy proxy(pin, leds.data(), count);
486 if (!proxy.valid()) {
487 result.set("success", false);
488 result.set("error", "Unsupported legacy pin");
489 return result;
490 }
491 FastLED.setBrightness(255);
492 const fl::u32 showStarted = fl::micros();
493 FastLED.show();
494 const fl::u32 showDurationUs = fl::micros() - showStarted;
495 auto stateAtReturn = driver->poll().state;
496 const bool returnedActive = stateAtReturn == fl::IChannelDriver::DriverState::BUSY ||
498 size_t encodedBytes = 0;
499 int firstEncodedByte = -1;
500#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
501 {
502 auto frame = proxy.frameData().lock();
503 if (frame) {
504 encodedBytes = frame->getSize();
505 if (encodedBytes) { firstEncodedByte = frame->getData()[0]; }
506 }
507 }
508#endif
509 // Mutating the caller's pixels must not mutate the encoded frame in flight.
510 fill_solid(leds.data(), count, CRGB::Blue);
511 unsigned highSamples = 0;
512 const fl::u32 start = fl::micros();
513 int firstHighDelayUs = -1;
514 // Startup may be queued when show returns. Sample through the full drain
515 // window without calling any driver service, wait, yield, or delay.
516 while (fl::micros() - start < 200000) {
517 const fl::u32 elapsed = fl::micros() - start;
518 if (digitalRead(pin)) {
519 ++highSamples;
520 if (firstHighDelayUs < 0) { firstHighDelayUs = static_cast<int>(elapsed); }
521 }
522 }
523 const auto stateAfterIdle = driver->poll().state;
524 const bool completedIndependently = stateAfterIdle == fl::IChannelDriver::DriverState::READY;
525 const bool drained = FastLED.wait(1000);
526 result.set("returnedActive", returnedActive);
527 result.set("stateAtReturn", static_cast<int>(stateAtReturn));
528 result.set("showDurationUs", static_cast<int>(showDurationUs));
529 result.set("encodedBytes", static_cast<int>(encodedBytes));
530 result.set("firstEncodedByte", firstEncodedByte);
531 result.set("firstHighDelayUs", firstHighDelayUs);
532 result.set("padHighSamplesAfterReturn", static_cast<int>(highSamples));
533 result.set("completedWithoutApplicationPolling", completedIndependently);
534 result.set("waitSucceeded", drained);
535#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES // ok platform specific - this fixture requires RMT4-only worker diagnostics or fault-test configuration, with no neutral equivalent.
536 addLegacyTxDiagnostics(result);
537#endif
538#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES && FL_RMT4_ASYNC // ok platform specific - measure the RMT4 worker's stack without adding a cross-platform virtual API.
539 auto& selected = static_cast<fl::Rmt4AsyncDriver&>(fl::BusTraits<fl::Bus::RMT>::instance()); // ok platform specific - observe the selected RMT4 worker internals without adding a generic driver diagnostic API.
540 result.set("workerStackUnusedBytes", static_cast<int>(selected.workerStackHighWaterBytes())); // ok platform specific - observe the selected RMT4 worker internals without adding a generic driver diagnostic API.
541#endif
542 result.set("success", returnedActive && highSamples > 0 && completedIndependently && drained);
543 return result;
544}
545
546} // namespace autoresearch
CRGB leds[1]
TestState state
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
@ CHANNELS
Remove all channels from controller list.
Definition FastLED.h:663
const char * legacyClocklessBoundDriverName()
Name of the driver to which public legacy templates are bound.
int pins[]
Definition Spi.ino:11
FastLED chrono implementation - duration types for time measurements.
static void add(fl::ChannelPtr channel) FL_NO_EXCEPT
Add a Channel-based LED controller (from ChannelPtr)
static fl::ChannelEvents & channelEvents() FL_NO_EXCEPT
Access the channel event system.
static void remove(fl::ChannelPtr channel) FL_NO_EXCEPT
Remove a channel from the LED controller list.
void show(fl::u8 scale) FL_NO_EXCEPT
Update all our controllers with the current led colors, using the passed in brightness.
void wait() FL_NO_EXCEPT
Wait for all channel bus transmissions to complete.
bool valid() const
Check if the proxy was successfully created.
Proxy that creates a legacy clockless controller from runtime inputs.
const fl::vector_psram< u8 > & getData() const FL_NO_EXCEPT
Get the encoded transmission data.
Definition data.h:96
size_t getSize() const FL_NO_EXCEPT
Get the data size in bytes.
Definition data.h:125
int getPin() const FL_NO_EXCEPT
Get the GPIO pin number.
Definition data.cpp.hpp:31
fl::shared_ptr< IChannelDriver > findDriverByName(const fl::string &name) const FL_NO_EXCEPT
Silent counterpart to getDriverByName().
Unified channel manager with priority-based driver selection.
Definition manager.h:51
static void removeListener(Listener *listener) FL_NO_EXCEPT
static void addListener(Listener *listener, int priority=0) FL_NO_EXCEPT
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
Definition json.h:873
void push_back(const json &value) FL_NO_EXCEPT
Definition json.h:925
static json object() FL_NO_EXCEPT
Definition json.h:864
static json array() FL_NO_EXCEPT
Definition json.h:860
void reset(pointer p=nullptr) FL_NO_EXCEPT
Definition unique_ptr.h:114
void resize(fl::size n) FL_NO_EXCEPT
Definition vector.h:593
shared_ptr< T > lock() const FL_NO_EXCEPT
Definition weak_ptr.h:140
bool expired() const FL_NO_EXCEPT
Definition weak_ptr.h:136
void fill_solid(CRGB *targetArray, int numToFill, const CRGB &color) FL_NO_EXCEPT
Fill a range of LEDs with a solid color.
Definition fill.cpp.hpp:10
constexpr EOrder RGB
Definition eorder.h:17
FastLED's Elegant JSON Library: fl::json
Unified manager for channel drivers with priority-based fallback.
unsigned sampleLegacyAsyncPad(int pin, fl::u32 durationUs)
fl::json legacyPixelAllocationFailure()
fl::json legacyAsyncProbe(const fl::json &args)
Call after the regular driver test has registered the selected backend.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
Definition unique_ptr.h:263
unsigned char u8
Definition stdint.h:131
ChannelManager & channelManager() FL_NO_EXCEPT
Get the global ChannelManager singleton instance.
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.
@ RMT
Definition bus.h:35
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
PinValue digitalRead(int pin) FL_NO_EXCEPT
Read digital value from pin.
Definition pin.cpp.hpp:55
Base definition for an LED controller.
Primary template — intentionally undefined.
Definition bus_traits.h:31
corkscrew_args args
Definition old.h:149
AutoResearch registers a ChannelManager for its selectable driver tests.
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
Definition crgb.h:519
@ White
<div style='background:#FFFFFF;width:4em;height:4em;'></div>
Definition crgb.h:653
fl::function_list< void(const IChannel &, const ChannelData &)> onChannelDataEncoded
Fired after pixel data is encoded into byte stream (before enqueuing) Second parameter is the encoded...
@ READY
Hardware idle; ready to accept new transmissions.
Definition driver.h:170
@ DRAINING
All channels submitted; still transmitting.
Definition driver.h:172
@ BUSY
Active: channels transmitting or queued.
Definition driver.h:171