314 {
316
317
318 if (!
args.is_object()) {
320 response.set(
"error",
"InvalidArgs");
321 response.set(
"message",
"Expected {driver, laneSizes, pattern?, iterations?, pinTx?, pinRx?, timing?}");
323 }
324
325 fl::json config =
args;
326
327
328
329
330 if (!config.
contains(
"driver") || !config[
"driver"].is_string()) {
332 response.set(
"error",
"MissingDriver");
333 response.set(
"message",
"Required field 'driver' (string) missing");
335 }
336 fl::string driver_name = config[
"driver"].
as_string().value();
337
338
339 bool driver_found = false;
340 for (fl::size i = 0; i <
mState->drivers_available.size(); i++) {
341 if (
mState->drivers_available[i].name == driver_name) {
342 driver_found = true;
343 break;
344 }
345 }
346 if (!driver_found) {
348 response.set(
"error",
"UnknownDriver");
349 fl::sstream msg;
350 msg <<
"Driver '" << driver_name.
c_str() <<
"' not available";
353 }
354
355
356 if (!config.
contains(
"laneSizes") || !config[
"laneSizes"].is_array()) {
358 response.set(
"error",
"MissingLaneSizes");
359 response.set(
"message",
"Required field 'laneSizes' (array) missing");
361 }
362
363 fl::json lane_sizes_json = config["laneSizes"];
364 if (lane_sizes_json.
size() == 0 || lane_sizes_json.
size() > 16) {
366 response.set(
"error",
"InvalidLaneCount");
367 response.set(
"message",
"laneSizes must have 1-16 elements");
369 }
370
371 fl::vector<int> lane_sizes;
372 const int max_leds_per_lane =
mState->rx_buffer.size() / 32;
373 for (fl::size i = 0; i < lane_sizes_json.
size(); i++) {
374 if (!lane_sizes_json[i].is_int()) {
376 response.set(
"error",
"InvalidLaneSizeType");
377 fl::sstream msg;
378 msg << "laneSizes[" << i << "] must be integer";
381 }
382 int size =
static_cast<int>(lane_sizes_json[i].
as_int().value());
383 if (size <= 0) {
385 response.set(
"error",
"InvalidLaneSize");
386 fl::sstream msg;
387 msg << "laneSizes[" << i << "] = " << size << " must be > 0";
390 }
391 if (size > max_leds_per_lane) {
393 response.set(
"error",
"LaneSizeTooLarge");
394 fl::sstream msg;
395 msg << "laneSizes[" << i << "] = " << size << " exceeds max " << max_leds_per_lane;
398 }
400 }
401
402
403
404
405 fl::string pattern = "MSB_LSB_A";
406 if (config.
contains(
"pattern") && config[
"pattern"].is_string()) {
407 pattern = config[
"pattern"].
as_string().value();
408 }
409
410
411 int iterations = 1;
412 if (config.
contains(
"iterations") && config[
"iterations"].is_int()) {
413 iterations =
static_cast<int>(config[
"iterations"].
as_int().value());
414 if (iterations < 1) {
416 response.set(
"error",
"InvalidIterations");
417 response.set(
"message",
"iterations must be >= 1");
419 }
420 }
421
422
423
424
425
426
427 int frame_count = 1;
428 if (config.
contains(
"frameCount") && config[
"frameCount"].is_int()) {
429 frame_count =
static_cast<int>(config[
"frameCount"].
as_int().value());
430 if (frame_count < 1 || frame_count > 16) {
432 response.set(
"error",
"InvalidFrameCount");
433 response.set(
"message",
"frameCount must be in [1, 16]");
435 }
436 }
437
438
439 int pin_tx =
mState->pin_tx;
440 if (config.
contains(
"pinTx") && config[
"pinTx"].is_int()) {
441 pin_tx =
static_cast<int>(config[
"pinTx"].
as_int().value());
442 if (pin_tx < 0 || pin_tx > 48) {
444 response.set(
"error",
"InvalidPinTx");
445 response.set(
"message",
"pinTx must be 0-48");
447 }
448 }
449
450
451 int pin_rx =
mState->pin_rx;
452 if (config.
contains(
"pinRx") && config[
"pinRx"].is_int()) {
453 pin_rx =
static_cast<int>(config[
"pinRx"].
as_int().value());
454 if (pin_rx < 0 || pin_rx > 48) {
456 response.set(
"error",
"InvalidPinRx");
457 response.set(
"message",
"pinRx must be 0-48");
459 }
460 }
461
462
463 fl::string timing_name = "WS2812B-V5";
464 if (config.
contains(
"timing") && config[
"timing"].is_string()) {
465 timing_name = config[
"timing"].
as_string().value();
466 }
467
468
469 bool use_legacy_api = false;
470 if (config.
contains(
"useLegacyApi") && config[
"useLegacyApi"].is_bool()) {
471 use_legacy_api = config[
"useLegacyApi"].
as_bool().value();
472 }
473
474
475 bool measure_tight_timing = false;
476 if (config.
contains(
"tightTiming") && config[
"tightTiming"].is_bool()) {
477 measure_tight_timing = config[
"tightTiming"].
as_bool().value();
478 }
479
480 int tight_timing_iterations = 8;
481 if (config.
contains(
"tightTimingIterations") &&
482 config["tightTimingIterations"].is_int()) {
483 tight_timing_iterations =
484 static_cast<int>(config[
"tightTimingIterations"].
as_int().value());
485 if (tight_timing_iterations < 1 || tight_timing_iterations > 64) {
487 response.set(
"error",
"InvalidTightTimingIterations");
488 response.set(
"message",
"tightTimingIterations must be in [1, 64]");
490 }
491 }
492
493 uint32_t tight_timing_max_overhead_us = 2000;
494 if (config.
contains(
"tightTimingMaxOverheadUs") &&
495 config["tightTimingMaxOverheadUs"].is_int()) {
496 const int max_overhead =
497 static_cast<int>(config[
"tightTimingMaxOverheadUs"].
as_int().value());
498 if (max_overhead < 1) {
500 response.set(
"error",
"InvalidTightTimingMaxOverheadUs");
501 response.set(
"message",
"tightTimingMaxOverheadUs must be >= 1");
503 }
504 tight_timing_max_overhead_us =
static_cast<uint32_t>(max_overhead);
505 }
506
507
508
509 if (use_legacy_api) {
510 int max_pin = pin_tx + (int)lane_sizes.
size() - 1;
511 if (pin_tx < 0 || max_pin > 8) {
513 response.set(
"error",
"LegacyApiPinRange");
514 fl::sstream msg;
515 msg << "Legacy template API requires all pins in range 0-8, got pins "
516 << pin_tx << "-" << max_pin;
519 }
520 }
521
522
523
525
526#if defined(FL_IS_TEENSY_4X)
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548 if (driver_name == "OBJECT_FLED") {
552 response.set(
"totalTests",
static_cast<int64_t
>(0));
553 response.set(
"passedTests",
static_cast<int64_t
>(0));
554 response.set(
"duration_ms",
static_cast<int64_t
>(duration_ms));
555 response.set(
"show_duration_ms",
static_cast<int64_t
>(0));
558 static_cast<int64_t
>(lane_sizes.
size()));
560 for (int size : lane_sizes) {
561 sizes_response.
push_back(
static_cast<int64_t
>(size));
562 }
563 response.set(
"laneSizes", sizes_response);
565 response.set(
"useLegacyApi",
false);
566 response.set(
"frameCount",
static_cast<int64_t
>(0));
569 "skippedReason",
570 "OBJECT_FLED on Teensy 4 is verified by the dedicated "
571 "`flexioObjectFledTest` RPC; the generic `runSingleTest` "
572 "loopback path hits the FlexPWM-RX bimodal-edge bug AND a "
573 "FlexIO-RX teardown hang documented in FastLED#3059. Use "
574 "`flexioObjectFledTest` directly for byte-level OBJECT_FLED "
575 "validation.");
577 }
578#endif
579
580
583 response.set(
"error",
"DriverSetupFailed");
584 fl::sstream msg;
585 msg <<
"Failed to set " << driver_name.
c_str() <<
" as exclusive driver";
588 }
589
590
591
592
593
594 fl::ChipsetTimingConfig resolved_timing;
596 if (use_legacy_api) {
599 timing_name = "WS2812-800KHZ";
600 } else if (timing_name == "UCS7604-800KHZ") {
603 } else {
606 }
607 fl::NamedTimingConfig timing_config(resolved_timing, timing_name.
c_str(), resolved_encoder);
608
609
610 fl::vector<fl::unique_ptr<fl::vector<CRGB>>> led_arrays;
611 fl::vector<fl::ChannelConfig> tx_configs;
612
613
614
615 bool is_spi_chipset_driver = (driver_name == "LCD_SPI" || driver_name == "I2S_SPI");
616
617 for (fl::size i = 0; i < lane_sizes.size(); i++) {
619 if (is_spi_chipset_driver) {
620
621
622
623
624
625 int data_pin = pin_tx;
626 int clock_pin = pin_rx + 1;
627
628
629
632 spi_cfg,
633 fl::span<CRGB>(
leds->data(),
leds->size()),
635 ));
636 } else {
638 pin_tx + i,
639 timing_config.timing,
640 fl::span<CRGB>(
leds->data(),
leds->size()),
642 ));
643 }
645 }
646
647
648 fl::shared_ptr<fl::RxChannel> rx_channel_to_use =
mState->rx_channel;
649
651 rx_channel_to_use =
mState->rx_factory(pin_rx);
652 if (!rx_channel_to_use) {
654 response.set(
"error",
"RxChannelCreationFailed");
655 response.set(
"message",
"Failed to create RX channel on custom pin");
657 }
658 }
659
660
661 fl::AutoResearchConfig autoresearch_config(
662 timing_config.timing,
663 timing_config.name,
664 tx_configs,
666 rx_channel_to_use,
668 lane_sizes[0],
670 timing_config.encoder
671 );
672
673
676 bool passed = false;
678 fl::vector<fl::RunResult> run_results;
680 bool tight_timing_passed = true;
681
682 {
683
684
685
686
687 if (use_legacy_api) {
688
689 for (int iter = 0; iter < iterations; iter++) {
690 int iter_total = 0, iter_passed = 0;
694 }
695 } else {
696
697 for (int iter = 0; iter < iterations; iter++) {
698 int iter_total = 0, iter_passed = 0;
702 }
703 }
704
706 }
707
708 if (measure_tight_timing) {
709 if (use_legacy_api) {
710 tight_timing_passed = false;
711 tight_timing_response.
set(
"requested",
true);
712 tight_timing_response.
set(
"supported",
false);
713 tight_timing_response.
set(
"passed",
false);
714 tight_timing_response.
set(
"driver", driver_name.
c_str());
715 tight_timing_response.
set(
"message",
"legacy API timing metric is not supported");
716 } else {
718 driver_name,
719 timing_config.timing,
720 tx_configs,
721 tight_timing_iterations,
722 tight_timing_max_overhead_us,
723 tight_timing_passed);
724 }
725 bool tight_timing_supported = false;
726 if (tight_timing_response.
contains(
"supported") &&
727 tight_timing_response["supported"].is_bool()) {
728 tight_timing_supported =
729 tight_timing_response[
"supported"].
as_bool().value();
730 }
731 if (tight_timing_supported) {
732 passed = passed && tight_timing_passed;
733 }
734 }
735
737
738
743 response.set(
"duration_ms",
static_cast<int64_t
>(duration_ms));
744 response.set(
"show_duration_ms",
static_cast<int64_t
>(show_duration_ms));
746 response.set(
"laneCount",
static_cast<int64_t
>(lane_sizes.size()));
747
749 for (int size : lane_sizes) {
750 sizes_response.
push_back(
static_cast<int64_t
>(size));
751 }
752 response.set(
"laneSizes", sizes_response);
754 response.set(
"useLegacyApi", use_legacy_api);
755 response.set(
"frameCount",
static_cast<int64_t
>(frame_count));
756 if (measure_tight_timing) {
757 response.set(
"tightTiming", tight_timing_response);
758 }
759
760
761
762 if (!passed && !run_results.
empty()) {
764 for (fl::size ri = 0; ri < run_results.
size(); ri++) {
765 const auto& rr = run_results[ri];
766 if (rr.passed) continue;
768 pat.
set(
"runNumber",
static_cast<int64_t
>(rr.run_number));
769 pat.
set(
"totalLeds",
static_cast<int64_t
>(rr.total_leds));
770 pat.
set(
"mismatchedLeds",
static_cast<int64_t
>(rr.mismatches));
771 pat.
set(
"mismatchedBytes",
static_cast<int64_t
>(rr.mismatchedBytes));
772 pat.
set(
"lsbOnlyErrors",
static_cast<int64_t
>(rr.lsbOnlyErrors));
773
774
775 constexpr fl::size kMaxSerializedErrors = 5;
776 if (!rr.errors.empty()) {
778 const fl::size errLimit = rr.errors.size() < kMaxSerializedErrors
779 ? rr.errors.size()
780 : kMaxSerializedErrors;
781 for (fl::size ei = 0; ei < errLimit; ei++) {
782 const auto& e = rr.errors[ei];
784 err.
set(
"led",
static_cast<int64_t
>(e.led_index));
786 expected.
push_back(
static_cast<int64_t
>(e.expected_r));
787 expected.
push_back(
static_cast<int64_t
>(e.expected_g));
788 expected.
push_back(
static_cast<int64_t
>(e.expected_b));
789 err.
set(
"expected", expected);
791 actual.
push_back(
static_cast<int64_t
>(e.actual_r));
792 actual.
push_back(
static_cast<int64_t
>(e.actual_g));
793 actual.
push_back(
static_cast<int64_t
>(e.actual_b));
794 err.
set(
"actual", actual);
796 }
797 pat.
set(
"errors", errs);
798 pat.
set(
"totalErrors",
static_cast<int64_t
>(rr.errors.size()));
799 }
801 }
803 }
805
806
808}
bool autoResearchSetExclusiveDriverByName(const char *name)
AutoResearch-style helper: set an exclusive driver by name.
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)
fl::shared_ptr< AutoResearchState > mState
const char * c_str() const FL_NOEXCEPT
void push_back(const json &value) FL_NOEXCEPT
fl::optional< i64 > as_int() const FL_NOEXCEPT
fl::optional< bool > as_bool() 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
bool empty() const FL_NOEXCEPT
void push_back(const T &value) FL_NOEXCEPT
fl::json measureTightTiming(const fl::string &driver_name, const fl::ChipsetTimingConfig &timing, const fl::vector< fl::ChannelConfig > &tx_configs, int iterations, uint32_t max_allowed_overhead_us, bool &out_passed)
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.
constexpr ClocklessEncoder encoder_for() FL_NOEXCEPT
Extract the encoder selector from a compile-time TIMING type.
@ RMT
RMT-based receiver (ESP32)
static SpiEncoder apa102(u32 clock_hz=6000000) FL_NOEXCEPT
Create APA102 encoder configuration.