810 {
812
813
814
815 if (!
args.is_object()) {
817 response.set(
"error",
"InvalidArgs");
818 response.set(
"message",
"Expected {drivers: [{driver, laneSizes}, ...]}");
820 }
821
822 fl::json config =
args;
823
824
825 if (!config.
contains(
"drivers") || !config[
"drivers"].is_array()) {
827 response.set(
"error",
"MissingDrivers");
828 response.set(
"message",
"Required field 'drivers' (array of {driver, laneSizes}) missing");
830 }
831
832 fl::json drivers_json = config["drivers"];
833 if (drivers_json.
size() < 2) {
835 response.set(
"error",
"TooFewDrivers");
836 response.set(
"message",
"Parallel test requires at least 2 drivers");
838 }
839
840
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
858 fl::ChipsetTimingConfig resolved_timing;
860 if (timing_name == "UCS7604-800KHZ") {
863 } else {
866 }
867 fl::NamedTimingConfig timing_config(resolved_timing, timing_name.
c_str(), resolved_encoder);
868
869
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;
878 for (fl::size i = 0; i < drivers_json.
size(); i++) {
879 if (!drivers_json[i].is_object()) {
881 response.set(
"error",
"InvalidDriverEntry");
882 fl::sstream msg;
883 msg << "drivers[" << i << "] must be an object {driver, laneSizes}";
886 }
887
888 fl::json entry = drivers_json[i];
889
890
891 if (!entry.
contains(
"driver") || !entry[
"driver"].is_string()) {
893 response.set(
"error",
"MissingDriverName");
894 fl::sstream msg;
895 msg << "drivers[" << i << "] missing 'driver' (string) field";
898 }
899 fl::string driver_name = entry[
"driver"].
as_string().value();
900
901
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) {
911 response.set(
"error",
"UnknownDriver");
912 fl::sstream msg;
913 msg <<
"Driver '" << driver_name.
c_str() <<
"' not available";
916 }
917
918
919 if (!entry.
contains(
"laneSizes") || !entry[
"laneSizes"].is_array()) {
921 response.set(
"error",
"MissingLaneSizes");
922 fl::sstream msg;
923 msg << "drivers[" << i << "] missing 'laneSizes' (array) field";
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()) {
933 response.set(
"error",
"InvalidLaneSizeType");
935 }
936 int size =
static_cast<int>(lane_sizes_json[li].
as_int().value());
937 if (size <= 0) {
939 response.set(
"error",
"InvalidLaneSize");
941 }
943 }
944
945
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;
956
957
958 next_pin = pin_tx + (int)lane_sizes.
size();
959 }
960
961
963
964
965
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
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++) {
981
982
983
984
985 fl::ChannelOptions opts;
986 {
987 const fl::string& n = de.name;
1002
1003 }
1004
1005 fl::ChannelConfig channel_config(
1006 de.pin_tx + (int)li,
1007 timing_config.timing,
1008 fl::span<CRGB>(
leds->data(),
leds->size()),
1010 opts
1011 );
1012
1014 if (!channel) {
1015
1018 response.set(
"error",
"ChannelCreationFailed");
1019 fl::sstream msg;
1020 msg << "Failed to create channel for driver '" << de.name.c_str()
1021 << "' lane " << li;
1024 }
1025
1028 }
1029 }
1030
1031
1032
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
1040 if (di == 0) {
1041 leds[led] =
CRGB(0xFF, 0x00, 0x00);
1042 } else {
1043 leds[led] =
CRGB(0x00, 0xFF, 0x00);
1044 }
1045 }
1046 array_idx++;
1047 }
1048 }
1049
1050
1051 bool show_success = true;
1053 for (int iter = 0; iter < iterations; iter++) {
1056 }
1058
1059
1060
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
1068 if (primary_driver.pin_tx ==
mState->pin_tx) {
1069 rx_validation_attempted = true;
1070
1071
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++) {
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()),
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(),
1091 primary_driver.lane_sizes[0],
1093 timing_config.encoder
1094 );
1095
1099
1101 val_show_duration_ms, nullptr);
1102
1104 }
1105 }
1106
1107
1109
1111
1112
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
1123 for (fl::size i = 0; i < driver_entries.
size(); i++) {
1125 drv.
set(
"driver", driver_entries[i].name.c_str());
1126 drv.
set(
"pinTx",
static_cast<int64_t
>(driver_entries[i].pin_tx));
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()));
1134 }
1135 response.set(
"drivers", drivers_tested);
1137
1139}
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.
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
fl::optional< i64 > as_int() const FL_NOEXCEPT
size_t size() const FL_NOEXCEPT
bool contains(size_t idx) const FL_NOEXCEPT
fl::optional< fl::string > as_string() const FL_NOEXCEPT
void set(const fl::string &key, const json &value) FL_NOEXCEPT
static json object() FL_NOEXCEPT
static json array() FL_NOEXCEPT
string str() const FL_NOEXCEPT
fl::size size() const FL_NOEXCEPT
void push_back(const T &value) FL_NOEXCEPT
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_WS2812
Default, no preamble (WS2812 and compatible)
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
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.
@ FLEX_IO
Teensy 4.x FlexIO2 driver.
@ I2S_SPI
Original ESP32 native I2S parallel SPI (true SPI chipsets).
@ SPI
Generic SPI clockless driver.
@ PARLIO
ESP32-P4/C6/H2/C5 parallel I/O peripheral.
@ OBJECT_FLED
Teensy 4.x ObjectFLED driver.
@ LCD_RGB
ESP32-P4 LCD RGB peripheral (parallel clockless).
@ LCD_CLOCKLESS
ESP32-S3 LCD_CAM clockless driver (replaces misnamed I2S).
@ I2S
ESP32-S3 LCD_CAM via legacy I80 bus (clockless).
@ BIT_BANG
Portable bit-bang fallback driver.
@ LCD_SPI
ESP32-S3 LCD_CAM SPI driver (true SPI chipsets).
@ RMT
ESP32 RMT peripheral (all ESP32 variants).
@ UART
ESP32 UART driver via wave8 framing.
@ STUB
Native/host/test stub driver.
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)