318 if (!
args.is_object()) {
319 response.set(
"success",
false);
320 response.set(
"error",
"InvalidArgs");
321 response.set(
"message",
"Expected {driver, laneSizes, pattern?, iterations?, pinTx?, pinRx?, timing?}");
331 response.set(
"success",
false);
332 response.set(
"error",
"MissingDriver");
333 response.set(
"message",
"Required field 'driver' (string) missing");
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) {
347 response.set(
"success",
false);
348 response.set(
"error",
"UnknownDriver");
350 msg <<
"Driver '" << driver_name.
c_str() <<
"' not available";
351 response.set(
"message", msg.
str().
c_str());
357 response.set(
"success",
false);
358 response.set(
"error",
"MissingLaneSizes");
359 response.set(
"message",
"Required field 'laneSizes' (array) missing");
363 fl::json lane_sizes_json = config[
"laneSizes"];
364 if (lane_sizes_json.
size() == 0 || lane_sizes_json.
size() > 16) {
365 response.set(
"success",
false);
366 response.set(
"error",
"InvalidLaneCount");
367 response.set(
"message",
"laneSizes must have 1-16 elements");
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()) {
375 response.set(
"success",
false);
376 response.set(
"error",
"InvalidLaneSizeType");
378 msg <<
"laneSizes[" << i <<
"] must be integer";
379 response.set(
"message", msg.
str().
c_str());
382 int size =
static_cast<int>(lane_sizes_json[i].
as_int().value());
384 response.set(
"success",
false);
385 response.set(
"error",
"InvalidLaneSize");
387 msg <<
"laneSizes[" << i <<
"] = " << size <<
" must be > 0";
388 response.set(
"message", msg.
str().
c_str());
391 if (size > max_leds_per_lane) {
392 response.set(
"success",
false);
393 response.set(
"error",
"LaneSizeTooLarge");
395 msg <<
"laneSizes[" << i <<
"] = " << size <<
" exceeds max " << max_leds_per_lane;
396 response.set(
"message", msg.
str().
c_str());
407 pattern = config[
"pattern"].
as_string().value();
412 if (config.
contains(
"iterations") && config[
"iterations"].
is_int()) {
413 iterations =
static_cast<int>(config[
"iterations"].
as_int().value());
414 if (iterations < 1) {
415 response.set(
"success",
false);
416 response.set(
"error",
"InvalidIterations");
417 response.set(
"message",
"iterations must be >= 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) {
431 response.set(
"success",
false);
432 response.set(
"error",
"InvalidFrameCount");
433 response.set(
"message",
"frameCount must be in [1, 16]");
439 int pin_tx =
mState->pin_tx;
441 pin_tx =
static_cast<int>(config[
"pinTx"].
as_int().value());
442 if (pin_tx < 0 || pin_tx > 48) {
443 response.set(
"success",
false);
444 response.set(
"error",
"InvalidPinTx");
445 response.set(
"message",
"pinTx must be 0-48");
451 int pin_rx =
mState->pin_rx;
453 pin_rx =
static_cast<int>(config[
"pinRx"].
as_int().value());
454 if (pin_rx < 0 || pin_rx > 48) {
455 response.set(
"success",
false);
456 response.set(
"error",
"InvalidPinRx");
457 response.set(
"message",
"pinRx must be 0-48");
465 timing_name = config[
"timing"].
as_string().value();
469 bool use_legacy_api =
false;
470 if (config.
contains(
"useLegacyApi") && config[
"useLegacyApi"].
is_bool()) {
471 use_legacy_api = config[
"useLegacyApi"].
as_bool().value();
475 bool measure_tight_timing =
false;
476 if (config.
contains(
"tightTiming") && config[
"tightTiming"].
is_bool()) {
477 measure_tight_timing = config[
"tightTiming"].
as_bool().value();
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) {
486 response.set(
"success",
false);
487 response.set(
"error",
"InvalidTightTimingIterations");
488 response.set(
"message",
"tightTimingIterations must be in [1, 64]");
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) {
499 response.set(
"success",
false);
500 response.set(
"error",
"InvalidTightTimingMaxOverheadUs");
501 response.set(
"message",
"tightTimingMaxOverheadUs must be >= 1");
504 tight_timing_max_overhead_us =
static_cast<uint32_t
>(max_overhead);
509 if (use_legacy_api) {
510 int max_pin = pin_tx + (int)lane_sizes.
size() - 1;
511 if (pin_tx < 0 || max_pin > 8) {
512 response.set(
"success",
false);
513 response.set(
"error",
"LegacyApiPinRange");
515 msg <<
"Legacy template API requires all pins in range 0-8, got pins "
516 << pin_tx <<
"-" << max_pin;
517 response.set(
"message", msg.
str().
c_str());
524 uint32_t start_ms = millis();
526#if defined(FL_IS_TEENSY_4X)
548 if (driver_name ==
"OBJECT_FLED") {
549 uint32_t duration_ms = millis() - start_ms;
550 response.set(
"success",
true);
551 response.set(
"passed",
true);
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));
556 response.set(
"driver", driver_name.
c_str());
557 response.set(
"laneCount",
558 static_cast<int64_t
>(lane_sizes.
size()));
560 for (
int size : lane_sizes) {
561 sizes_response.
push_back(
static_cast<int64_t
>(size));
563 response.set(
"laneSizes", sizes_response);
564 response.set(
"pattern",
"skipped");
565 response.set(
"useLegacyApi",
false);
566 response.set(
"frameCount",
static_cast<int64_t
>(0));
567 response.set(
"skipped",
true);
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 "
582 response.set(
"success",
false);
583 response.set(
"error",
"DriverSetupFailed");
585 msg <<
"Failed to set " << driver_name.
c_str() <<
" as exclusive driver";
586 response.set(
"message", msg.
str().
c_str());
596 if (use_legacy_api) {
599 timing_name =
"WS2812-800KHZ";
600 }
else if (timing_name ==
"UCS7604-800KHZ") {
615 bool is_spi_chipset_driver = (driver_name ==
"LCD_SPI" || driver_name ==
"I2S_SPI");
617 for (fl::size i = 0; i < lane_sizes.
size(); i++) {
619 if (is_spi_chipset_driver) {
625 int data_pin = pin_tx;
626 int clock_pin = pin_rx + 1;
651 rx_channel_to_use =
mState->rx_factory(pin_rx);
652 if (!rx_channel_to_use) {
653 response.set(
"success",
false);
654 response.set(
"error",
"RxChannelCreationFailed");
655 response.set(
"message",
"Failed to create RX channel on custom pin");
677 uint32_t show_duration_ms = 0;
680 bool tight_timing_passed =
true;
687 if (use_legacy_api) {
689 for (
int iter = 0; iter < iterations; iter++) {
690 int iter_total = 0, iter_passed = 0;
697 for (
int iter = 0; iter < iterations; iter++) {
698 int iter_total = 0, iter_passed = 0;
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");
717 tight_timing_response = measureTightTiming(
721 tight_timing_iterations,
722 tight_timing_max_overhead_us,
723 tight_timing_passed);
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();
731 if (tight_timing_supported) {
732 passed = passed && tight_timing_passed;
736 uint32_t duration_ms = millis() - start_ms;
739 response.set(
"success",
true);
740 response.set(
"passed", passed);
741 response.set(
"totalTests",
static_cast<int64_t
>(
total_tests));
742 response.set(
"passedTests",
static_cast<int64_t
>(
passed_tests));
743 response.set(
"duration_ms",
static_cast<int64_t
>(duration_ms));
744 response.set(
"show_duration_ms",
static_cast<int64_t
>(show_duration_ms));
745 response.set(
"driver", driver_name.
c_str());
746 response.set(
"laneCount",
static_cast<int64_t
>(lane_sizes.
size()));
749 for (
int size : lane_sizes) {
750 sizes_response.
push_back(
static_cast<int64_t
>(size));
752 response.set(
"laneSizes", sizes_response);
753 response.set(
"pattern", pattern.
c_str());
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);
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));
775 constexpr fl::size kMaxSerializedErrors = 5;
776 if (!rr.errors.empty()) {
778 const fl::size errLimit = rr.errors.size() < kMaxSerializedErrors
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);
797 pat.
set(
"errors", errs);
798 pat.
set(
"totalErrors",
static_cast<int64_t
>(rr.errors.size()));
802 response.set(
"patterns", patterns);
815 if (!
args.is_object()) {
816 response.set(
"success",
false);
817 response.set(
"error",
"InvalidArgs");
818 response.set(
"message",
"Expected {drivers: [{driver, laneSizes}, ...]}");
826 response.set(
"success",
false);
827 response.set(
"error",
"MissingDrivers");
828 response.set(
"message",
"Required field 'drivers' (array of {driver, laneSizes}) missing");
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");
843 pattern = config[
"pattern"].
as_string().value();
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;
854 timing_name = config[
"timing"].
as_string().value();
860 if (timing_name ==
"UCS7604-800KHZ") {
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()) {
880 response.set(
"success",
false);
881 response.set(
"error",
"InvalidDriverEntry");
883 msg <<
"drivers[" << i <<
"] must be an object {driver, laneSizes}";
884 response.set(
"message", msg.
str().
c_str());
892 response.set(
"success",
false);
893 response.set(
"error",
"MissingDriverName");
895 msg <<
"drivers[" << i <<
"] missing 'driver' (string) field";
896 response.set(
"message", msg.
str().
c_str());
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) {
910 response.set(
"success",
false);
911 response.set(
"error",
"UnknownDriver");
913 msg <<
"Driver '" << driver_name.
c_str() <<
"' not available";
914 response.set(
"message", msg.
str().
c_str());
920 response.set(
"success",
false);
921 response.set(
"error",
"MissingLaneSizes");
923 msg <<
"drivers[" << i <<
"] missing 'laneSizes' (array) field";
924 response.set(
"message", msg.
str().
c_str());
928 fl::json lane_sizes_json = entry[
"laneSizes"];
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");
936 int size =
static_cast<int>(lane_sizes_json[li].
as_int().value());
938 response.set(
"success",
false);
939 response.set(
"error",
"InvalidLaneSize");
946 int pin_tx = next_pin;
948 pin_tx =
static_cast<int>(entry[
"pinTx"].
as_int().value());
952 de.name = driver_name;
953 de.lane_sizes = lane_sizes;
958 next_pin = pin_tx + (int)lane_sizes.
size();
962 uint32_t start_ms = millis();
966 for (fl::size i = 0; i <
mState->drivers_available.size(); i++) {
967 FastLED.setDriverEnabled(
mState->drivers_available[i].name.c_str(),
false);
969 for (fl::size i = 0; i < driver_entries.
size(); i++) {
970 FastLED.setDriverEnabled(driver_entries[i].name.c_str(),
true);
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++) {
1006 de.pin_tx + (
int)li,
1013 auto channel =
FastLED.add(channel_config);
1017 response.set(
"success",
false);
1018 response.set(
"error",
"ChannelCreationFailed");
1020 msg <<
"Failed to create channel for driver '" << de.name.
c_str()
1022 response.set(
"message", msg.
str().
c_str());
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++) {
1041 leds[led] =
CRGB(0xFF, 0x00, 0x00);
1043 leds[led] =
CRGB(0x00, 0xFF, 0x00);
1051 bool show_success =
true;
1052 uint32_t show_start = micros();
1053 for (
int iter = 0; iter < iterations; iter++) {
1057 uint32_t show_duration_us = micros() - show_start;
1061 bool rx_validation_passed =
true;
1062 bool rx_validation_attempted =
false;
1064 if (
mState->rx_channel && driver_entries.
size() > 0) {
1065 const auto& primary_driver = driver_entries[0];
1068 if (primary_driver.pin_tx ==
mState->pin_tx) {
1069 rx_validation_attempted =
true;
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,
1079 all_led_arrays[led_array_offset + li]->
size()),
1088 primary_driver.name.c_str(),
1091 primary_driver.lane_sizes[0],
1098 uint32_t val_show_duration_ms = 0;
1101 val_show_duration_ms,
nullptr);
1110 uint32_t duration_ms = millis() - start_ms;
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);
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));
1131 drv.
set(
"laneSizes", sizes);
1132 drv.
set(
"laneCount",
static_cast<int64_t
>(driver_entries[i].lane_sizes.
size()));
1135 response.set(
"drivers", drivers_tested);
1136 response.set(
"pattern", pattern.
c_str());
1313 status.
set(
"ready",
true);
1314 status.
set(
"pinTx",
static_cast<int64_t
>(
mState->pin_tx));
1315 status.
set(
"pinRx",
static_cast<int64_t
>(
mState->pin_rx));
1326 response.set(
"success",
true);
1327 response.set(
"received",
args);
1339 FL_WARN(
"[deliberateHang] watchdog test: spinning forever in 200 ms");
1340 mState->deliberate_hang_requested =
true;
1342 response.set(
"success",
true);
1343 response.set(
"message",
"device will hang after RPC returns; watchdog should reset within configured timeout");
1350 for (fl::size i = 0; i <
mState->drivers_available.size(); i++) {
1352 driver.
set(
"name",
mState->drivers_available[i].name.c_str());
1353 driver.
set(
"priority",
static_cast<int64_t
>(
mState->drivers_available[i].priority));
1354 driver.
set(
"enabled",
mState->drivers_available[i].enabled);
1370 if (!result.is_null()) {
1371 mRemote->sendAsyncResponse(
"runSingleTest", result);
1383 if (!result.is_null()) {
1384 mRemote->sendAsyncResponse(
"runParallelTest", result);
1395 uint32_t now = millis();
1398 response.set(
"success",
true);
1399 response.set(
"message",
"pong");
1400 response.set(
"timestamp",
static_cast<int64_t
>(now));
1401 response.set(
"uptimeMs",
static_cast<int64_t
>(now));
1408 response.set(
"success",
true);
1409 response.set(
"message",
"RPC works from task context");
1410 response.set(
"serial_safe",
false);
1435#if !defined(FL_IS_TEENSY_4X)
1437 response.set(
"success",
false);
1438 response.set(
"error",
"PlatformNotSupported");
1439 response.set(
"message",
1440 "flexioRxBenchmark is Teensy 4.x-only (FLEXIO1 capture).");
1444 int frequency_hz = 1000;
1445 int duration_ms = 100;
1448 if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
1450 if (cfg.
contains(
"frequency_hz") && cfg[
"frequency_hz"].
is_int()) {
1451 frequency_hz =
static_cast<int>(cfg[
"frequency_hz"].
as_int().value());
1453 if (cfg.
contains(
"duration_ms") && cfg[
"duration_ms"].
is_int()) {
1454 duration_ms =
static_cast<int>(cfg[
"duration_ms"].
as_int().value());
1457 tx_pin =
static_cast<int>(cfg[
"tx_pin"].
as_int().value());
1460 rx_pin =
static_cast<int>(cfg[
"rx_pin"].
as_int().value());
1463 if (frequency_hz < 1 || frequency_hz > 5000000 ||
1464 duration_ms < 1 || duration_ms > 5000) {
1465 response.set(
"success",
false);
1466 response.set(
"error",
"InvalidArgs");
1467 response.set(
"message",
1468 "frequency_hz in [1, 5_000_000]; duration_ms in [1, 5000].");
1473 analogWriteFrequency(tx_pin, (
float)frequency_hz);
1474 analogWrite(tx_pin, 128);
1483 const fl::u64 expected_edges =
1485 fl::u64 target = expected_edges + expected_edges / 2;
1486 if (target < 1024ULL) target = 1024ULL;
1487 if (target > 16384ULL) target = 16384ULL;
1492 analogWrite(tx_pin, 0);
1493 response.set(
"success",
false);
1494 response.set(
"error",
"RxCreateFailed");
1495 response.set(
"message",
1496 "Failed to create FlexIO RX channel on pin (no FLEXIO1 mapping).");
1499 if (!rx_channel->begin(rx_cfg)) {
1500 analogWrite(tx_pin, 0);
1501 response.set(
"success",
false);
1502 response.set(
"error",
"RxBeginFailed");
1503 response.set(
"message",
1504 "RxChannel::begin() returned false (see device WARN log).");
1509 rx_channel->wait((u32)duration_ms);
1512 analogWrite(tx_pin, 0);
1517 size_t edges_captured =
1519 edges.
resize(edges_captured);
1525 fl::u32 min_ns = 0xFFFFFFFFu;
1528 for (
size_t i = 0; i + 1 < edges_captured; i += 2) {
1529 const fl::u32 period_ns =
1530 (fl::u32)edges[i].ns + (fl::u32)edges[i + 1].ns;
1531 if (period_ns == 0)
continue;
1532 sum_ns += period_ns;
1534 if (period_ns < min_ns) min_ns = period_ns;
1535 if (period_ns > max_ns) max_ns = period_ns;
1538 fl::u32 mean_ns = 0;
1539 fl::u32 sigma_ns = 0;
1541 mean_ns = (fl::u32)(sum_ns / (
fl::u64)periods);
1544 periods > 0 ? (sum_sq_ns / (
fl::u64)periods) - (mean64 * mean64)
1552 if (min_ns == 0xFFFFFFFFu) min_ns = 0;
1554 response.set(
"success",
true);
1555 response.set(
"frequency_hz",
static_cast<int64_t
>(frequency_hz));
1556 response.set(
"duration_ms",
static_cast<int64_t
>(duration_ms));
1557 response.set(
"tx_pin",
static_cast<int64_t
>(tx_pin));
1558 response.set(
"rx_pin",
static_cast<int64_t
>(rx_pin));
1559 response.set(
"edges_captured",
static_cast<int64_t
>(edges_captured));
1560 response.set(
"periods",
static_cast<int64_t
>(periods));
1561 response.set(
"period_mean_ns",
static_cast<int64_t
>(mean_ns));
1562 response.set(
"period_sigma_ns",
static_cast<int64_t
>(sigma_ns));
1563 response.set(
"period_min_ns",
static_cast<int64_t
>(min_ns));
1564 response.set(
"period_max_ns",
static_cast<int64_t
>(max_ns));
1598#if !defined(FL_IS_TEENSY_4X)
1600 response.set(
"success",
false);
1601 response.set(
"error",
"PlatformNotSupported");
1602 response.set(
"message",
1603 "flexioObjectFledTest is Teensy 4.x-only (FLEXIO1 RX + "
1604 "Teensy-core ObjectFLED driver).");
1611 int capture_ms = 50;
1612 if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
1615 test_case =
static_cast<int>(cfg[
"test_case"].
as_int().value());
1618 tx_pin =
static_cast<int>(cfg[
"tx_pin"].
as_int().value());
1621 rx_pin =
static_cast<int>(cfg[
"rx_pin"].
as_int().value());
1624 capture_ms =
static_cast<int>(cfg[
"capture_ms"].
as_int().value());
1627 if (test_case < 0 || test_case > 4 ||
1628 capture_ms < 1 || capture_ms > 5000) {
1629 response.set(
"success",
false);
1630 response.set(
"error",
"InvalidArgs");
1631 response.set(
"message",
1632 "test_case in [0,4]; capture_ms in [1, 5000].");
1638 static CRGB leds_buf[100];
1640 switch (test_case) {
1657 leds_buf[0] =
CRGB(0xFF, 0xFF, 0xFF);
1661 for (
int i = 0; i < 100; ++i) {
1671 expected.
reserve((
size_t)num_leds * 3);
1672 for (
int i = 0; i < num_leds; ++i) {
1680 const size_t expected_edges = (
size_t)num_leds * 24u * 2u;
1681 size_t edge_capacity = expected_edges + expected_edges / 4u + 64u;
1682 if (edge_capacity > 16384u) edge_capacity = 16384u;
1688 if (!rx_channel || !rx_channel->begin(rx_cfg)) {
1689 response.set(
"success",
false);
1690 response.set(
"error",
"RxBeginFailed");
1691 response.set(
"message",
1692 "Failed to bring up FlexIO RX on the requested pin "
1693 "(check kFlexIo1Pins[] mapping).");
1710 auto tx_channel =
FastLED.add(tx_cfg);
1712 response.set(
"success",
false);
1713 response.set(
"error",
"TxAddFailed");
1714 response.set(
"message",
1715 "FastLED.add() rejected the ObjectFLED ChannelConfig.");
1728 const u32 ws2812_tx_us =
1729 (u32)num_leds * 24u * 13u / 10u + 100u;
1730 const u32 min_capture_ms = (ws2812_tx_us + 999u) / 1000u + 10u;
1731 const u32 effective_capture_ms = ((u32)capture_ms > min_capture_ms)
1734 rx_channel->wait(effective_capture_ms);
1743 auto decode_result =
1747 u32 decoded_bytes = 0u;
1748 if (decode_result) {
1749 decoded_bytes = decode_result.value();
1751 const size_t cmp_n = (decoded_bytes < expected.
size())
1755 size_t mismatched = 0;
1756 for (
size_t i = 0; i < cmp_n; ++i) {
1757 if (decoded[i] == expected[i]) ++matched;
else ++mismatched;
1759 if (decoded_bytes < expected.
size()) {
1760 mismatched += expected.
size() - decoded_bytes;
1766 const size_t edges_captured =
1774 response.set(
"success", mismatched == 0 && decoded_bytes == expected.
size());
1775 response.set(
"test_case",
static_cast<int64_t
>(test_case));
1776 response.set(
"tx_pin",
static_cast<int64_t
>(tx_pin));
1777 response.set(
"rx_pin",
static_cast<int64_t
>(rx_pin));
1778 response.set(
"num_leds",
static_cast<int64_t
>(num_leds));
1779 response.set(
"expected_bytes",
static_cast<int64_t
>(expected.
size()));
1780 response.set(
"decoded_bytes",
static_cast<int64_t
>(decoded_bytes));
1781 response.set(
"matched",
static_cast<int64_t
>(matched));
1782 response.set(
"mismatched",
static_cast<int64_t
>(mismatched));
1783 response.set(
"edges_captured",
static_cast<int64_t
>(edges_captured));
1814#if !defined(FL_IS_TEENSY_4X)
1816 response.set(
"success",
false);
1817 response.set(
"error",
"PlatformNotSupported");
1818 response.set(
"message",
1819 "flexioRxLoopbackPing is Teensy 4.x-only (FLEXIO1 RX).");
1826 if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
1829 tx_pin =
static_cast<int>(cfg[
"tx_pin"].
as_int().value());
1832 rx_pin =
static_cast<int>(cfg[
"rx_pin"].
as_int().value());
1835 toggle_us =
static_cast<int>(cfg[
"toggle_us"].
as_int().value());
1838 edges =
static_cast<int>(cfg[
"edges"].
as_int().value());
1841 if (edges < 2 || edges > 64) {
1842 response.set(
"success",
false);
1843 response.set(
"error",
"InvalidEdges");
1844 response.set(
"message",
"edges must be in [2, 64]");
1853 if (!rx_channel || !rx_channel->begin(rx_cfg)) {
1854 response.set(
"success",
false);
1855 response.set(
"error",
"RxBeginFailed");
1856 response.set(
"message",
1857 "FlexIO RX begin() failed (kFlexIo1Pins[] mapping?).");
1864 pinMode(tx_pin, OUTPUT);
1865 digitalWrite(tx_pin, LOW);
1866 delayMicroseconds(100);
1869 for (
int i = 0; i < edges; ++i) {
1870 digitalWrite(tx_pin, level ? HIGH : LOW);
1871 delayMicroseconds(toggle_us);
1880 constexpr uintptr_t kFLEXIO1_BASE_DIAG = 0x401AC000u;
1881 volatile uint32_t *pin = (
volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x00C);
1882 volatile uint32_t *shftstat = (
volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x010);
1883 volatile uint32_t *timstat = (
volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x018);
1884 volatile uint32_t *shftbuf = (
volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x200);
1885 volatile uint32_t *shftbufbis = (
volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x280);
1886 volatile uint32_t *shftbufbys = (
volatile uint32_t *)(kFLEXIO1_BASE_DIAG + 0x300);
1887 FL_WARN(
"[ping diag] post-toggle FLEXIO1: PIN=0x"
1889 <<
" SHIFTSTAT=0x" << *shftstat
1890 <<
" TIMSTAT=0x" << *timstat <<
fl::dec);
1892 <<
" SHIFTBUFBIS[0]=0x" << *shftbufbis
1893 <<
" SHIFTBUFBYS[0]=0x" << *shftbufbys <<
fl::dec);
1900 rx_channel->wait(50);
1909 pinMode(tx_pin, INPUT);
1911 response.set(
"success",
true);
1912 response.set(
"tx_pin",
static_cast<int64_t
>(tx_pin));
1913 response.set(
"rx_pin",
static_cast<int64_t
>(rx_pin));
1914 response.set(
"toggle_us",
static_cast<int64_t
>(toggle_us));
1915 response.set(
"edges",
static_cast<int64_t
>(edges));
1916 response.set(
"edges_captured",
static_cast<int64_t
>(edge_count));
1922 const size_t to_dump = (edge_count < 8u) ? edge_count : 8u;
1923 for (
size_t i = 0; i < to_dump; ++i) {
1925 e.
set(
"high", captured_edges[i].high);
1926 e.
set(
"ns",
static_cast<int64_t
>(captured_edges[i].ns));
1929 response.set(
"first_edges", edges_arr);
1939 if (!
args.is_array() ||
args.size() != 1) {
1940 response.set(
"success",
false);
1941 response.set(
"error",
"InvalidArgs");
1942 response.set(
"message",
"Expected [enabled: bool]");
1946 if (!
args[0].is_bool()) {
1947 response.set(
"success",
false);
1948 response.set(
"error",
"InvalidType");
1949 response.set(
"message",
"Argument must be boolean");
1953 bool enabled =
args[0].as_bool().value();
1954 mState->debug_enabled = enabled;
1956 response.set(
"success",
true);
1957 response.set(
"debug_enabled", enabled);
1958 response.set(
"message", enabled ?
"Debug logging enabled" :
"Debug logging disabled");
1969 if (!
args.is_array() ||
args.size() != 2) {
1970 response.set(
"error",
"InvalidArgs");
1971 response.set(
"message",
"Expected [txPin, rxPin]");
1975 if (!
args[0].is_int() || !
args[1].is_int()) {
1976 response.set(
"error",
"InvalidPinType");
1977 response.set(
"message",
"Pin numbers must be integers");
1981 int tx_pin =
static_cast<int>(
args[0].as_int().value());
1982 int rx_pin =
static_cast<int>(
args[1].as_int().value());
1985 pinMode(tx_pin, OUTPUT);
1986 pinMode(rx_pin, INPUT_PULLUP);
1987 digitalWrite(tx_pin, LOW);
1989 int rx_when_tx_low = digitalRead(rx_pin);
1992 digitalWrite(tx_pin, HIGH);
1994 int rx_when_tx_high = digitalRead(rx_pin);
1997 pinMode(tx_pin, INPUT);
1998 pinMode(rx_pin, INPUT);
2001 bool connected = (rx_when_tx_low == LOW) && (rx_when_tx_high == HIGH);
2003 response.set(
"txPin",
static_cast<int64_t
>(tx_pin));
2004 response.set(
"rxPin",
static_cast<int64_t
>(rx_pin));
2005 response.set(
"rxWhenTxLow", rx_when_tx_low == LOW ?
"LOW" :
"HIGH");
2006 response.set(
"rxWhenTxHigh", rx_when_tx_high == HIGH ?
"HIGH" :
"LOW");
2007 response.set(
"connected", connected);
2010 response.set(
"success",
true);
2011 response.set(
"message",
"GPIO pins are connected");
2013 response.set(
"success",
false);
2014 if (rx_when_tx_low == HIGH && rx_when_tx_high == HIGH) {
2015 response.set(
"message",
"RX pin stuck HIGH - no connection detected (check jumper wire)");
2016 }
else if (rx_when_tx_low == LOW && rx_when_tx_high == LOW) {
2017 response.set(
"message",
"RX pin stuck LOW - possible short to ground");
2019 response.set(
"message",
"Unexpected GPIO behavior - check wiring");
2033 response.set(
"success",
true);
2034 response.set(
"txPin",
static_cast<int64_t
>(
mState->pin_tx));
2035 response.set(
"rxPin",
static_cast<int64_t
>(
mState->pin_rx));
2038 defaults.
set(
"txPin",
static_cast<int64_t
>(
mState->default_pin_tx));
2039 defaults.
set(
"rxPin",
static_cast<int64_t
>(
mState->default_pin_rx));
2040 response.set(
"defaults", defaults);
2042 #if defined(FL_IS_ESP_32S3)
2043 response.set(
"platform",
"ESP32-S3");
2044 #elif defined(FL_IS_ESP_32S2)
2045 response.set(
"platform",
"ESP32-S2");
2046 #elif defined(FL_IS_ESP_32C6)
2047 response.set(
"platform",
"ESP32-C6");
2048 #elif defined(FL_IS_ESP_32C3)
2049 response.set(
"platform",
"ESP32-C3");
2051 response.set(
"platform",
"unknown");
2061 if (!
args.is_array() ||
args.size() != 1 || !
args[0].is_int()) {
2062 response.set(
"error",
"InvalidArgs");
2063 response.set(
"message",
"Expected [pin: int]");
2067 int new_pin =
static_cast<int>(
args[0].as_int().value());
2070 if (new_pin < 0 || new_pin > 48) {
2071 response.set(
"error",
"InvalidPin");
2072 response.set(
"message",
"Pin must be 0-48");
2076 int old_pin =
mState->pin_tx;
2077 mState->pin_tx = new_pin;
2079 FL_PRINT(
"[RPC] setTxPin(" << new_pin <<
") - TX pin changed from " << old_pin <<
" to " << new_pin);
2081 response.set(
"success",
true);
2082 response.set(
"txPin",
static_cast<int64_t
>(new_pin));
2083 response.set(
"previousTxPin",
static_cast<int64_t
>(old_pin));
2091 if (!
args.is_array() ||
args.size() != 1 || !
args[0].is_int()) {
2092 response.set(
"error",
"InvalidArgs");
2093 response.set(
"message",
"Expected [pin: int]");
2097 int new_pin =
static_cast<int>(
args[0].as_int().value());
2100 if (new_pin < 0 || new_pin > 48) {
2101 response.set(
"error",
"InvalidPin");
2102 response.set(
"message",
"Pin must be 0-48");
2106 int old_pin =
mState->pin_rx;
2107 bool pin_changed = (new_pin != old_pin);
2108 bool rx_recreated =
false;
2111 mState->pin_rx = new_pin;
2114 FL_PRINT(
"[RPC] setRxPin(" << new_pin <<
") - Recreating RX channel...");
2117 mState->rx_channel.reset();
2122 if (
mState->rx_channel) {
2123 FL_PRINT(
"[RPC] setRxPin - RX channel recreated on GPIO " << new_pin);
2124 rx_recreated =
true;
2126 FL_ERROR(
"[RPC] setRxPin - Failed to create RX channel on GPIO " << new_pin);
2127 response.set(
"error",
"RxChannelCreationFailed");
2128 response.set(
"message",
"Failed to create RX channel on new pin");
2130 mState->pin_rx = old_pin;
2135 response.set(
"success",
true);
2136 response.set(
"rxPin",
static_cast<int64_t
>(new_pin));
2137 response.set(
"previousRxPin",
static_cast<int64_t
>(old_pin));
2138 response.set(
"rxChannelRecreated", rx_recreated);
2147 int new_tx_pin = -1;
2148 int new_rx_pin = -1;
2150 if (
args.is_array() &&
args.size() == 1 &&
args[0].is_object()) {
2154 new_tx_pin =
static_cast<int>(config[
"txPin"].
as_int().value());
2157 new_rx_pin =
static_cast<int>(config[
"rxPin"].
as_int().value());
2159 }
else if (
args.is_array() &&
args.size() == 2) {
2161 if (
args[0].is_int()) {
2162 new_tx_pin =
static_cast<int>(
args[0].as_int().value());
2164 if (
args[1].is_int()) {
2165 new_rx_pin =
static_cast<int>(
args[1].as_int().value());
2168 response.set(
"error",
"InvalidArgs");
2169 response.set(
"message",
"Expected [{txPin, rxPin}] or [txPin, rxPin]");
2174 if (new_tx_pin < 0 || new_tx_pin > 48) {
2175 response.set(
"error",
"InvalidTxPin");
2176 response.set(
"message",
"TX pin must be 0-48");
2179 if (new_rx_pin < 0 || new_rx_pin > 48) {
2180 response.set(
"error",
"InvalidRxPin");
2181 response.set(
"message",
"RX pin must be 0-48");
2185 int old_tx_pin =
mState->pin_tx;
2186 int old_rx_pin =
mState->pin_rx;
2187 bool rx_pin_changed = (new_rx_pin != old_rx_pin);
2188 bool rx_recreated =
false;
2191 mState->pin_tx = new_tx_pin;
2194 if (rx_pin_changed) {
2195 mState->pin_rx = new_rx_pin;
2197 FL_PRINT(
"[RPC] setPins - Recreating RX channel on GPIO " << new_rx_pin <<
"...");
2200 mState->rx_channel.reset();
2205 if (
mState->rx_channel) {
2206 FL_PRINT(
"[RPC] setPins - RX channel recreated successfully");
2207 rx_recreated =
true;
2209 FL_ERROR(
"[RPC] setPins - Failed to create RX channel on GPIO " << new_rx_pin);
2211 mState->pin_tx = old_tx_pin;
2212 mState->pin_rx = old_rx_pin;
2213 response.set(
"error",
"RxChannelCreationFailed");
2214 response.set(
"message",
"Failed to create RX channel - pins restored to previous values");
2218 mState->pin_rx = new_rx_pin;
2221 FL_PRINT(
"[RPC] setPins - TX: " << old_tx_pin <<
" → " << new_tx_pin
2222 <<
", RX: " << old_rx_pin <<
" → " << new_rx_pin);
2224 response.set(
"success",
true);
2225 response.set(
"txPin",
static_cast<int64_t
>(new_tx_pin));
2226 response.set(
"rxPin",
static_cast<int64_t
>(new_rx_pin));
2227 response.set(
"previousTxPin",
static_cast<int64_t
>(old_tx_pin));
2228 response.set(
"previousRxPin",
static_cast<int64_t
>(old_rx_pin));
2229 response.set(
"rxChannelRecreated", rx_recreated);
2245 start_fn.
set(
"name",
"start");
2246 start_fn.
set(
"phase",
"Phase 1: Basic Control");
2247 start_fn.
set(
"args",
"[]");
2248 start_fn.
set(
"returns",
"void");
2249 start_fn.
set(
"description",
"Trigger test matrix execution");
2253 status_fn.
set(
"name",
"status");
2254 status_fn.
set(
"phase",
"Phase 1: Basic Control");
2255 status_fn.
set(
"args",
"[]");
2256 status_fn.
set(
"returns",
"{startReceived, testComplete, frameCounter, state}");
2257 status_fn.
set(
"description",
"Query current test state");
2261 drivers_fn.
set(
"name",
"drivers");
2262 drivers_fn.
set(
"phase",
"Phase 1: Basic Control");
2263 drivers_fn.
set(
"args",
"[]");
2264 drivers_fn.
set(
"returns",
"[{name, priority, enabled}, ...]");
2265 drivers_fn.
set(
"description",
"List available drivers");
2270 getConfig_fn.
set(
"name",
"getConfig");
2271 getConfig_fn.
set(
"phase",
"Phase 2: Configuration");
2272 getConfig_fn.
set(
"args",
"[]");
2273 getConfig_fn.
set(
"returns",
"{drivers, laneRange, stripSizes, totalTestCases}");
2274 getConfig_fn.
set(
"description",
"Query current test matrix configuration");
2278 setDrivers_fn.
set(
"name",
"setDrivers");
2279 setDrivers_fn.
set(
"phase",
"Phase 2: Configuration");
2280 setDrivers_fn.
set(
"args",
"[driver1, driver2, ...]");
2281 setDrivers_fn.
set(
"returns",
"{success, driversSet, testCases}");
2282 setDrivers_fn.
set(
"description",
"Configure enabled drivers");
2286 setLaneRange_fn.
set(
"name",
"setLaneRange");
2287 setLaneRange_fn.
set(
"phase",
"Phase 2: Configuration");
2288 setLaneRange_fn.
set(
"args",
"[minLanes, maxLanes]");
2289 setLaneRange_fn.
set(
"returns",
"{success, minLanes, maxLanes, testCases}");
2290 setLaneRange_fn.
set(
"description",
"Configure lane range (1-16)");
2294 setStripSizes_fn.
set(
"name",
"setStripSizes");
2295 setStripSizes_fn.
set(
"phase",
"Phase 2: Configuration");
2296 setStripSizes_fn.
set(
"args",
"[size] or [shortSize, longSize]");
2297 setStripSizes_fn.
set(
"returns",
"{success, stripSizesSet, testCases}");
2298 setStripSizes_fn.
set(
"description",
"Configure strip sizes");
2303 runTestCase_fn.
set(
"name",
"runTestCase");
2304 runTestCase_fn.
set(
"phase",
"Phase 3: Selective Execution");
2305 runTestCase_fn.
set(
"args",
"[testCaseIndex]");
2306 runTestCase_fn.
set(
"returns",
"{success, testCaseIndex, result}");
2307 runTestCase_fn.
set(
"description",
"Run single test case by index");
2311 runDriver_fn.
set(
"name",
"runDriver");
2312 runDriver_fn.
set(
"phase",
"Phase 3: Selective Execution");
2313 runDriver_fn.
set(
"args",
"[driverName]");
2314 runDriver_fn.
set(
"returns",
"{success, driver, testsRun, results}");
2315 runDriver_fn.
set(
"description",
"Run all tests for specific driver");
2319 runAll_fn.
set(
"name",
"runAll");
2320 runAll_fn.
set(
"phase",
"Phase 3: Selective Execution");
2321 runAll_fn.
set(
"args",
"[]");
2322 runAll_fn.
set(
"returns",
"{success, totalCases, passedCases, skippedCases, results}");
2323 runAll_fn.
set(
"description",
"Run full test matrix with JSON results");
2327 getResults_fn.
set(
"name",
"getResults");
2328 getResults_fn.
set(
"phase",
"Phase 3: Selective Execution");
2329 getResults_fn.
set(
"args",
"[]");
2330 getResults_fn.
set(
"returns",
"[{driver, lanes, stripSize, ...}, ...]");
2331 getResults_fn.
set(
"description",
"Return all test results");
2335 getResult_fn.
set(
"name",
"getResult");
2336 getResult_fn.
set(
"phase",
"Phase 3: Selective Execution");
2337 getResult_fn.
set(
"args",
"[testCaseIndex]");
2338 getResult_fn.
set(
"returns",
"{driver, lanes, stripSize, ...}");
2339 getResult_fn.
set(
"description",
"Return specific test case result");
2344 reset_fn.
set(
"name",
"reset");
2345 reset_fn.
set(
"phase",
"Phase 4: Utility");
2346 reset_fn.
set(
"args",
"[]");
2347 reset_fn.
set(
"returns",
"{success, message, testCasesCleared}");
2348 reset_fn.
set(
"description",
"Reset test state without device reboot");
2352 halt_fn.
set(
"name",
"halt");
2353 halt_fn.
set(
"phase",
"Phase 4: Utility");
2354 halt_fn.
set(
"args",
"[]");
2355 halt_fn.
set(
"returns",
"{success, message}");
2356 halt_fn.
set(
"description",
"Trigger sketch halt");
2360 ping_fn.
set(
"name",
"ping");
2361 ping_fn.
set(
"phase",
"Phase 4: Utility");
2362 ping_fn.
set(
"args",
"[]");
2363 ping_fn.
set(
"returns",
"{success, message, timestamp, uptimeMs, frameCounter}");
2364 ping_fn.
set(
"description",
"Health check with timestamp");
2369 getPins_fn.
set(
"name",
"getPins");
2370 getPins_fn.
set(
"phase",
"Phase 5: Pin Configuration");
2371 getPins_fn.
set(
"args",
"[]");
2372 getPins_fn.
set(
"returns",
"{txPin, rxPin, defaults: {txPin, rxPin}, platform}");
2373 getPins_fn.
set(
"description",
"Query current and default pin configuration");
2377 setTxPin_fn.
set(
"name",
"setTxPin");
2378 setTxPin_fn.
set(
"phase",
"Phase 5: Pin Configuration");
2379 setTxPin_fn.
set(
"args",
"[pin]");
2380 setTxPin_fn.
set(
"returns",
"{success, txPin, previousTxPin, testCases}");
2381 setTxPin_fn.
set(
"description",
"Set TX pin (regenerates test cases)");
2385 setRxPin_fn.
set(
"name",
"setRxPin");
2386 setRxPin_fn.
set(
"phase",
"Phase 5: Pin Configuration");
2387 setRxPin_fn.
set(
"args",
"[pin]");
2388 setRxPin_fn.
set(
"returns",
"{success, rxPin, previousRxPin, rxChannelRecreated}");
2389 setRxPin_fn.
set(
"description",
"Set RX pin (recreates RX channel)");
2393 setPins_fn.
set(
"name",
"setPins");
2394 setPins_fn.
set(
"phase",
"Phase 5: Pin Configuration");
2395 setPins_fn.
set(
"args",
"[{txPin, rxPin}] or [txPin, rxPin]");
2396 setPins_fn.
set(
"returns",
"{success, txPin, rxPin, rxChannelRecreated, testCases}");
2397 setPins_fn.
set(
"description",
"Set both TX and RX pins atomically");
2401 findConnectedPins_fn.
set(
"name",
"findConnectedPins");
2402 findConnectedPins_fn.
set(
"phase",
"Phase 5: Pin Configuration");
2403 findConnectedPins_fn.
set(
"args",
"[{startPin, endPin, autoApply}] (all optional)");
2404 findConnectedPins_fn.
set(
"returns",
"{success, found, txPin, rxPin, autoApplied, testedPairs}");
2405 findConnectedPins_fn.
set(
"description",
"Probe adjacent pin pairs to find jumper wire connection");
2406 functions.
push_back(findConnectedPins_fn);
2409 help_fn.
set(
"name",
"help");
2410 help_fn.
set(
"phase",
"Phase 4: Utility");
2411 help_fn.
set(
"args",
"[]");
2412 help_fn.
set(
"returns",
"[{name, phase, args, returns, description}, ...]");
2413 help_fn.
set(
"description",
"List all RPC functions with descriptions");
2417 testSimd_fn.
set(
"name",
"testSimd");
2418 testSimd_fn.
set(
"phase",
"Phase 4: Utility");
2419 testSimd_fn.
set(
"args",
"[]");
2420 testSimd_fn.
set(
"returns",
"{success, passed, totalTests, passedTests, failedTests, failures:[string]}");
2421 testSimd_fn.
set(
"description",
"Run comprehensive SIMD test suite (85 tests)");
2425 testSimdBenchmark_fn.
set(
"name",
"testSimdBenchmark");
2426 testSimdBenchmark_fn.
set(
"phase",
"Phase 4: Utility");
2427 testSimdBenchmark_fn.
set(
"args",
"[{iterations}] (optional, default 10000)");
2428 testSimdBenchmark_fn.
set(
"returns",
"{success, iterations, float_us, s16x16_us, simd_us}");
2429 testSimdBenchmark_fn.
set(
"description",
"Benchmark multiply speed: float vs s16x16 vs s16x16x4 SIMD");
2430 functions.
push_back(testSimdBenchmark_fn);
2433 animartrixPerlinBench_fn.
set(
"name",
"animartrixPerlinBench");
2434 animartrixPerlinBench_fn.
set(
"phase",
"Phase 4: Utility");
2435 animartrixPerlinBench_fn.
set(
"args",
"[{iterations}] (optional, default 100, max 10000)");
2436 animartrixPerlinBench_fn.
set(
"returns",
"{success, iterations, pnoise_calls_per_iter, pnoise_float_us, pnoise_i16_us, speedup_x1000}");
2437 animartrixPerlinBench_fn.
set(
"description",
"Animartrix-representative Perlin noise bench: scalar float pnoise vs s16x16 fixed-point pnoise2d (16x16 grid per iter, mirrors a real frame). Answers: how much does fixed-point beat float for Animartrix's hot path on this hardware?");
2438 functions.
push_back(animartrixPerlinBench_fn);
2441 wave8ExpandBenchmark_fn.
set(
"name",
"wave8ExpandBenchmark");
2442 wave8ExpandBenchmark_fn.
set(
"phase",
"Phase 4: Utility");
2443 wave8ExpandBenchmark_fn.
set(
"args",
"[{iterations}] (optional, default 30000, max 200000)");
2444 wave8ExpandBenchmark_fn.
set(
"returns",
"{success, iterations, expand_nibble_us, expand_byte_us, expand_batched_us, transpose16_nibble_us, transpose16_byte_us, sink}");
2445 wave8ExpandBenchmark_fn.
set(
"description",
"Bench PARLIO Wave8 expansion (#2526): nibble vs byte vs batched LUT, plus full per-byte-position cost (expansion + 16-lane transpose)");
2446 functions.
push_back(wave8ExpandBenchmark_fn);
2449 parlioEncodeBenchmark_fn.
set(
"name",
"parlioEncodeBenchmark");
2450 parlioEncodeBenchmark_fn.
set(
"phase",
"Phase 4: Utility");
2451 parlioEncodeBenchmark_fn.
set(
"args",
"[{iterations}] (optional, default 12000, max 200000)");
2452 parlioEncodeBenchmark_fn.
set(
"returns",
"{success, iters, lanes, leds_per_lane, scratchPsramOk, outputPsramOk, perpos_ss_us, perpos_sp_us, perpos_ps_us, perpos_pp_us, frame_ss_us, frame_sp_us, frame_ps_us, frame_pp_us, sink}");
2453 parlioEncodeBenchmark_fn.
set(
"description",
"Bench full PARLIO encode hot loop (16-lane gather + BF1 pipe4 direct encode) with SRAM and optional PSRAM placements; answers PSRAM hypothesis + ISR-streaming feasibility");
2454 functions.
push_back(parlioEncodeBenchmark_fn);
2457 parlioStreamValidate_fn.
set(
"name",
"parlioStreamValidate");
2458 parlioStreamValidate_fn.
set(
"phase",
"Phase 4: Utility");
2459 parlioStreamValidate_fn.
set(
"args",
"[{baseTxPin, txPins, numLanes, numLeds, iterations, timeoutMs}] (all optional; txPins overrides contiguous baseTxPin)");
2460 parlioStreamValidate_fn.
set(
"returns",
"{success, completed, baseTxPin, txPins, lanes, leds_per_lane, iterations, perIterUs:[...], steadyAvgUs, failedIter, underrunCount, txDoneCount, workerIsrCount, ringError, hardwareIdle}");
2461 parlioStreamValidate_fn.
set(
"description",
"Functional test of the PARLIO ISR-chunked streaming engine (#2548). Drives N back-to-back FastLED.show() calls through the production engine (which uses BF1+pipe4 on 16-lane Wave8 since #2559) and verifies all complete within timeout. Catches hangs/stalls.");
2462 functions.
push_back(parlioStreamValidate_fn);
2465 flexioRxBenchmark_fn.
set(
"name",
"flexioRxBenchmark");
2466 flexioRxBenchmark_fn.
set(
"phase",
"Phase 4: Utility");
2467 flexioRxBenchmark_fn.
set(
"args",
"[{frequency_hz=1000, duration_ms=100, tx_pin=3, rx_pin=4}] (all optional)");
2468 flexioRxBenchmark_fn.
set(
"returns",
"{success, frequency_hz, duration_ms, tx_pin, rx_pin, edges_captured, periods, period_mean_ns, period_sigma_ns, period_min_ns, period_max_ns}");
2469 flexioRxBenchmark_fn.
set(
"description",
"Square-wave validation for the FlexIO RX backend (Teensy 4.x only, FastLED#2764 Phase 2). Drives tx_pin via analogWriteFrequency at 50%% duty, captures via RxBackend::FLEXIO on rx_pin, reports per-period statistics.");
2470 functions.
push_back(flexioRxBenchmark_fn);
2473 flexioObjectFledTest_fn.
set(
"name",
"flexioObjectFledTest");
2474 flexioObjectFledTest_fn.
set(
"phase",
"Phase 4: Utility");
2475 flexioObjectFledTest_fn.
set(
"args",
"[{test_case=0..4, tx_pin=3, rx_pin=4, capture_ms=50}] (all optional)");
2476 flexioObjectFledTest_fn.
set(
"returns",
"{success, test_case, tx_pin, rx_pin, num_leds, expected_bytes, decoded_bytes, matched, mismatched, edges_captured}");
2477 flexioObjectFledTest_fn.
set(
"description",
"End-to-end ObjectFLED TX -> FlexIO RX loopback verification (Teensy 4.x only, FastLED#2764 Phase 3). Drives WS2812 patterns through Bus::OBJECT_FLED, captures via RxBackend::FLEXIO, decodes the bit stream, and reports byte-level match counts. Five fixed test patterns: 0=red, 1=RGB triple, 2=all zeros, 3=all ones, 4=100-LED alternating.");
2478 functions.
push_back(flexioObjectFledTest_fn);
2481 response.set(
"success",
true);
2482 response.set(
"totalFunctions",
static_cast<int64_t
>(28));
2483 response.set(
"functions", functions);
2493 const SimdTestEntry* tests =
nullptr;
2497 int passed_count = 0;
2498 int failed_count = 0;
2501 for (
int i = 0; i < num_tests; i++) {
2502 bool ok = tests[i].func();
2511 response.set(
"success",
true);
2512 response.set(
"passed", failed_count == 0);
2513 response.set(
"totalTests",
static_cast<int64_t
>(num_tests));
2514 response.set(
"passedTests",
static_cast<int64_t
>(passed_count));
2515 response.set(
"failedTests",
static_cast<int64_t
>(failed_count));
2516 if (failed_count > 0) {
2517 response.set(
"failures", failures);
2528 if (
args.is_object()) {
2530 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2534 iters =
static_cast<int>(config[
"iterations"].
as_int().value());
2535 if (iters < 1) iters = 1;
2536 if (iters > 1000000) iters = 1000000;
2541 response.set(
"success",
true);
2542 response.set(
"iterations", result.iterations);
2545 add.set(
"float_us", result.add_float_us);
2546 add.set(
"s8x8_us", result.add_s8x8_us);
2547 add.set(
"s16x16_us", result.add_s16x16_us);
2548 add.set(
"u16x16_us", result.add_u16x16_us);
2549 add.set(
"simd_us", result.add_simd_us);
2550 response.set(
"add", add);
2553 sub.
set(
"float_us", result.sub_float_us);
2554 sub.
set(
"s8x8_us", result.sub_s8x8_us);
2555 sub.
set(
"s16x16_us", result.sub_s16x16_us);
2556 sub.
set(
"u16x16_us", result.sub_u16x16_us);
2557 sub.
set(
"simd_us", result.sub_simd_us);
2558 response.set(
"sub", sub);
2561 mul.
set(
"float_us", result.mul_float_us);
2562 mul.
set(
"s8x8_us", result.mul_s8x8_us);
2563 mul.
set(
"s16x16_us", result.mul_s16x16_us);
2564 mul.
set(
"u16x16_us", result.mul_u16x16_us);
2565 mul.
set(
"simd_us", result.mul_simd_us);
2566 response.set(
"mul", mul);
2569 div.
set(
"float_us", result.div_float_us);
2570 div.
set(
"s8x8_us", result.div_s8x8_us);
2571 div.
set(
"s16x16_us", result.div_s16x16_us);
2572 div.
set(
"u16x16_us", result.div_u16x16_us);
2573 response.set(
"div", div);
2588 if (
args.is_object()) {
2590 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2594 iters =
static_cast<int>(config[
"iterations"].
as_int().value());
2595 if (iters < 1) iters = 1;
2596 if (iters > 10000) iters = 10000;
2601 response.set(
"success",
true);
2602 response.set(
"iterations", result.iterations);
2605 response.set(
"pnoise_calls_per_iter",
static_cast<int64_t
>(256));
2606 response.set(
"pnoise_float_us", result.pnoise_float_us);
2607 response.set(
"pnoise_i16_us", result.pnoise_i16_us);
2610 if (result.pnoise_i16_us > 0) {
2611 double speedup =
static_cast<double>(result.pnoise_float_us) /
2612 static_cast<double>(result.pnoise_i16_us);
2614 response.set(
"speedup_x1000",
static_cast<int64_t
>(speedup * 1000.0));
2628 if (
args.is_object()) {
2630 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2634 iters =
static_cast<int>(config[
"iterations"].
as_int().value());
2639 response.set(
"success",
true);
2640 response.set(
"iterations",
static_cast<int64_t
>(r.iters));
2641 response.set(
"expand_nibble_us",
static_cast<int64_t
>(r.expand_nibble_us));
2642 response.set(
"expand_byte_us",
static_cast<int64_t
>(r.expand_byte_us));
2643 response.set(
"expand_batched_us",
static_cast<int64_t
>(r.expand_batched_us));
2644 response.set(
"transpose16_nibble_us",
static_cast<int64_t
>(r.transpose16_nibble_us));
2645 response.set(
"transpose16_byte_us",
static_cast<int64_t
>(r.transpose16_byte_us));
2646 response.set(
"sink",
static_cast<int64_t
>(r.sink));
2657 auto invalidArgs = [](
const char* message) ->
fl::json {
2659 error.
set(
"success",
false);
2660 error.
set(
"error",
"InvalidArgs");
2661 error.
set(
"message", message);
2668 int timeout_ms = 200;
2669 int base_tx_pin =
mState->pin_tx;
2671 bool has_tx_pins =
false;
2672 bool num_lanes_provided =
false;
2678 if (
args.is_object()) {
2680 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2684 if (config.
contains(
"baseTxPin") && config[
"baseTxPin"].
is_int())
2685 base_tx_pin =
static_cast<int>(config[
"baseTxPin"].
as_int().value());
2687 if (!config[
"numLanes"].is_int()) {
2688 return invalidArgs(
"numLanes must be an integer");
2690 num_lanes =
static_cast<int>(config[
"numLanes"].
as_int().value());
2691 num_lanes_provided =
true;
2694 num_leds =
static_cast<int>(config[
"numLeds"].
as_int().value());
2695 if (config.
contains(
"iterations") && config[
"iterations"].
is_int())
2696 iterations =
static_cast<int>(config[
"iterations"].
as_int().value());
2697 if (config.
contains(
"timeoutMs") && config[
"timeoutMs"].
is_int())
2698 timeout_ms =
static_cast<int>(config[
"timeoutMs"].
as_int().value());
2700 if (!config[
"txPins"].is_array()) {
2701 return invalidArgs(
"txPins must be an integer array");
2703 if (!num_lanes_provided) {
2704 return invalidArgs(
"txPins requires numLanes");
2706 if (num_lanes < 1 ||
2708 return invalidArgs(
"numLanes out of range for txPins");
2712 if (
pins.size() !=
static_cast<size_t>(num_lanes)) {
2713 return invalidArgs(
"txPins length must match numLanes");
2716 for (
int i = 0; i < num_lanes; ++i) {
2717 if (!
pins[i].is_int()) {
2718 return invalidArgs(
"txPins entries must be integers");
2720 int64_t pin_value =
pins[i].as_int().value();
2721 if (pin_value < 0 || pin_value >= 64) {
2722 return invalidArgs(
"txPins entries must be in range 0..63");
2724 tx_pins[i] =
static_cast<int>(pin_value);
2727 base_tx_pin = tx_pins[0];
2732 if (base_tx_pin < 0) base_tx_pin = 0;
2733 if (num_lanes < 1) num_lanes = 1;
2734 if (num_lanes > 16) num_lanes = 16;
2735 if (num_leds < 1) num_leds = 1;
2736 if (num_leds > 256) num_leds = 256;
2737 if (iterations < 1) iterations = 1;
2741 if (timeout_ms < 1) timeout_ms = 1;
2742 if (timeout_ms > 5000) timeout_ms = 5000;
2745 base_tx_pin, num_lanes, num_leds, iterations,
2746 static_cast<uint32_t
>(timeout_ms),
2747 has_tx_pins ? tx_pins :
nullptr);
2749 response.set(
"success",
true);
2750 response.set(
"channelsOk", r.channels_ok);
2751 response.set(
"completed", r.completed);
2752 response.set(
"baseTxPin",
static_cast<int64_t
>(r.base_tx_pin));
2753 int last_tx_pin = r.base_tx_pin + r.lanes - 1;
2754 if (r.explicit_tx_pins) {
2755 for (
int i = r.lanes - 1; i >= 0; --i) {
2756 if (r.tx_pins[i] >= 0) {
2757 last_tx_pin = r.tx_pins[i];
2762 response.set(
"lastTxPin",
static_cast<int64_t
>(last_tx_pin));
2763 response.set(
"explicitTxPins", r.explicit_tx_pins);
2764 response.set(
"lanes",
static_cast<int64_t
>(r.lanes));
2765 response.set(
"ledsPerLane",
static_cast<int64_t
>(r.leds_per_lane));
2766 response.set(
"iterations",
static_cast<int64_t
>(r.iterations));
2767 response.set(
"steadyAvgUs",
static_cast<int64_t
>(r.steady_avg_us));
2768 response.set(
"steadyAvgShowUs",
static_cast<int64_t
>(r.steady_avg_show_us));
2769 response.set(
"steadyAvgWaitUs",
static_cast<int64_t
>(r.steady_avg_wait_us));
2770 response.set(
"failedIter",
static_cast<int64_t
>(r.failed_iter));
2771 response.set(
"timeoutMs",
static_cast<int64_t
>(r.timeout_ms));
2772 response.set(
"txDoneCount",
static_cast<int64_t
>(r.tx_done_count));
2773 response.set(
"workerIsrCount",
static_cast<int64_t
>(r.worker_isr_count));
2774 response.set(
"underrunCount",
static_cast<int64_t
>(r.underrun_count));
2775 response.set(
"ringCount",
static_cast<int64_t
>(r.ring_count));
2776 response.set(
"bytesTotal",
static_cast<int64_t
>(r.bytes_total));
2777 response.set(
"bytesTransmitted",
static_cast<int64_t
>(r.bytes_transmitted));
2778 response.set(
"ringError", r.ring_error);
2779 response.set(
"hardwareIdle", r.hardware_idle);
2781 for (
int i = 0; i < r.lanes; ++i) {
2782 response_tx_pins.
push_back(
static_cast<int64_t
>(r.tx_pins[i]));
2784 response.set(
"txPins", response_tx_pins);
2788 for (
int i = 0; i < r.iterations; ++i) {
2789 per_iter.
push_back(
static_cast<int64_t
>(r.per_iter_us[i]));
2790 per_iter_show.
push_back(
static_cast<int64_t
>(r.per_iter_show_us[i]));
2791 per_iter_wait.
push_back(
static_cast<int64_t
>(r.per_iter_wait_us[i]));
2793 response.set(
"perIterUs", per_iter);
2794 response.set(
"perIterShowUs", per_iter_show);
2795 response.set(
"perIterWaitUs", per_iter_wait);
2808 if (
args.is_object()) {
2810 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2814 iters =
static_cast<int>(config[
"iterations"].
as_int().value());
2819 response.set(
"success", r.iters > 0);
2820 response.set(
"iters",
static_cast<int64_t
>(r.iters));
2821 response.set(
"lanes",
static_cast<int64_t
>(r.lanes));
2822 response.set(
"leds_per_lane",
static_cast<int64_t
>(r.leds_per_lane));
2823 response.set(
"scratchPsramOk", r.scratch_psram_ok);
2824 response.set(
"outputPsramOk", r.output_psram_ok);
2825 response.set(
"perpos_ss_us",
static_cast<int64_t
>(r.perpos_ss_us));
2826 response.set(
"perpos_sp_us",
static_cast<int64_t
>(r.perpos_sp_us));
2827 response.set(
"perpos_ps_us",
static_cast<int64_t
>(r.perpos_ps_us));
2828 response.set(
"perpos_pp_us",
static_cast<int64_t
>(r.perpos_pp_us));
2830 constexpr fl::u32 kFrameBytePositions = 256 * 3;
2831 response.set(
"frame_ss_us",
static_cast<int64_t
>(
2832 static_cast<fl::u64>(r.perpos_ss_us) * kFrameBytePositions / r.iters));
2833 response.set(
"frame_sp_us",
static_cast<int64_t
>(
2834 static_cast<fl::u64>(r.perpos_sp_us) * kFrameBytePositions / r.iters));
2835 response.set(
"frame_ps_us",
static_cast<int64_t
>(
2836 static_cast<fl::u64>(r.perpos_ps_us) * kFrameBytePositions / r.iters));
2837 response.set(
"frame_pp_us",
static_cast<int64_t
>(
2838 static_cast<fl::u64>(r.perpos_pp_us) * kFrameBytePositions / r.iters));
2840 response.set(
"sink",
static_cast<int64_t
>(r.sink));
2853 if (
args.is_object()) {
2855 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2860 num_leds =
static_cast<int>(config[
"numLeds"].
as_int().value());
2863 requested_driver = config[
"driver"].
as_string().value();
2868 if (!requested_driver.
empty()) {
2870 response.set(
"success",
false);
2871 response.set(
"error",
"DriverSetupFailed");
2873 msg <<
"Failed to set '" << requested_driver.
c_str() <<
"' as exclusive driver";
2874 response.set(
"message", msg.
str().
c_str());
2879 if (num_leds < 10 || num_leds > 1000) {
2880 response.set(
"success",
false);
2881 response.set(
"error",
"InvalidNumLeds");
2882 response.set(
"message",
"numLeds must be 10-1000");
2888 for (
int i = 0; i < num_leds; i++) {
2899 auto channel =
FastLED.add(channel_config);
2901 response.set(
"success",
false);
2902 response.set(
"error",
"ChannelCreationFailed");
2903 response.set(
"message",
"Failed to create channel");
2908 uint32_t t0 = micros();
2910 uint32_t t1 = micros();
2912 uint32_t t2 = micros();
2914 uint32_t show1_us = t1 - t0;
2915 uint32_t wait1_us = t2 - t1;
2916 uint32_t total1_us = t2 - t0;
2919 uint32_t t3 = micros();
2921 uint32_t t4 = micros();
2923 uint32_t t5 = micros();
2925 uint32_t show2_us = t4 - t3;
2926 uint32_t wait2_us = t5 - t4;
2927 uint32_t total2_us = t5 - t3;
2936 bool passed = (total2_us > 0) && (show2_us < total2_us / 2);
2940 for (fl::size i = 0; i <
mState->drivers_available.size(); i++) {
2941 if (
mState->drivers_available[i].enabled) {
2942 driver_name =
mState->drivers_available[i].name;
2947 response.set(
"success",
true);
2948 response.set(
"passed", passed);
2950 response.set(
"show1_us",
static_cast<int64_t
>(show1_us));
2951 response.set(
"wait1_us",
static_cast<int64_t
>(wait1_us));
2952 response.set(
"total1_us",
static_cast<int64_t
>(total1_us));
2954 response.set(
"show2_us",
static_cast<int64_t
>(show2_us));
2955 response.set(
"wait2_us",
static_cast<int64_t
>(wait2_us));
2956 response.set(
"total2_us",
static_cast<int64_t
>(total2_us));
2957 response.set(
"num_leds",
static_cast<int64_t
>(num_leds));
2958 response.set(
"driver", driver_name.
c_str());
2962 msg <<
"Async OK: draw2 show()=" << show2_us
2963 <<
"us, wait()=" << wait2_us
2964 <<
"us, total=" << total2_us <<
"us"
2965 <<
" (draw1 show=" << show1_us <<
"us)";
2967 msg <<
"Async FAIL: draw2 show()=" << show2_us
2968 <<
"us out of " << total2_us
2969 <<
"us total (expected <50%)"
2970 <<
" (draw1 show=" << show1_us <<
"us)";
2972 response.set(
"message", msg.
str().
c_str());
2983 mState->net_server_active =
true;
2989 mState->net_client_active =
true;
2999 if (!
args.is_object()) {
3000 response.set(
"success",
false);
3001 response.set(
"error",
"Expected object with host_ip and port");
3009 response.set(
"success",
false);
3010 response.set(
"error",
"Expected {host_ip: string, port: int}");
3015 uint16_t port =
static_cast<uint16_t
>(port_val.
as_int().value());
3028 mState->net_server_active =
false;
3029 mState->net_client_active =
false;
3066 if (ext !=
".mp4") {
3067 response.set(
"success",
false);
3068 response.set(
"error",
"Only .mp4 supported for device decode");
3076 response.set(
"success",
false);
3077 response.set(
"error", error.
c_str());
3080 response.set(
"width",
static_cast<int64_t
>(info.
width));
3081 response.set(
"height",
static_cast<int64_t
>(info.
height));
3084 response.set(
"success",
false);
3085 response.set(
"error",
"H264 decoder not supported on this platform");
3093 response.set(
"success",
false);
3094 response.set(
"error", dec_error.
c_str());
3099 stream->write(data);
3101 if (!decoder->begin(stream)) {
3103 decoder->hasError(&msg);
3104 response.set(
"success",
false);
3105 response.set(
"error", msg.
empty() ?
"Decoder begin() failed" : msg.
c_str());
3109 auto result = decoder->decode();
3111 response.set(
"success",
false);
3112 response.set(
"error",
"Decode returned non-success");
3113 response.set(
"decode_result",
static_cast<int64_t
>(
static_cast<int>(result)));
3118 fl::Frame frame = decoder->getCurrentFrame();
3122 response.set(
"success",
false);
3123 response.set(
"error",
"Decoded frame is invalid");
3127 response.set(
"success",
true);
3128 response.set(
"frame_width",
static_cast<int64_t
>(frame.
getWidth()));
3129 response.set(
"frame_height",
static_cast<int64_t
>(frame.
getHeight()));
3132 auto pixels = frame.
rgb();
3134 int count = pixels.size() < 16 ?
static_cast<int>(pixels.size()) : 16;
3135 for (
int i = 0; i < count; i++) {
3137 px.
push_back(
static_cast<int64_t
>(pixels[i].r));
3138 px.
push_back(
static_cast<int64_t
>(pixels[i].g));
3139 px.
push_back(
static_cast<int64_t
>(pixels[i].b));
3142 response.set(
"pixels", pixel_array);
3150 response.set(
"ble_active",
mState->ble_server_active);
3152 response.set(
"connected", info.
connected);
3153 response.set(
"connected_count",
static_cast<int64_t
>(info.
connectedCount));
3155 response.set(
"tx_value_len",
static_cast<int64_t
>(info.
txValueLen));
3156 response.set(
"ring_head",
static_cast<int64_t
>(info.
ringHead));
3157 response.set(
"ring_tail",
static_cast<int64_t
>(info.
ringTail));
3174 cfg.
func = [&task_ran, &task_completed]() {
3175 task_ran.
store(
true);
3177 task_completed.
store(
true);
3179 cfg.
name =
"test_basic";
3182 uint32_t start = millis();
3183 while (
t.isRunning() && (millis() - start) < 2000) {
3187 bool passed = task_ran.
load() && task_completed.
load() && !
t.isRunning();
3188 r.
set(
"success", passed);
3189 r.
set(
"taskRan", task_ran.
load());
3190 r.
set(
"taskCompleted", task_completed.
load());
3191 r.
set(
"isRunning",
t.isRunning());
3192 r.
set(
"durationMs",
static_cast<int64_t
>(millis() - start));
3204 cfg.
func = [&task_started]() {
3205 task_started.
store(
true);
3210 cfg.
name =
"test_stop";
3213 uint32_t start = millis();
3214 while (!task_started.
load() && (millis() - start) < 2000) {
3218 bool was_running =
t.isRunning();
3220 bool stopped = !
t.isRunning();
3222 bool passed = task_started.
load() && was_running && stopped;
3223 r.
set(
"success", passed);
3224 r.
set(
"taskStarted", task_started.
load());
3225 r.
set(
"wasRunning", was_running);
3226 r.
set(
"stopped", stopped);
3227 r.
set(
"durationMs",
static_cast<int64_t
>(millis() - start));
3236 const int NUM_TASKS = 3;
3239 for (
int i = 0; i < NUM_TASKS; i++) {
3240 task_flags[i].
store(
false);
3244 for (
int i = 0; i < NUM_TASKS; i++) {
3246 cfg.
func = [i, &task_flags, &completed_count]() {
3248 task_flags[i].store(
true);
3251 cfg.
name =
"test_concurrent";
3255 uint32_t start = millis();
3256 while (completed_count.
load() < NUM_TASKS && (millis() - start) < 3000) {
3260 bool all_completed =
true;
3261 bool all_stopped =
true;
3262 for (
int i = 0; i < NUM_TASKS; i++) {
3263 if (!task_flags[i].load()) all_completed =
false;
3264 if (tasks[i].isRunning()) all_stopped =
false;
3267 bool passed = all_completed && all_stopped && (completed_count.
load() == NUM_TASKS);
3268 r.
set(
"success", passed);
3269 r.
set(
"completedCount",
static_cast<int64_t
>(completed_count.
load()));
3270 r.
set(
"allCompleted", all_completed);
3271 r.
set(
"allStopped", all_stopped);
3272 r.
set(
"durationMs",
static_cast<int64_t
>(millis() - start));
3294 consumer_cfg.
func = [promise_ptr, &consumer_started, &consumer_finished,
3295 &consumer_value, &consumer_ok]() {
3296 consumer_started.store(
true);
3300 consumer_ok.
store(
true);
3301 consumer_value.
store(result.value());
3303 consumer_finished.
store(
true);
3305 consumer_cfg.
name =
"await_consumer";
3313 producer_cfg.
func = [promise_ptr, &producer_started, &producer_finished]() {
3314 producer_started.
store(
true);
3316 promise_ptr->complete_with_value(42);
3317 producer_finished.
store(
true);
3319 producer_cfg.
name =
"await_producer";
3323 uint32_t start = millis();
3324 while ((!consumer_finished.
load() || producer.isRunning()) && (millis() - start) < 5000) {
3329 bool passed = consumer_started.load() && consumer_finished.
load()
3330 && consumer_ok.
load() && (consumer_value.
load() == 42)
3331 && producer_started.
load() && producer_finished.
load();
3332 r.
set(
"success", passed);
3333 r.
set(
"consumerStarted", consumer_started.load());
3334 r.
set(
"consumerFinished", consumer_finished.
load());
3335 r.
set(
"consumerOk", consumer_ok.
load());
3336 r.
set(
"consumerValue",
static_cast<int64_t
>(consumer_value.
load()));
3337 r.
set(
"producerStarted", producer_started.
load());
3338 r.
set(
"producerFinished", producer_finished.
load());
3339 r.
set(
"durationMs",
static_cast<int64_t
>(millis() - start));
3356 consumer_cfg.
func = [promise_ptr, &consumer_finished, &got_error]() {
3359 got_error.
store(
true);
3361 consumer_finished.store(
true);
3363 consumer_cfg.
name =
"await_err_consumer";
3370 producer_cfg.
func = [promise_ptr, &producer_finished]() {
3373 producer_finished.
store(
true);
3375 producer_cfg.
name =
"await_err_producer";
3378 uint32_t start = millis();
3379 while ((!consumer_finished.load() || producer.isRunning()) && (millis() - start) < 5000) {
3383 bool passed = consumer_finished.load() && got_error.
load() && producer_finished.
load();
3384 r.
set(
"success", passed);
3385 r.
set(
"consumerFinished", consumer_finished.load());
3386 r.
set(
"gotError", got_error.
load());
3387 r.
set(
"producerFinished", producer_finished.
load());
3388 r.
set(
"durationMs",
static_cast<int64_t
>(millis() - start));
3406 promise_ptr->then([&then_called, &then_value](
const int& val) {
3407 then_called.store(
true);
3408 then_value.
store(val);
3411 catch_called.
store(
true);
3416 producer_cfg.
func = [promise_ptr, &producer_finished]() {
3418 promise_ptr->complete_with_value(99);
3419 producer_finished.
store(
true);
3421 producer_cfg.
name =
"cb_producer";
3424 uint32_t start = millis();
3425 while (producer.isRunning() && (millis() - start) < 5000) {
3427 promise_ptr->update();
3429 promise_ptr->update();
3432 bool passed = then_called.load() && (then_value.
load() == 99)
3433 && !catch_called.
load() && producer_finished.
load();
3434 r.
set(
"success", passed);
3435 r.
set(
"thenCalled", then_called.load());
3436 r.
set(
"thenValue",
static_cast<int64_t
>(then_value.
load()));
3437 r.
set(
"catchCalled", catch_called.
load());
3438 r.
set(
"producerFinished", producer_finished.
load());
3439 r.
set(
"durationMs",
static_cast<int64_t
>(millis() - start));
3453 promise_ptr->then([&then_called](
const int&) {
3454 then_called.store(
true);
3457 catch_called.
store(
true);
3462 producer_cfg.
func = [promise_ptr, &producer_finished]() {
3465 producer_finished.
store(
true);
3467 producer_cfg.
name =
"catch_producer";
3470 uint32_t start = millis();
3471 while (producer.isRunning() && (millis() - start) < 5000) {
3473 promise_ptr->update();
3475 promise_ptr->update();
3478 bool passed = !then_called.load() && catch_called.
load() && producer_finished.
load();
3479 r.
set(
"success", passed);
3480 r.
set(
"thenCalled", then_called.load());
3481 r.
set(
"catchCalled", catch_called.
load());
3482 r.
set(
"producerFinished", producer_finished.
load());
3483 r.
set(
"durationMs",
static_cast<int64_t
>(millis() - start));
3503 cfg_c.
func = [p2, &final_value, &chain_complete, &c_done]() {
3506 final_value.store(result.value());
3509 chain_complete.
store(
true);
3511 cfg_c.
name =
"chain_c";
3516 cfg_b.
func = [p1, p2, &b_done]() {
3520 p2->complete_with_value(result.value() * 10);
3522 p2->complete_with_error(result.error());
3526 cfg_b.
name =
"chain_b";
3531 cfg_a.
func = [p1, &a_done]() {
3533 p1->complete_with_value(7);
3536 cfg_a.
name =
"chain_a";
3539 uint32_t start = millis();
3540 while (!chain_complete.
load() && (millis() - start) < 5000) {
3545 bool passed = chain_complete.
load() && (final_value.load() == 70)
3547 r.
set(
"success", passed);
3548 r.
set(
"finalValue",
static_cast<int64_t
>(final_value.load()));
3549 r.
set(
"aDone", a_done.
load());
3550 r.
set(
"bDone", b_done.
load());
3551 r.
set(
"cDone", c_done.
load());
3552 r.
set(
"durationMs",
static_cast<int64_t
>(millis() - start));
3561 const char* test_names[] = {
3562 "testCoroutineBasic",
3563 "testCoroutineStop",
3564 "testCoroutineConcurrent",
3565 "testCoroutineAwait",
3566 "testCoroutineAwaitError",
3567 "testCoroutinePromiseCallbacks",
3568 "testCoroutinePromiseCatchCallback",
3569 "testCoroutineChainedAwait"
3571 const int num_tests = 8;
3577 for (
int i = 0; i < num_tests; i++) {
3582 bool success =
false;
3583 if (test_result.
contains(
"success")) {
3584 auto val = test_result[
"success"].
as_bool();
3585 success = val.has_value() && val.value();
3589 FL_PRINT(
"[COROUTINE] PASS: " << test_names[i]);
3592 FL_PRINT(
"[COROUTINE] FAIL: " << test_names[i]);
3594 results.
set(test_names[i], test_result);
3598 err.
set(
"success",
false);
3599 err.
set(
"error",
"Method not found");
3600 results.
set(test_names[i], err);
3601 FL_PRINT(
"[COROUTINE] FAIL: " << test_names[i] <<
" (not found)");
3605 r.
set(
"success", failed == 0);
3606 r.
set(
"passed",
static_cast<int64_t
>(passed));
3607 r.
set(
"failed",
static_cast<int64_t
>(failed));
3608 r.
set(
"total",
static_cast<int64_t
>(num_tests));
3609 r.
set(
"results", results);