Register all RPC functions with shared autoresearch state.
1301 {
1302
1304
1305
1306
1307
1308
1309
1310
1311 mRemote->bind(
"status", [
this](
const fl::json&
args) -> fl::json {
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));
1316 return status;
1317 });
1318
1319
1320
1321
1322
1323
1324 mRemote->bind(
"debugTest", [](
const fl::json&
args) -> fl::json {
1329 });
1330
1331
1332
1333
1334
1335
1336
1337 mRemote->bind(
"deliberateHang", [
this](
const fl::json&
args) -> fl::json {
1339 FL_WARN(
"[deliberateHang] watchdog test: spinning forever in 200 ms");
1340 mState->deliberate_hang_requested =
true;
1343 response.set(
"message",
"device will hang after RPC returns; watchdog should reset within configured timeout");
1345 });
1346
1347
1348 mRemote->bind(
"drivers", [
this](
const fl::json&
args) -> fl::json {
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);
1356 }
1357 return drivers;
1358 });
1359
1360
1361
1362
1363
1364
1365
1366 mRemote->bind(
"runSingleTest", [
this](
const fl::json&
args) -> fl::json {
1368
1369
1371 mRemote->sendAsyncResponse(
"runSingleTest", result);
1372 }
1373 return fl::json(nullptr);
1375
1376
1377
1378
1379
1380
1381 mRemote->bind(
"runParallelTest", [
this](
const fl::json&
args) -> fl::json {
1384 mRemote->sendAsyncResponse(
"runParallelTest", result);
1385 }
1386 return fl::json(nullptr);
1388
1389
1390
1391
1392
1393
1394 mRemote->bind(
"ping", [
this](
const fl::json&
args) -> fl::json {
1396
1400 response.set(
"timestamp",
static_cast<int64_t
>(now));
1401 response.set(
"uptimeMs",
static_cast<int64_t
>(now));
1403 });
1404
1405
1406 mRemote->bind(
"testNoSerial", [
this](
const fl::json&
args) -> fl::json {
1409 response.set(
"message",
"RPC works from task context");
1410 response.set(
"serial_safe",
false);
1412 });
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433 mRemote->bind(
"flexioRxBenchmark", [
this](
const fl::json&
args) -> fl::json {
1435#if !defined(FL_IS_TEENSY_4X)
1438 response.set(
"error",
"PlatformNotSupported");
1440 "flexioRxBenchmark is Teensy 4.x-only (FLEXIO1 capture).");
1442#else
1443
1444 int frequency_hz = 1000;
1445 int duration_ms = 100;
1446 int tx_pin = 3;
1447 int rx_pin = 4;
1448 if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
1449 fl::json cfg =
args[0];
1450 if (cfg.
contains(
"frequency_hz") && cfg[
"frequency_hz"].is_int()) {
1451 frequency_hz =
static_cast<int>(cfg[
"frequency_hz"].
as_int().value());
1452 }
1453 if (cfg.
contains(
"duration_ms") && cfg[
"duration_ms"].is_int()) {
1454 duration_ms =
static_cast<int>(cfg[
"duration_ms"].
as_int().value());
1455 }
1456 if (cfg.
contains(
"tx_pin") && cfg[
"tx_pin"].is_int()) {
1457 tx_pin =
static_cast<int>(cfg[
"tx_pin"].
as_int().value());
1458 }
1459 if (cfg.
contains(
"rx_pin") && cfg[
"rx_pin"].is_int()) {
1460 rx_pin =
static_cast<int>(cfg[
"rx_pin"].
as_int().value());
1461 }
1462 }
1463 if (frequency_hz < 1 || frequency_hz > 5000000 ||
1464 duration_ms < 1 || duration_ms > 5000) {
1466 response.set(
"error",
"InvalidArgs");
1468 "frequency_hz in [1, 5_000_000]; duration_ms in [1, 5000].");
1470 }
1471
1472
1473 analogWriteFrequency(tx_pin, (float)frequency_hz);
1475
1476
1477
1478
1479
1480
1481
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;
1488 rx_cfg.edge_capacity = (
size_t)target;
1489 rx_cfg.start_low = false;
1491 if (!rx_channel) {
1494 response.set(
"error",
"RxCreateFailed");
1496 "Failed to create FlexIO RX channel on pin (no FLEXIO1 mapping).");
1498 }
1499 if (!rx_channel->begin(rx_cfg)) {
1502 response.set(
"error",
"RxBeginFailed");
1504 "RxChannel::begin() returned false (see device WARN log).");
1506 }
1507
1508
1509 rx_channel->wait((u32)duration_ms);
1510
1511
1513
1514
1515 fl::vector<fl::EdgeTime> edges;
1516 edges.
assign(rx_cfg.edge_capacity, fl::EdgeTime());
1517 size_t edges_captured =
1518 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(edges), 0);
1519 edges.
resize(edges_captured);
1520
1521
1522
1525 fl::u32 min_ns = 0xFFFFFFFFu;
1526 fl::u32 max_ns = 0;
1527 size_t periods = 0;
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;
1536 ++periods;
1537 }
1538 fl::u32 mean_ns = 0;
1539 fl::u32 sigma_ns = 0;
1540 if (periods > 0) {
1541 mean_ns = (fl::u32)(sum_ns / (
fl::u64)periods);
1544 periods > 0 ? (sum_sq_ns / (
fl::u64)periods) - (mean64 * mean64)
1545 : 0;
1546
1547
1548 fl::u32 s = 0;
1550 sigma_ns = s;
1551 }
1552 if (min_ns == 0xFFFFFFFFu) min_ns = 0;
1553
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));
1566#endif
1567 });
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596 mRemote->bind(
"flexioObjectFledTest", [
this](
const fl::json&
args) -> fl::json {
1598#if !defined(FL_IS_TEENSY_4X)
1601 response.set(
"error",
"PlatformNotSupported");
1603 "flexioObjectFledTest is Teensy 4.x-only (FLEXIO1 RX + "
1604 "Teensy-core ObjectFLED driver).");
1606#else
1607
1608 int test_case = 0;
1609 int tx_pin = 3;
1610 int rx_pin = 4;
1611 int capture_ms = 50;
1612 if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
1613 fl::json cfg =
args[0];
1614 if (cfg.
contains(
"test_case") && cfg[
"test_case"].is_int()) {
1615 test_case =
static_cast<int>(cfg[
"test_case"].
as_int().value());
1616 }
1617 if (cfg.
contains(
"tx_pin") && cfg[
"tx_pin"].is_int()) {
1618 tx_pin =
static_cast<int>(cfg[
"tx_pin"].
as_int().value());
1619 }
1620 if (cfg.
contains(
"rx_pin") && cfg[
"rx_pin"].is_int()) {
1621 rx_pin =
static_cast<int>(cfg[
"rx_pin"].
as_int().value());
1622 }
1623 if (cfg.
contains(
"capture_ms") && cfg[
"capture_ms"].is_int()) {
1624 capture_ms =
static_cast<int>(cfg[
"capture_ms"].
as_int().value());
1625 }
1626 }
1627 if (test_case < 0 || test_case > 4 ||
1628 capture_ms < 1 || capture_ms > 5000) {
1630 response.set(
"error",
"InvalidArgs");
1632 "test_case in [0,4]; capture_ms in [1, 5000].");
1634 }
1635
1636
1637
1638 static CRGB leds_buf[100];
1639 int num_leds = 0;
1640 switch (test_case) {
1641 case 0:
1642 num_leds = 1;
1644 break;
1645 case 1:
1646 num_leds = 3;
1650 break;
1651 case 2:
1652 num_leds = 1;
1654 break;
1655 case 3:
1656 num_leds = 1;
1657 leds_buf[0] =
CRGB(0xFF, 0xFF, 0xFF);
1658 break;
1659 case 4:
1660 num_leds = 100;
1661 for (int i = 0; i < 100; ++i) {
1663 : (i % 3 == 1) ?
CRGB::Green
1665 }
1666 break;
1667 }
1668
1669
1670 fl::vector<u8> expected;
1671 expected.
reserve((
size_t)num_leds * 3);
1672 for (int i = 0; i < num_leds; ++i) {
1676 }
1677
1678
1679
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;
1683
1685 rx_cfg.edge_capacity = edge_capacity;
1686 rx_cfg.start_low = true;
1688 if (!rx_channel || !rx_channel->begin(rx_cfg)) {
1690 response.set(
"error",
"RxBeginFailed");
1692 "Failed to bring up FlexIO RX on the requested pin "
1693 "(check kFlexIo1Pins[] mapping).");
1695 }
1696
1697
1698 fl::ChannelOptions opts;
1702 fl::NamedTimingConfig timing_cfg(resolved_timing, "WS2812B-V5",
1703 resolved_encoder);
1704 fl::ChannelConfig tx_cfg(
1705 tx_pin,
1706 timing_cfg.timing,
1707 fl::span<CRGB>(leds_buf, (size_t)num_leds),
1709 opts);
1711 if (!tx_channel) {
1713 response.set(
"error",
"TxAddFailed");
1715 "FastLED.add() rejected the ObjectFLED ChannelConfig.");
1717 }
1718
1719
1720
1721
1722
1723
1724
1725
1726
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)
1732 ? (u32)capture_ms
1733 : min_capture_ms;
1734 rx_channel->wait(effective_capture_ms);
1735
1736
1737 const fl::ChipsetTiming ws2812_timing =
1739 fl::ChipsetTiming4Phase rx_timing =
1741 fl::vector<u8> decoded;
1743 auto decode_result =
1744 rx_channel->decode(rx_timing, fl::span<u8>(decoded));
1745
1746
1747 u32 decoded_bytes = 0u;
1748 if (decode_result) {
1749 decoded_bytes = decode_result.value();
1750 }
1751 const size_t cmp_n = (decoded_bytes < expected.
size())
1753 : expected.size();
1754 size_t matched = 0;
1755 size_t mismatched = 0;
1756 for (size_t i = 0; i < cmp_n; ++i) {
1757 if (decoded[i] == expected[i]) ++matched; else ++mismatched;
1758 }
1759 if (decoded_bytes < expected.
size()) {
1760 mismatched += expected.size() - decoded_bytes;
1761 }
1762
1763
1764 fl::vector<fl::EdgeTime> edges;
1765 edges.
assign(edge_capacity, fl::EdgeTime());
1766 const size_t edges_captured =
1767 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(edges), 0);
1768
1769
1770
1771
1773
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));
1784 return response;
1785#endif
1786 });
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812 mRemote->bind(
"flexioRxLoopbackPing", [
this](
const fl::json&
args) -> fl::json {
1814#if !defined(FL_IS_TEENSY_4X)
1817 response.set(
"error",
"PlatformNotSupported");
1819 "flexioRxLoopbackPing is Teensy 4.x-only (FLEXIO1 RX).");
1821#else
1822 int tx_pin = 3;
1823 int rx_pin = 4;
1824 int toggle_us = 50;
1825 int edges = 8;
1826 if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
1827 const fl::json &cfg =
args[0];
1828 if (cfg.
contains(
"tx_pin") && cfg[
"tx_pin"].is_int()) {
1829 tx_pin =
static_cast<int>(cfg[
"tx_pin"].
as_int().value());
1830 }
1831 if (cfg.
contains(
"rx_pin") && cfg[
"rx_pin"].is_int()) {
1832 rx_pin =
static_cast<int>(cfg[
"rx_pin"].
as_int().value());
1833 }
1834 if (cfg.
contains(
"toggle_us") && cfg[
"toggle_us"].is_int()) {
1835 toggle_us =
static_cast<int>(cfg[
"toggle_us"].
as_int().value());
1836 }
1837 if (cfg.
contains(
"edges") && cfg[
"edges"].is_int()) {
1838 edges =
static_cast<int>(cfg[
"edges"].
as_int().value());
1839 }
1840 }
1841 if (edges < 2 || edges > 64) {
1843 response.set(
"error",
"InvalidEdges");
1844 response.set(
"message",
"edges must be in [2, 64]");
1846 }
1847
1848
1850 rx_cfg.edge_capacity = 1024;
1851 rx_cfg.start_low = true;
1853 if (!rx_channel || !rx_channel->begin(rx_cfg)) {
1855 response.set(
"error",
"RxBeginFailed");
1857 "FlexIO RX begin() failed (kFlexIo1Pins[] mapping?).");
1859 }
1860
1861
1862
1863
1867
1868 bool level = true;
1869 for (int i = 0; i < edges; ++i) {
1872 level = !level;
1873 }
1874
1875
1876
1877
1878
1879 {
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);
1894 }
1895
1896
1897
1898
1899
1900 rx_channel->wait(50);
1901
1902
1903 fl::vector<fl::EdgeTime> captured_edges;
1904 captured_edges.
assign(64, fl::EdgeTime());
1905 size_t edge_count =
1906 rx_channel->getRawEdgeTimes(fl::span<fl::EdgeTime>(captured_edges), 0);
1907
1908
1910
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));
1917
1918
1919
1920
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));
1928 }
1929 response.set(
"first_edges", edges_arr);
1931#endif
1932 });
1933
1934
1935 mRemote->bind(
"setDebug", [
this](
const fl::json&
args) -> fl::json {
1937
1938
1939 if (!
args.is_array() ||
args.size() != 1) {
1941 response.set(
"error",
"InvalidArgs");
1942 response.set(
"message",
"Expected [enabled: bool]");
1944 }
1945
1946 if (!
args[0].is_bool()) {
1948 response.set(
"error",
"InvalidType");
1949 response.set(
"message",
"Argument must be boolean");
1951 }
1952
1953 bool enabled =
args[0].as_bool().value();
1954 mState->debug_enabled = enabled;
1955
1957 response.set(
"debug_enabled", enabled);
1958 response.set(
"message", enabled ?
"Debug logging enabled" :
"Debug logging disabled");
1959
1961 });
1962
1963
1964
1965 mRemote->bind(
"testGpioConnection", [](
const fl::json&
args) -> fl::json {
1967
1968
1969 if (!
args.is_array() ||
args.size() != 2) {
1970 response.set(
"error",
"InvalidArgs");
1971 response.set(
"message",
"Expected [txPin, rxPin]");
1973 }
1974
1975 if (!
args[0].is_int() || !
args[1].is_int()) {
1976 response.set(
"error",
"InvalidPinType");
1977 response.set(
"message",
"Pin numbers must be integers");
1979 }
1980
1981 int tx_pin =
static_cast<int>(
args[0].as_int().value());
1982 int rx_pin =
static_cast<int>(
args[1].as_int().value());
1983
1984
1986 pinMode(rx_pin, INPUT_PULLUP);
1990
1991
1995
1996
1999
2000
2001 bool connected = (rx_when_tx_low == LOW) && (rx_when_tx_high == HIGH);
2002
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);
2008
2009 if (connected) {
2011 response.set(
"message",
"GPIO pins are connected");
2012 } else {
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");
2018 } else {
2019 response.set(
"message",
"Unexpected GPIO behavior - check wiring");
2020 }
2021 }
2022
2024 });
2025
2026
2027
2028
2029
2030
2031 mRemote->bind(
"getPins", [
this](
const fl::json&
args) -> fl::json {
2036
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);
2041
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");
2050 #else
2051 response.set(
"platform",
"unknown");
2052 #endif
2053
2055 });
2056
2057
2058 mRemote->bind(
"setTxPin", [
this](
const fl::json&
args) -> fl::json {
2060
2061 if (!
args.is_array() ||
args.size() != 1 || !
args[0].is_int()) {
2062 response.set(
"error",
"InvalidArgs");
2063 response.set(
"message",
"Expected [pin: int]");
2065 }
2066
2067 int new_pin =
static_cast<int>(
args[0].as_int().value());
2068
2069
2070 if (new_pin < 0 || new_pin > 48) {
2071 response.set(
"error",
"InvalidPin");
2072 response.set(
"message",
"Pin must be 0-48");
2074 }
2075
2076 int old_pin =
mState->pin_tx;
2077 mState->pin_tx = new_pin;
2078
2079 FL_PRINT(
"[RPC] setTxPin(" << new_pin <<
") - TX pin changed from " << old_pin <<
" to " << new_pin);
2080
2082 response.set(
"txPin",
static_cast<int64_t
>(new_pin));
2083 response.set(
"previousTxPin",
static_cast<int64_t
>(old_pin));
2085 });
2086
2087
2088 mRemote->bind(
"setRxPin", [
this](
const fl::json&
args) -> fl::json {
2090
2091 if (!
args.is_array() ||
args.size() != 1 || !
args[0].is_int()) {
2092 response.set(
"error",
"InvalidArgs");
2093 response.set(
"message",
"Expected [pin: int]");
2095 }
2096
2097 int new_pin =
static_cast<int>(
args[0].as_int().value());
2098
2099
2100 if (new_pin < 0 || new_pin > 48) {
2101 response.set(
"error",
"InvalidPin");
2102 response.set(
"message",
"Pin must be 0-48");
2104 }
2105
2106 int old_pin =
mState->pin_rx;
2107 bool pin_changed = (new_pin != old_pin);
2108 bool rx_recreated = false;
2109
2110 if (pin_changed) {
2111 mState->pin_rx = new_pin;
2112
2113
2114 FL_PRINT(
"[RPC] setRxPin(" << new_pin <<
") - Recreating RX channel...");
2115
2116
2117 mState->rx_channel.reset();
2118
2119
2121
2122 if (
mState->rx_channel) {
2123 FL_PRINT(
"[RPC] setRxPin - RX channel recreated on GPIO " << new_pin);
2124 rx_recreated = true;
2125 } else {
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");
2129
2130 mState->pin_rx = old_pin;
2132 }
2133 }
2134
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);
2140 });
2141
2142
2143 mRemote->bind(
"setPins", [
this](
const fl::json&
args) -> fl::json {
2145
2146
2147 int new_tx_pin = -1;
2148 int new_rx_pin = -1;
2149
2150 if (
args.is_array() &&
args.size() == 1 &&
args[0].is_object()) {
2151
2152 fl::json config =
args[0];
2153 if (config.
contains(
"txPin") && config[
"txPin"].is_int()) {
2154 new_tx_pin =
static_cast<int>(config[
"txPin"].
as_int().value());
2155 }
2156 if (config.
contains(
"rxPin") && config[
"rxPin"].is_int()) {
2157 new_rx_pin =
static_cast<int>(config[
"rxPin"].
as_int().value());
2158 }
2159 }
else if (
args.is_array() &&
args.size() == 2) {
2160
2161 if (
args[0].is_int()) {
2162 new_tx_pin =
static_cast<int>(
args[0].as_int().value());
2163 }
2164 if (
args[1].is_int()) {
2165 new_rx_pin =
static_cast<int>(
args[1].as_int().value());
2166 }
2167 } else {
2168 response.set(
"error",
"InvalidArgs");
2169 response.set(
"message",
"Expected [{txPin, rxPin}] or [txPin, rxPin]");
2171 }
2172
2173
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");
2178 }
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");
2183 }
2184
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;
2189
2190
2191 mState->pin_tx = new_tx_pin;
2192
2193
2194 if (rx_pin_changed) {
2195 mState->pin_rx = new_rx_pin;
2196
2197 FL_PRINT(
"[RPC] setPins - Recreating RX channel on GPIO " << new_rx_pin <<
"...");
2198
2199
2200 mState->rx_channel.reset();
2201
2202
2204
2205 if (
mState->rx_channel) {
2206 FL_PRINT(
"[RPC] setPins - RX channel recreated successfully");
2207 rx_recreated = true;
2208 } else {
2209 FL_ERROR(
"[RPC] setPins - Failed to create RX channel on GPIO " << new_rx_pin);
2210
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");
2216 }
2217 } else {
2218 mState->pin_rx = new_rx_pin;
2219 }
2220
2221 FL_PRINT(
"[RPC] setPins - TX: " << old_tx_pin <<
" → " << new_tx_pin
2222 << ", RX: " << old_rx_pin << " → " << new_rx_pin);
2223
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);
2231 });
2232
2233
2234
2235 mRemote->bind(
"findConnectedPins", [
this](
const fl::json&
args) -> fl::json {
2237 });
2238
2239
2240 mRemote->bind(
"help", [
this](
const fl::json&
args) -> fl::json {
2242
2243
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");
2251
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");
2259
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");
2267
2268
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");
2276
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");
2284
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)");
2292
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");
2300
2301
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");
2309
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");
2317
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");
2325
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");
2333
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");
2341
2342
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");
2350
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");
2358
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");
2366
2367
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");
2375
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)");
2383
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)");
2391
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");
2399
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);
2407
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");
2415
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)");
2423
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);
2431
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);
2439
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);
2447
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);
2455
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);
2463
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);
2471
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);
2479
2482 response.set(
"totalFunctions",
static_cast<int64_t
>(28));
2483 response.set(
"functions", functions);
2485 });
2486
2487
2488 mRemote->bind(
"testSimd", [](
const fl::json&
args) -> fl::json {
2490
2491
2492 using autoresearch::simd_check::SimdTestEntry;
2493 const SimdTestEntry* tests = nullptr;
2494 int num_tests = 0;
2496
2497 int passed_count = 0;
2498 int failed_count = 0;
2500
2501 for (int i = 0; i < num_tests; i++) {
2502 bool ok = tests[i].func();
2503 if (ok) {
2504 passed_count++;
2505 } else {
2506 failed_count++;
2507 failures.
push_back(fl::string(tests[i].name));
2508 }
2509 }
2510
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);
2518 }
2520 });
2521
2522
2523 mRemote->bind(
"testSimdBenchmark", [](
const fl::json&
args) -> fl::json {
2525
2526 int iters = 10000;
2527 fl::json config;
2528 if (
args.is_object()) {
2530 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2532 }
2533 if (!config.
is_null() && config.
contains(
"iterations") && config[
"iterations"].is_int()) {
2534 iters =
static_cast<int>(config[
"iterations"].
as_int().value());
2535 if (iters < 1) iters = 1;
2536 if (iters > 1000000) iters = 1000000;
2537 }
2538
2540
2543
2545 add.set(
"float_us",
result.add_float_us);
2547 add.set(
"s16x16_us",
result.add_s16x16_us);
2548 add.set(
"u16x16_us",
result.add_u16x16_us);
2551
2553 sub.
set(
"float_us",
result.sub_float_us);
2555 sub.
set(
"s16x16_us",
result.sub_s16x16_us);
2556 sub.
set(
"u16x16_us",
result.sub_u16x16_us);
2559
2561 mul.
set(
"float_us",
result.mul_float_us);
2563 mul.
set(
"s16x16_us",
result.mul_s16x16_us);
2564 mul.
set(
"u16x16_us",
result.mul_u16x16_us);
2567
2569 div.
set(
"float_us",
result.div_float_us);
2571 div.
set(
"s16x16_us",
result.div_s16x16_us);
2572 div.
set(
"u16x16_us",
result.div_u16x16_us);
2574
2576 });
2577
2578
2579
2580
2581
2582
2583 mRemote->bind(
"animartrixPerlinBench", [](
const fl::json&
args) -> fl::json {
2585
2586 int iters = 100;
2587 fl::json config;
2588 if (
args.is_object()) {
2590 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2592 }
2593 if (!config.
is_null() && config.
contains(
"iterations") && config[
"iterations"].is_int()) {
2594 iters =
static_cast<int>(config[
"iterations"].
as_int().value());
2595 if (iters < 1) iters = 1;
2596 if (iters > 10000) iters = 10000;
2597 }
2598
2600
2603
2604
2605 response.set(
"pnoise_calls_per_iter",
static_cast<int64_t
>(256));
2608
2609
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);
2613
2614 response.set(
"speedup_x1000",
static_cast<int64_t
>(speedup * 1000.0));
2615 }
2617 });
2618
2619
2620
2621
2622
2623 mRemote->bind(
"wave8ExpandBenchmark", [](
const fl::json&
args) -> fl::json {
2625
2626 int iters = 30000;
2627 fl::json config;
2628 if (
args.is_object()) {
2630 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2632 }
2633 if (!config.
is_null() && config.
contains(
"iterations") && config[
"iterations"].is_int()) {
2634 iters =
static_cast<int>(config[
"iterations"].
as_int().value());
2635 }
2636
2638
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));
2648 });
2649
2650
2651
2652
2653
2654
2655 mRemote->bind(
"parlioStreamValidate", [
this](
const fl::json&
args) -> fl::json {
2657 auto invalidArgs = [](const char* message) -> fl::json {
2659 error.
set(
"success",
false);
2660 error.
set(
"error",
"InvalidArgs");
2661 error.
set(
"message", message);
2662 return error;
2663 };
2664
2665 int num_lanes = 16;
2666 int num_leds = 256;
2667 int iterations = 5;
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;
2674 tx_pins[i] = -1;
2675 }
2676
2677 fl::json config;
2678 if (
args.is_object()) {
2680 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2682 }
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");
2689 }
2690 num_lanes =
static_cast<int>(config[
"numLanes"].
as_int().value());
2691 num_lanes_provided = true;
2692 }
2693 if (config.
contains(
"numLeds") && config[
"numLeds"].is_int())
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");
2702 }
2703 if (!num_lanes_provided) {
2704 return invalidArgs("txPins requires numLanes");
2705 }
2706 if (num_lanes < 1 ||
2708 return invalidArgs("numLanes out of range for txPins");
2709 }
2710
2711 const fl::json
pins = config[
"txPins"];
2712 if (
pins.size() !=
static_cast<size_t>(num_lanes)) {
2713 return invalidArgs("txPins length must match numLanes");
2714 }
2715
2716 for (int i = 0; i < num_lanes; ++i) {
2717 if (!
pins[i].is_int()) {
2718 return invalidArgs("txPins entries must be integers");
2719 }
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");
2723 }
2724 tx_pins[i] = static_cast<int>(pin_value);
2725 }
2726 has_tx_pins = true;
2727 base_tx_pin = tx_pins[0];
2728 }
2729 }
2730
2731
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;
2740 }
2741 if (timeout_ms < 1) timeout_ms = 1;
2742 if (timeout_ms > 5000) timeout_ms = 5000;
2743
2745 base_tx_pin, num_lanes, num_leds, iterations,
2747 has_tx_pins ? tx_pins : nullptr);
2748
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];
2758 break;
2759 }
2760 }
2761 }
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]));
2783 }
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]));
2792 }
2793 response.set(
"perIterUs", per_iter);
2794 response.set(
"perIterShowUs", per_iter_show);
2795 response.set(
"perIterWaitUs", per_iter_wait);
2797 });
2798
2799
2800
2801
2802
2803 mRemote->bind(
"parlioEncodeBenchmark", [](
const fl::json&
args) -> fl::json {
2805
2806 int iters = 12000;
2807 fl::json config;
2808 if (
args.is_object()) {
2810 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2812 }
2813 if (!config.
is_null() && config.
contains(
"iterations") && config[
"iterations"].is_int()) {
2814 iters =
static_cast<int>(config[
"iterations"].
as_int().value());
2815 }
2816
2818
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));
2829 if (r.iters > 0) {
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));
2839 }
2840 response.set(
"sink",
static_cast<int64_t
>(r.sink));
2842 });
2843
2844
2845
2846 mRemote->bind(
"testAsync", [
this](
const fl::json&
args) -> fl::json {
2848
2849
2850 int num_leds = 300;
2851 fl::string requested_driver;
2852 fl::json config;
2853 if (
args.is_object()) {
2855 }
else if (
args.is_array() &&
args.size() >= 1 &&
args[0].is_object()) {
2857 }
2859 if (config.
contains(
"numLeds") && config[
"numLeds"].is_int()) {
2860 num_leds =
static_cast<int>(config[
"numLeds"].
as_int().value());
2861 }
2862 if (config.
contains(
"driver") && config[
"driver"].is_string()) {
2863 requested_driver = config[
"driver"].
as_string().value();
2864 }
2865 }
2866
2867
2868 if (!requested_driver.
empty()) {
2871 response.set(
"error",
"DriverSetupFailed");
2872 fl::sstream msg;
2873 msg <<
"Failed to set '" << requested_driver.
c_str() <<
"' as exclusive driver";
2876 }
2877 }
2878
2879 if (num_leds < 10 || num_leds > 1000) {
2881 response.set(
"error",
"InvalidNumLeds");
2882 response.set(
"message",
"numLeds must be 10-1000");
2884 }
2885
2886
2887 fl::vector<CRGB>
leds(num_leds);
2888 for (int i = 0; i < num_leds; i++) {
2890 }
2891
2892 fl::ChannelConfig channel_config(
2897 );
2898
2900 if (!channel) {
2902 response.set(
"error",
"ChannelCreationFailed");
2903 response.set(
"message",
"Failed to create channel");
2905 }
2906
2907
2913
2917
2918
2924
2928
2929
2931
2932
2933
2934
2935
2936 bool passed = (total2_us > 0) && (show2_us < total2_us / 2);
2937
2938
2939 fl::string driver_name = "unknown";
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;
2943 break;
2944 }
2945 }
2946
2949
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));
2953
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));
2959
2960 fl::sstream msg;
2961 if (passed) {
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)";
2966 } else {
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)";
2971 }
2973
2975 });
2976
2977
2978
2979
2980
2981
2982 mRemote->bind(
"startNetServer", [
this](
const fl::json&
args) -> fl::json {
2983 mState->net_server_active =
true;
2985 });
2986
2987
2988 mRemote->bind(
"startNetClient", [
this](
const fl::json&
args) -> fl::json {
2989 mState->net_client_active =
true;
2991 });
2992
2993
2994
2995 mRemote->bind(
"runNetClientTest", [](
const fl::json&
args) -> fl::json {
2997
2998
2999 if (!
args.is_object()) {
3001 response.set(
"error",
"Expected object with host_ip and port");
3003 }
3004
3005 fl::json host_ip_val =
args[fl::string(
"host_ip")];
3006 fl::json port_val =
args[fl::string(
"port")];
3007
3010 response.set(
"error",
"Expected {host_ip: string, port: int}");
3012 }
3013
3014 fl::string host_ip = host_ip_val.
as_string().value();
3016
3018 });
3019
3020
3021
3022 mRemote->bind(
"runNetLoopback", [](
const fl::json&
args) -> fl::json {
3024 });
3025
3026
3027 mRemote->bind(
"stopNet", [
this](
const fl::json&
args) -> fl::json {
3028 mState->net_server_active =
false;
3029 mState->net_client_active =
false;
3031 });
3032
3033
3034
3035
3036
3037
3038 mRemote->bind(
"startOta", [](
const fl::json&
args) -> fl::json {
3040 });
3041
3042
3043 mRemote->bind(
"stopOta", [](
const fl::json&
args) -> fl::json {
3045 });
3046
3047
3048
3049
3050
3051
3052 mRemote->bind(
"startBle", [
this](
const fl::json&
args) -> fl::json {
3054 });
3055
3056
3057 mRemote->bind(
"stopBle", [
this](
const fl::json&
args) -> fl::json {
3059 });
3060
3061
3062
3063 mRemote->bind(
"decodeFile", [](fl::vector<fl::u8> data, fl::string ext) -> fl::json {
3065
3066 if (ext != ".mp4") {
3068 response.set(
"error",
"Only .mp4 supported for device decode");
3070 }
3071
3072
3073 fl::string error;
3079 }
3082
3085 response.set(
"error",
"H264 decoder not supported on this platform");
3087 }
3088
3089
3090 fl::string dec_error;
3092 if (!decoder) {
3096 }
3097
3099 stream->write(data);
3100
3101 if (!decoder->begin(stream)) {
3102 fl::string msg;
3103 decoder->hasError(&msg);
3107 }
3108
3109 auto result = decoder->decode();
3112 response.set(
"error",
"Decode returned non-success");
3113 response.set(
"decode_result",
static_cast<int64_t
>(
static_cast<int>(result)));
3114 decoder->end();
3116 }
3117
3118 fl::Frame frame = decoder->getCurrentFrame();
3119 decoder->end();
3120
3123 response.set(
"error",
"Decoded frame is invalid");
3125 }
3126
3130
3131
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));
3141 }
3142 response.set(
"pixels", pixel_array);
3143
3145 });
3146
3147
3148 mRemote->bind(
"bleStatus", [
this](
const fl::json&
args) -> fl::json {
3159 });
3160
3161
3162
3163
3164
3165
3166 mRemote->bind(
"testCoroutineBasic", [](
const fl::json&
args) -> fl::json {
3169
3172
3173 fl::task::CoroutineConfig cfg;
3174 cfg.
func = [&task_ran, &task_completed]() {
3175 task_ran.store(true);
3177 task_completed.store(true);
3178 };
3179 cfg.
name =
"test_basic";
3181
3183 while (
t.isRunning() && (
millis() - start) < 2000) {
3185 }
3186
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));
3193 return r;
3194 });
3195
3196
3197 mRemote->bind(
"testCoroutineStop", [](
const fl::json&
args) -> fl::json {
3200
3202
3203 fl::task::CoroutineConfig cfg;
3204 cfg.
func = [&task_started]() {
3205 task_started.store(true);
3206 while (true) {
3208 }
3209 };
3210 cfg.
name =
"test_stop";
3212
3214 while (!task_started.load() && (
millis() - start) < 2000) {
3216 }
3217
3218 bool was_running =
t.isRunning();
3220 bool stopped = !
t.isRunning();
3221
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));
3228 return r;
3229 });
3230
3231
3232 mRemote->bind(
"testCoroutineConcurrent", [](
const fl::json&
args) -> fl::json {
3235
3236 const int NUM_TASKS = 3;
3239 for (int i = 0; i < NUM_TASKS; i++) {
3240 task_flags[i].
store(
false);
3241 }
3242
3243 fl::task::Handle tasks[NUM_TASKS];
3244 for (int i = 0; i < NUM_TASKS; i++) {
3245 fl::task::CoroutineConfig cfg;
3246 cfg.
func = [i, &task_flags, &completed_count]() {
3248 task_flags[i].store(true);
3249 completed_count.fetch_add(1);
3250 };
3251 cfg.
name =
"test_concurrent";
3253 }
3254
3256 while (completed_count.load() < NUM_TASKS && (
millis() - start) < 3000) {
3258 }
3259
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;
3265 }
3266
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));
3273 return r;
3274 });
3275
3276
3277
3278
3279 mRemote->bind(
"testCoroutineAwait", [](
const fl::json&
args) -> fl::json {
3282
3290
3291
3292
3293 fl::task::CoroutineConfig consumer_cfg;
3294 consumer_cfg.
func = [promise_ptr, &consumer_started, &consumer_finished,
3295 &consumer_value, &consumer_ok]() {
3296 consumer_started.store(true);
3297
3300 consumer_ok.store(true);
3301 consumer_value.store(
result.value());
3302 }
3303 consumer_finished.store(true);
3304 };
3305 consumer_cfg.
name =
"await_consumer";
3307
3308
3310
3311
3312 fl::task::CoroutineConfig producer_cfg;
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);
3318 };
3319 producer_cfg.
name =
"await_producer";
3321
3322
3324 while ((!consumer_finished.load() || producer.isRunning()) && (
millis() - start) < 5000) {
3326 }
3327
3328
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));
3340 return r;
3341 });
3342
3343
3344
3345 mRemote->bind(
"testCoroutineAwaitError", [](
const fl::json&
args) -> fl::json {
3348
3353
3354
3355 fl::task::CoroutineConfig consumer_cfg;
3356 consumer_cfg.
func = [promise_ptr, &consumer_finished, &got_error]() {
3359 got_error.store(true);
3360 }
3361 consumer_finished.store(true);
3362 };
3363 consumer_cfg.
name =
"await_err_consumer";
3365
3367
3368
3369 fl::task::CoroutineConfig producer_cfg;
3370 producer_cfg.
func = [promise_ptr, &producer_finished]() {
3372 promise_ptr->complete_with_error(fl::task::Error("test error"));
3373 producer_finished.store(true);
3374 };
3375 producer_cfg.
name =
"await_err_producer";
3377
3379 while ((!consumer_finished.load() || producer.isRunning()) && (
millis() - start) < 5000) {
3381 }
3382
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));
3389 return r;
3390 });
3391
3392
3393
3394
3395 mRemote->bind(
"testCoroutinePromiseCallbacks", [](
const fl::json&
args) -> fl::json {
3398
3404
3405
3406 promise_ptr->then([&then_called, &then_value](const int& val) {
3407 then_called.store(true);
3408 then_value.store(val);
3409 });
3410 promise_ptr->catch_([&catch_called](const fl::task::Error&) {
3411 catch_called.store(true);
3412 });
3413
3414
3415 fl::task::CoroutineConfig producer_cfg;
3416 producer_cfg.
func = [promise_ptr, &producer_finished]() {
3418 promise_ptr->complete_with_value(99);
3419 producer_finished.store(true);
3420 };
3421 producer_cfg.
name =
"cb_producer";
3423
3425 while (producer.isRunning() && (
millis() - start) < 5000) {
3427 promise_ptr->update();
3428 }
3429 promise_ptr->update();
3430
3431
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));
3440 return r;
3441 });
3442
3443
3444 mRemote->bind(
"testCoroutinePromiseCatchCallback", [](
const fl::json&
args) -> fl::json {
3447
3452
3453 promise_ptr->then([&then_called](const int&) {
3454 then_called.store(true);
3455 });
3456 promise_ptr->catch_([&catch_called](const fl::task::Error&) {
3457 catch_called.store(true);
3458 });
3459
3460
3461 fl::task::CoroutineConfig producer_cfg;
3462 producer_cfg.
func = [promise_ptr, &producer_finished]() {
3464 promise_ptr->complete_with_error(fl::task::Error("rejection test"));
3465 producer_finished.store(true);
3466 };
3467 producer_cfg.
name =
"catch_producer";
3469
3471 while (producer.isRunning() && (
millis() - start) < 5000) {
3473 promise_ptr->update();
3474 }
3475 promise_ptr->update();
3476
3477
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));
3484 return r;
3485 });
3486
3487
3488
3489 mRemote->bind(
"testCoroutineChainedAwait", [](
const fl::json&
args) -> fl::json {
3492
3500
3501
3502 fl::task::CoroutineConfig cfg_c;
3503 cfg_c.
func = [p2, &final_value, &chain_complete, &c_done]() {
3506 final_value.store(
result.value());
3507 }
3508 c_done.store(true);
3509 chain_complete.store(true);
3510 };
3511 cfg_c.
name =
"chain_c";
3513
3514
3515 fl::task::CoroutineConfig cfg_b;
3516 cfg_b.
func = [p1, p2, &b_done]() {
3519
3520 p2->complete_with_value(
result.value() * 10);
3521 } else {
3522 p2->complete_with_error(
result.error());
3523 }
3524 b_done.store(true);
3525 };
3526 cfg_b.
name =
"chain_b";
3528
3529
3530 fl::task::CoroutineConfig cfg_a;
3531 cfg_a.
func = [p1, &a_done]() {
3533 p1->complete_with_value(7);
3534 a_done.store(true);
3535 };
3536 cfg_a.
name =
"chain_a";
3538
3540 while (!chain_complete.load() && (
millis() - start) < 5000) {
3542 }
3543
3544
3545 bool passed = chain_complete.load() && (final_value.load() == 70)
3546 && a_done.load() && b_done.load() && c_done.load();
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));
3553 return r;
3554 });
3555
3556
3557 mRemote->bind(
"testCoroutineAll", [
this](
const fl::json&
args) -> fl::json {
3560
3561 const char* test_names[] = {
3562 "testCoroutineBasic",
3563 "testCoroutineStop",
3564 "testCoroutineConcurrent",
3565 "testCoroutineAwait",
3566 "testCoroutineAwaitError",
3567 "testCoroutinePromiseCallbacks",
3568 "testCoroutinePromiseCatchCallback",
3569 "testCoroutineChainedAwait"
3570 };
3571 const int num_tests = 8;
3572
3573 int passed = 0;
3574 int failed = 0;
3576
3577 for (int i = 0; i < num_tests; i++) {
3579 auto bound =
mRemote->get<fl::json(
const fl::json&)>(test_names[i]);
3580 if (bound.ok()) {
3581 fl::json test_result = bound.value()(empty_args);
3583 if (test_result.
contains(
"success")) {
3584 auto val = test_result[
"success"].
as_bool();
3585 success = val.has_value() && val.value();
3586 }
3587 if (success) {
3588 passed++;
3589 FL_PRINT(
"[COROUTINE] PASS: " << test_names[i]);
3590 } else {
3591 failed++;
3592 FL_PRINT(
"[COROUTINE] FAIL: " << test_names[i]);
3593 }
3594 results.
set(test_names[i], test_result);
3595 } else {
3596 failed++;
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)");
3602 }
3603 }
3604
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);
3610 return r;
3611 });
3612}
bool autoResearchSetExclusiveDriverByName(const char *name)
AutoResearch-style helper: set an exclusive driver by name.
fl::json runNetLoopback()
Run self-contained loopback test: start HTTP server, client GETs localhost.
fl::json startNetClient()
Start WiFi Soft AP only (for net-client mode).
fl::json runNetClientTest(const char *host_ip, uint16_t port)
Run HTTP client tests against a host server.
fl::json startNetServer()
Start WiFi Soft AP and HTTP server with test endpoints.
fl::json stopNet()
Stop WiFi AP and HTTP server/client, release all resources.
fl::json stopOta()
Stop OTA server and WiFi AP, release all resources.
fl::json startOta()
Start WiFi Soft AP and OTA HTTP server.
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
@ CHANNELS
Remove all channels from controller list.
fl::json stopBleRemote()
Stop BLE remote (destroys BLE Remote + GATT server)
fl::json runParallelTestImpl(const fl::json &args)
fl::json runSingleTestImpl(const fl::json &args)
fl::net::ble::TransportState * mBleState
fl::shared_ptr< AutoResearchState > mState
fl::json findConnectedPinsImpl(const fl::json &args)
fl::json startBleRemote()
Start BLE remote (creates BLE GATT server + second Remote instance)
fl::unique_ptr< fl::Remote > mRemote
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.
void store(T value, memory_order=memory_order_seq_cst) FL_NOEXCEPT
fl::u16 getHeight() const
static IDecoderPtr createDecoder(const H264Config &config, fl::string *error_message=nullptr)
static H264Info parseH264Info(fl::span< const fl::u8 > mp4Data, fl::string *error_message=nullptr)
static bool isSupported()
static RxChannelPtr create(const RxChannelConfig &config) FL_NOEXCEPT
bool empty() const FL_NOEXCEPT
const char * c_str() const FL_NOEXCEPT
void push_back(const json &value) FL_NOEXCEPT
fl::optional< i64 > as_int() const FL_NOEXCEPT
bool is_null() const FL_NOEXCEPT
fl::optional< bool > as_bool() const FL_NOEXCEPT
bool is_int() const FL_NOEXCEPT
bool is_string() 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
static Promise< T > create() FL_NOEXCEPT
Create a pending Promise.
fl::size size() const FL_NOEXCEPT
void reserve(fl::size n) FL_NOEXCEPT
void assign(InputIt first, InputIt last) FL_NOEXCEPT
void push_back(const T &value) FL_NOEXCEPT
void resize(fl::size n) FL_NOEXCEPT
#define FL_PRINT(X)
Print without prefix (like FL_WARN but without "WARN: " prefix) Uses sstream for dynamic formatting (...
PerlinBenchResult runPerlinBenchmark(int iters)
ParlioEncodeResult measureParlioEncode(int=12000)
ValidateResult validateParlioStreaming(int base_tx_pin, int num_lanes, int num_leds, int iterations, uint32_t timeout_ms, const int *tx_pins=nullptr)
Run the PARLIO streaming functional test.
constexpr int kMaxIterations
void getTests(const SimdTestEntry **out_tests, int *out_count)
Get the static test table. Used by both runSimdTests() and the RPC handler.
BenchmarkResult runMultiplyBenchmark(int iters=10000)
Wave8ExpandResult measureWave8Expand(int=30000)
StatusInfo queryStatus(const TransportState *) FL_NOEXCEPT
Query BLE connection/subscription diagnostics.
Handle coroutine(const CoroutineConfig &config)
PromiseResult< T > await(Promise< T > p)
Await promise completion in a coroutine (Trampoline to platform implementation)
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
constexpr ChipsetTiming to_runtime_timing() FL_NOEXCEPT
Convert enum-based timing type to runtime ChipsetTiming struct.
void delay(u32 ms, bool run_async=true) FL_NOEXCEPT
Public delay wrapper that keeps bare Arduino delay() preferred after using fl::delay; while still all...
constexpr ChipsetTimingConfig makeTimingConfig() FL_NOEXCEPT
Convert compile-time CHIPSET type to runtime timing config.
@ FLEXIO
Teensy 4.x-only FlexIO capture backend (FLEXIO1 — FLEXIO2 is owned by the WS2812 TX driver)....
@ OBJECT_FLED
Teensy 4.x ObjectFLED driver.
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
expected< T, E > result
Alias for expected (Rust-style naming)
ChipsetTiming4Phase make4PhaseTiming(const ChipsetTiming &timing_3phase, u32 tolerance_ns) FL_NOEXCEPT
Create 4-phase RX timing from 3-phase chipset timing with tolerance.
fl::u32 micros()
Universal microsecond timer - returns microseconds since system startup.
void pinMode(int pin, PinMode mode)
Set pin mode (input, output, pull-up, pull-down)
PinValue digitalRead(int pin)
Read digital value from pin.
float add(float &a, float &b)
void analogWrite(int pin, u16 val)
Write analog value to pin (PWM)
void digitalWrite(int pin, PinValue val)
Write digital value to pin.
constexpr ClocklessEncoder encoder_for() FL_NOEXCEPT
Extract the encoder selector from a compile-time TIMING type.
void delayMicroseconds(u32 us)
Delay for a given number of microseconds.
@ Green
<div style='background:#008000;width:4em;height:4em;'></div>
@ Red
<div style='background:#FF0000;width:4em;height:4em;'></div>
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>