7#if FL_PLATFORM_HAS_LARGE_MEMORY
10#define DEBUG_PRINT(x) do {} while(0)
11#define DEBUG_PRINTLN(x) do {} while(0)
63 writer.
member(
"name", entry.name);
64 writer.
member(
"phase", entry.phase);
65 writer.
member(
"args", entry.args);
66 writer.
member(
"returns", entry.returns);
67 writer.
member(
"description", entry.description);
71static const HelpEntry kHelpEntries[] = {
72 {
"rpc.discover",
"JSON-RPC",
"[]",
"{schema:[[name, ...], ...]}",
"Discover the registered JSON-RPC method schema"},
73 {
"start",
"Phase 1: Basic Control",
"[]",
"void",
"Trigger test matrix execution"},
74 {
"status",
"Phase 1: Basic Control",
"[]",
"{ready, platform, rpcReady, ledRxAvailable, pinTx, pinRx}",
"Query RPC, platform, and LED RX readiness"},
75 {
"drivers",
"Phase 1: Basic Control",
"[]",
"[{name, priority, enabled}, ...]",
"List available drivers"},
76 {
"getConfig",
"Phase 2: Configuration",
"[]",
"{drivers, laneRange, stripSizes, totalTestCases}",
"Query current test matrix configuration"},
77 {
"setDrivers",
"Phase 2: Configuration",
"[driver1, driver2, ...]",
"{success, driversSet, testCases}",
"Configure enabled drivers"},
78 {
"setLaneRange",
"Phase 2: Configuration",
"[minLanes, maxLanes]",
"{success, minLanes, maxLanes, testCases}",
"Configure lane range (1-16)"},
79 {
"setStripSizes",
"Phase 2: Configuration",
"[size] or [shortSize, longSize]",
"{success, stripSizesSet, testCases}",
"Configure strip sizes"},
80 {
"runTestCase",
"Phase 3: Selective Execution",
"[testCaseIndex]",
"{success, testCaseIndex, result}",
"Run single test case by index"},
81 {
"runDriver",
"Phase 3: Selective Execution",
"[driverName]",
"{success, driver, testsRun, results}",
"Run all tests for specific driver"},
82 {
"runAll",
"Phase 3: Selective Execution",
"[]",
"{success, totalCases, passedCases, skippedCases, results}",
"Run full test matrix with JSON results"},
83 {
"getResults",
"Phase 3: Selective Execution",
"[]",
"[{driver, lanes, stripSize, ...}, ...]",
"Return all test results"},
84 {
"getResult",
"Phase 3: Selective Execution",
"[testCaseIndex]",
"{driver, lanes, stripSize, ...}",
"Return specific test case result"},
85 {
"perfProbeBitBangCost",
"Phase 2: Performance",
"[{iterations, ns, pin}]",
"{success, nop_us_per_iter, delay_us_per_iter, delay_hz_us_per_iter, write_byte_us_per_iter, clock_query_us, predicted_bit_overhead_us}",
"Attribute the BIT_BANG per-bit overhead across delay vs pin-write calls (FastLED#4203)"},
86#if FL_COLOR_PIPELINE_SHARED
87 {
"mixedPowerShowPerf",
"Phase 2: Performance",
"[{pixels, frames, budget_mw}] (optional; max 128 pixels/channel)",
"{success, on_median_us, off_median_us, on_plan_modeled_mw, off_plan_modeled_mw, on_plan_flux_q16, off_plan_flux_q16, on_fnv1a, off_fnv1a}",
"Benchmark mixed RGB/RGBW/RGBWW power planning plus encode through FastLED.show(), excluding physical wire time; resets power settings to unlimited (#4499)"},
89 {
"reset",
"Phase 4: Utility",
"[]",
"{success, message, testCasesCleared}",
"Reset test state without device reboot"},
90 {
"halt",
"Phase 4: Utility",
"[]",
"{success, message}",
"Trigger sketch halt"},
91 {
"ping",
"Phase 4: Utility",
"[]",
"{success, message, timestamp, uptimeMs, lastResetCause, lastResetWasWatchdog, frameCounter}",
"Health check with timestamp and reset evidence"},
92 {
"debugTest",
"Phase 4: Utility",
"object",
"{success, received}",
"Echo an exact nested JSON payload"},
93 {
"testNoSerial",
"Phase 4: Utility",
"[]",
"{success, message, serial_safe}",
"Verify RPC execution without device-side serial logging"},
94 {
"testRpConcurrency",
"Phase 4: Utility",
"[]",
"{success, supported, backend, core1Ready, core1Done, recursiveMutexReady, generation, expected, actual}",
"Exercise mutex and semaphore synchronization across both RP physical cores"},
95 {
"testRpPioContention",
"Phase 4: Utility",
"[]",
"{success, numPios, pin, baselineFreeSms, control:{proxyValid, freeDuring, claimedOne, freeAfter, released}, contention:{starvedSms, freeAfterStarve, proxyValid, claimsIntact, noTheft, survivedShow, freeAfter}, noLeak}",
"FastLED#1471 repro: claim every PIO state machine the way Adafruit TinyUSB does, then build a clockless controller and verify FastLED declines cleanly instead of stealing a state machine from the other library."},
96 {
"testRpPioParallelResources",
"Phase 4: Utility",
"[{pinA, pinB, numLeds, iterations}] (all optional)",
"{success, pinA, pinB, baseline, busiestInFlight, released, framesSampled, samplesTaken, pio0HeldOne, pio1HeldOne, bothHeldAtOnce, noOverClaim, observedInFlight, collisionFree, showsCompleted, noResidueBetweenFrames, noLeak}",
"FastLED#3899: drive PIO0 and PIO1 simultaneously and assert at the SDK-claim level that neither engine over-claims mid-flight, both give everything back between frames, and nothing is left claimed on teardown. Whether the two are ever in flight at the same instant is reported as a diagnostic, not asserted."},
97 {
"getPins",
"Phase 5: Pin Configuration",
"[]",
"{txPin, rxPin, defaults: {txPin, rxPin}, platform}",
"Query current and default pin configuration"},
98 {
"setTxPin",
"Phase 5: Pin Configuration",
"[pin]",
"{success, txPin, previousTxPin, testCases}",
"Set TX pin (regenerates test cases)"},
99 {
"setRxPin",
"Phase 5: Pin Configuration",
"[pin]",
"{success, rxPin, previousRxPin, rxChannelRecreated}",
"Set RX pin (recreates RX channel)"},
100 {
"setPins",
"Phase 5: Pin Configuration",
"[{txPin, rxPin}] or [txPin, rxPin]",
"{success, txPin, rxPin, rxChannelRecreated, testCases}",
"Set both TX and RX pins atomically"},
101 {
"findConnectedPins",
"Phase 5: Pin Configuration",
"[{startPin, endPin, autoApply}] (all optional)",
"{success, found, txPin, rxPin, autoApplied, testedPairs}",
"Probe adjacent pin pairs to find jumper wire connection"},
102 {
"help",
"Phase 4: Utility",
"[]",
"[{name, phase, args, returns, description}, ...]",
"List all RPC functions with descriptions"},
103 {
"testSimd",
"Phase 4: Utility",
"[]",
"{success, passed, totalTests, passedTests, failedTests, failures:[string]}",
"Run comprehensive SIMD test suite (85 tests)"},
104 {
"testSimdBenchmark",
"Phase 4: Utility",
"[{iterations}] (optional, default 10000)",
"{success, iterations, float_us, s16x16_us, simd_us}",
"Benchmark multiply speed: float vs s16x16 vs s16x16x4 SIMD"},
105 {
"animartrixPerlinBench",
"Phase 4: Utility",
"[{iterations}] (optional, default 100, max 10000)",
"{success, iterations, pnoise_calls_per_iter, pnoise_float_us, pnoise_i16_us, speedup_x1000}",
"Animartrix-representative Perlin noise bench: scalar float pnoise vs s16x16 fixed-point pnoise2d (16x16 grid per iter, mirrors a real frame). Answers: how much does fixed-point beat float for Animartrix's hot path on this hardware?"},
106 {
"wave8ExpandBenchmark",
"Phase 4: Utility",
"[{iterations}] (optional, default 30000, max 200000)",
"{success, iterations, expand_nibble_us, expand_byte_us, expand_batched_us, transpose16_nibble_us, transpose16_byte_us, sink}",
"Bench PARLIO Wave8 expansion (#2526): nibble vs byte vs batched LUT, plus full per-byte-position cost (expansion + 16-lane transpose)"},
107 {
"ieee754CodecTest",
"Phase 4: Utility",
"[]",
"{success, tests_run, tests_failed, first_failure, expected_bits, actual_bits}",
"On-device verification of the integer-only IEEE 754 decimal codec (#3039): parse, format, round-trip, overflow/underflow, and NaN rejection without strtof/libm."},
108 {
"mp3CodecTest",
"Phase 4: Utility",
"[]",
"{success, streams_run, streams_failed, first_failure, expected_fnv1a, actual_fnv1a, expected_samples, actual_samples, decode_micros, scratch_bytes, shared_decoder_matched, samples_decoded, frames_decoded, combined_fnv1a}",
"Bit-exact on-device check of the fixed-point MP3 decoder against host-computed FNV-1a checksums, over a Layer I and a Layer III stream, decoded both with a fresh decoder each and with one decoder shared across layers (FastLED#4116 scratch union)."},
109 {
"parlioEncodeBenchmark",
"Phase 4: Utility",
"[{iterations}] (optional, default 12000, max 200000)",
"{success, iters, lanes, leds_per_lane, scratchPsramOk, outputPsramOk, perpos_ss_us, perpos_sp_us, perpos_ps_us, perpos_pp_us, frame_ss_us, frame_sp_us, frame_ps_us, frame_pp_us, sink}",
"Bench full PARLIO encode hot loop (16-lane gather + BF1 pipe4 direct encode) with SRAM and optional PSRAM placements; answers PSRAM hypothesis + ISR-streaming feasibility"},
110 {
"timingDriftTest",
"Phase 4: Utility",
"[{pin, numLeds, iterations}] (all optional; default pin=4, numLeds=35, iterations=10)",
"{success, pin, num_leds, iterations, cpu_mhz, show_count, show_min_us, show_max_us, show_total_us, iter_ms:[...]}",
"Issue #2994 repro for compounded per-sequence timing drift on master vs 3.10.3. Replays the reporter's WS2812B-ring + millisDelay-gated fade sketch and returns per-sequence wall time (theoretical 2495 ms; on 3.10.3 rock-steady, on master 2563-2752)."},
111 {
"parlioStreamValidate",
"Phase 4: Utility",
"[{baseTxPin, txPins, numLanes, numLeds, iterations, timeoutMs}] (all optional; txPins overrides contiguous baseTxPin)",
"{success, completed, baseTxPin, txPins, lanes, leds_per_lane, iterations, perIterUs:[...], steadyAvgUs, failedIter, underrunCount, txDoneCount, workerIsrCount, ringError, hardwareIdle}",
"Functional test of the PARLIO ISR-chunked streaming engine (#2548). Drives N back-to-back FastLED.show() calls through the production engine (which uses BF1+pipe4 on 16-lane Wave8 since #2559) and verifies all complete within timeout. Catches hangs/stalls."},
112 {
"flexioRxBenchmark",
"Phase 4: Utility",
"[{frequency_hz=1000, duration_ms=100, tx_pin=3, rx_pin=4}] (all optional)",
"{success, frequency_hz, duration_ms, tx_pin, rx_pin, edges_captured, periods, period_mean_ns, period_sigma_ns, period_min_ns, period_max_ns}",
"Square-wave validation for the FlexIO RX backend (Teensy 4.x only, FastLED#2764 Phase 2). Drives tx_pin via analogWriteFrequency at 50%% duty, captures via RxBackend::FLEXIO on rx_pin, reports per-period statistics."},
113 {
"dualSpiHostLoopback",
"Phase 4: Utility",
"[{activeHost:2|3, dummyFirst:false|true}]",
"{success, activeHost, dummyFirst, verifiedLeds, wireMismatches, rawEdges}",
"On ESP32 boards with two SPI hosts, register one APA102 output on each host and verify eight LED frames from GPIO 1 to GPIO 2. Reset between calls."},
114 {
"flexioObjectFledTest",
"Phase 4: Utility",
"[{test_case=0..4, tx_pin=3, rx_pin=4, capture_ms=50}] (all optional)",
"{success, test_case, tx_pin, rx_pin, num_leds, expected_bytes, decoded_bytes, matched, mismatched, edges_captured}",
"End-to-end ObjectFLED TX -> FlexIO RX loopback verification (Teensy 4.x only, FastLED#2764 Phase 3). Drives WS2812 patterns through Bus::FLEX_IO slot 0, captures via RxBackend::FLEXIO, decodes the bit stream, and reports byte-level match counts. Five fixed test patterns: 0=red, 1=RGB triple, 2=all zeros, 3=all ones, 4=100-LED alternating."},
117static const fl::size kHelpEntryCount =
sizeof(kHelpEntries) /
sizeof(kHelpEntries[0]);
124 remote.bind(
"setDebug", [
this](
const fl::json&
args) -> fl::json {
128 if (!
args.is_array() ||
args.size() != 1) {
130 response.set(
"error",
"InvalidArgs");
131 response.set(
"message",
"Expected [enabled: bool]");
135 if (!
args[0].is_bool()) {
137 response.set(
"error",
"InvalidType");
138 response.set(
"message",
"Argument must be boolean");
142 bool enabled =
args[0].as_bool().value();
143 mState->debug_enabled = enabled;
149 response.set(
"debug_enabled", enabled);
150 response.set(
"message", enabled ?
"Debug logging enabled" :
"Debug logging disabled");
157 remote.bind(
"testGpioConnection", [](
const fl::json&
args) -> fl::json {
161 if (!
args.is_array() ||
args.size() != 2) {
162 response.set(
"error",
"InvalidArgs");
163 response.set(
"message",
"Expected [txPin, rxPin]");
167 if (!
args[0].is_int() || !
args[1].is_int()) {
168 response.set(
"error",
"InvalidPinType");
169 response.set(
"message",
"Pin numbers must be integers");
173 int tx_pin =
static_cast<int>(
args[0].as_int().value());
174 int rx_pin =
static_cast<int>(
args[1].as_int().value());
180 if (rx_pin != tx_pin) {
197 bool connected = (rx_when_tx_low == LOW) && (rx_when_tx_high == HIGH);
199 response.set(
"txPin",
static_cast<int64_t
>(tx_pin));
200 response.set(
"rxPin",
static_cast<int64_t
>(rx_pin));
201 response.set(
"rxWhenTxLow", rx_when_tx_low == LOW ?
"LOW" :
"HIGH");
202 response.set(
"rxWhenTxHigh", rx_when_tx_high == HIGH ?
"HIGH" :
"LOW");
203 response.set(
"connected", connected);
207 response.set(
"message",
"GPIO pins are connected");
210 if (rx_when_tx_low == HIGH && rx_when_tx_high == HIGH) {
211 response.set(
"message",
"RX pin stuck HIGH - no connection detected (check jumper wire)");
212 }
else if (rx_when_tx_low == LOW && rx_when_tx_high == LOW) {
213 response.set(
"message",
"RX pin stuck LOW - possible short to ground");
215 response.set(
"message",
"Unexpected GPIO behavior - check wiring");
227 remote.bind(
"getPins", [
this](
const fl::json&
args) -> fl::json {
234 defaults.
set(
"txPin",
static_cast<int64_t
>(
mState->default_pin_tx));
235 defaults.
set(
"rxPin",
static_cast<int64_t
>(
mState->default_pin_rx));
252 remote.bind(
"setTxPin", [
this](
const fl::json&
args) -> fl::json {
255 if (!
args.is_array() ||
args.size() != 1 || !
args[0].is_int()) {
256 response.set(
"error",
"InvalidArgs");
257 response.set(
"message",
"Expected [pin: int]");
261 int new_pin =
static_cast<int>(
args[0].as_int().value());
264 if (new_pin < 0 || new_pin > 48) {
265 response.set(
"error",
"InvalidPin");
266 response.set(
"message",
"Pin must be 0-48");
270 int old_pin =
mState->pin_tx;
271 if (new_pin != old_pin) {
272 mState->invalidateLedRxValidation();
277 response.set(
"txPin",
static_cast<int64_t
>(new_pin));
278 response.set(
"previousTxPin",
static_cast<int64_t
>(old_pin));
283 remote.bind(
"setRxPin", [
this](
const fl::json&
args) -> fl::json {
286 if (!
args.is_array() ||
args.size() != 1 || !
args[0].is_int()) {
287 response.set(
"error",
"InvalidArgs");
288 response.set(
"message",
"Expected [pin: int]");
292 int new_pin =
static_cast<int>(
args[0].as_int().value());
295 if (new_pin < 0 || new_pin > 48) {
296 response.set(
"error",
"InvalidPin");
297 response.set(
"message",
"Pin must be 0-48");
301 int old_pin =
mState->pin_rx;
302 bool pin_changed = (new_pin != old_pin);
303 bool rx_recreated =
false;
306 fl::shared_ptr<fl::RxChannel> replacement_rx;
308 replacement_rx =
mState->rx_factory(new_pin);
310 if (!replacement_rx) {
311 response.set(
"error",
"RxChannelCreationFailed");
312 response.set(
"message",
"Failed to create RX channel on new pin");
313 response.set(
"pinRx",
static_cast<int64_t
>(new_pin));
317 mState->invalidateLedRxValidation();
319 mState->rx_channel = replacement_rx;
324 response.set(
"rxPin",
static_cast<int64_t
>(new_pin));
325 response.set(
"previousRxPin",
static_cast<int64_t
>(old_pin));
326 response.set(
"rxChannelRecreated", rx_recreated);
331 remote.bind(
"setPins", [
this](
const fl::json&
args) -> fl::json {
338 if (
args.is_array() &&
args.size() == 1 &&
args[0].is_object()) {
340 fl::json config =
args[0];
341 if (config.
contains(
"txPin") && config[
"txPin"].is_int()) {
342 new_tx_pin =
static_cast<int>(config[
"txPin"].
as_int().value());
344 if (config.
contains(
"rxPin") && config[
"rxPin"].is_int()) {
345 new_rx_pin =
static_cast<int>(config[
"rxPin"].
as_int().value());
347 }
else if (
args.is_array() &&
args.size() == 2) {
349 if (
args[0].is_int()) {
350 new_tx_pin =
static_cast<int>(
args[0].as_int().value());
352 if (
args[1].is_int()) {
353 new_rx_pin =
static_cast<int>(
args[1].as_int().value());
356 response.set(
"error",
"InvalidArgs");
357 response.set(
"message",
"Expected [{txPin, rxPin}] or [txPin, rxPin]");
362 if (new_tx_pin < 0 || new_tx_pin > 48) {
363 response.set(
"error",
"InvalidTxPin");
364 response.set(
"message",
"TX pin must be 0-48");
367 if (new_rx_pin < 0 || new_rx_pin > 48) {
368 response.set(
"error",
"InvalidRxPin");
369 response.set(
"message",
"RX pin must be 0-48");
373 int old_tx_pin =
mState->pin_tx;
374 int old_rx_pin =
mState->pin_rx;
375 bool tx_pin_changed = (new_tx_pin != old_tx_pin);
376 bool rx_pin_changed = (new_rx_pin != old_rx_pin);
377 bool rx_recreated =
false;
379 fl::shared_ptr<fl::RxChannel> replacement_rx;
380 if (rx_pin_changed) {
382 replacement_rx =
mState->rx_factory(new_rx_pin);
384 if (!replacement_rx) {
385 response.set(
"error",
"RxChannelCreationFailed");
386 response.set(
"message",
"Failed to create RX channel - previous pins preserved");
387 response.set(
"pinRx",
static_cast<int64_t
>(new_rx_pin));
394 if (tx_pin_changed || rx_pin_changed) {
395 mState->invalidateLedRxValidation();
397 mState->pin_tx = new_tx_pin;
398 mState->pin_rx = new_rx_pin;
399 if (rx_pin_changed) {
400 mState->rx_channel = replacement_rx;
405 response.set(
"txPin",
static_cast<int64_t
>(new_tx_pin));
406 response.set(
"rxPin",
static_cast<int64_t
>(new_rx_pin));
407 response.set(
"previousTxPin",
static_cast<int64_t
>(old_tx_pin));
408 response.set(
"previousRxPin",
static_cast<int64_t
>(old_rx_pin));
409 response.set(
"rxChannelRecreated", rx_recreated);
415 remote.bind(
"findConnectedPins", [
this](
const fl::json&
args) -> fl::json {
420 remote.bindStreaming(
"help", [](fl::JsonStreamWriter& writer,
const fl::json&
args,
const fl::json&
requestId) {
425 writer.
member(
"success",
true);
426 writer.
member(
"totalFunctions",
static_cast<fl::i64>(kHelpEntryCount));
427 writer.
key(
"functions");
429 for (fl::size i = 0; i < kHelpEntryCount; ++i) {
430 streamHelpEntry(writer, kHelpEntries[i]);
fl::UIDescription description("Demo of the Animatrix effects. @author of fx is StefanPetrick")
Full PARLIO encode bench for ESP32-P4 (post-byte-LUT, #2526 landed).
PARLIO ISR-streaming functional validation (#2548 deep-dive follow-up).
Issue #2994 repro: compounded per-sequence timing drift vs 3.10.3.
Wave8 expansion micro-benchmark for #2526.
fl::unique_ptr< fl::Remote > remote
fl::net::ble — BLE GATT transport layer for JSON-RPC
SPI encoder configuration for clocked LED chipsets.
fl::shared_ptr< AutoResearchState > mState
fl::json findConnectedPinsImpl(const fl::json &args)
void bindPinMethods(fl::Remote &remote)
void endArray() FL_NO_EXCEPT
void beginArray() FL_NO_EXCEPT
void beginObject() FL_NO_EXCEPT
void key(const char *k) FL_NO_EXCEPT
void member(const char *k, const char *s) FL_NO_EXCEPT
void endObject() FL_NO_EXCEPT
JSON-RPC server with scheduling support.
fl::optional< i64 > as_int() const FL_NO_EXCEPT
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
bool contains(size_t idx) const FL_NO_EXCEPT
static json object() FL_NO_EXCEPT
Task executor — runs registered task runners and manages the run loop.
Platform-abstracted heap memory query functions.
FastLED's Elegant JSON Library: fl::json
constexpr const char * chipName()
void delay(u32 ms, bool run_async=true) FL_NO_EXCEPT
Public delay wrapper that keeps bare Arduino delay() preferred after using fl::delay; while still all...
void setLogLevel(u8 level) FL_NO_EXCEPT
Set the global log level.
@ FL_LOG_LEVEL_DEBUG
All logging including debug (default)
@ FL_LOG_LEVEL_NONE
No logging (completely silent)
void digitalWrite(int pin, PinValue val) FL_NO_EXCEPT
Write digital value to pin.
PinValue digitalRead(int pin) FL_NO_EXCEPT
Read digital value from pin.
void pinMode(int pin, PinMode mode) FL_NO_EXCEPT
Set pin mode (input, output, pull-up, pull-down)
Promise-based fluent API for FastLED - standalone async primitives.
Umbrella header for SIMD subsystem.