750 {
751
753 ss << "\n╔════════════════════════════════════════════════════════════════╗\n";
754 ss << "║ MULTI-RUN TEST: " << test_name << "\n";
755 ss <<
"║ Runs: " << multi_config.
num_runs <<
" | Print Mode: "
757 ss << "╚════════════════════════════════════════════════════════════════╝";
759
761
762
764
767 }
768
769
771
772 if (run % 3 == 1 || multi_config.
num_runs <= 5) {
773 FL_WARN(
"[Run " << run <<
"/" << multi_config.
num_runs <<
"] Testing...");
774 }
775
778
779
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;
785
786
788
789 if (bytes_captured == 0) {
790 FL_WARN(
"[Run " << run <<
"] Capture failed");
792 break;
793 }
794
795
796 int mismatches = 0;
797
798
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++) {
803 }
804
806
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]) {
816 if ((expected_encoded[i] ^ config.
rx_buffer[i]) == 0x01) {
818 }
819 mismatches++;
820
821
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])
826 }
827
828
831 static_cast<int>(i), expected_encoded[i], 0, 0,
833 ));
834 }
835 }
836 }
837
838 if (bytes_captured < expected_len) {
839 mismatches += static_cast<int>(expected_len - bytes_captured);
840 }
841 } else {
842
843 size_t bytes_expected = num_leds * 3;
844
845
846 const size_t rx_buffer_offset = 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;
856 if (byte_offset + 2 >= bytes_captured) {
857 break;
858 }
859 verified_leds = i + 1;
860
864
868
869
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]) {
876 if ((exp_bytes[ch] ^ act_bytes[ch]) == 0x01) {
878 }
879 led_mismatch = true;
880 }
881 }
882
883 if (led_mismatch) {
884
885 if (mismatches == 0) {
886 FL_WARN(
"\n[CORRUPTION @ LED " <<
static_cast<int>(i) <<
", Run " << run <<
"]");
887
888
889 size_t corruption_edge_index = i * 48;
890 size_t offset = corruption_edge_index > 4 ? corruption_edge_index - 4 : 0;
891
892
894 }
895
896 mismatches++;
897
898
901 i, expected_r, expected_g, expected_b,
902 actual_r, actual_g, actual_b
903 ));
904 }
905 }
906 }
907
908
909
910
911
912
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
930
931
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
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
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
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) <<
"%)";
971
972 if (total_failed > 0) {
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 }
990 }
991
992
993 if (out_results) {
994 for (const auto& r : run_results) {
996 }
997 }
998
999
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}
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
string str() const FL_NOEXCEPT
fl::size size() const FL_NOEXCEPT
void push_back(const T &value) FL_NOEXCEPT
fl::UISlider offset("Offset", 0.0f, 0.0f, 1.0f, 0.01f)
void run(fl::u32 microseconds, ExecFlags flags)
Run selected task subsystems.
expected< T, E > result
Alias for expected (Rust-style naming)
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.
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.