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