FastLED 3.9.15
Loading...
Searching...
No Matches
AutoResearch.ino
Go to the documentation of this file.
1// @filter
2// require:
3// - board: esp32s3,esp32c6,esp32p4,teensy41,teensy40,lpc845brk,lpcxpresso845max,lpcxpresso804
4// @end-filter
5//
6// Low-memory mode (FastLED #3030): on Low-tier targets (LPC8xx etc., per
7// FastLED #3000 memory classification), this sketch is folded down to the
8// JSON-RPC echo + SCT-RX bring-up surface inherited from the retired
9// `examples/AutoResearchLpc/AutoResearchLpc.ino`. Auto-enabled when
10// `FL_PLATFORM_HAS_LARGE_MEMORY == 0`; can be forced on with
11// `-DFASTLED_AUTORESEARCH_LOW_MEMORY=1` or off with `=0`.
12
14#if !defined(FASTLED_AUTORESEARCH_LOW_MEMORY) && !FL_PLATFORM_HAS_LARGE_MEMORY
15 #define FASTLED_AUTORESEARCH_LOW_MEMORY 1
16#endif
17
18#if defined(FASTLED_AUTORESEARCH_LOW_MEMORY) && FASTLED_AUTORESEARCH_LOW_MEMORY
22#else // !FASTLED_AUTORESEARCH_LOW_MEMORY
23
24// examples/AutoResearch/AutoResearch.ino
25//
26// FastLED LED Timing AutoResearch Sketch for ESP32.
27//
28// This sketch validates LED output by reading back timing values using the
29// RMT peripheral in receive mode. It performs TX→RX loopback testing to verify
30// that transmitted LED data matches received data.
31//
32// DEMONSTRATES:
33// 1. Runtime Channel API (FastLED.add) for iterating through all available
34// drivers (RMT, SPI, PARLIO) and testing multiple chipset timings dynamically
35// by creating and destroying controllers for each driver.
36// 2. Multi-channel autoresearch support: Pass span<const ChannelConfig> to validate
37// multiple LED strips/channels simultaneously. Each channel is independently
38// validated with its own RX loopback channel.
39//
40// Use case: When developing a FastLED driver for a new peripheral, it is useful
41// to read back the LED's received data to verify that the timing is correct.
42//
43// MULTI-CHANNEL MODE:
44// - Single-channel: Pass one ChannelConfig - uses shared RX channel object (created in setup())
45// - Multi-channel: Pass multiple ChannelConfigs - creates dynamic RX channels
46// on each TX pin for independent jumper wire autoresearch
47// - Each channel in the span is validated sequentially with its own RX channel
48
49// Hardware Setup:
50// ⚠️ IMPORTANT: Physical jumper wire required for non-RMT TX → RMT RX loopback
51//
52// When non-RMT peripherals are used for TX (e.g., SPI, ParallelIO):
53// - Connect GPIO PIN_TX to itself with a physical jumper wire
54// - Internal loopback (io_loop_back flag) only works for RMT TX → RMT RX
55// - ESP32 GPIO matrix cannot route other peripheral outputs internally to RMT input
56//
57// When RMT is used for TX (lower peripheral priority or disable other peripherals):
58// - No jumper wire needed - io_loop_back works for RMT TX → RMT RX
59//
60// Alternative: Connect an LED strip between TX pin and ground, then connect
61// RX pin to LED data line to capture actual LED protocol timing (requires
62// two separate GPIO pins for TX and RX).
63//
64// Platform Support:
65// - ESP32 (classic)
66// - ESP32-S3 (Xtensa)
67// - ESP32-C3 (RISC-V)
68// - ESP32-C6 (RISC-V)
69//
70// Expected Output:
71// Serial monitor will show:
72// - List of discovered drivers (RMT, SPI, PARLIO - availability depends on platform)
73// - Test results for each driver with PASS/FAIL status for each chipset timing
74// - Each driver is tested independently by creating/destroying channels
75// - In multi-channel mode: Separate autoresearch results for each channel/pin
76//
77// MULTI-CHANNEL EXAMPLE:
78// ```cpp
79// // Create multiple LED arrays
80// CRGB leds1[100], leds2[100], leds3[100];
81//
82// // Create channel configs for each strip on different pins
83// fl::ChannelConfig configs[] = {
84// fl::ChannelConfig(5, timing, fl::span<CRGB>(leds1, 100), RGB),
85// fl::ChannelConfig(6, timing, fl::span<CRGB>(leds2, 100), RGB),
86// fl::ChannelConfig(7, timing, fl::span<CRGB>(leds3, 100), RGB)
87// };
88//
89// // Validate all 3 channels (each gets its own RX loopback)
90// autoResearchChipsetTiming(timing, "WS2812", fl::span(configs, 3), nullptr, buffer);
91// ```
92//
93// ASYNC ARCHITECTURE:
94// The JSON-RPC system runs on an async task (10ms interval) registered during setup().
95// The task calls RemoteControlSingleton::tick() and processSerialInput() automatically.
96// loop() aggressively pumps fl::task::run() (100x per iteration) to ensure responsiveness.
97//
98
99// ============================================================================
100// AGENT INSTRUCTIONS
101// ============================================================================
102// This sketch is an autoresearch test that uses the "ERROR" keyword in FL_ERROR
103// statements to signal test failures. The `bash debug` command monitors for
104// the "ERROR" keyword and fails the test when detected (exit code 1).
105//
106// 🚫 DO NOT "CHEAT" THE TEST:
107// - DO NOT change "ERROR" to "FAIL", "WARNING", "FAILURE", or any other
108// keyword to avoid test detection
109// - DO NOT modify FL_ERROR statements unless explicitly requested by the user
110// - The "ERROR" keyword is INTENTIONAL and part of the autoresearch contract
111//
112// ✅ VALID MODIFICATIONS (only if explicitly requested):
113// - User asks: "Change the error message format"
114// - User asks: "Use a different warning macro"
115// - User asks: "Remove the ERROR prefix"
116//
117// ❌ INVALID MODIFICATIONS (considered "cheating"):
118// - Changing "ERROR" to avoid `bash debug --fail-on ERROR` detection
119// - Renaming to bypass automated test failure detection
120// - Any modification that circumvents the autoresearch contract
121//
122// If the test is failing, investigate and FIX THE ACTUAL BUG that causes
123// the "ERROR" message to be printed. Do not hide the symptom by changing
124// the error message keyword.
125// ============================================================================
126// JSON-RPC SCRIPTING LANGUAGE:
127// This sketch uses JSON-RPC for all test orchestration. Examples:
128//
129// # Run autoresearch with pure JSON-RPC (no text patterns needed)
130// bash autoresearch --all
131//
132// # Custom JSON-RPC workflow
133// uv run python -c "
134// from ci.rpc_client import RpcClient
135// with RpcClient('/dev/ttyUSB0') as c:
136// c.send('configure', {'driver':'PARLIO', 'laneSizes':[100]})
137// result = c.send('runTest')
138// print(result)
139// "
140//
141// Text output (FL_PRINT, FL_WARN) is for human diagnostics ONLY.
142// Machine coordination uses ONLY JSON-RPC commands and JSONL events.
143// ============================================================================
144
145
146// Enable async PARLIO logging to prevent ISR watchdog timeout
147// Async logging queues messages from ISRs and drains them from the main loop
148#define FASTLED_LOG_PARLIO_ASYNC_ENABLED
149
150// Disable PARLIO ISR logging BEFORE any FastLED includes to prevent watchdog timeout
151// (Serial printing inside ISR exceeds watchdog threshold - must disable at translation unit level)
152// #undef FASTLED_LOG_PARLIO_ENABLED // COMMENTED OUT: Agent loop instructions require FL_LOG_PARLIO_ENABLED to be kept enabled
153
154// ============================================================================
155// Test Matrix Configuration - Multi-Driver, Multi-Lane, Variable Strip Size
156// ============================================================================
157//
158// This sketch now supports comprehensive matrix testing with the following dimensions:
159//
160// 1. DRIVER SELECTION (5 options):
161// - Uncomment to test ONLY a specific driver:
162// // #define JUST_PARLIO // Test only PARLIO driver
163// // #define JUST_RMT // Test only RMT driver
164// // #define JUST_SPI // Test only SPI driver
165// // #define JUST_UART // Test only UART driver
166// // #define JUST_I2S // Test only I2S LCD_CAM driver (ESP32-S3 only)
167// - Default: Test all available drivers (RMT, SPI, PARLIO, UART, I2S)
168//
169// 2. LANE RANGE (1-16 lanes):
170// - Uncomment to override lane range:
171// // #define MIN_LANES 1 // Minimum number of lanes to test
172// // #define MAX_LANES 16 // Maximum number of lanes to test
173// - Default: MIN_LANES=1, MAX_LANES=16 (tests all lane counts)
174// - Each lane has decreasing LED count: Lane 0=base, Lane 1=base-1, ..., Lane N=base-N
175// - Multi-lane RX autoresearch: Only Lane 0 is validated (hardware limitation)
176//
177// 3. STRIP SIZE (2 options):
178// - Uncomment to test ONLY a specific strip size:
179// // #define JUST_SMALL_STRIPS // Test only short strips (10 LEDs)
180// // #define JUST_LARGE_STRIPS // Test only long strips (300 LEDs)
181// - Default: Test both small (10 LEDs) and large (300 LEDs) strips
182//
183// TEST MATRIX SIZE:
184// - Full matrix: 3 drivers × 16 lane counts × 2 strip sizes = 96 test cases
185// - Use defines above to narrow scope for faster debugging
186//
187// EXAMPLES:
188// - Test only RMT with 4 lanes on small strips:
189// #define JUST_RMT
190// #define MIN_LANES 4
191// #define MAX_LANES 4
192// #define JUST_SMALL_STRIPS
193//
194// - Test all drivers with 1-3 lanes on large strips:
195// #define MAX_LANES 3
196// #define JUST_LARGE_STRIPS
197//
198// ============================================================================
199
200#include <FastLED.h>
201#include "fl/channels/all_drivers.h" // for FastLED.enableAllDrivers() post-#2428
202#include "fl/wdt/watchdog.h" // FL_WATCHDOG_AUTO() — unified cross-platform WDT guard
203#include "fl/stl/undef.h" // Undefine Arduino macros (DEFAULT, INPUT, OUTPUT)
204#include "fl/stl/sstream.h"
205#include "Common.h"
206#include "AutoResearchTest.h"
207#include "AutoResearchHelpers.h"
208#include "AutoResearchRemote.h"
209#include "AutoResearchBle.h"
210#include "AutoResearchNet.h"
211#include "AutoResearchAsync.h"
212#include "AutoResearchPlatform.h"
213#include "AutoResearchSimd.h"
214#include "AutoResearchWave8Expand.h" // boot-time #2526 micro-bench
215#include "AutoResearchParlioEncode.h" // full parlio encode bench (post-byte-LUT)
216#include "AutoResearchParlioStream.h" // #2548 PARLIO streaming validation (RPC-driven)
217
218// ============================================================================
219// Hardware Watchdog (crash recovery)
220// ============================================================================
221// Use the unified cross-platform fl::Watchdog API via the FL_WATCHDOG_AUTO()
222// macro at the top of loop(). It lazily arms the WDT on first call, prints
223// the prior-boot reset/crash diagnostic, pauses 3 s on a crash so the
224// developer can read the message, and feeds the WDT on both ctor and dtor.
225// Works portably across ESP32 / Teensy 4 / Teensy 3 / RP2040 / nRF52 /
226// STM32 / SAMD / Apollo3 / MGM240 / AVR (the noop fallback on platforms
227// without a real WDT keeps the call safe).
228//
229// The previous `fl::watchdog_setup(...)` prototype is superseded by
230// `fl::Watchdog::instance().begin()` and the FL_WATCHDOG_AUTO() macro.
231
232// ============================================================================
233// Configuration
234// ============================================================================
235
236// Serial port timeout (milliseconds) - wait for serial monitor to attach
237static constexpr uint32_t SERIAL_TIMEOUT_MS = 120000; // 120 seconds
238#if defined(FL_IS_ESP_32S3) || defined(FL_IS_ESP_32C3) || defined(FL_IS_ESP_32C6) || defined(FL_IS_ESP_32H2) || defined(CONFIG_IDF_TARGET_ESP32P4)
239static constexpr uint32_t AUTORESEARCH_SERIAL_WAIT_MS = 2000;
240#else
242#endif
243
245
246// AutoResearch must recover from a wedged driver/RPC command without host
247// intervention. Keep this shorter than the Python RPC timeout so a device-side
248// hang becomes a watchdog reboot instead of a permanently owned/dead port.
249static constexpr uint32_t AUTORESEARCH_WATCHDOG_TIMEOUT_MS = 5000;
250
251// ============================================================================
252// Platform-Specific Pin Defaults
253// ============================================================================
254// These defaults are chosen based on hardware constraints of each platform.
255// They can be overridden at runtime via JSON-RPC (setPins, setTxPin, setRxPin).
256
259
260// Legacy macros for backward compatibility in existing code
261#define PIN_TX g_autoresearch_state->pin_tx
262#define PIN_RX g_autoresearch_state->pin_rx
263
264#define CHIPSET WS2812B
265#define COLOR_ORDER RGB // No reordering needed.
266
267// RX buffer sized for maximum expected strip size
268// Each LED = 24 bits = 24 symbols, plus headroom for RESET pulses
269// Maximum: 3000 LEDs (hardcoded for ESP32/S3 with PSRAM support)
270#if defined(FL_IS_TEENSY_4X) || defined(FL_IS_ESP_32C6) || defined(FL_IS_ESP_32H2) || defined(FL_IS_ESP_32C5)
271constexpr int RX_BUFFER_SIZE = 100 * 32 + 100; // Memory-constrained: 100 LEDs max for autoresearch
272#else
273constexpr int RX_BUFFER_SIZE = 3000 * 32 + 100; // LEDs × 32:1 expansion + headroom
274#endif
275
276// Factory function for creating RxChannel instances
277// This allows AutoResearchRemoteControl to recreate the RX channel when the pin changes
279 fl::RxChannelConfig config(pin, RX_BACKEND);
280 return FastLED.addRx(config);
281}
282
283// Global autoresearch state (shared between main loop and RPC handlers)
284// Use PSRAM-backed vector for RX buffer to avoid DRAM overflow on ESP32S2
285fl::vector_psram<uint8_t> g_rx_buffer_storage; // Actual buffer storage (falls back to SRAM without PSRAM)
287
288// ============================================================================
289// Serial Initialization Helper
290// ============================================================================
291
292// Initialize serial buffers with platform-specific configuration
293// Note: Some boards (ESP32S2) don't support setTxBufferSize() on USBCDC interface
295#if defined(FL_IS_ESP32) && !defined(FL_IS_ESP_32S2)
296 Serial.setTxBufferSize(4096); // 4KB buffer (default is 256 bytes) // ok serial - platform-specific TX buffer sizing, no fl:: equivalent
297#endif
298}
299
300// ============================================================================
301// Global State
302// ============================================================================
303
304// Remote RPC system for dynamic test control via JSON commands
305// Using Singleton pattern for thread-safe lazy initialization
307
308
309// ============================================================================
310// Global AutoResearch State (Simplified - No Test Matrix)
311// ============================================================================
312
313// Frame counter - tracks which iteration of loop() we're on (diagnostic)
314uint32_t frame_counter = 0;
315
316
317#if defined(FL_IS_TEENSY_4X) && defined(__IMXRT1062__)
318// =============================================================================
319// CRITICAL: WDOG3 (RTWDOG) startup-early-hook recovery (FastLED#2731)
320// =============================================================================
321// The iMXRT1062 RTWDOG (WDOG3) is enabled by default at chip reset with
322// TOVAL = 0x400 LPO ticks ≈ 32 ms. The Teensy 4 core does NOT disable it in
323// startup.c — most sketches happen to be fast enough or stay in WAIT mode
324// where WDOG3 stops counting, so it never bites in practice.
325//
326// But if a previous run armed WDOG3 with a short timeout, or if any
327// peripheral init takes longer than 32 ms, the chip enters a reset loop
328// where the new sketch can't even complete USB enumeration. Symptom: red
329// LED double-blink + "Unknown USB Device (Device Descriptor Request Failed)".
330//
331// `startup_early_hook` is a weak symbol in Teensyduino's startup.c that runs
332// very early in ResetHandler2(), before main() and before ITCM is even
333// initialized. Overriding it here disables WDOG3 BEFORE any other code can
334// observe a reset. Must be in FLASHMEM (ITCM not ready yet).
335extern "C" FLASHMEM void startup_early_hook(void) {
336 // Enable the WDOG3 clock gate (CCM_CCGR5 bits 4-5) so the WDOG3
337 // peripheral registers are addressable. Per imxrt.h comment:
338 // "WDOG3 requires CCM_CCGR5_WDOG3".
339 CCM_CCGR5 |= CCM_CCGR5_WDOG3(3);
340 // Ensure the CCM store is visible to the peripheral bus before
341 // touching WDOG3.
342 __asm__ volatile ("dsb" ::: "memory");
343 __asm__ volatile ("isb" ::: "memory");
344
345 // Unlock RTWDOG with the 32-bit key (works because CS.CMD32EN=1 at reset).
346 WDOG3_CNT = 0xD928C520u;
347
348 // Wait for the unlock window to open. Bounded spin — if for any reason
349 // the unlock never lands, we'd rather continue and let the WDOG bite
350 // than hang here forever.
351 {
352 volatile uint32_t spin = 0;
353 while (!(WDOG3_CS & WDOG_CS_ULK)) {
354 if (++spin > 200000u) break;
355 }
356 }
357
358 // Disable: clear EN, keep CMD32EN+UPDATE+CLK(LPO) so future arms work.
359 // Set TOVAL to max so even if the disable doesn't take, we have ~524 sec
360 // before any reset can occur.
361 WDOG3_TOVAL = 0xFFFFu;
362 WDOG3_WIN = 0;
363 WDOG3_CS = WDOG_CS_CMD32EN | WDOG_CS_UPDATE | WDOG_CS_CLK(1); // EN=0
364
365 // Wait for reconfigure complete (bounded).
366 {
367 volatile uint32_t spin = 0;
368 while (!(WDOG3_CS & WDOG_CS_RCS)) {
369 if (++spin > 200000u) break;
370 }
371 }
372}
373#endif
374
375void setup() {
376 // Initialize serial buffers with platform-specific configuration
377 // Must be called BEFORE Serial.begin()
379 fl::serial_begin(115200);
380#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
381 // Make HWCDC writes drop instead of block when no host is reading.
382 // Without this, Serial.print() on ESP32-C3/C6/H2 can stall up to ~2s
383 // per call once the TX ring fills with no host draining it.
384 // See FastLED issue #2668 and arduino-esp32 PR #7583.
385 // Redundant with fl::serial_begin() which already sets this on HWCDC,
386 // kept as defense-in-depth for sketches that bypass fl::serial_begin.
387 Serial.setTxTimeoutMs(0); // ok serial - platform-specific TX timeout, no fl:: equivalent
388#endif
389 uint32_t serial_wait_start = millis();
390 while (!fl::serial_ready() && (millis() - serial_wait_start) < AUTORESEARCH_SERIAL_WAIT_MS); // Wait for serial monitor (early exits when connected)
391
392 FL_WARN("[SETUP] AutoResearch sketch starting - serial output active");
393
394 // Note: the unified watchdog is armed lazily by FL_WATCHDOG_AUTO() at the
395 // top of loop() — no explicit setup() call needed. The macro prints the
396 // prior-boot reset info via ResetInfo::describe() and pauses 3 s on crash
397 // before the new timer arms. Per-platform boot diagnostics (Teensy 4
398 // SRC_SRSR bit decode + bundled CrashReport, ESP32 panic backtrace, etc.)
399 // are emitted by the platform watchdog impl from Watchdog::begin() — see
400 // src/fl/wdt/watchdog.h and src/platforms/*/watchdog_*.impl.hpp.
401
402 // Initialize RX buffer dynamically (uses PSRAM if available, falls back to heap)
404
405 // Initialize global autoresearch state
409 g_autoresearch_state->default_pin_tx = DEFAULT_PIN_TX;
410 g_autoresearch_state->default_pin_rx = DEFAULT_PIN_RX;
413
414 const char* loop_back_mode = PIN_TX == PIN_RX ? "INTERNAL" : "JUMPER WIRE";
415
416 // Build header and platform/hardware info
417 fl::sstream ss;
418 ss << "\n╔════════════════════════════════════════════════════════════════╗\n";
419 ss << "║ FastLED AutoResearch - Test Matrix Configuration ║\n";
420 ss << "╚════════════════════════════════════════════════════════════════╝\n";
421
422 // Platform information
423 ss << "\n[PLATFORM]\n";
424 ss << " Chip: " << autoresearch::chipName() << "\n";
425
426 // Hardware configuration - machine-parseable for --expect autoresearch
427 ss << "\n[HARDWARE]\n";
428 ss << " TX Pin: " << PIN_TX << "\n"; // --expect "TX Pin: 0"
429 ss << " RX Pin: " << PIN_RX << "\n"; // --expect "RX Pin: 1"
430 ss << " RX Device: " << fl::toString(RX_BACKEND) << "\n";
431 ss << " Loopback Mode: " << loop_back_mode << "\n";
432 ss << " Color Order: RGB\n";
433 ss << " RX Buffer Size: " << RX_BUFFER_SIZE << " bytes";
434 FL_PRINT(ss.str());
435
436 // SIMD validation suite, Wave8 expansion micro-bench, and full PARLIO
437 // encode bench are RPC-driven — invoke via the `testSimd`,
438 // `wave8ExpandBenchmark`, and `parlioEncodeBenchmark` handlers in
439 // AutoResearchRemote.cpp. Do not add boot-time invocation blocks here;
440 // if RPC routing is broken on a platform, fix the routing (see #2541).
441
442 // ========================================================================
443 // RX Channel Setup
444 // ========================================================================
445
446 ss.clear();
447 ss << "\n[RX SETUP] Creating RX channel for LED autoresearch\n";
448 ss << "[RX CREATE] Creating RX channel on PIN " << PIN_RX
449 << " (" << (40000000 / 1000000) << "MHz, " << RX_BUFFER_SIZE << " symbols)";
450 FL_PRINT(ss.str());
451
453
454 if (!g_autoresearch_state->rx_channel) {
455 ss.clear();
456 ss << "[RX SETUP]: Failed to create RX channel\n";
457 ss << "[RX SETUP]: Check that RMT peripheral is available and not in use";
458 FL_ERROR(ss.str());
459 FL_ERROR("Sanity check failed - RX channel creation failed");
460 return;
461 }
462
463 ss.clear();
464 ss << "[RX CREATE] ✓ RX channel created successfully (will be initialized with config in begin())\n";
465 ss << "[RX SETUP] ✓ RX channel ready for LED autoresearch";
466 FL_PRINT(ss.str());
467
468 // PARLIO streaming validation (#2548) is now RPC-driven via the
469 // `parlioStreamValidate` handler registered in AutoResearchRemote.cpp.
470
471 // ========================================================================
472 // Remote RPC Function Registration (EARLY - before GPIO baseline test)
473 // ========================================================================
474 // IMPORTANT: Register RPC functions BEFORE the GPIO baseline test so that
475 // even if setup() fails early, the testGpioConnection command can be used
476 // to diagnose hardware connection issues.
477
478 ss.clear();
479 ss << "\n[REMOTE RPC] Registering JSON RPC functions for dynamic control";
480 FL_PRINT(ss.str());
481
482 // Initialize RemoteControl singleton and register all RPC functions
484
485 FL_PRINT("[REMOTE RPC] ✓ RPC system initialized (testGpioConnection available)");
486
487 // ========================================================================
488 // Async Task Setup - JSON-RPC Processing
489 // ========================================================================
490 FL_PRINT("[ASYNC] Setting up JSON-RPC async task (10ms interval)");
492 FL_PRINT("[ASYNC] ✓ JSON-RPC task registered with scheduler");
493
494 // Stub: register self-running autoresearch client (no-op on ESP32)
497
498 // ========================================================================
499 // GPIO Baseline Test - Verify GPIO→GPIO path works before testing PARLIO
500 // ========================================================================
501 ss.clear();
502 ss << "\n[GPIO BASELINE TEST] Testing GPIO " << PIN_TX << " → GPIO " << PIN_RX << " connectivity";
503 FL_PRINT(ss.str());
504
505 // GPIO baseline test moved to loop() - wait for RPC start signal before testing
506 // This allows JSON-RPC commands (testGpioConnection, findConnectedPins) to run first
507 FL_WARN("[GPIO BASELINE TEST] Deferred to loop() - waiting for RPC start signal");
508
509 // Post-#2428 the channel driver registry no longer auto-populates. This
510 // example needs every available driver enrolled with ChannelManager so the
511 // discovery + autoresearch loop below can enumerate them.
512 FastLED.enableAllDrivers();
513
514 // List all available drivers and store globally
515 g_autoresearch_state->drivers_available = FastLED.getDriverInfos();
516 ss.clear();
517 ss << "\n[DRIVER DISCOVERY]\n";
518 ss << " Found " << g_autoresearch_state->drivers_available.size() << " driver(s) available:\n";
519 for (fl::size i = 0; i < g_autoresearch_state->drivers_available.size(); i++) {
520 ss << " " << (i+1) << ". " << g_autoresearch_state->drivers_available[i].name.c_str()
521 << " (priority: " << g_autoresearch_state->drivers_available[i].priority
522 << ", enabled: " << (g_autoresearch_state->drivers_available[i].enabled ? "yes" : "no") << ")\n";
523 }
524 FL_PRINT(ss.str());
525
526 // Validate that expected drivers are available for this platform
528
529 // Emit JSON-RPC ready event for Python orchestration
530 fl::json readyData = fl::json::object();
531 readyData.set("ready", true);
532 readyData.set("setupTimeMs", static_cast<int64_t>(millis()));
533 readyData.set("drivers", static_cast<int64_t>(g_autoresearch_state->drivers_available.size()));
534 readyData.set("pinTx", static_cast<int64_t>(PIN_TX));
535 readyData.set("pinRx", static_cast<int64_t>(PIN_RX));
536 printStreamRaw("ready", readyData);
537
538 // Human-readable diagnostics (not machine-parsed)
539 FL_PRINT("\n[SETUP COMPLETE] AutoResearch ready - awaiting JSON-RPC commands");
540 delay(2000);
541}
542
543// ============================================================================
544// Main Loop - Pure Command Runner (Phase 6 Refactoring)
545// ============================================================================
546// The main loop is simplified to be a pure JSON-RPC command runner.
547// All test orchestration is handled by Python via RPC commands like:
548// - testGpioConnection: Pre-flight hardware check
549// - runQuickTest: Fast single-test execution
550// - runAll: Full test matrix execution
551// - configure: Setup test parameters
552//
553// This architecture enables:
554// - Fast test iteration (<100ms per test case)
555// - Python-controlled test sequencing
556// - Easy retry logic and error recovery
557
558void loop() {
559 // Unified watchdog guard. First iteration: arms the WDT, prints any
560 // prior-boot reset/crash info, pauses 3 s on a crash. Every iteration:
561 // feeds on construction (now) and again on destruction (end of scope).
563
564 // Aggressively pump async tasks (including JSON-RPC task)
565 // This ensures RPC commands are processed frequently even without delay() calls
566 for (int i = 0; i < 100; i++) {
568 }
569
570 // ========================================================================
571 // Watchdog autoresearch trigger (FastLED#2731) — when the host RPC sends
572 // `deliberateHang`, the handler flips this flag. We let one more pass of
573 // task::run() drain the response queue so the host sees the ACK, then
574 // we disable interrupts (so no async machinery can keep the WDT happy)
575 // and spin forever. The next watchdog tick must fire and reset the chip.
576 // The host's recovery test passes if the device re-enumerates afterward
577 // and is reflashable.
578 // ========================================================================
579 if (g_autoresearch_state->deliberate_hang_requested) {
580 FL_WARN("[deliberateHang] entering forced-hang loop NOW");
581 delay(200); // give Serial TX FIFO time to flush
582 // noInterrupts() is an Arduino-only macro; guard it so the host stub
583 // build (which compiles this .ino for the unit-test framework) doesn't
584 // hit an undeclared identifier. On host the while(1) below still spins
585 // and prevents feed(), which is the actual hang we want to test.
586#if !defined(FL_IS_STUB) && !defined(FL_IS_WASM)
587 noInterrupts();
588#endif
589 while (true) { /* deliberate hang */ }
590 }
591
592 // Feed the watchdog at the end of every loop iteration. On platforms
593 // where begin() was a no-op the feed is also a no-op. The mark-clean
594 // shutdown zeros the consecutive-crash counter so a transient hang
595 // doesn't eventually push the board into safe mode.
596 FastLED.watchdog().feed();
597 FastLED.watchdog().markCleanShutdown();
598
599 // Run GPIO baseline test once after device is ready (allows JSON-RPC to be operational first)
600 // This test is informational only - we continue regardless of pass/fail
601 if (!g_autoresearch_state->gpio_baseline_test_done) {
602 // Wait 500ms after boot to ensure JSON-RPC is fully operational
603 if (millis() > 500) {
604 g_autoresearch_state->gpio_baseline_test_done = true;
605
606 FL_PRINT("\n[GPIO BASELINE TEST] Testing GPIO " << PIN_TX << " → GPIO " << PIN_RX << " connectivity");
607
608 // Test RX channel with manual GPIO toggle to confirm hardware path works
609 // This isolates GPIO/hardware issues from PARLIO driver issues
610 // Buffer size = 100 symbols, hz = 40MHz (same as LED autoresearch)
611 if (!testRxChannel(g_autoresearch_state->rx_channel, PIN_TX, PIN_RX, 40000000, 100)) {
612 FL_WARN("[GPIO BASELINE TEST] FAILED - RX did not capture manual GPIO toggles");
613 FL_WARN("[GPIO BASELINE TEST] Possible causes:");
614 FL_WARN(" 1. GPIO " << PIN_TX << " and GPIO " << PIN_RX << " are not physically connected");
615 FL_WARN(" 2. RX channel initialization failed");
616 FL_WARN(" 3. GPIO conflict with other peripherals (USB Serial JTAG on C6 uses certain GPIOs)");
617 FL_WARN("[GPIO BASELINE TEST] Continuing - JSON-RPC pin discovery/testing available");
618 } else {
619 FL_WARN("\n[GPIO BASELINE TEST] ✓ PASSED - GPIO path confirmed working");
620 FL_WARN("[GPIO BASELINE TEST] ✓ RX successfully captured manual GPIO toggles");
621 FL_WARN("[GPIO BASELINE TEST] ✓ Hardware connectivity verified (GPIO " << PIN_TX << " → GPIO " << PIN_RX << ")");
622 }
623 }
624 }
625
626 // Emit periodic ready status (every 5 seconds) for Python connection detection
627 static uint32_t last_status_ms = 0;
628 uint32_t now = millis();
629 if (now - last_status_ms >= 5000) {
630 fl::json status = fl::json::object();
631 status.set("ready", true);
632 status.set("uptimeMs", static_cast<int64_t>(now));
633 printStreamRaw("status", status);
634 last_status_ms = now;
635 }
636}
637
638#endif // FASTLED_AUTORESEARCH_LOW_MEMORY
fl::shared_ptr< AutoResearchState > g_autoresearch_state
constexpr int DEFAULT_PIN_RX
static constexpr uint32_t SERIAL_TIMEOUT_MS
constexpr int RX_BUFFER_SIZE
#define PIN_TX
void setup()
fl::Singleton< AutoResearchRemoteControl > RemoteControlSingleton
const fl::RxBackend RX_BACKEND
static constexpr uint32_t AUTORESEARCH_WATCHDOG_TIMEOUT_MS
#define PIN_RX
constexpr int DEFAULT_PIN_TX
fl::vector_psram< uint8_t > g_rx_buffer_storage
void init_serial_buffers()
static constexpr uint32_t AUTORESEARCH_SERIAL_WAIT_MS
uint32_t frame_counter
fl::shared_ptr< fl::RxChannel > createRxDevice(int pin)
void loop()
bool testRxChannel(fl::shared_ptr< fl::RxChannel > rx_channel, int pin_tx, int pin_rx, uint32_t hz, size_t buffer_size)
Test RX channel with manual GPIO toggle.
void autoResearchExpectedEngines()
AutoResearch that expected engines are available for this platform Prints ERROR if any expected engin...
void autoResearchLowMemoryLoop()
void autoResearchLowMemorySetup()
Full PARLIO encode bench for ESP32-P4 (post-byte-LUT, #2526 landed).
PARLIO ISR-streaming functional validation (#2548 deep-dive follow-up).
void printStreamRaw(const char *messageType, const fl::json &data)
Print JSONL stream message directly to Serial, bypassing fl::println.
Wave8 expansion micro-benchmark for #2526.
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
Declaration of fl::enableAllDrivers() — enrolls every channel driver available on the current platfor...
void registerFunctions(fl::shared_ptr< AutoResearchState > state)
Register all RPC functions with shared autoresearch state.
static AutoResearchRemoteControl & instance() FL_NOEXCEPT
Definition singleton.h:41
void set(const fl::string &key, const json &value) FL_NOEXCEPT
Definition json.h:701
static json object() FL_NOEXCEPT
Definition json.h:692
string str() const FL_NOEXCEPT
Definition strstream.h:43
void clear() FL_NOEXCEPT
Definition strstream.h:358
#define FL_WARN(X)
Definition log.h:276
#define FL_ERROR(X)
Definition log.h:219
#define FL_PRINT(X)
Print without prefix (like FL_WARN but without "WARN: " prefix) Uses sstream for dynamic formatting (...
Definition log.h:457
fl::task::Handle setupRpcAsyncTask(AutoResearchRemoteControl &remote_control, int interval_ms=10)
Setup async task for JSON-RPC processing.
constexpr int defaultTxPin()
void maybeRegisterStubAutorun(AutoResearchRemoteControl &, fl::shared_ptr< AutoResearchState > state)
On FL_IS_STUB: register a one-shot async task that drives autoresearch.
constexpr int defaultRxPin()
constexpr const char * chipName()
void run(fl::u32 microseconds, ExecFlags flags)
Run selected task subsystems.
void serial_begin(u32 baudRate)
RxBackend
Definition types.h:8
@ PLATFORM_DEFAULT
Use the recommended backend for the active platform (RMT on ESP32; FlexPWM on Teensy 4....
Definition types.h:9
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
bool serial_ready()
const char * toString(RxDeviceType type) FL_NOEXCEPT
Convert RxDeviceType to human-readable string.
Definition rx.h:177
#define Serial
Definition serial.h:304
#define FL_WATCHDOG_AUTO(...)
Definition watchdog.h:260
Unified cross-platform Watchdog Timer API for FastLED.