FastLED 3.9.15
Loading...
Searching...
No Matches

◆ runMultiTest()

void runMultiTest ( const char * test_name,
fl::AutoResearchConfig & config,
const fl::MultiRunConfig & multi_config,
int & total,
int & passed,
fl::vector< fl::RunResult > * out_results = nullptr )

Definition at line 746 of file AutoResearchTest.cpp.

750 {
751
752 fl::sstream ss;
753 ss << "\n╔════════════════════════════════════════════════════════════════╗\n";
754 ss << "║ MULTI-RUN TEST: " << test_name << "\n";
755 ss << "║ Runs: " << multi_config.num_runs << " | Print Mode: "
756 << (multi_config.print_all_runs ? "All" : "Errors ONLY") << "\n";
757 ss << "╚════════════════════════════════════════════════════════════════╝";
758 FL_WARN(ss.str());
759
760 fl::vector<fl::RunResult> run_results;
761
762 // Multi-lane limitation: Only test Lane 0
763 size_t channels_to_test = config.tx_configs.size() > 1 ? 1 : config.tx_configs.size();
764
765 if (config.tx_configs.size() > 1) {
766 FL_WARN("[MULTI-LANE] Testing " << config.tx_configs.size() << " lanes, testing Lane 0 only");
767 }
768
769 // Execute multiple runs
770 for (int run = 1; run <= multi_config.num_runs; run++) {
771 // Print progress to keep output flowing (prevents auto-exit timeout)
772 if (run % 3 == 1 || multi_config.num_runs <= 5) {
773 FL_WARN("[Run " << run << "/" << multi_config.num_runs << "] Testing...");
774 }
775
777 result.run_number = run;
778
779 // Test Lane 0 only
780 for (size_t config_idx = 0; config_idx < channels_to_test; config_idx++) {
781 const auto& leds = config.tx_configs[config_idx].mLeds;
782 size_t num_leds = leds.size();
783 result.total_leds = num_leds;
784 result.totalBytes = num_leds * 3;
785
786 // Capture RX data
787 size_t bytes_captured = capture(config.rx_channel, config.rx_buffer, config.timing, config.driver_name);
788
789 if (bytes_captured == 0) {
790 FL_WARN("[Run " << run << "] Capture failed");
791 result.passed = false;
792 break;
793 }
794
795 // Check pixel data
796 int mismatches = 0;
797
798 // DEBUG: Print first 24 bytes of captured data
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++) {
802 FL_WARN(" [" << i << "] = 0x" << fl::hex << static_cast<int>(config.rx_buffer[i]) << fl::dec);
803 }
804
805 if (isUCS7604(config.encoder)) {
806 // UCS7604: Compare full encoded frame byte-for-byte
807 fl::vector<uint8_t> expected_encoded = buildExpectedUCS7604(
808 config.tx_configs[config_idx].mLeds, config.encoder);
809 size_t expected_len = expected_encoded.size();
810 result.totalBytes = static_cast<int>(expected_len);
811
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++;
818 }
819 mismatches++;
820
821 // Print corruption context for first mismatch only
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])
825 << " actual=0x" << static_cast<int>(config.rx_buffer[i]) << fl::dec);
826 }
827
828 // Store first N errors (using byte-level reporting)
829 if (result.errors.size() < static_cast<size_t>(multi_config.max_errors_per_run)) {
830 result.errors.push_back(fl::LEDError(
831 static_cast<int>(i), expected_encoded[i], 0, 0,
832 config.rx_buffer[i], 0, 0
833 ));
834 }
835 }
836 }
837 // Count missing bytes as mismatches
838 if (bytes_captured < expected_len) {
839 mismatches += static_cast<int>(expected_len - bytes_captured);
840 }
841 } else {
842 // WS2812: Per-LED RGB comparison
843 size_t bytes_expected = num_leds * 3;
844
845 // Determine front padding offset (PARLIO only)
846 const size_t rx_buffer_offset = 0; // No front padding: PARLIO FRONT_PAD_BYTES=0
847
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) :
850 bytes_expected;
851 (void)bytes_to_check;
852
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; // Apply offset for PARLIO front padding
856 if (byte_offset + 2 >= bytes_captured) { // Check against total captured bytes
857 break;
858 }
859 verified_leds = i + 1;
860
861 uint8_t expected_r = leds[i].r;
862 uint8_t expected_g = leds[i].g;
863 uint8_t expected_b = leds[i].b;
864
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];
868
869 // Per-byte comparison for byte-level stats
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++;
878 }
879 led_mismatch = true;
880 }
881 }
882
883 if (led_mismatch) {
884 // Print corruption context for first mismatch only
885 if (mismatches == 0) {
886 FL_WARN("\n[CORRUPTION @ LED " << static_cast<int>(i) << ", Run " << run << "]");
887
888 // Calculate edge index and print timing around corruption point
889 size_t corruption_edge_index = i * 48;
890 size_t offset = corruption_edge_index > 4 ? corruption_edge_index - 4 : 0;
891
892 // Dump raw edge timing around corruption point (9 edges: -4 to +4)
894 }
895
896 mismatches++;
897
898 // Store first N errors
899 if (result.errors.size() < static_cast<size_t>(multi_config.max_errors_per_run)) {
900 result.errors.push_back(fl::LEDError(
901 i, expected_r, expected_g, expected_b,
902 actual_r, actual_g, actual_b
903 ));
904 }
905 }
906 }
907
908 // Truncated capture: RX returned fewer bytes than expected. Count
909 // the unchecked LEDs as mismatches rather than silently passing.
910 // A short capture means we did NOT verify those LEDs, so the run
911 // MUST fail. The old silent break hid real bugs on strips longer
912 // than the RMT DMA buffer (~170 LEDs for WS2812B). See issue #2254.
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.");
922 }
923 }
924
925 result.mismatches = mismatches;
926 result.passed = (mismatches == 0);
927 }
928
929 run_results.push_back(result);
930
931 // Print run result if configured
932 if (multi_config.print_all_runs || !result.passed) {
933 FL_WARN("[Run " << run << "/" << multi_config.num_runs << "] "
934 << (result.passed ? "PASS" : "FAIL")
935 << " | Errors: " << result.mismatches << "/" << result.total_leds
936 << " (" << (100.0 * (result.total_leds - result.mismatches) / result.total_leds) << "%)");
937
938 // Print error details if enabled
939 if (!result.passed && multi_config.print_per_led_errors && !result.errors.empty()) {
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) << ")");
950 }
951 }
952 }
953 }
954
955 // Summary statistics
956 int total_passed = 0;
957 int total_failed = 0;
958 for (const auto& r : run_results) {
959 if (r.passed) total_passed++;
960 else total_failed++;
961 }
962
963 ss.clear();
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) << "%)";
970 FL_WARN(ss.str());
971
972 if (total_failed > 0) {
973 ss.clear();
974 ss << "\nFailed Run Numbers:\n";
975 for (const auto& r : run_results) {
976 if (!r.passed) {
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";
986 }
987 }
988 }
989 FL_WARN(ss.str());
990 }
991
992 // Copy results to caller if requested
993 if (out_results) {
994 for (const auto& r : run_results) {
995 out_results->push_back(r);
996 }
997 }
998
999 // Update totals
1000 total++;
1001 if (total_failed == 0) {
1002 passed++;
1003 FL_WARN("\n[OVERALL] PASS ✓ - All " << multi_config.num_runs << " runs succeeded");
1004 } else {
1005 FL_WARN("\n[OVERALL] FAIL ✗ - " << total_failed << "/" << multi_config.num_runs << " runs failed");
1006 }
1007}
fl::CRGB leds[NUM_LEDS]
static bool isUCS7604(fl::ClocklessEncoder encoder)
Check if an encoder selector identifies a UCS7604 variant.
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)
constexpr fl::size size() const FL_NOEXCEPT
Definition span.h:458
string str() const FL_NOEXCEPT
Definition strstream.h:43
void clear() FL_NOEXCEPT
Definition strstream.h:358
fl::size size() const FL_NOEXCEPT
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
#define FL_WARN(X)
Definition log.h:276
void run(fl::u32 microseconds, ExecFlags flags)
Run selected task subsystems.
const dec_t dec
Definition ios.cpp.hpp:7
const hex_t hex
Definition ios.cpp.hpp:6
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
unsigned char uint8_t
Definition s16x16x4.h:209
const fl::ChipsetTimingConfig & timing
Chipset timing configuration to test.
const char * driver_name
Driver name for logging (e.g., "RMT", "SPI", "PARLIO")
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.
Edge range specification for getRawEdgeTimes() debugging.
Definition rx.h:56
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.
Single run result with error tracking.

References buildExpectedUCS7604(), capture(), fl::sstream::clear(), fl::dec, fl::AutoResearchConfig::driver_name, dumpRawEdgeTiming(), fl::AutoResearchConfig::encoder, FL_WARN, fl::hex, isUCS7604(), leds, fl::MultiRunConfig::max_errors_per_run, fl::MultiRunConfig::num_runs, offset(), fl::MultiRunConfig::print_all_runs, fl::MultiRunConfig::print_per_led_errors, fl::vector< T >::push_back(), fl::AutoResearchConfig::rx_buffer, fl::AutoResearchConfig::rx_channel, fl::span< T, Extent >::size(), fl::vector_basic::size(), fl::sstream::str(), fl::AutoResearchConfig::timing, and fl::AutoResearchConfig::tx_configs.

Referenced by autoResearchChipsetTiming(), and autoResearchChipsetTimingLegacy().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: