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

◆ runParallelTestImpl()

fl::json AutoResearchRemoteControl::runParallelTestImpl ( const fl::json & args)
private

Definition at line 810 of file AutoResearchRemote.cpp.

810 {
811 fl::json response = fl::json::object();
812
813 // Expects: {drivers: [{driver: "PARLIO", laneSizes: [100]}, {driver: "LCD_RGB", laneSizes: [100]}],
814 // pattern?: "MSB_LSB_A", iterations?: 1, timing?: "WS2812B-V5"}
815 if (!args.is_object()) {
816 response.set("success", false);
817 response.set("error", "InvalidArgs");
818 response.set("message", "Expected {drivers: [{driver, laneSizes}, ...]}");
819 return response;
820 }
821
822 fl::json config = args;
823
824 // 1. Extract drivers array (required)
825 if (!config.contains("drivers") || !config["drivers"].is_array()) {
826 response.set("success", false);
827 response.set("error", "MissingDrivers");
828 response.set("message", "Required field 'drivers' (array of {driver, laneSizes}) missing");
829 return response;
830 }
831
832 fl::json drivers_json = config["drivers"];
833 if (drivers_json.size() < 2) {
834 response.set("success", false);
835 response.set("error", "TooFewDrivers");
836 response.set("message", "Parallel test requires at least 2 drivers");
837 return response;
838 }
839
840 // 2. Extract shared optional parameters
841 fl::string pattern = "MSB_LSB_A";
842 if (config.contains("pattern") && config["pattern"].is_string()) {
843 pattern = config["pattern"].as_string().value();
844 }
845
846 int iterations = 1;
847 if (config.contains("iterations") && config["iterations"].is_int()) {
848 iterations = static_cast<int>(config["iterations"].as_int().value());
849 if (iterations < 1) iterations = 1;
850 }
851
852 fl::string timing_name = "WS2812B-V5";
853 if (config.contains("timing") && config["timing"].is_string()) {
854 timing_name = config["timing"].as_string().value();
855 }
856
857 // Get timing configuration
858 fl::ChipsetTimingConfig resolved_timing;
860 if (timing_name == "UCS7604-800KHZ") {
863 } else {
865 resolved_encoder = fl::encoder_for<fl::TIMING_WS2812B_V5>();
866 }
867 fl::NamedTimingConfig timing_config(resolved_timing, timing_name.c_str(), resolved_encoder);
868
869 // 3. Parse each driver entry and validate
870 struct DriverEntry {
871 fl::string name;
872 fl::vector<int> lane_sizes;
873 int pin_tx;
874 };
875 fl::vector<DriverEntry> driver_entries;
876
877 int next_pin = mState->pin_tx; // Start from the configured TX pin
878 for (fl::size i = 0; i < drivers_json.size(); i++) {
879 if (!drivers_json[i].is_object()) {
880 response.set("success", false);
881 response.set("error", "InvalidDriverEntry");
882 fl::sstream msg;
883 msg << "drivers[" << i << "] must be an object {driver, laneSizes}";
884 response.set("message", msg.str().c_str());
885 return response;
886 }
887
888 fl::json entry = drivers_json[i];
889
890 // Validate driver name
891 if (!entry.contains("driver") || !entry["driver"].is_string()) {
892 response.set("success", false);
893 response.set("error", "MissingDriverName");
894 fl::sstream msg;
895 msg << "drivers[" << i << "] missing 'driver' (string) field";
896 response.set("message", msg.str().c_str());
897 return response;
898 }
899 fl::string driver_name = entry["driver"].as_string().value();
900
901 // Validate driver exists
902 bool driver_found = false;
903 for (fl::size j = 0; j < mState->drivers_available.size(); j++) {
904 if (mState->drivers_available[j].name == driver_name) {
905 driver_found = true;
906 break;
907 }
908 }
909 if (!driver_found) {
910 response.set("success", false);
911 response.set("error", "UnknownDriver");
912 fl::sstream msg;
913 msg << "Driver '" << driver_name.c_str() << "' not available";
914 response.set("message", msg.str().c_str());
915 return response;
916 }
917
918 // Validate laneSizes
919 if (!entry.contains("laneSizes") || !entry["laneSizes"].is_array()) {
920 response.set("success", false);
921 response.set("error", "MissingLaneSizes");
922 fl::sstream msg;
923 msg << "drivers[" << i << "] missing 'laneSizes' (array) field";
924 response.set("message", msg.str().c_str());
925 return response;
926 }
927
928 fl::json lane_sizes_json = entry["laneSizes"];
929 fl::vector<int> lane_sizes;
930 for (fl::size li = 0; li < lane_sizes_json.size(); li++) {
931 if (!lane_sizes_json[li].is_int()) {
932 response.set("success", false);
933 response.set("error", "InvalidLaneSizeType");
934 return response;
935 }
936 int size = static_cast<int>(lane_sizes_json[li].as_int().value());
937 if (size <= 0) {
938 response.set("success", false);
939 response.set("error", "InvalidLaneSize");
940 return response;
941 }
942 lane_sizes.push_back(size);
943 }
944
945 // Extract optional pinTx per driver (default: auto-assign consecutive pins)
946 int pin_tx = next_pin;
947 if (entry.contains("pinTx") && entry["pinTx"].is_int()) {
948 pin_tx = static_cast<int>(entry["pinTx"].as_int().value());
949 }
950
951 DriverEntry de;
952 de.name = driver_name;
953 de.lane_sizes = lane_sizes;
954 de.pin_tx = pin_tx;
955 driver_entries.push_back(de);
956
957 // Advance pin for next driver (skip past this driver's lanes)
958 next_pin = pin_tx + (int)lane_sizes.size();
959 }
960
961 // ========== EXECUTION ==========
962 uint32_t start_ms = millis();
963
964 // Step 1: Enable all requested drivers (not exclusive)
965 // First disable all, then enable only the ones we want
966 for (fl::size i = 0; i < mState->drivers_available.size(); i++) {
967 FastLED.setDriverEnabled(mState->drivers_available[i].name.c_str(), false);
968 }
969 for (fl::size i = 0; i < driver_entries.size(); i++) {
970 FastLED.setDriverEnabled(driver_entries[i].name.c_str(), true);
971 }
972
973 // Step 2: Create channels for each driver using affinity binding
974 fl::vector<fl::unique_ptr<fl::vector<CRGB>>> all_led_arrays;
975 fl::vector<fl::ChannelPtr> all_channels;
976
977 for (fl::size di = 0; di < driver_entries.size(); di++) {
978 const auto& de = driver_entries[di];
979 for (fl::size li = 0; li < de.lane_sizes.size(); li++) {
980 auto leds = fl::make_unique<fl::vector<CRGB>>(de.lane_sizes[li]);
981
982 // Set up channel with typed driver selection (#2459).
983 // The pre-#2459 string `mAffinity` field is gone — translate the
984 // discovered driver name back to a `fl::Bus` enum value.
985 fl::ChannelOptions opts;
986 {
987 const fl::string& n = de.name;
988 if (n == "RMT") opts.mBus = fl::Bus::RMT;
989 else if (n == "PARLIO") opts.mBus = fl::Bus::PARLIO;
990 else if (n == "SPI") opts.mBus = fl::Bus::SPI;
991 else if (n == "I2S") opts.mBus = fl::Bus::I2S;
992 else if (n == "I2S_SPI") opts.mBus = fl::Bus::I2S_SPI;
993 else if (n == "LCD_RGB") opts.mBus = fl::Bus::LCD_RGB;
994 else if (n == "LCD_SPI") opts.mBus = fl::Bus::LCD_SPI;
995 else if (n == "LCD_CLOCKLESS") opts.mBus = fl::Bus::LCD_CLOCKLESS;
996 else if (n == "UART") opts.mBus = fl::Bus::UART;
997 else if (n == "FLEX_IO") opts.mBus = fl::Bus::FLEX_IO;
998 else if (n == "OBJECT_FLED") opts.mBus = fl::Bus::OBJECT_FLED;
999 else if (n == "LPUART") opts.mBus = fl::Bus::LPUART;
1000 else if (n == "BIT_BANG") opts.mBus = fl::Bus::BIT_BANG;
1001 else if (n == "STUB") opts.mBus = fl::Bus::STUB;
1002 // else: leave Bus::AUTO; priority dispatch will pick.
1003 }
1004
1005 fl::ChannelConfig channel_config(
1006 de.pin_tx + (int)li, // Consecutive pins per lane
1007 timing_config.timing,
1008 fl::span<CRGB>(leds->data(), leds->size()),
1009 RGB,
1010 opts
1011 );
1012
1013 auto channel = FastLED.add(channel_config);
1014 if (!channel) {
1015 // Clean up already-created channels
1017 response.set("success", false);
1018 response.set("error", "ChannelCreationFailed");
1019 fl::sstream msg;
1020 msg << "Failed to create channel for driver '" << de.name.c_str()
1021 << "' lane " << li;
1022 response.set("message", msg.str().c_str());
1023 return response;
1024 }
1025
1026 all_channels.push_back(channel);
1027 all_led_arrays.push_back(fl::move(leds));
1028 }
1029 }
1030
1031 // Step 3: Set LED data patterns and call show()
1032 // Use a simple pattern: fill each driver's LEDs with a known color
1033 int array_idx = 0;
1034 for (fl::size di = 0; di < driver_entries.size(); di++) {
1035 const auto& de = driver_entries[di];
1036 for (fl::size li = 0; li < de.lane_sizes.size(); li++) {
1037 auto& leds = *all_led_arrays[array_idx];
1038 for (int led = 0; led < de.lane_sizes[li]; led++) {
1039 // Pattern: alternate colors per driver for visual distinction
1040 if (di == 0) {
1041 leds[led] = CRGB(0xFF, 0x00, 0x00); // Red for first driver
1042 } else {
1043 leds[led] = CRGB(0x00, 0xFF, 0x00); // Green for second driver
1044 }
1045 }
1046 array_idx++;
1047 }
1048 }
1049
1050 // Step 4: Call show() - both drivers transmit simultaneously
1051 bool show_success = true;
1052 uint32_t show_start = micros();
1053 for (int iter = 0; iter < iterations; iter++) {
1054 FastLED.show();
1055 FastLED.wait(5000); // 5 second timeout for DMA completion
1056 }
1057 uint32_t show_duration_us = micros() - show_start;
1058
1059 // Step 5: Validate first driver's output via RX loopback (if available)
1060 // Only the first driver (PARLIO) is typically connected to the RX pin
1061 bool rx_validation_passed = true;
1062 bool rx_validation_attempted = false;
1063
1064 if (mState->rx_channel && driver_entries.size() > 0) {
1065 const auto& primary_driver = driver_entries[0];
1066
1067 // Only attempt RX validation if the primary driver's pin matches our TX pin
1068 if (primary_driver.pin_tx == mState->pin_tx) {
1069 rx_validation_attempted = true;
1070
1071 // Create validation config for the primary driver
1072 fl::vector<fl::ChannelConfig> tx_configs;
1073 int led_array_offset = 0;
1074 for (fl::size li = 0; li < primary_driver.lane_sizes.size(); li++) {
1075 tx_configs.push_back(fl::ChannelConfig(
1076 primary_driver.pin_tx + (int)li,
1077 timing_config.timing,
1078 fl::span<CRGB>(all_led_arrays[led_array_offset + li]->data(),
1079 all_led_arrays[led_array_offset + li]->size()),
1080 RGB
1081 ));
1082 }
1083
1084 fl::AutoResearchConfig autoresearch_config(
1085 timing_config.timing,
1086 timing_config.name,
1087 tx_configs,
1088 primary_driver.name.c_str(),
1089 mState->rx_channel,
1090 mState->rx_buffer,
1091 primary_driver.lane_sizes[0],
1093 timing_config.encoder
1094 );
1095
1096 int total_tests = 0;
1097 int passed_tests = 0;
1098 uint32_t val_show_duration_ms = 0;
1099
1101 val_show_duration_ms, nullptr);
1102
1103 rx_validation_passed = (total_tests > 0) && (passed_tests == total_tests);
1104 }
1105 }
1106
1107 // Step 6: Clean up channels
1109
1110 uint32_t duration_ms = millis() - start_ms;
1111
1112 // ========== RESPONSE ==========
1113 response.set("success", true);
1114 response.set("passed", show_success && rx_validation_passed);
1115 response.set("duration_ms", static_cast<int64_t>(duration_ms));
1116 response.set("show_duration_us", static_cast<int64_t>(show_duration_us));
1117 response.set("iterations", static_cast<int64_t>(iterations));
1118 response.set("rx_validation_attempted", rx_validation_attempted);
1119 response.set("rx_validation_passed", rx_validation_passed);
1120
1121 // List drivers tested
1122 fl::json drivers_tested = fl::json::array();
1123 for (fl::size i = 0; i < driver_entries.size(); i++) {
1124 fl::json drv = fl::json::object();
1125 drv.set("driver", driver_entries[i].name.c_str());
1126 drv.set("pinTx", static_cast<int64_t>(driver_entries[i].pin_tx));
1127 fl::json sizes = fl::json::array();
1128 for (int s : driver_entries[i].lane_sizes) {
1129 sizes.push_back(static_cast<int64_t>(s));
1130 }
1131 drv.set("laneSizes", sizes);
1132 drv.set("laneCount", static_cast<int64_t>(driver_entries[i].lane_sizes.size()));
1133 drivers_tested.push_back(drv);
1134 }
1135 response.set("drivers", drivers_tested);
1136 response.set("pattern", pattern.c_str());
1137
1138 return response;
1139}
fl::CRGB leds[NUM_LEDS]
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)
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
@ CHANNELS
Remove all channels from controller list.
Definition FastLED.h:580
int passed_tests
Definition SIMD.ino:75
int total_tests
Definition SIMD.ino:74
fl::shared_ptr< AutoResearchState > mState
void show(fl::u8 scale)
Update all our controllers with the current led colors, using the passed in brightness.
static void add(fl::ChannelPtr channel)
Add a Channel-based LED controller (from ChannelPtr)
void wait()
Wait for all channel bus transmissions to complete.
void clear(bool writeData=false)
Clear the leds, wiping the local array of data.
const char * c_str() const FL_NOEXCEPT
void push_back(const json &value) FL_NOEXCEPT
Definition json.h:745
fl::optional< i64 > as_int() const FL_NOEXCEPT
Definition json.h:255
size_t size() const FL_NOEXCEPT
Definition json.h:633
bool contains(size_t idx) const FL_NOEXCEPT
Definition json.h:625
fl::optional< fl::string > as_string() const FL_NOEXCEPT
Definition json.h:282
void set(const fl::string &key, const json &value) FL_NOEXCEPT
Definition json.h:701
static json object() FL_NOEXCEPT
Definition json.h:692
static json array() FL_NOEXCEPT
Definition json.h:688
string str() const FL_NOEXCEPT
Definition strstream.h:43
fl::size size() const FL_NOEXCEPT
void push_back(const T &value) FL_NOEXCEPT
Definition vector.h:624
constexpr EOrder RGB
Definition eorder.h:17
fl::CRGB CRGB
Definition crgb.h:25
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition move.h:28
ClocklessEncoder
Identifies which encoder to use for clockless chipsets in the Channel API.
@ CLOCKLESS_ENCODER_WS2812
Default, no preamble (WS2812 and compatible)
fl::u32 uint32_t
Definition s16x16x4.h:219
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
Definition unique_ptr.h:261
constexpr ChipsetTimingConfig makeTimingConfig() FL_NOEXCEPT
Convert compile-time CHIPSET type to runtime timing config.
@ LPUART
Teensy 4.x iMXRT1062 LPUART (inverted-TX + eDMA) clockless driver.
Definition bus.h:73
@ FLEX_IO
Teensy 4.x FlexIO2 driver.
Definition bus.h:71
@ I2S_SPI
Original ESP32 native I2S parallel SPI (true SPI chipsets).
Definition bus.h:66
@ SPI
Generic SPI clockless driver.
Definition bus.h:64
@ PARLIO
ESP32-P4/C6/H2/C5 parallel I/O peripheral.
Definition bus.h:63
@ OBJECT_FLED
Teensy 4.x ObjectFLED driver.
Definition bus.h:72
@ LCD_RGB
ESP32-P4 LCD RGB peripheral (parallel clockless).
Definition bus.h:67
@ LCD_CLOCKLESS
ESP32-S3 LCD_CAM clockless driver (replaces misnamed I2S).
Definition bus.h:69
@ I2S
ESP32-S3 LCD_CAM via legacy I80 bus (clockless).
Definition bus.h:65
@ BIT_BANG
Portable bit-bang fallback driver.
Definition bus.h:74
@ LCD_SPI
ESP32-S3 LCD_CAM SPI driver (true SPI chipsets).
Definition bus.h:68
@ RMT
ESP32 RMT peripheral (all ESP32 variants).
Definition bus.h:62
@ UART
ESP32 UART driver via wave8 framing.
Definition bus.h:70
@ STUB
Native/host/test stub driver.
Definition bus.h:75
fl::u32 micros()
Universal microsecond timer - returns microseconds since system startup.
constexpr ClocklessEncoder encoder_for() FL_NOEXCEPT
Extract the encoder selector from a compile-time TIMING type.
@ RMT
RMT-based receiver (ESP32)
Definition rx.h:166
corkscrew_args args
Definition old.h:149

References args, fl::json::array(), fl::json::as_int(), fl::json::as_string(), autoResearchChipsetTiming(), fl::BIT_BANG, fl::basic_string::c_str(), fl::sstream::c_str(), CHANNELS, fl::CLOCKLESS_ENCODER_WS2812, fl::json::contains(), fl::vector< T >::data(), fl::NamedTimingConfig::encoder, fl::encoder_for(), FastLED, fl::FLEX_IO, fl::I2S, fl::I2S_SPI, fl::json::is_array(), fl::json::is_int(), fl::json::is_string(), fl::LCD_CLOCKLESS, fl::LCD_RGB, fl::LCD_SPI, leds, fl::LPUART, fl::make_unique(), fl::makeTimingConfig(), fl::move(), mState, fl::NamedTimingConfig::name, fl::json::object(), fl::OBJECT_FLED, fl::PARLIO, passed_tests, fl::json::push_back(), fl::vector< T >::push_back(), RGB, fl::RMT, fl::json::set(), fl::json::size(), fl::vector_basic::size(), fl::SPI, fl::sstream::str(), fl::STUB, fl::NamedTimingConfig::timing, total_tests, and fl::UART.

Referenced by registerFunctions().

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