8#if FL_PLATFORM_HAS_LARGE_MEMORY
11#define DEBUG_PRINT(x) do {} while(0)
12#define DEBUG_PRINTLN(x) do {} while(0)
32#include "platforms/is_platform.h"
34#include "platforms/esp/32/feature_flags/enabled.h"
35#if FASTLED_ESP32_HAS_PARLIO
36#include "platforms/esp/32/drivers/parlio/parlio_engine.h"
39#include "driver/parlio_tx.h"
45#include "esp_heap_caps.h"
46#include "freertos/FreeRTOS.h"
47#include "freertos/task.h"
79#if defined(FL_IS_ESP32) && FASTLED_ESP32_HAS_PARLIO
80struct ParlioRawTestState {
84ParlioRawTestState& parlioRawTestState() {
95template <
typename Cfg>
96auto setParlioRawTxEdge(Cfg& cfg,
int) ->
decltype(cfg.shift_edge, void()) {
97 cfg.shift_edge =
static_cast<decltype(cfg.shift_edge)
>(0);
99template <
typename Cfg>
100void setParlioRawTxEdge(Cfg& cfg,
long) {
101 cfg.sample_edge = PARLIO_SAMPLE_EDGE_POS;
124 if (name ==
"UART" || name ==
"LPUART" || name ==
"UART0") {
130 if (name ==
"FLEX_IO" || name ==
"LCD_RGB") {
133 if (name ==
"OBJECT_FLED" || name ==
"PARLIO" || name ==
"LCD_SPI" ||
134 name ==
"LCD_CLOCKLESS" || name ==
"I2S" || name ==
"I2S_SPI") {
142 info.
set(
"bus",
static_cast<int64_t
>(0));
144 info.
set(
"vendor_name", name.
c_str());
145 info.
set(
"device_name", name.
c_str());
146 info.
set(
"which",
static_cast<int64_t
>(0));
147 info.
set(
"is_noop",
false);
148 info.
set(
"notes",
"registered runtime driver");
150 runtime.
set(
"available",
true);
151 runtime.
set(
"initialized",
false);
152 runtime.
set(
"ready",
true);
153 runtime.
set(
"busy",
false);
154 runtime.
set(
"error",
"");
155 info.
set(
"runtime", runtime);
165 remote.bind(
"status", [
this](
const fl::json&
args) -> fl::json {
167 status.set(
"ready",
true);
169 status.set(
"rpcReady",
true);
171 status.set(
"pinTx",
static_cast<int64_t
>(
mState->pin_tx));
172 status.set(
"pinRx",
static_cast<int64_t
>(
mState->pin_rx));
181 remote.bind(
"debugTest", [](
const fl::json&
args) -> fl::json {
194 remote.bind(
"deliberateHang", [
this](
const fl::json&
args) -> fl::json {
196 mState->deliberate_hang_requested =
true;
199 response.set(
"message",
"device will hang after RPC returns; watchdog should reset within configured timeout");
200 response.set(
"hangDelayMs",
static_cast<int64_t
>(200));
201 response.set(
"watchdogExpected",
true);
206 remote.bind(
"drivers", [
this](
const fl::json&
args) -> fl::json {
208 for (fl::size i = 0; i <
mState->drivers_available.size(); i++) {
210 driver.
set(
"name",
mState->drivers_available[i].name.c_str());
211 driver.
set(
"priority",
static_cast<int64_t
>(
mState->drivers_available[i].priority));
212 driver.
set(
"enabled",
mState->drivers_available[i].enabled);
213 fl::json device = autoResearchDeviceJson(
mState->drivers_available[i].name);
214 driver.
set(
"bus_name", device[
"bus_name"]);
215 driver.
set(
"vendor_name", device[
"vendor_name"]);
216 driver.
set(
"device_name", device[
"device_name"]);
217 driver.
set(
"which", device[
"which"]);
218 driver.
set(
"is_noop", device[
"is_noop"]);
219 driver.
set(
"runtime", device[
"runtime"]);
231 remote.bind(
"runSingleTest", [
this](
const fl::json&
args) -> fl::json {
236 mRemote->sendAsyncResponse(
"runSingleTest", result);
238 return fl::json(
nullptr);
246 remote.bind(
"runParallelTest", [
this](
const fl::json&
args) -> fl::json {
249 mRemote->sendAsyncResponse(
"runParallelTest", result);
251 return fl::json(
nullptr);
259 remote.bind(
"ping", [](
const fl::json&
args) -> fl::json {
266 response.set(
"timestamp",
static_cast<int64_t
>(now));
267 response.set(
"uptimeMs",
static_cast<int64_t
>(now));
274 remote.bind(
"testNoSerial", [](
const fl::json&
args) -> fl::json {
277 response.set(
"message",
"RPC works from task context");
282 remote.bind(
"testRpConcurrency", [
this](
const fl::json&
args) -> fl::json {
284 mRemote->sendAsyncResponse(
"testRpConcurrency",
286 return fl::json(
nullptr);
291 remote.bind(
"testRpPioContention", [
this](
const fl::json&
args) -> fl::json {
293 mRemote->sendAsyncResponse(
"testRpPioContention",
295 return fl::json(
nullptr);
300 remote.bind(
"testRpPioParallelResources", [
this](
const fl::json&
args) -> fl::json {
301 mRemote->sendAsyncResponse(
"testRpPioParallelResources",
303 return fl::json(
nullptr);
309#if defined(FL_IS_ESP32) && FASTLED_ESP32_HAS_PARLIO
310 remote.bind(
"parlioMetrics", [](
const fl::json&
args) -> fl::json {
312 const auto m = fl::detail::ParlioEngine::getInstance().getDebugMetrics();
314 response.set(
"isrCount",
static_cast<int64_t
>(m.mIsrCount));
315 response.set(
"txDoneCount",
static_cast<int64_t
>(m.mTxDoneCount));
316 response.set(
"chunksQueued",
static_cast<int64_t
>(m.mChunksQueued));
317 response.set(
"chunksCompleted",
static_cast<int64_t
>(m.mChunksCompleted));
318 response.set(
"bytesTotal",
static_cast<int64_t
>(m.mBytesTotal));
319 response.set(
"bytesTransmitted",
static_cast<int64_t
>(m.mBytesTransmitted));
320 response.set(
"underruns",
static_cast<int64_t
>(m.mUnderrunCount));
321 response.set(
"errorCode",
static_cast<int64_t
>(m.mErrorCode));
322 response.set(
"hardwareIdle", m.mHardwareIdle);
323 response.set(
"transmissionActive", m.mTransmissionActive);
338#if defined(FL_IS_ESP32) && FASTLED_ESP32_HAS_PARLIO
339 remote.bind(
"parlioRawTest", [](
const fl::json&
args) -> fl::json {
350 if (
args.is_object()) {
351 if (
args.contains(
"pin") &&
args[
"pin"].is_int()) pin = (int)
args[
"pin"].as_int().value();
352 if (
args.contains(
"hz") &&
args[
"hz"].is_int()) hz = (int)
args[
"hz"].as_int().value();
353 if (
args.contains(
"width") &&
args[
"width"].is_int())
width = (int)
args[
"width"].as_int().value();
354 if (
args.contains(
"pattern") &&
args[
"pattern"].is_int()) pattern = (int)
args[
"pattern"].as_int().value();
355 if (
args.contains(
"qnb") &&
args[
"qnb"].is_int()) qnb = (int)
args[
"qnb"].as_int().value();
356 if (
args.contains(
"mts") &&
args[
"mts"].is_int()) mts = (int)
args[
"mts"].as_int().value();
357 if (
args.contains(
"burst") &&
args[
"burst"].is_int()) burst = (int)
args[
"burst"].as_int().value();
358 if (
args.contains(
"depth") &&
args[
"depth"].is_int()) depth = (int)
args[
"depth"].as_int().value();
359 if (
args.contains(
"chunks") &&
args[
"chunks"].is_int()) chunks = (int)
args[
"chunks"].as_int().value();
361 parlio_tx_unit_config_t cfg = {};
362 cfg.clk_src = PARLIO_CLK_SRC_DEFAULT;
363 cfg.data_width = (
size_t)width;
364 cfg.clk_in_gpio_num = (gpio_num_t)-1;
365 cfg.clk_out_gpio_num = (gpio_num_t)-1;
366 cfg.valid_gpio_num = (gpio_num_t)-1;
367 cfg.trans_queue_depth = (
size_t)depth;
368 cfg.max_transfer_size = (
size_t)mts;
369 cfg.output_clk_freq_hz = (
uint32_t)hz;
370 setParlioRawTxEdge(cfg, 0);
371 cfg.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB;
372 if (burst > 0) cfg.dma_burst_size = (
size_t)burst;
373 for (
int i = 0; i < 16; ++i) cfg.data_gpio_nums[i] = (gpio_num_t)-1;
374 cfg.data_gpio_nums[0] = (gpio_num_t)pin;
375 parlio_tx_unit_handle_t unit =
nullptr;
376 esp_err_t err = parlio_new_tx_unit(&cfg, &unit);
379 response.set(
"error", esp_err_to_name(err));
382 err = parlio_tx_unit_enable(unit);
383 uint8_t* buf = parlioRawTestState().buffer;
384 for (
int i = 0; i < 512; ++i) buf[i] = (
uint8_t)pattern;
385 parlio_transmit_config_t tcfg = {};
387 tcfg.flags.queue_nonblocking = qnb ? 1 : 0;
392 esp_err_t terr = ESP_OK;
393 const size_t chunk_bytes = 512 / (chunks > 0 ? chunks : 1);
396 for (
int rep = 0; rep < 300 && terr == ESP_OK; ++rep) {
397 for (
int c = 0; c < chunks && terr == ESP_OK; ++c) {
398 terr = parlio_tx_unit_transmit(unit, buf + c * chunk_bytes, chunk_bytes * 8, &tcfg);
400 if (terr == ESP_OK) {
401 (void)parlio_tx_unit_wait_all_done(unit, 100);
403 vTaskDelay(pdMS_TO_TICKS(10));
405 esp_err_t werr = parlio_tx_unit_wait_all_done(unit, 1000);
406 parlio_tx_unit_disable(unit);
407 parlio_del_tx_unit(unit);
409 response.set(
"enableErr", esp_err_to_name(err));
410 response.set(
"txErr", esp_err_to_name(terr));
411 response.set(
"waitErr", esp_err_to_name(werr));
421#if defined(FL_IS_ESP32)
422 remote.bind(
"edgeProbe", [](
const fl::json&
args) -> fl::json {
425 bool passive =
false;
426 if (
args.is_object() &&
args.contains(
"pin") &&
args[
"pin"].is_int()) {
427 pin =
static_cast<int>(
args[
"pin"].as_int().value());
429 if (
args.is_object() &&
args.contains(
"passive") &&
args[
"passive"].is_bool()) {
430 passive =
args[
"passive"].as_bool().value();
434 response.set(
"pin",
static_cast<int64_t
>(pin));
439 remote.bind(
"edgeProbeRead", [](
const fl::json&
args) -> fl::json {
444 response.set(
"edges",
static_cast<int64_t
>(count));
449 for (
int k = 0; k < 8; ++k) live.
push_back(
static_cast<int64_t
>(rl[k]));
455 for (fl::u32 i = 1; i < count && i < 200; ++i) {
456 const fl::u32 prev = stamps[i - 1];
457 const fl::u32
cur = stamps[i];
458 const fl::u32 d_cycles = (
cur & 0x7FFFFFFFu) - (prev & 0x7FFFFFFFu);
459 const fl::u32 level = prev >> 31;
461 const fl::u64 ns = (
static_cast<fl::u64
>(d_cycles) * 1000u) /
edgeProbeCpuMhz();
463 e.
set(
"level",
static_cast<int64_t
>(level));
464 e.
set(
"ns",
static_cast<int64_t
>(ns));
475#if defined(FL_IS_ESP32)
476 remote.bind(
"heapCheck", [](
const fl::json&
args) -> fl::json {
478 const bool intact = heap_caps_check_integrity_all(
true);
481 response.set(
"free",
static_cast<int64_t
>(heap_caps_get_free_size(MALLOC_CAP_8BIT)));
482 response.set(
"minFree",
static_cast<int64_t
>(heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT)));
483 response.set(
"largest",
static_cast<int64_t
>(heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)));
484 response.set(
"loopStackHighWater",
static_cast<int64_t
>(uxTaskGetStackHighWaterMark(
nullptr)));
485#if configUSE_TRACE_FACILITY
490 TaskStatus_t statuses[16];
491 const UBaseType_t n = uxTaskGetSystemState(statuses, 16,
nullptr);
493 for (UBaseType_t i = 0; i < n; ++i) {
495 t.set(
"name", statuses[i].pcTaskName);
496 t.set(
"hw",
static_cast<int64_t
>(statuses[i].usStackHighWaterMark));
506 remote.bind(
"peekMem", [](
const fl::json&
args) -> fl::json {
508 if (!
args.is_object() || !
args.contains(
"addr") ||
509 !
args[
"addr"].is_int()) {
511 response.set(
"error",
"InvalidArgs");
512 response.set(
"message",
"Expected {addr: u32, count?: 1-16}");
518 if (
args.contains(
"count") &&
args[
"count"].is_int()) {
519 count =
static_cast<int>(
args[
"count"].as_int().value());
521 if (count < 1 || count > 16 || (addr & 3u) != 0) {
523 response.set(
"error",
"InvalidArgs");
524 response.set(
"message",
"count must be 1-16, addr 4-byte aligned");
529 reinterpret_cast<const volatile uint32_t*
>(addr);
530 for (
int i = 0; i < count; ++i) {
531 values.
push_back(
static_cast<int64_t
>(p[i]));
534 response.set(
"addr",
static_cast<int64_t
>(addr));
const fl::u32 * edgeProbeStamps(fl::u32 &count)
Timestamp buffer: entry = (level_before_edge << 31) | cycle_count.
fl::u32 edgeProbeState()
Probe task progress: 0=not started 1=started 2=triggered 3=sampled.
const fl::u32 * edgeProbeRmtLive()
RMT registers snapshotted at trigger time (INT_RAW, CH2STATUS, CH3STATUS, CH2_RX_CONF1).
fl::u32 edgeProbeSelfTestEdges()
Edges recorded by the arm-time pull-toggle self-test.
void edgeProbeArmMode(int pin, bool passive)
Passive variant: no pinMode/attachInterrupt/pull toggles — only the triggered tight-loop sampler task...
fl::u32 edgeProbeCpuMhz()
CPU frequency in MHz for cycle→ns conversion.
Full PARLIO encode bench for ESP32-P4 (post-byte-LUT, #2526 landed).
PARLIO ISR-streaming functional validation (#2548 deep-dive follow-up).
Issue #2994 repro: compounded per-sequence timing drift vs 3.10.3.
Wave8 expansion micro-benchmark for #2526.
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
fl::unique_ptr< fl::Remote > remote
fl::net::ble — BLE GATT transport layer for JSON-RPC
JSON adapter for bus_info.h.
SPI encoder configuration for clocked LED chipsets.
fl::json runParallelTestImpl(const fl::json &args)
fl::json runSingleTestImpl(const fl::json &args)
void bindSystemMethods(fl::Remote &remote)
fl::shared_ptr< AutoResearchState > mState
fl::unique_ptr< fl::Remote > mRemote
static fl::Watchdog & watchdog()
Access the unified cross-platform watchdog timer.
JSON-RPC server with scheduling support.
static T & instance() FL_NO_EXCEPT
ResetCause lastResetCause() const FL_NO_EXCEPT
const char * c_str() const FL_NO_EXCEPT
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
void push_back(const json &value) FL_NO_EXCEPT
static json object() FL_NO_EXCEPT
static json array() FL_NO_EXCEPT
#define FL_EXTERN_C_BEGIN
Task executor — runs registered task runners and manages the run loop.
Platform-abstracted heap memory query functions.
FastLED's Elegant JSON Library: fl::json
fl::json runRpPioParallelResourceTest(const fl::json &args)
Prove PIO0 + PIO1 simultaneous operation at the resource level (#3899).
fl::json runRpPioContentionTest()
Reproduce FastLED#1471's PIO state-machine contention on real RP silicon.
fl::json runRpConcurrencyTest()
Exercise the RP dual-core mutex and semaphore backends from both cores.
constexpr const char * chipName()
Status status() FL_NO_EXCEPT
Current connection state.
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
ResetCause
Cause of the previous boot. Call once early in setup().
expected< T, E > result
Alias for expected (Rust-style naming)
fl::string_view resetCauseName(ResetCause c) FL_NO_EXCEPT
Return a static-lifetime name for c — zero-cost, no allocation.
Promise-based fluent API for FastLED - standalone async primitives.
Umbrella header for SIMD subsystem.
Unified cross-platform Watchdog Timer API for FastLED.