13#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES
14#include "platforms/esp/32/drivers/rmt/rmt_4/bus_traits.h"
21 result.set(
"success",
false);
22 result.set(
"error",
"Cannot allocate caller pixels for hardware fixture");
32 :
manager(
fl::ChannelManager::registry()),
45 samples += digitalRead(pin) ? 1u : 0u;
50#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES
54 int count,
bool white) {
55 auto frame = observer.
lock();
56 if (!frame || frame->getSize() !=
static_cast<size_t>(count) * 3) {
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; }
67inline bool legacyFrameLeased(
const fl::weak_ptr<fl::ChannelData> &observer) {
68 auto frame = observer.
lock();
69 return frame && frame->isInUse();
72inline fl::json legacyAsyncControllerProbe(
const fl::string &scenario,
int pin,
73 int count,
const fl::shared_ptr<fl::IChannelDriver> &driver) {
75 fl::vector<CRGB> pixels(
static_cast<size_t>(count),
CRGB::White);
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;
85 LegacyClocklessProxy proxy(pin, pixels.data(), count);
87 result.set(
"success",
false);
88 result.set(
"error",
"Unsupported legacy pin");
91 observer = proxy.frameData();
92 if (scenario ==
"enable") {
96 proxy.setEnabled(
false);
98 disabledSkipped = !legacyFrameLeased(observer) &&
100 proxy.setEnabled(
true);
102 if (scenario ==
"standalone") {
107 firstLease = legacyFrameLeased(observer);
109 firstSnapshot = legacyFrameMatches(observer, count,
true);
110 if (scenario ==
"backpressure") {
113 fl::vector<CRGB> replacement(
static_cast<size_t>(count + 1),
CRGB::Blue);
115 proxy.setLeds(fl::span<CRGB>(replacement));
120 secondSnapshot = legacyFrameMatches(observer, count + 1,
false) &&
121 legacyFrameLeased(observer);
125 pixels.shrink_to_fit();
126 retainedAfterRemoval = legacyFrameLeased(observer);
128 const bool autonomous = observer.
expired() &&
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);
146inline fl::json legacyAsyncReplacementProbe(
int pin,
int count,
147 const fl::shared_ptr<fl::IChannelDriver> &driver) {
149 fl::weak_ptr<fl::ChannelData> oldFrame;
150 fl::weak_ptr<fl::ChannelData> nextFrame;
153 fl::vector<CRGB> oldPixels(
static_cast<size_t>(count),
CRGB::White);
155 LegacyClocklessProxy oldController(pin, oldPixels.data(), count);
156 oldFrame = oldController.frameData();
158 auto snapshot = oldFrame.
lock();
159 if (snapshot) { resetUs = snapshot->getTiming().reset_us; }
161 bool bothLeased =
false;
162 bool snapshots =
false;
164 fl::vector<CRGB> nextPixels(
static_cast<size_t>(count),
CRGB::Blue);
166 LegacyClocklessProxy nextController(pin, nextPixels.data(), count);
167 nextFrame = nextController.frameData();
169 bothLeased = legacyFrameLeased(oldFrame) && legacyFrameLeased(nextFrame);
170 snapshots = legacyFrameMatches(oldFrame, count,
true) &&
171 legacyFrameMatches(nextFrame, count,
false);
173 unsigned oldHigh = 0;
174 unsigned nextHigh = 0;
175 fl::u32 lastOldHigh = 0;
176 fl::u32 firstNextHigh = 0;
177 bool oldReleased =
false;
179 while (
fl::micros() - started <
static_cast<fl::u32
>(count) * 60 + 100000) {
180 const fl::u32 elapsed =
fl::micros() - started;
181 oldReleased |= oldFrame.
expired();
184 if (!nextHigh) { firstNextHigh = elapsed; }
188 lastOldHigh = elapsed;
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);
208 result.set(
"success", bothLeased && snapshots && oldHigh && nextHigh &&
209 observedGap >= resetUs && released && autonomous && waited);
213inline fl::json legacyAsyncMixedProbe(
int pin,
int count,
214 const fl::shared_ptr<fl::IChannelDriver> &driver) {
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) ||
221 fl::ChannelOptions options;
223 const fl::ChannelConfig config(modernPin,
225 fl::span<CRGB>(modernPixels),
RGB, options);
227 const bool modernCreated = modern !=
nullptr;
228 bool modernEncoded =
false;
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;
237 fl::weak_ptr<fl::ChannelData> legacyFrame;
238 bool legacyLeased =
false;
240 LegacyClocklessProxy legacy(pin, legacyPixels.data(), count);
241 legacyFrame = legacy.frameData();
243 legacyLeased = legacyFrameLeased(legacyFrame) &&
244 legacyFrameMatches(legacyFrame, count,
true);
249 unsigned legacyHigh = 0;
250 unsigned modernHigh = 0;
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));
268 result.set(
"completedWithoutApplicationPolling", autonomous);
269 result.set(
"waitSucceeded", waited);
270 result.set(
"success", modernCreated && modernEncoded && legacyLeased && legacyHigh && modernHigh &&
271 legacyFrame.
expired() && autonomous && waited);
276#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES
277inline void setLegacyTxDiagnostics(fl::json &result,
278 const fl::Rmt4TxDiagnostics &diagnostics) {
279 result.set(
"translatedBytes",
static_cast<int>(diagnostics.translatedBytes));
280 result.set(
"cumulativeTranslatedBytes",
static_cast<int64_t
>(diagnostics.cumulativeTranslatedBytes));
281 result.set(
"latchChecks",
static_cast<int>(diagnostics.latchChecks));
282 result.set(
"latchViolations",
static_cast<int>(diagnostics.latchViolations));
283 result.set(
"minimumLatchHeldUs",
static_cast<int>(diagnostics.minLatchHeldUs));
284 result.set(
"maximumRequiredResetUs",
static_cast<int>(diagnostics.maxRequiredResetUs));
285 result.set(
"lateAborts",
static_cast<int>(diagnostics.lateAborts));
286 result.set(
"maxRefillDeltaCycles",
static_cast<int64_t
>(diagnostics.maxDeltaCycles));
287 result.set(
"abortDeltaCycles",
static_cast<int64_t
>(diagnostics.abortDeltaCycles));
288 result.set(
"allowedDeltaCycles",
static_cast<int64_t
>(diagnostics.allowedDeltaCycles));
289 result.set(
"preloadCycles",
static_cast<int64_t
>(diagnostics.preloadCycles));
290 result.set(
"firstIsrCycles",
static_cast<int64_t
>(diagnostics.firstIsrCycles));
291 result.set(
"postStartCycles",
static_cast<int64_t
>(diagnostics.postStartCycles));
292 result.set(
"postStartUs",
static_cast<int64_t
>(diagnostics.postStartUs));
293 result.set(
"firstIsrUs",
static_cast<int64_t
>(diagnostics.firstIsrUs));
294 result.set(
"maxRefillDeltaUs",
static_cast<int>(diagnostics.maxDeltaUs));
295 result.set(
"abortDeltaUs",
static_cast<int>(diagnostics.abortDeltaUs));
296 result.set(
"allowedDeltaUs",
static_cast<int>(diagnostics.allowedDeltaUs));
297 result.set(
"preloadCore", diagnostics.preloadCore);
298 result.set(
"isrCore", diagnostics.isrCore);
301inline fl::Rmt4TxDiagnostics legacyTxDiagnostics(
int channel = -1) {
302 auto &selected =
static_cast<fl::detail::Rmt4LegacyDriver &
>(fl::BusTraits<fl::Bus::RMT>::instance());
303 return selected.txDiagnostics(channel);
306inline void addLegacyTxDiagnostics(fl::json &result) {
307 result.set(
"translatorDiagnosticsEnabled", FL_RMT4_DIAGNOSTICS != 0);
308 setLegacyTxDiagnostics(result, legacyTxDiagnostics());
309#if FL_RMT4_DIAGNOSTICS
311 for (
int channel = 0; channel < FASTLED_RMT_MAX_CHANNELS; ++channel) {
313 entry.
set(
"channel", channel);
314 setLegacyTxDiagnostics(entry, legacyTxDiagnostics(channel));
317 result.set(
"translatorChannels", channels);
322#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES
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();
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));
345 pins[i], pixels[i].data(), count);
346 if (!proxies[i]->valid()) {
348 failure.
set(
"success",
false);
349 failure.
set(
"error",
"Unsupported overflow probe pin");
352 frames[i] = proxies[i]->frameData();
355 const auto state = driver->poll().state;
358 for (
unsigned i = 0; i < 5; ++i) {
360 auto frame = frames[i].
lock();
362 encodedSizes[i] = frame->getSize();
363 encodedCapacities[i] = frame->getData().capacity();
365 if (frame && frame->isInUse()) { ++leasedAtReturn; }
366 snapshotsIntact &= legacyFrameMatches(frames[i], count,
true);
369 auto retained = frames[i].
lock();
370 if (retained && retained->isInUse()) { ++retainedAfterRemoval; }
374 const fl::u32 observeUs =
static_cast<fl::u32
>(count) * 60 + 50000;
376 for (
unsigned i = 0; i < 5; ++i) {
382 unsigned released = 0;
383 unsigned observedPins = 0;
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]));
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;
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 &&
418 latchChecks == 5 && latchViolations == 0)));
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()); }
434 if (count < 100 || count > 4096) {
435 result.set(
"success",
false);
436 result.set(
"error",
"numLeds must be in [100,4096]");
442 result.set(
"success",
false);
443 result.set(
"error",
"Run the regular driver test first to register the backend");
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");
458#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES
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);
465 if (scenario ==
"replacement") {
466 if (count < 1000) { count = 1000; }
467 result = legacyAsyncReplacementProbe(pin, count, driver);
468 result.set(
"legacyOnly", legacyOnly);
471 if (scenario ==
"mixed") {
472 if (count < 1000) { count = 1000; }
473 return legacyAsyncMixedProbe(pin, count, driver);
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);
482 leds.resize(
static_cast<size_t>(count));
486 if (!proxy.
valid()) {
487 result.set(
"success",
false);
488 result.set(
"error",
"Unsupported legacy pin");
494 const fl::u32 showDurationUs =
fl::micros() - showStarted;
495 auto stateAtReturn = driver->poll().state;
498 size_t encodedBytes = 0;
499 int firstEncodedByte = -1;
500#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES
502 auto frame = proxy.frameData().lock();
504 encodedBytes = frame->getSize();
505 if (encodedBytes) { firstEncodedByte = frame->getData()[0]; }
511 unsigned highSamples = 0;
513 int firstHighDelayUs = -1;
518 if (digitalRead(pin)) {
520 if (firstHighDelayUs < 0) { firstHighDelayUs =
static_cast<int>(elapsed); }
523 const auto stateAfterIdle = driver->poll().state;
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
536 addLegacyTxDiagnostics(result);
538#if AUTORESEARCH_LEGACY_OBSERVES_RMT4_FRAMES && FL_RMT4_ASYNC
540 result.set(
"workerStackUnusedBytes",
static_cast<int>(selected.workerStackHighWaterBytes()));
542 result.set(
"success", returnedActive && highSamples > 0 && completedIndependently && drained);
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
@ CHANNELS
Remove all channels from controller list.
const char * legacyClocklessBoundDriverName()
Name of the driver to which public legacy templates are bound.
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.
size_t getSize() const FL_NO_EXCEPT
Get the data size in bytes.
int getPin() const FL_NO_EXCEPT
Get the GPIO pin number.
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.
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
void push_back(const json &value) FL_NO_EXCEPT
static json object() FL_NO_EXCEPT
static json array() FL_NO_EXCEPT
void reset(pointer p=nullptr) FL_NO_EXCEPT
void resize(fl::size n) FL_NO_EXCEPT
shared_ptr< T > lock() const FL_NO_EXCEPT
bool expired() const FL_NO_EXCEPT
void fill_solid(CRGB *targetArray, int numToFill, const CRGB &color) FL_NO_EXCEPT
Fill a range of LEDs with a solid color.
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
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.
expected< T, E > result
Alias for expected (Rust-style naming)
PinValue digitalRead(int pin) FL_NO_EXCEPT
Read digital value from pin.
Base definition for an LED controller.
Primary template — intentionally undefined.
LegacyAsyncManagerScope(bool legacyOnly)
~LegacyAsyncManagerScope()
fl::ChannelManager & manager
AutoResearch registers a ChannelManager for its selectable driver tests.
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
@ White
<div style='background:#FFFFFF;width:4em;height:4em;'></div>
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.
@ DRAINING
All channels submitted; still transmitting.
@ BUSY
Active: channels transmitting or queued.