forked from FastLED/FastLED
-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathValidation.ino
More file actions
677 lines (591 loc) · 29.2 KB
/
Validation.ino
File metadata and controls
677 lines (591 loc) · 29.2 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
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
// @filter: (platform is esp32)
// examples/Validation/Validation.ino
//
// FastLED LED Timing Validation 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 validation 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 validation
// - 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 validation 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)
// validateChipsetTiming(timing, "WS2812", fl::span(configs, 3), nullptr, buffer);
// ```
//
// ============================================================================
// AGENT INSTRUCTIONS
// ============================================================================
// This sketch is a validation 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 validation 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 validation 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 validation with pure JSON-RPC (no text patterns needed)
// bash validate --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
// ⚠️ IMPORTANT: Test matrix configuration moved to ValidationConfig.h
// This ensures consistent configuration across all compilation units (.ino and .cpp files)
#include "ValidationConfig.h"
// ============================================================================
// 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-8 lanes):
// - Uncomment to override lane range:
// // #define MIN_LANES 1 // Minimum number of lanes to test
// // #define MAX_LANES 8 // Maximum number of lanes to test
// - Default: MIN_LANES=1, MAX_LANES=8 (tests all lane counts)
// - Each lane has decreasing LED count: Lane 0=base, Lane 1=base-1, ..., Lane N=base-N
// - Multi-lane RX validation: 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 × 8 lane counts × 2 strip sizes = 48 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
//
// ============================================================================
// Configuration is now in ValidationConfig.h (included above)
// Includes:
#include <Arduino.h>
#include <FastLED.h>
#include "fl/stl/sstream.h"
#include "Common.h"
#include "ValidationTest.h"
#include "ValidationHelpers.h"
#include "ValidationRemote.h"
#include "SketchHalt.h"
#include "platforms/esp/32/watchdog_esp32.h" // For fl::watchdog_setup()
// ============================================================================
// Configuration
// ============================================================================
// Serial port timeout (milliseconds) - wait for serial monitor to attach
#define SERIAL_TIMEOUT_MS 120000 // 120 seconds
const fl::RxDeviceType RX_TYPE = fl::RxDeviceType::RMT;
// ============================================================================
// 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).
#if defined(FL_IS_ESP_32S3)
// ESP32-S3: Standard configuration with jumper wire
// Connect GPIO 1 (TX) to GPIO 2 (RX) with a physical jumper wire
constexpr int DEFAULT_PIN_TX = 1;
constexpr int DEFAULT_PIN_RX = 2;
#elif defined(FL_IS_ESP_32S2)
// ESP32-S2: Standard configuration
constexpr int DEFAULT_PIN_TX = 1;
constexpr int DEFAULT_PIN_RX = 0;
#elif defined(FL_IS_ESP_32C6)
// ESP32-C6: RISC-V - standard configuration
constexpr int DEFAULT_PIN_TX = 1;
constexpr int DEFAULT_PIN_RX = 0;
#elif defined(FL_IS_ESP_32C3)
// ESP32-C3: RISC-V - standard configuration
constexpr int DEFAULT_PIN_TX = 1;
constexpr int DEFAULT_PIN_RX = 0;
#else
// ESP32 (classic) and other variants: Standard configuration
constexpr int DEFAULT_PIN_TX = 1;
constexpr int DEFAULT_PIN_RX = 0;
#endif
// Runtime pin variables - can be modified via JSON-RPC
int g_pin_tx = DEFAULT_PIN_TX;
int g_pin_rx = DEFAULT_PIN_RX;
// Legacy macros for backward compatibility in existing code
#define PIN_TX g_pin_tx
#define PIN_RX g_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)
#define RX_BUFFER_SIZE (3000 * 32 + 100) // LEDs × 32:1 expansion + headroom
// Use PSRAM-backed vector for dynamic allocation (avoids DRAM overflow on ESP32S2)
fl::vector_psram<uint8_t> rx_buffer; // Shared RX buffer - initialized in setup()
// ⚠️ CRITICAL: RMT RX channel - MUST persist across ALL loop iterations
// Created ONCE in setup(), reused for all driver tests
// DO NOT reset, destroy, or recreate this channel in loop()
fl::shared_ptr<fl::RxDevice> rx_channel;
// Factory function for creating RxDevice instances
// This allows ValidationRemoteControl to recreate the RX channel when the pin changes
fl::shared_ptr<fl::RxDevice> createRxDevice(int pin) {
return fl::RxDevice::create<RX_TYPE>(pin);
}
// ============================================================================
// Global Error Tracking and Halt Control
// ============================================================================
// Sketch halt controller - handles safe halting without watchdog timer resets
SketchHalt halt;
// Remote RPC system for dynamic test control via JSON commands
// Using Singleton pattern for thread-safe lazy initialization
using RemoteControlSingleton = fl::Singleton<ValidationRemoteControl>;
// ============================================================================
// Global Test Matrix State
// ============================================================================
// Available drivers discovered in setup()
fl::vector<fl::DriverInfo> drivers_available;
// Test matrix configuration (built from defines and available drivers)
fl::TestMatrixConfig test_matrix;
// All test cases generated from matrix configuration
fl::vector<fl::TestCaseConfig> test_cases;
// Test case results (one per test case)
fl::vector<fl::TestCaseResult> test_results;
// Frame counter - tracks which iteration of loop() we're on
uint32_t frame_counter = 0;
// Test completion flag - set to true after first test matrix run
bool test_matrix_complete = false;
// Test start flag - set to true when start() JSON-RPC command is called
bool start_command_received = false;
void setup() {
Serial.begin(115200);
// Initialize watchdog with 5 second timeout (ESP32 only)
// Provides automatic proof-of-life monitoring and USB disconnect fix for Windows
// DISABLED FOR DEBUGGING: Investigating if watchdog causes crash at 7.69s
// fl::watchdog_setup(5000);
while (!Serial && millis() < 10000); // Wait max 10 seconds for serial
FL_WARN("[SETUP] Validation sketch starting - serial output active");
// Initialize RX buffer dynamically (uses PSRAM if available, falls back to heap)
rx_buffer.resize(RX_BUFFER_SIZE);
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 Validation - Test Matrix Configuration ║\n";
ss << "╚════════════════════════════════════════════════════════════════╝\n";
// Platform information
ss << "\n[PLATFORM]\n";
#if defined(FL_IS_ESP_32C6)
ss << " Chip: ESP32-C6 (RISC-V)\n";
#elif defined(FL_IS_ESP_32S3)
ss << " Chip: ESP32-S3 (Xtensa)\n";
#elif defined(FL_IS_ESP_32C3)
ss << " Chip: ESP32-C3 (RISC-V)\n";
#elif defined(FL_IS_ESP_32DEV)
ss << " Chip: ESP32 (Xtensa)\n";
#else
ss << " Chip: Unknown ESP32 variant\n";
#endif
// Hardware configuration - machine-parseable for --expect validation
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: " << (RX_TYPE == fl::RxDeviceType::RMT ? "RMT" : "ISR") << "\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());
// ========================================================================
// RX Channel Setup
// ========================================================================
ss.clear();
ss << "\n[RX SETUP] Creating RX channel for LED validation\n";
ss << "[RX CREATE] Creating RX channel on PIN " << PIN_RX
<< " (" << (40000000 / 1000000) << "MHz, " << RX_BUFFER_SIZE << " symbols)";
FL_PRINT(ss.str());
rx_channel = fl::RxDevice::create<RX_TYPE>(PIN_RX);
if (!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());
halt.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 validation";
FL_PRINT(ss.str());
// ========================================================================
// 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
// Note: Global variables (drivers_available, test_matrix, etc.) are empty at this point
// but are passed by reference, so RPC functions will access current values when called.
RemoteControlSingleton::instance().registerFunctions(
drivers_available,
test_matrix,
test_cases,
test_results,
start_command_received,
test_matrix_complete,
frame_counter,
rx_channel,
rx_buffer,
g_pin_tx,
g_pin_rx,
DEFAULT_PIN_TX,
DEFAULT_PIN_RX,
createRxDevice // Factory function for RxDevice creation
);
FL_PRINT("[REMOTE RPC] ✓ RPC system initialized (testGpioConnection available)");
// ========================================================================
// 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());
// 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 validation)
if (!testRxChannel(rx_channel, PIN_TX, PIN_RX, 40000000, 100)) {
FL_ERROR("[GPIO BASELINE TEST] FAILED - RX did not capture manual GPIO toggles");
FL_ERROR("[GPIO BASELINE TEST] Possible causes:");
FL_ERROR(" 1. GPIO " << PIN_TX << " and GPIO " << PIN_RX << " are not physically connected");
FL_ERROR(" 2. RX channel initialization failed");
FL_ERROR(" 3. GPIO conflict with other peripherals (USB Serial JTAG on C6 uses certain GPIOs)");
halt.error("GPIO baseline test failed - check hardware connections");
return;
}
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 << ")");
// List all available drivers and store globally
drivers_available = FastLED.getDriverInfos();
ss.clear();
ss << "\n[DRIVER DISCOVERY]\n";
ss << " Found " << drivers_available.size() << " driver(s) available:\n";
for (fl::size i = 0; i < drivers_available.size(); i++) {
ss << " " << (i+1) << ". " << drivers_available[i].name.c_str()
<< " (priority: " << drivers_available[i].priority
<< ", enabled: " << (drivers_available[i].enabled ? "yes" : "no") << ")\n";
}
FL_PRINT(ss.str());
// Validate that expected engines are available for this platform
validateExpectedEngines();
// Test matrix configuration
ss.clear();
ss << "\n[TEST MATRIX CONFIGURATION]\n";
// Driver filtering
#if defined(JUST_PARLIO)
ss << " Driver Filter: JUST_PARLIO (testing PARLIO only)\n";
#elif defined(JUST_RMT)
ss << " Driver Filter: JUST_RMT (testing RMT only)\n";
#elif defined(JUST_SPI)
ss << " Driver Filter: JUST_SPI (testing SPI only)\n";
#elif defined(JUST_UART)
ss << " Driver Filter: JUST_UART (testing UART only)\n";
#else
ss << " Driver Filter: None (testing all available drivers)\n";
#endif
// Build test matrix and show which drivers will be tested
test_matrix = buildTestMatrix(drivers_available);
ss << " Drivers to Test (" << test_matrix.enabled_drivers.size() << "):\n";
// Machine-parseable driver validation prints (for --expect flags)
for (fl::size i = 0; i < test_matrix.enabled_drivers.size(); i++) {
const char* driver_name = test_matrix.enabled_drivers[i].c_str();
ss << " → " << driver_name << "\n";
// Explicit validation print: "DRIVER_ENABLED: <name>"
ss << " DRIVER_ENABLED: " << driver_name << "\n";
}
FL_PRINT(ss.str());
// Lane range - machine-parseable for --expect validation (runtime configured)
ss.clear();
ss << " Lane Range: " << test_matrix.min_lanes << "-" << test_matrix.max_lanes << " lanes\n";
ss << " → Lane N has base_size - N LEDs (decreasing pattern)\n";
ss << " → Multi-lane: Only Lane 0 validated (hardware limitation)\n";
// Explicit validation prints: "LANE_MIN: X" and "LANE_MAX: Y"
ss << " LANE_MIN: " << test_matrix.min_lanes << "\n";
ss << " LANE_MAX: " << test_matrix.max_lanes << "\n";
// Strip sizes - machine-parseable for --expect validation (runtime configured)
if (test_matrix.strip_sizes.empty()) {
ss << " Strip Sizes: None (ERROR: no strip sizes configured)\n";
} else {
ss << " Strip Sizes: [";
for (fl::size i = 0; i < test_matrix.strip_sizes.size(); i++) {
if (i > 0) ss << ", ";
ss << test_matrix.strip_sizes[i];
}
ss << "] LEDs\n";
// Emit STRIP_SIZE_TESTED for each size (for --expect validation)
for (fl::size i = 0; i < test_matrix.strip_sizes.size(); i++) {
ss << " STRIP_SIZE_TESTED: " << test_matrix.strip_sizes[i] << "\n";
}
}
// Bit patterns
ss << " Bit Patterns: 4 mixed RGB patterns (MSB/LSB testing)\n";
ss << " → Pattern A: R=0xF0, G=0x0F, B=0xAA\n";
ss << " → Pattern B: R=0x55, G=0xFF, B=0x00\n";
ss << " → Pattern C: R=0x0F, G=0xAA, B=0xF0\n";
ss << " → Pattern D: RGB Solid Alternating\n";
// Total test cases
int total_test_cases = test_matrix.getTotalTestCases();
int total_validation_tests = total_test_cases * 4; // 4 bit patterns per test case
ss << " Total Test Cases: " << total_test_cases << "\n";
ss << " Total Validation Tests: " << total_validation_tests << " (" << total_test_cases << " cases × 4 patterns)";
FL_PRINT(ss.str());
ss.clear();
ss << "\n⚠️ [HARDWARE SETUP REQUIRED]\n";
ss << " If using non-RMT peripherals for TX (e.g., SPI, ParallelIO):\n";
ss << " → Connect GPIO " << PIN_TX << " (Lane 0 TX) to GPIO " << PIN_RX << " (RX) with a physical jumper wire\n";
ss << " → Multi-lane: Only Lane 0 is validated via RX (other lanes transmit but not verified)\n";
ss << " → ESP32 GPIO matrix cannot route other peripheral outputs to RMT input\n";
ss << "\n";
ss << " ESP32-S3 IMPORTANT: Use GPIO 11 (MOSI) for best performance\n";
ss << " → GPIO 11 is SPI2 IO_MUX pin (bypasses GPIO matrix for 80MHz speed)\n";
ss << " → Other GPIOs use GPIO matrix routing (limited to 26MHz, may see timing issues)\n";
FL_PRINT(ss.str());
// Generate all test cases from the matrix
test_cases = generateTestCases(test_matrix, PIN_TX, PIN_RX);
ss.clear();
ss << "\n[TEST CASE GENERATION]\n";
ss << " Generated " << test_cases.size() << " test case(s)\n";
// Machine-parseable: "TEST_CASES_GENERATED: X"
ss << " TEST_CASES_GENERATED: " << test_cases.size();
FL_PRINT(ss.str());
// Initialize result tracking for each test case
for (fl::size i = 0; i < test_cases.size(); i++) {
const auto& test_case = test_cases[i];
test_results.push_back(fl::TestCaseResult(
test_case.driver_name.c_str(),
test_case.lane_count,
test_case.base_strip_size
));
}
// Note: RPC functions already registered early in setup() (before GPIO baseline test)
// This allows testGpioConnection to work even if setup() fails early.
// 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("testCases", static_cast<int64_t>(test_cases.size()));
readyData.set("drivers", static_cast<int64_t>(test_matrix.enabled_drivers.size()));
printStreamRaw("ready", readyData);
// Human-readable diagnostics (not machine-parsed)
FL_PRINT("\n[SETUP COMPLETE] Validation ready - awaiting JSON-RPC commands");
delay(2000);
}
// ============================================================================
// Helper Functions
// ============================================================================
/// @brief Run a single test case (one driver × lane count × strip size combination)
/// @param test_case Test case configuration (driver, lanes, strip sizes)
/// @param test_result Test result tracker (modified with pass/fail counts)
/// @param timing_config Chipset timing to test
/// @param rx_channel RX channel for loopback validation
/// @param rx_buffer RX buffer for capture
void runSingleTestCase(
fl::TestCaseConfig& test_case,
fl::TestCaseResult& test_result,
const fl::NamedTimingConfig& timing_config,
fl::shared_ptr<fl::RxDevice> rx_channel,
fl::span<uint8_t> rx_buffer) {
fl::sstream ss;
ss << "\n╔════════════════════════════════════════════════════════════════╗\n";
ss << "║ TEST CASE: " << test_case.driver_name.c_str()
<< " | " << test_case.lane_count << " lane(s)"
<< " | " << test_case.base_strip_size << " LEDs\n";
ss << "╚════════════════════════════════════════════════════════════════╝";
FL_PRINT(ss.str());
// Set this driver as exclusive for testing
if (!FastLED.setExclusiveDriver(test_case.driver_name.c_str())) {
FL_ERROR("Failed to set " << test_case.driver_name.c_str() << " as exclusive driver");
test_result.skipped = true;
return;
}
FL_PRINT(test_case.driver_name.c_str() << " driver enabled exclusively");
// Build TX channel configs for all lanes
fl::vector<fl::ChannelConfig> tx_configs;
for (int lane_idx = 0; lane_idx < test_case.lane_count; lane_idx++) {
auto& lane = test_case.lanes[lane_idx];
FL_PRINT("[Lane " << lane_idx << "] Pin: " << lane.pin << ", LEDs: " << lane.num_leds);
// Create channel config for this lane
tx_configs.push_back(fl::ChannelConfig(
lane.pin,
timing_config.timing,
lane.leds,
COLOR_ORDER
));
}
// Create validation configuration
fl::ValidationConfig validation_config(
timing_config.timing,
timing_config.name,
tx_configs,
test_case.driver_name.c_str(),
rx_channel,
rx_buffer,
test_case.base_strip_size,
RX_TYPE
);
// Run warm-up frame (discard results)
FL_PRINT("\n[INFO] Running warm-up frame (results will be discarded)");
int warmup_total = 0, warmup_passed = 0;
validateChipsetTiming(validation_config, warmup_total, warmup_passed);
FL_PRINT("[INFO] Warm-up complete (" << warmup_passed << "/" << warmup_total << " passed - discarding)");
// Run actual test frame (keep results)
FL_PRINT("\n[INFO] Running actual test frame");
validateChipsetTiming(validation_config, test_result.total_tests, test_result.passed_tests);
// Log test case result
if (test_result.allPassed()) {
FL_PRINT("\n[PASS] Test case " << test_case.driver_name.c_str()
<< " (" << test_case.lane_count << " lanes, "
<< test_case.base_strip_size << " LEDs) completed successfully");
} else {
FL_ERROR("[FAIL] Test case " << test_case.driver_name.c_str()
<< " (" << test_case.lane_count << " lanes, "
<< test_case.base_strip_size << " LEDs) FAILED: "
<< test_result.passed_tests << "/" << test_result.total_tests << " tests passed");
}
}
// ============================================================================
// 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() {
// Process RPC commands - this is the primary entry point for all test control
RemoteControlSingleton::instance().tick(millis());
RemoteControlSingleton::instance().processSerialInput();
// Check halt state after processing RPC (allows reset to work)
if (halt.check()) return;
// 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));
status.set("testCases", static_cast<int64_t>(test_cases.size()));
printStreamRaw("status", status);
last_status_ms = now;
}
// Minimal delay to prevent tight loop and reduce power consumption
delay(1);
}