20#if defined(ESP32) && FASTLED_ESP32_HAS_PARLIO
21#include "platforms/esp/32/drivers/parlio/channel_driver_parlio.h"
32 FL_WARN(
"[RAW EDGE TIMING] ERROR: RX channel is null");
38 size_t buffer_size = range.
count < 256 ? range.
count : 256;
42 size_t edge_count = rx_channel->getRawEdgeTimes(edges, range.
offset);
44 if (edge_count == 0) {
45 FL_WARN(
"[RAW EDGE TIMING] WARNING: No edge data captured at offset " << range.
offset);
50 size_t start_idx = range.
offset;
51 size_t end_idx = range.
offset + edge_count;
53 FL_WARN(
"[RAW EDGES " << start_idx <<
".." << (end_idx - 1) <<
"]");
56 for (
size_t i = 0; i < edge_count; i++) {
57 const char* level = edges[i].high ?
"H" :
"L";
58 size_t absolute_index = start_idx + i;
59 FL_WARN(
" [" << absolute_index <<
"] " << level <<
" " << edges[i].ns);
63 if (range.
offset == 0 && edge_count >= 16) {
65 uint32_t expected_bit0_high = timing.
t1_ns;
66 uint32_t expected_bit0_low = timing.
t2_ns + timing.
t3_ns;
67 uint32_t expected_bit1_high = timing.
t1_ns + timing.
t2_ns;
68 uint32_t expected_bit1_low = timing.
t3_ns;
70 const uint32_t tolerance = 150;
72 bool has_short_high =
false, has_long_high =
false;
73 bool has_short_low =
false, has_long_low =
false;
75 for (
size_t i = 0; i < edge_count; i++) {
76 uint32_t ns = edges[i].ns;
78 if (ns >= expected_bit0_high - tolerance && ns <= expected_bit0_high + tolerance)
79 has_short_high =
true;
80 if (ns >= expected_bit1_high - tolerance && ns <= expected_bit1_high + tolerance)
83 if (ns >= expected_bit1_low - tolerance && ns <= expected_bit1_low + tolerance)
85 if (ns >= expected_bit0_low - tolerance && ns <= expected_bit0_low + tolerance)
91 ss <<
"\n[RAW EDGE TIMING] Pattern Analysis:\n";
92 ss <<
" Short HIGH (~" << expected_bit0_high <<
"ns, Bit 0): " << (has_short_high ?
"FOUND ✓" :
"MISSING ✗") <<
"\n";
93 ss <<
" Long HIGH (~" << expected_bit1_high <<
"ns, Bit 1): " << (has_long_high ?
"FOUND ✓" :
"MISSING ✗") <<
"\n";
94 ss <<
" Short LOW (~" << expected_bit1_low <<
"ns, Bit 1): " << (has_short_low ?
"FOUND ✓" :
"MISSING ✗") <<
"\n";
95 ss <<
" Long LOW (~" << expected_bit0_low <<
"ns, Bit 0): " << (has_long_low ?
"FOUND ✓" :
"MISSING ✗");
98 if (has_short_high && has_long_high && has_short_low && has_long_low) {
99 FL_WARN(
"\n[RAW EDGE TIMING] ✓ Encoder appears to be working correctly (varied timing patterns)");
100 }
else if (!has_short_high && !has_long_high) {
102 ss <<
"[RAW EDGE TIMING] ✗ ENCODER BROKEN: No valid HIGH pulses detected!\n";
103 ss <<
"[RAW EDGE TIMING] Possible causes:\n";
104 ss <<
"[RAW EDGE TIMING] 1. Encoder not reading pixel buffer data\n";
105 ss <<
"[RAW EDGE TIMING] 2. Bytes encoder state machine stuck\n";
106 ss <<
"[RAW EDGE TIMING] 3. Data pointer not passed correctly to encoder";
108 }
else if (!has_short_low && !has_long_low) {
110 FL_WARN(
"[RAW EDGE TIMING] ✗ ENCODER BROKEN: No valid LOW pulses detected!");
112 FL_WARN(
"[RAW EDGE TIMING] ⚠ Partial pattern match - encoder may have issues");
164 mode, current,
false , gamma.get());
177 if (!rx_channel || clock_hz == 0) {
178 FL_WARN(
"[SPI DECODE] Invalid parameters");
182 const uint32_t bit_period_ns =
static_cast<uint32_t
>(1000000000ULL / clock_hz);
183 const uint32_t half_bit_ns = bit_period_ns / 2;
184 FL_WARN(
"[SPI DECODE] clock=" << clock_hz <<
" Hz, bit_period=" << bit_period_ns <<
" ns");
187 constexpr size_t MAX_EDGES = 4096;
190 size_t edge_count = rx_channel->getRawEdgeTimes(edge_span, 0);
192 if (edge_count == 0) {
193 FL_WARN(
"[SPI DECODE] No edges captured");
196 FL_WARN(
"[SPI DECODE] Captured " << edge_count <<
" edges");
204 for (
size_t i = 0; i < edge_count; i++) {
205 uint32_t duration = edges[i].ns;
206 uint32_t num_bits = (duration + half_bit_ns) / bit_period_ns;
207 if (num_bits == 0) num_bits = 1;
208 uint8_t bit_val = edges[i].high ? 1 : 0;
209 for (uint32_t b = 0; b < num_bits; b++) {
214 FL_WARN(
"[SPI DECODE] Reconstructed " << bits.
size() <<
" bits");
216 if (bits.
size() < 32) {
217 FL_WARN(
"[SPI DECODE] Too few bits for APA102 frame");
222 size_t total_bytes = bits.
size() / 8;
224 for (
size_t i = 0; i < total_bytes; i++) {
225 uint8_t byte_val = 0;
226 for (
int bit = 7; bit >= 0; bit--) {
227 size_t bit_idx = i * 8 + (7 - bit);
228 if (bit_idx < bits.
size() && bits[bit_idx]) {
229 byte_val |= (1 << bit);
232 raw_bytes[i] = byte_val;
235 FL_WARN(
"[SPI DECODE] Decoded " << total_bytes <<
" raw bytes");
240 dbg <<
"[SPI DECODE] First bytes:";
241 size_t show = total_bytes < 20 ? total_bytes : 20;
242 for (
size_t i = 0; i <
show; i++) {
244 uint8_t hi = raw_bytes[i] >> 4;
245 uint8_t lo = raw_bytes[i] & 0xF;
246 dbg << (char)(hi < 10 ?
'0' + hi :
'A' + hi - 10);
247 dbg << (char)(lo < 10 ?
'0' + lo :
'A' + lo - 10);
256 size_t data_start = 0;
259 if (total_bytes >= 4 && raw_bytes[0] == 0x00 && raw_bytes[1] == 0x00 &&
260 raw_bytes[2] == 0x00 && raw_bytes[3] == 0x00) {
262 FL_WARN(
"[SPI DECODE] Found start frame at offset 0");
266 FL_WARN(
"[SPI DECODE] No start frame (first edge = first LED data)");
276 size_t led_bytes_written = 0;
277 size_t pos = data_start;
279 while (
pos + 4 <= total_bytes) {
280 uint8_t header = raw_bytes[
pos];
283 if ((header & 0xE0) != 0xE0) {
284 FL_WARN(
"[SPI DECODE] End of LED data at byte " <<
pos
285 <<
" (header=0x" << ((header >> 4) < 10 ?
'0' + (header >> 4) :
'A' + (header >> 4) - 10)
286 << ((header & 0xF) < 10 ?
'0' + (header & 0xF) :
'A' + (header & 0xF) - 10) <<
")");
290 uint8_t c0 = raw_bytes[
pos + 1];
291 uint8_t c1 = raw_bytes[
pos + 2];
292 uint8_t c2 = raw_bytes[
pos + 3];
294 if (led_bytes_written + 3 <= rx_buffer.
size()) {
295 rx_buffer[led_bytes_written + 0] = c0;
296 rx_buffer[led_bytes_written + 1] = c1;
297 rx_buffer[led_bytes_written + 2] = c2;
298 led_bytes_written += 3;
300 FL_WARN(
"[SPI DECODE] rx_buffer full at LED " << (led_bytes_written / 3));
306 size_t num_leds = led_bytes_written / 3;
307 FL_WARN(
"[SPI DECODE] Extracted " << num_leds <<
" LEDs (" << led_bytes_written <<
" RGB bytes)");
308 return led_bytes_written;
328 rx_config.
hz = 40000000;
332 bool is_uart_driver = (
fl::strcmp(driver_name,
"UART") == 0);
339 bool is_rmt_driver = (
fl::strcmp(driver_name,
"RMT") == 0);
348 rx_config.
use_dma = !is_rmt_driver;
350 FL_WARN(
"[CAPTURE] RMT TX -> RMT RX: Internal loopback enabled (io_loop_back=true)");
357 FL_WARN(
"[CAPTURE] " << driver_name <<
" TX -> external RX: External GPIO wire (io_loop_back=false, use_dma=true)");
366 const uint32_t TX_WAIT_TIMEOUT_MS = 1000;
371 FL_WARN(
"[CAPTURE] RMT: Two-TX approach (ESP32-S3 workaround)");
373 if (!
FastLED.wait(TX_WAIT_TIMEOUT_MS)) {
374 FL_ERROR(
"[CAPTURE] TX wait timeout (pre-arm) - driver may be stalled");
379 if (!rx_channel->begin(rx_config)) {
380 FL_ERROR(
"Failed to arm RX receiver");
385 if (!
FastLED.wait(TX_WAIT_TIMEOUT_MS)) {
386 FL_ERROR(
"[CAPTURE] TX wait timeout (capture) - driver may be stalled");
391 if (!rx_channel->begin(rx_config)) {
392 FL_WARN(
"[CAPTURE] RX begin() failed for pin " << rx_channel->getPin());
395 FL_WARN(
"[CAPTURE] RX armed, calling FastLED.show()...");
398 FL_WARN(
"[CAPTURE] FastLED.show() returned, calling wait...");
399 if (!
FastLED.wait(TX_WAIT_TIMEOUT_MS)) {
400 FL_WARN(
"[CAPTURE] FastLED.wait() timed out");
403 FL_WARN(
"[CAPTURE] FastLED.wait() done");
412 const uint32_t rx_wait_ms = is_uart_driver ? 500 : 150;
413 FL_WARN(
"[CAPTURE] Waiting for RX completion (" << rx_wait_ms <<
"ms timeout)...");
414 auto wait_result = rx_channel->wait(rx_wait_ms);
415 FL_WARN(
"[CAPTURE] RX wait returned: " <<
static_cast<int>(wait_result));
418 FL_WARN(
"RX wait failed (timeout or no data received)");
420 ss <<
"\n⚠️ TROUBLESHOOTING:\n";
421 ss <<
" 1. Connect physical jumper wire from TX GPIO to RX GPIO " << rx_channel->getPin() <<
"\n";
422 ss <<
" 2. Check that both TX and RX pins are correctly configured\n";
423 ss <<
" 3. Verify the GPIO connection is working (GPIO baseline test should pass)\n";
424 ss <<
" 4. For RMT TX → RMT RX: Ensure io_loop_back=true in RxConfig";
426 if (!is_uart_driver) {
430 FL_WARN(
"[CAPTURE] UART: attempting decode with captured edges despite timeout");
439 if (is_uart_driver) {
440 FL_WARN(
"[CAPTURE] UART (inverted TX): using standard WS2812 decoder with UART timing...");
452 rx_timing.gap_tolerance_ns = 100000;
454 FL_WARN(
"[CAPTURE] UART RX timing: T0H=" << uart_timing.T1
455 <<
" T1H=" << (uart_timing.T1 + uart_timing.T2)
456 <<
" T0L=" << (uart_timing.T2 + uart_timing.T3)
457 <<
" T1L=" << uart_timing.T3);
459 auto decode_result = rx_channel->decode(rx_timing, rx_buffer);
460 if (!decode_result.ok()) {
461 FL_WARN(
"[CAPTURE] UART decode failed (error: " <<
static_cast<int>(decode_result.error()) <<
")");
465 FL_WARN(
"[CAPTURE] UART decoded " << decode_result.value() <<
" LED bytes");
466 return decode_result.value();
472 bool is_lcd_spi_driver = (
fl::strcmp(driver_name,
"LCD_SPI") == 0);
473 bool is_i2s_spi_driver = (
fl::strcmp(driver_name,
"I2S_SPI") == 0);
474 if (is_lcd_spi_driver || is_i2s_spi_driver) {
476 const uint32_t spi_clock_hz = 2400000;
477 FL_WARN(
"[CAPTURE] SPI chipset decode: clock=" << spi_clock_hz <<
" Hz");
478 size_t decoded =
decodeSpiEdges(rx_channel, rx_buffer, spi_clock_hz);
480 FL_WARN(
"[CAPTURE] SPI decode failed");
501 bool is_spi_driver = (
fl::strcmp(driver_name,
"SPI") == 0);
502 bool is_parlio_driver = (
fl::strcmp(driver_name,
"PARLIO") == 0);
503 bool is_i2s_driver = (
fl::strcmp(driver_name,
"I2S") == 0);
507 bool is_lcd_clockless_driver = (
fl::strcmp(driver_name,
"LCD_CLOCKLESS") == 0);
508 bool lcd_clockless_uses_wave3 =
false;
509 if (is_lcd_clockless_driver) {
513 bool uses_wave8 = is_spi_driver || is_parlio_driver || is_i2s_driver
514 || (is_lcd_clockless_driver && !lcd_clockless_uses_wave3);
515 bool uses_wave3 = is_lcd_clockless_driver && lcd_clockless_uses_wave3;
522 const uint32_t tick_ns = is_parlio_driver ? 125 : (period / 8);
524 const uint32_t pulses_bit0 =
static_cast<uint32_t
>(
525 static_cast<float>(timing.
t1_ns) / period * 8.0f + 0.5f);
526 const uint32_t pulses_bit1 =
static_cast<uint32_t
>(
527 static_cast<float>(timing.
t1_ns + timing.
t2_ns) / period * 8.0f + 0.5f);
529 const uint32_t actual_t0h = pulses_bit0 * tick_ns;
530 const uint32_t actual_t1h = pulses_bit1 * tick_ns;
531 const uint32_t actual_period = 8 * tick_ns;
532 const char* wave8_name = is_spi_driver ?
"SPI_wave8" :
533 is_parlio_driver ?
"PARLIO_wave8" :
534 is_lcd_clockless_driver ?
"LCD_CLOCKLESS_wave8" :
"I2S_wave8";
537 actual_t1h - actual_t0h,
538 actual_period - actual_t1h,
542 FL_WARN(
"[RX TIMING] " << wave8_name <<
": pulses_bit0=" << pulses_bit0
543 <<
" pulses_bit1=" << pulses_bit1
544 <<
" tick_ns=" << tick_ns
545 <<
" -> T1=" << tx_timing.
T1 <<
" T2=" << tx_timing.
T2
546 <<
" T3=" << tx_timing.
T3);
547 }
else if (uses_wave3) {
550 const uint32_t tick_ns = period / 3;
551 const uint32_t ticks_bit0 =
static_cast<uint32_t
>(
552 static_cast<float>(timing.
t1_ns) / period * 3.0f + 0.5f);
553 const uint32_t ticks_bit1 =
static_cast<uint32_t
>(
554 static_cast<float>(timing.
t1_ns + timing.
t2_ns) / period * 3.0f + 0.5f);
555 const uint32_t actual_t0h = ticks_bit0 * tick_ns;
556 const uint32_t actual_t1h = ticks_bit1 * tick_ns;
557 const uint32_t actual_period = 3 * tick_ns;
560 actual_t1h - actual_t0h,
561 actual_period - actual_t1h,
563 "LCD_CLOCKLESS_wave3"
565 FL_WARN(
"[RX TIMING] LCD_CLOCKLESS_wave3: ticks_bit0=" << ticks_bit0
566 <<
" ticks_bit1=" << ticks_bit1
567 <<
" tick_ns=" << tick_ns
568 <<
" -> T1=" << tx_timing.
T1 <<
" T2=" << tx_timing.
T2
569 <<
" T3=" << tx_timing.
T3);
575 const uint32_t tolerance = (uses_wave8 || uses_wave3) ? 200 : 170;
582 rx_timing.gap_tolerance_ns = 100000;
584 FL_WARN(
"[CAPTURE] Decoding...");
585 auto decode_result = rx_channel->decode(rx_timing, rx_buffer);
587 if (!decode_result.ok()) {
590 FL_WARN(
"Decode failed (error code: " <<
static_cast<int>(decode_result.error()) <<
")");
596 FL_WARN(
"[CAPTURE] Decoded " << decode_result.value() <<
" bytes");
597 return decode_result.value();
604 int& total,
int& passed) {
610 FL_WARN(
"\n[MULTI-LANE] Testing " << config.
tx_configs.
size() <<
" lanes, testing Lane 0 only (hardware limitation)");
614 for (
size_t config_idx = 0; config_idx < channels_to_test; config_idx++) {
619 size_t num_leds =
leds.size();
627 static_cast<int>(config_idx),
629 static_cast<int>(num_leds),
634 <<
", Pin " << config.
tx_configs[config_idx].getDataPin()
635 <<
", LEDs " << config.
tx_configs[config_idx].mLeds.
size() <<
"] ===");
639 FL_ERROR(
"[" << ctx.driver_name <<
"/" << ctx.timing_name <<
"/" << ctx.pattern_name
640 <<
" | Lane " << ctx.lane_index <<
"/" << ctx.lane_count
641 <<
" (Pin " << ctx.pin_number <<
", " << ctx.num_leds <<
" LEDs) | RX:" << ctx.rx_type_name <<
"] "
642 <<
"RX channel is null - must be created in .ino and passed via AutoResearchConfig");
643 FL_ERROR(
"Result: FAIL ✗ (RX channel not provided)");
649 if (bytes_captured == 0) {
650 FL_ERROR(
"[" << ctx.driver_name <<
"/" << ctx.timing_name <<
"/" << ctx.pattern_name
651 <<
" | Lane " << ctx.lane_index <<
"/" << ctx.lane_count
652 <<
" (Pin " << ctx.pin_number <<
", " << ctx.num_leds <<
" LEDs) | RX:" << ctx.rx_type_name <<
"] "
653 <<
"Result: FAIL ✗ (capture failed)");
663 size_t expected_len = expected_encoded.
size();
665 FL_WARN(
"UCS7604 encoded comparison: expected " << expected_len <<
" bytes, captured " << bytes_captured);
667 size_t compare_len = (bytes_captured < expected_len) ? bytes_captured : expected_len;
668 for (
size_t i = 0; i < compare_len; i++) {
669 if (expected_encoded[i] != config.
rx_buffer[i]) {
674 if (bytes_captured < expected_len) {
675 mismatches +=
static_cast<int>(expected_len - bytes_captured);
678 FL_WARN(
"Bytes Captured: " << bytes_captured <<
" (expected: " << expected_len <<
")");
679 int total_bytes =
static_cast<int>(expected_len);
680 FL_WARN(
"Accuracy: " << (100.0 * (total_bytes - mismatches) / total_bytes) <<
"% ("
681 << (total_bytes - mismatches) <<
"/" << total_bytes <<
" bytes match)");
684 size_t bytes_expected = num_leds * 3;
687 const size_t front_padding_bytes = 0;
688 const size_t rx_buffer_offset = front_padding_bytes;
690 if (bytes_captured > bytes_expected + front_padding_bytes) {
691 FL_WARN(
"Info: Captured " << bytes_captured <<
" bytes ("
692 << front_padding_bytes <<
" front pad + "
693 << bytes_expected <<
" LED data + "
694 << (bytes_captured - bytes_expected - front_padding_bytes) <<
" back pad/RESET)");
698 size_t bytes_to_check = (bytes_captured < bytes_expected + rx_buffer_offset) ?
699 (bytes_captured > rx_buffer_offset ? bytes_captured - rx_buffer_offset : 0) :
701 (void)bytes_to_check;
703 for (
size_t i = 0; i < num_leds; i++) {
704 size_t byte_offset = rx_buffer_offset + i * 3;
705 if (byte_offset + 2 >= bytes_captured) {
706 FL_ERROR(
"[" << ctx.driver_name <<
"/" << ctx.timing_name <<
"/" << ctx.pattern_name
707 <<
" | Lane " << ctx.lane_index <<
"/" << ctx.lane_count
708 <<
" (Pin " << ctx.pin_number <<
", " << ctx.num_leds <<
" LEDs) | RX:" << ctx.rx_type_name <<
"] "
709 <<
"Incomplete data for LED[" <<
static_cast<int>(i)
710 <<
"] (only " << bytes_captured <<
" bytes captured)");
714 uint8_t expected_r =
leds[i].r;
715 uint8_t expected_g =
leds[i].g;
716 uint8_t expected_b =
leds[i].b;
718 uint8_t actual_r = config.
rx_buffer[byte_offset + 0];
719 uint8_t actual_g = config.
rx_buffer[byte_offset + 1];
720 uint8_t actual_b = config.
rx_buffer[byte_offset + 2];
722 if (expected_r != actual_r || expected_g != actual_g || expected_b != actual_b) {
727 FL_WARN(
"Bytes Captured: " << bytes_captured <<
" (expected: " << bytes_expected <<
")");
728 FL_WARN(
"Accuracy: " << (100.0 * (num_leds - mismatches) / num_leds) <<
"% ("
729 << (num_leds - mismatches) <<
"/" << num_leds <<
" LEDs match)");
732 if (mismatches == 0) {
736 FL_ERROR(
"[" << ctx.driver_name <<
"/" << ctx.timing_name <<
"/" << ctx.pattern_name
737 <<
" | Lane " << ctx.lane_index <<
"/" << ctx.lane_count
738 <<
" (Pin " << ctx.pin_number <<
", " << ctx.num_leds <<
" LEDs) | RX:" << ctx.rx_type_name <<
"] "
739 <<
"Result: FAIL ✗");
749 int& total,
int& passed,
753 ss <<
"\n╔════════════════════════════════════════════════════════════════╗\n";
754 ss <<
"║ MULTI-RUN TEST: " << test_name <<
"\n";
755 ss <<
"║ Runs: " << multi_config.
num_runs <<
" | Print Mode: "
757 ss <<
"╚════════════════════════════════════════════════════════════════╝";
770 for (
int run = 1; run <= multi_config.
num_runs; run++) {
772 if (run % 3 == 1 || multi_config.
num_runs <= 5) {
773 FL_WARN(
"[Run " << run <<
"/" << multi_config.
num_runs <<
"] Testing...");
777 result.run_number = run;
780 for (
size_t config_idx = 0; config_idx < channels_to_test; config_idx++) {
782 size_t num_leds =
leds.size();
783 result.total_leds = num_leds;
784 result.totalBytes = num_leds * 3;
789 if (bytes_captured == 0) {
790 FL_WARN(
"[Run " << run <<
"] Capture failed");
791 result.passed =
false;
799 FL_WARN(
"[RUN " << run <<
"] Driver=" << config.
driver_name <<
", bytes_captured=" << bytes_captured);
800 FL_WARN(
"[RUN " << run <<
"] First 24 bytes:");
801 for (
size_t i = 0; i < 24 && i < bytes_captured; i++) {
809 size_t expected_len = expected_encoded.
size();
810 result.totalBytes =
static_cast<int>(expected_len);
812 size_t compare_len = (bytes_captured < expected_len) ? bytes_captured : expected_len;
813 for (
size_t i = 0; i < compare_len; i++) {
814 if (expected_encoded[i] != config.
rx_buffer[i]) {
815 result.mismatchedBytes++;
816 if ((expected_encoded[i] ^ config.
rx_buffer[i]) == 0x01) {
817 result.lsbOnlyErrors++;
822 if (mismatches == 1) {
823 FL_WARN(
"\n[CORRUPTION @ byte " <<
static_cast<int>(i) <<
", Run " << run
824 <<
"] expected=0x" <<
fl::hex <<
static_cast<int>(expected_encoded[i])
831 static_cast<int>(i), expected_encoded[i], 0, 0,
838 if (bytes_captured < expected_len) {
839 mismatches +=
static_cast<int>(expected_len - bytes_captured);
843 size_t bytes_expected = num_leds * 3;
846 const size_t rx_buffer_offset = 0;
848 size_t bytes_to_check = (bytes_captured < bytes_expected + rx_buffer_offset) ?
849 (bytes_captured > rx_buffer_offset ? bytes_captured - rx_buffer_offset : 0) :
851 (void)bytes_to_check;
853 size_t verified_leds = 0;
854 for (
size_t i = 0; i < num_leds; i++) {
855 size_t byte_offset = rx_buffer_offset + i * 3;
856 if (byte_offset + 2 >= bytes_captured) {
859 verified_leds = i + 1;
861 uint8_t expected_r =
leds[i].r;
862 uint8_t expected_g =
leds[i].g;
863 uint8_t expected_b =
leds[i].b;
865 uint8_t actual_r = config.
rx_buffer[byte_offset + 0];
866 uint8_t actual_g = config.
rx_buffer[byte_offset + 1];
867 uint8_t actual_b = config.
rx_buffer[byte_offset + 2];
870 uint8_t exp_bytes[3] = {expected_r, expected_g, expected_b};
871 uint8_t act_bytes[3] = {actual_r, actual_g, actual_b};
872 bool led_mismatch =
false;
873 for (
int ch = 0; ch < 3; ch++) {
874 if (exp_bytes[ch] != act_bytes[ch]) {
875 result.mismatchedBytes++;
876 if ((exp_bytes[ch] ^ act_bytes[ch]) == 0x01) {
877 result.lsbOnlyErrors++;
885 if (mismatches == 0) {
886 FL_WARN(
"\n[CORRUPTION @ LED " <<
static_cast<int>(i) <<
", Run " << run <<
"]");
889 size_t corruption_edge_index = i * 48;
890 size_t offset = corruption_edge_index > 4 ? corruption_edge_index - 4 : 0;
901 i, expected_r, expected_g, expected_b,
902 actual_r, actual_g, actual_b
913 if (verified_leds < num_leds) {
914 size_t unchecked = num_leds - verified_leds;
915 mismatches +=
static_cast<int>(unchecked);
916 result.mismatchedBytes +=
static_cast<int>(unchecked * 3);
917 FL_WARN(
"[TRUNCATED CAPTURE] Only verified " << verified_leds
918 <<
"/" << num_leds <<
" LEDs (" << bytes_captured
919 <<
" bytes captured, needed " << (num_leds * 3)
920 <<
"). Marking " << unchecked
921 <<
" unchecked LEDs as mismatches.");
925 result.mismatches = mismatches;
926 result.passed = (mismatches == 0);
934 << (result.passed ?
"PASS" :
"FAIL")
935 <<
" | Errors: " << result.mismatches <<
"/" << result.total_leds
936 <<
" (" << (100.0 * (result.total_leds - result.mismatches) / result.total_leds) <<
"%)");
940 FL_WARN(
" First " << result.errors.size() <<
" error(s):");
941 for (
size_t i = 0; i < result.errors.size(); i++) {
942 const auto& err = result.errors[i];
943 FL_WARN(
" LED[" << err.led_index <<
"]: expected RGB("
944 <<
static_cast<int>(err.expected_r) <<
","
945 <<
static_cast<int>(err.expected_g) <<
","
946 <<
static_cast<int>(err.expected_b) <<
") got RGB("
947 <<
static_cast<int>(err.actual_r) <<
","
948 <<
static_cast<int>(err.actual_g) <<
","
949 <<
static_cast<int>(err.actual_b) <<
")");
956 int total_passed = 0;
957 int total_failed = 0;
958 for (
const auto& r : run_results) {
959 if (r.passed) total_passed++;
964 ss <<
"\n╔════════════════════════════════════════════════════════════════╗\n";
965 ss <<
"║ MULTI-RUN SUMMARY\n";
966 ss <<
"╚════════════════════════════════════════════════════════════════╝\n";
967 ss <<
"Total Runs: " << multi_config.
num_runs <<
"\n";
968 ss <<
"Passed: " << total_passed <<
" (" << (100.0 * total_passed / multi_config.
num_runs) <<
"%)\n";
969 ss <<
"Failed: " << total_failed <<
" (" << (100.0 * total_failed / multi_config.
num_runs) <<
"%)";
972 if (total_failed > 0) {
974 ss <<
"\nFailed Run Numbers:\n";
975 for (
const auto& r : run_results) {
977 ss <<
" Run #" << r.run_number <<
" - " << r.mismatches <<
" errors\n";
978 if (!r.errors.empty()) {
979 ss <<
" First error at LED[" << r.errors[0].led_index <<
"]: "
980 <<
"expected RGB(" <<
static_cast<int>(r.errors[0].expected_r) <<
","
981 <<
static_cast<int>(r.errors[0].expected_g) <<
","
982 <<
static_cast<int>(r.errors[0].expected_b) <<
") got RGB("
983 <<
static_cast<int>(r.errors[0].actual_r) <<
","
984 <<
static_cast<int>(r.errors[0].actual_g) <<
","
985 <<
static_cast<int>(r.errors[0].actual_b) <<
")\n";
994 for (
const auto& r : run_results) {
1001 if (total_failed == 0) {
1003 FL_WARN(
"\n[OVERALL] PASS ✓ - All " << multi_config.
num_runs <<
" runs succeeded");
1005 FL_WARN(
"\n[OVERALL] FAIL ✗ - " << total_failed <<
"/" << multi_config.
num_runs <<
" runs failed");
1012 int& driver_total,
int& driver_passed,
1013 uint32_t& out_show_duration_ms,
1015 int num_runs_per_pattern) {
1017 ss <<
"\n========================================\n";
1020 if (has_spi_config) {
1021 const auto* spi_cfg = config.
tx_configs[0].getSpiChipset();
1022 ss <<
" Protocol: SPI (APA102)\n";
1023 ss <<
" Clock: " << (spi_cfg ? spi_cfg->timing.clock_hz : 0) <<
" Hz\n";
1031 ss <<
"========================================";
1044 channel =
FastLED.add(channel_config);
1047 FL_ERROR(
"Failed to create channel " << i <<
" (pin " << config.
tx_configs[i].getDataPin() <<
") - platform not supported");
1049 for (
auto& ch : channels) {
1065 FL_ERROR(
"[PREINIT] TX wait timeout - driver may be stalled on this platform");
1085 multi_config.
num_runs = (num_runs_per_pattern > 0) ? num_runs_per_pattern : 1;
1091 uint32_t show_start_ms = millis();
1094 for (
int pattern_id = 0; pattern_id < 4; pattern_id++) {
1106 out_show_duration_ms += millis() - show_start_ms;
1111 driver_total += total;
1112 driver_passed += passed;
1126 int& driver_total,
int& driver_passed,
1127 uint32_t& out_show_duration_ms,
1129 int num_runs_per_pattern) {
1131 ss <<
"\n========================================\n";
1132 ss <<
"Testing (LEGACY API): " << config.
timing_name <<
"\n";
1139 ss <<
" Lane " << i <<
": pin=" << config.
tx_configs[i].getDataPin()
1142 ss <<
"========================================";
1150 int numLeds =
static_cast<int>(config.
tx_configs[i].mLeds.
size());
1153 if (!proxy->valid()) {
1154 FL_ERROR(
"Legacy proxy invalid for lane " << i <<
" (pin " << pin <<
" out of range 0-8)");
1168 FL_ERROR(
"[LEGACY] TX wait timeout - driver may be stalled");
1179 multi_config.
num_runs = (num_runs_per_pattern > 0) ? num_runs_per_pattern : 1;
1185 uint32_t show_start_ms = millis();
1187 for (
int pattern_id = 0; pattern_id < 4; pattern_id++) {
1199 out_show_duration_ms += millis() - show_start_ms;
1201 driver_total += total;
1202 driver_passed += passed;
1209 switch (pattern_id) {
1213 for (
size_t i = 0; i < count; i++) {
1221 for (
size_t i = 0; i < count; i++) {
1229 for (
size_t i = 0; i < count; i++) {
1237 for (
size_t i = 0; i < count; i++) {
1238 int color_index = i % 3;
1239 if (color_index == 0) {
1241 }
else if (color_index == 1) {
1258 switch (pattern_id) {
1259 case 0:
return "Pattern A (R=0xF0, G=0x0F, B=0xAA)";
1260 case 1:
return "Pattern B (R=0x55, G=0xFF, B=0x00)";
1261 case 2:
return "Pattern C (R=0x0F, G=0xAA, B=0xF0)";
1262 case 3:
return "Pattern D (RGB Solid Alternating)";
1263 default:
return "Unknown Pattern";
const char * getBitPatternName(int pattern_id)
void autoResearchChipsetTiming(fl::AutoResearchConfig &config, int &driver_total, int &driver_passed, uint32_t &out_show_duration_ms, fl::vector< fl::RunResult > *out_results, int num_runs_per_pattern)
void autoResearchChipsetTimingLegacy(fl::AutoResearchConfig &config, int &driver_total, int &driver_passed, uint32_t &out_show_duration_ms, fl::vector< fl::RunResult > *out_results, int num_runs_per_pattern)
static bool isUCS7604(fl::ClocklessEncoder encoder)
Check if an encoder selector identifies a UCS7604 variant.
void runMultiTest(const char *test_name, fl::AutoResearchConfig &config, const fl::MultiRunConfig &multi_config, int &total, int &passed, fl::vector< fl::RunResult > *out_results)
void runTest(const char *test_name, fl::AutoResearchConfig &config, int &total, int &passed)
void dumpRawEdgeTiming(fl::shared_ptr< fl::RxChannel > rx_channel, const fl::ChipsetTimingConfig &timing, fl::EdgeRange range)
Dump raw edge timing data to console for debugging.
static fl::vector< uint8_t > buildExpectedUCS7604(fl::span< CRGB > leds, fl::ClocklessEncoder encoder)
Build expected UCS7604 encoded bytes from LED data.
size_t capture(fl::shared_ptr< fl::RxChannel > rx_channel, fl::span< uint8_t > rx_buffer, const fl::ChipsetTimingConfig &timing, const char *driver_name)
static size_t decodeSpiEdges(fl::shared_ptr< fl::RxChannel > rx_channel, fl::span< uint8_t > rx_buffer, uint32_t clock_hz)
void setMixedBitPattern(CRGB *leds, size_t count, int pattern_id)
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
@ CHANNELS
Remove all channels from controller list.
Runtime chipset timing configuration for clockless LED drivers.
void resize(fl::size n) FL_NOEXCEPT
static fl::shared_ptr< const Gamma8 > getOrCreate(float gamma) FL_NOEXCEPT
const T * data() const FL_NOEXCEPT
constexpr fl::size size() const FL_NOEXCEPT
string str() const FL_NOEXCEPT
fl::size size() const FL_NOEXCEPT
void reserve(fl::size n) FL_NOEXCEPT
void push_back(const T &value) FL_NOEXCEPT
void fill_solid(CRGB *targetArray, int numToFill, const CRGB &color) FL_NOEXCEPT
Fill a range of LEDs with a solid color.
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
#define DISABLE_DITHER
Disable dithering.
UCS7604 LED chipset encoder implementation.
Non-templated low level pixel data writing class.
Centralized LED chipset timing definitions with nanosecond precision.
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
ClocklessEncoder
Identifies which encoder to use for clockless chipsets in the Channel API.
@ CLOCKLESS_ENCODER_UCS7604_8BIT
UCS7604 8-bit 800KHz.
@ CLOCKLESS_ENCODER_UCS7604_16BIT
UCS7604 16-bit 800KHz.
@ CLOCKLESS_ENCODER_UCS7604_16BIT_1600
UCS7604 16-bit 1600KHz.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
back_insert_iterator< Container > back_inserter(Container &c) FL_NOEXCEPT
Helper function to create a back_insert_iterator.
void * memset(void *s, int c, size_t n) FL_NOEXCEPT
ChipsetTiming4Phase make4PhaseTiming(const ChipsetTiming &timing_3phase, u32 tolerance_ns) FL_NOEXCEPT
Create 4-phase RX timing from 3-phase chipset timing with tolerance.
UCS7604Mode
UCS7604 protocol configuration modes.
@ UCS7604_MODE_8BIT_800KHZ
@ UCS7604_MODE_16BIT_1600KHZ
@ UCS7604_MODE_16BIT_800KHZ
@ SUCCESS
Operation completed successfully.
int strcmp(const char *s1, const char *s2) FL_NOEXCEPT
FL_OPTIMIZE_FUNCTION bool canUseWave3(const ChipsetTiming &timing)
Check if a chipset timing is eligible for wave3 encoding.
const char * toString(RxDeviceType type) FL_NOEXCEPT
Convert RxDeviceType to human-readable string.
void encodeUCS7604(PixelIterator &pixel_iter, size_t num_leds, OutputIterator out, UCS7604Mode mode, const UCS7604CurrentControl ¤t, bool is_rgbw, const Gamma8 *gamma=nullptr)
Encode complete UCS7604 frame (preamble + padding + pixel data)
u32 T2
Additional high time for bit 1 (nanoseconds)
u32 T3
Low tail duration (nanoseconds)
u32 T1
High time for bit 0 (nanoseconds)
Test context for detailed error reporting Aggregates all test configuration parameters for error mess...
Generic chipset timing entry Provides T1, T2, T3 timing parameters in nanoseconds for any LED protoco...
Low level pixel data writing class.
static ColorAdjustment noAdjustment()
the per-channel scale values premixed with brightness.
FASTLED_FORCE_INLINE fl::PixelIterator as_iterator(const Rgbw &rgbw)
const fl::ChipsetTimingConfig & timing
Chipset timing configuration to test.
fl::RxDeviceType rx_type
RX device type (RMT or ISR)
const char * timing_name
Timing name for logging (e.g., "WS2812B-V5")
const char * driver_name
Driver name for logging (e.g., "RMT", "SPI", "PARLIO")
int base_strip_size
Base strip size (10 or 300 LEDs)
fl::span< fl::ChannelConfig > tx_configs
TX channel configurations to test (mutable for LED manipulation)
fl::ClocklessEncoder encoder
Encoder selector (peer of timing; see issue #2467)
fl::shared_ptr< fl::RxChannel > rx_channel
RX channel for loopback capture (created in .ino, passed in)
fl::span< uint8_t > rx_buffer
Buffer to store received bytes.
Configuration for driver-agnostic autoresearch testing Contains all input parameters needed for autor...
@ Green
<div style='background:#008000;width:4em;height:4em;'></div>
@ Red
<div style='background:#FF0000;width:4em;height:4em;'></div>
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Configuration for a single LED channel.
u32 t1_ns
T0H: High time for bit 0 (nanoseconds)
u32 t2_ns
T1H-T0H: Additional high time for bit 1 (nanoseconds)
u32 reset_us
Reset/latch time (microseconds)
const char * name
Human-readable chipset name.
u32 t3_ns
T0L: Low tail duration (nanoseconds)
Runtime bit-period timing for a clockless chipset.
size_t offset
Starting edge index.
size_t count
Number of edges to extract.
Edge range specification for getRawEdgeTimes() debugging.
LED error information for a single run.
bool print_per_led_errors
Print every LED error (default: false)
int max_errors_per_run
Max errors to store per run (default: 5)
bool print_all_runs
Print all run results (default: only errors)
int num_runs
Number of runs to execute.
Multi-run test configuration.
Single run result with error tracking.
UCS7604 current control structure with 4-bit fields for each channel.