FastLED 3.10.6
Loading...
Searching...
No Matches
AutoResearchRemoteSystemMethods.cpp
Go to the documentation of this file.
1// System control RPC bindings: device status, debug toggles, driver
2// inventory, RPC ack/health (ping, testNoSerial), and the user-facing
3// runSingleTest/runParallelTest dispatchers. "System" = device control,
4// not feature exercise (those live in the math/network/async/driver bins).
5// Extracted from AutoResearchRemote.cpp as part of #3132 / meta #3127.
6
8#if FL_PLATFORM_HAS_LARGE_MEMORY
9
10// Legacy debug macros (no-ops, kept for debugTest RPC function)
11#define DEBUG_PRINT(x) do {} while(0)
12#define DEBUG_PRINTLN(x) do {} while(0)
13
14#include "AutoResearchRemote.h"
15#include "AutoResearchBle.h"
16#include "AutoResearchNet.h"
17#include "AutoResearchOta.h"
19#include "fl/system/heap.h"
20#include "Common.h"
21#include "AutoResearchTest.h"
22#include "AutoResearchHelpers.h"
28#include "fl/wdt/watchdog.h"
29#include "fl/stl/sstream.h"
30#include "fl/stl/unique_ptr.h"
31#include "fl/stl/singleton.h"
32#include "platforms/is_platform.h"
33#ifdef FL_IS_ESP32
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"
38// IWYU pragma: begin_keep
39#include "driver/parlio_tx.h" // ok platform headers - parlioRawTest RPC
40// IWYU pragma: end_keep
42#endif
44// IWYU pragma: begin_keep
45#include "esp_heap_caps.h" // ok platform headers - heapCheck RPC
46#include "freertos/FreeRTOS.h" // ok platform headers - heapCheck RPC
47#include "freertos/task.h" // ok platform headers - heapCheck RPC
48// IWYU pragma: end_keep
50#endif
51#include "fl/stl/optional.h"
52#include "fl/stl/json.h"
53#include "fl/task/task.h"
54#include "fl/task/executor.h"
56#include "fl/stl/atomic.h"
57#include "fl/task/promise.h"
58#include "fl/math/simd.h"
59#include "AutoResearchSimd.h"
65#include "fl/chipsets/spi.h"
67#include "fl/channels/config.h"
68#include <Arduino.h>
69
70#include "fl/net/ble.h"
71
72#include "fl/codec/h264.h"
73#include "fl/codec/mp4_parser.h"
75#include "fl/fx/frame.h"
76
77namespace {
78
79#if defined(FL_IS_ESP32) && FASTLED_ESP32_HAS_PARLIO
80struct ParlioRawTestState {
81 uint8_t buffer[512];
82};
83
84ParlioRawTestState& parlioRawTestState() {
86}
87
88// The TX clock edge field was renamed within IDF 5.5: 5.5.1 has only
89// `sample_edge`; 5.5.5 has `shift_edge` and keeps `sample_edge` as a
90// deprecated alias. Which one exists is detected here rather than guessed
91// from a version number. The two enums are ordered differently
92// (PARLIO_SAMPLE_EDGE_POS == 1, PARLIO_SHIFT_EDGE_POS == 0), and the newer
93// enumerator is not declared on older IDFs, so the new branch names the
94// member's own type and the value's meaning is in the comment.
95template <typename Cfg>
96auto setParlioRawTxEdge(Cfg& cfg, int) -> decltype(cfg.shift_edge, void()) {
97 cfg.shift_edge = static_cast<decltype(cfg.shift_edge)>(0); // PARLIO_SHIFT_EDGE_POS
98}
99template <typename Cfg>
100void setParlioRawTxEdge(Cfg& cfg, long) {
101 cfg.sample_edge = PARLIO_SAMPLE_EDGE_POS; // IDF before the rename
102}
103#endif
104
105fl::json autoResearchDeviceJson(const fl::string& name) {
106 if (name == "RMT") return fl::deviceJson<fl::Bus::RMT>();
107 if (name == "SPI" || name == "SPI_UNIFIED") return fl::deviceJson<fl::Bus::SPI>();
108 // RP registers its two fixed PL022 blocks under the concrete names
109 // "SPI0"/"SPI1". Without these they fell through to the generic tail
110 // below, which hardcodes which=0 and echoes the driver name into
111 // bus_name/vendor_name/device_name -- so `drivers` reported SPI1 with
112 // which=0 and no real bus metadata, unlike UART0/UART1 and PIO0/1/2.
113 // RP-only. SAMD21 (spi_hw_manager_samd21.cpp.hpp:38-39) and nRF52
114 // (init_channel_driver_nrf52.cpp.hpp:49) also register drivers called
115 // "SPI0"/"SPI1", but DeviceInfoResolver<Bus::SPI, 0|1> is specialized
116 // only in the RP branch of bus_info.h. Routing those platforms here
117 // would hand them the unspecialized no-op resolver and report
118 // is_noop=true / available=false -- the very regression this mapping
119 // was added to fix, just moved to a different chip.
120#if defined(FL_IS_RP)
121 if (name == "SPI0") return fl::deviceJson<fl::Bus::SPI, 0>();
122 if (name == "SPI1") return fl::deviceJson<fl::Bus::SPI, 1>();
123#endif
124 if (name == "UART" || name == "LPUART" || name == "UART0") {
126 }
127 if (name == "UART1") return fl::deviceJson<fl::Bus::UART, 1>();
128 if (name == "BIT_BANG" || name == "STUB") return fl::deviceJson<fl::Bus::BIT_BANG>();
129
130 if (name == "FLEX_IO" || name == "LCD_RGB") {
132 }
133 if (name == "OBJECT_FLED" || name == "PARLIO" || name == "LCD_SPI" ||
134 name == "LCD_CLOCKLESS" || name == "I2S" || name == "I2S_SPI") {
136 }
137 if (name == "PIO0") return fl::deviceJson<fl::Bus::FLEX_IO, 0>();
138 if (name == "PIO1") return fl::deviceJson<fl::Bus::FLEX_IO, 1>();
139 if (name == "PIO2") return fl::deviceJson<fl::Bus::FLEX_IO, 2>();
140
141 fl::json info = fl::json::object();
142 info.set("bus", static_cast<int64_t>(0));
143 info.set("bus_name", name.c_str());
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");
149 fl::json runtime = fl::json::object();
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);
156 return info;
157}
158
159} // namespace
160
161
163
164 // Register "status" function - device readiness check
165 remote.bind("status", [this](const fl::json& args) -> fl::json {
166 fl::json status = fl::json::object();
167 status.set("ready", true);
168 status.set("platform", autoresearch::chipName());
169 status.set("rpcReady", true);
170 status.set("ledRxAvailable", mState->led_rx_available);
171 status.set("pinTx", static_cast<int64_t>(mState->pin_tx));
172 status.set("pinRx", static_cast<int64_t>(mState->pin_rx));
173 return status;
174 });
175
176 // NOTE: getSchema is no longer needed - use built-in "rpc.discover" instead
177 // The rpc.discover method is automatically available via Remote->Rpc and returns
178 // the full OpenRPC schema. Our custom getSchema was causing stack overflow on ESP32-C6.
179
180 // Register "debugTest" function - test RPC argument passing
181 remote.bind("debugTest", [](const fl::json& args) -> fl::json {
182 fl::json response = fl::json::object();
183 response.set("success", true);
184 response.set("received", args);
185 return response;
186 });
187
188 // Register "deliberateHang" - force an infinite loop with interrupts
189 // disabled to validate the unified Watchdog implementation (#2731).
190 // The watchdog should fire within its configured timeout, reset the
191 // device, and let the bootloader become reachable again.
192 // Returns success BEFORE hanging so the caller sees the ACK; the hang
193 // begins in the next iteration of the loop.
194 remote.bind("deliberateHang", [this](const fl::json& args) -> fl::json {
195 (void)args;
196 mState->deliberate_hang_requested = true;
197 fl::json response = fl::json::object();
198 response.set("success", 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);
202 return response;
204
205 // Register "drivers" function - list available drivers
206 remote.bind("drivers", [this](const fl::json& args) -> fl::json {
207 fl::json drivers = fl::json::array();
208 for (fl::size i = 0; i < mState->drivers_available.size(); i++) {
209 fl::json driver = fl::json::object();
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"]);
220 drivers.push_back(driver);
221 }
222 return drivers;
223 });
224
225 // Returns: {success, passed, totalTests, passedTests, duration_ms, driver,
226 // laneCount, laneSizes, pattern, firstFailure?}
227 // ASYNC: Sends ACK immediately, final response sent via sendAsyncResponse()
228 // NOTE: runSingleTestImpl() may return early (error cases) without calling
229 // sendAsyncResponse(). This wrapper ensures a response is ALWAYS sent so
230 // the Python client never times out waiting 120s for a missing response.
231 remote.bind("runSingleTest", [this](const fl::json& args) -> fl::json {
232 fl::json result = this->runSingleTestImpl(args);
233 // If runSingleTestImpl returned a non-null response, it exited early without
234 // calling sendAsyncResponse(). Send it now so the client gets a response.
235 if (!result.is_null()) {
236 mRemote->sendAsyncResponse("runSingleTest", result);
237 }
238 return fl::json(nullptr);
240
241 // Register "runParallelTest" - test multiple drivers simultaneously
242 // Args: {drivers: [{driver: "PARLIO", laneSizes: [100]}, {driver: "LCD_RGB", laneSizes: [100]}],
243 // pattern?: "MSB_LSB_A", iterations?: 1, timing?: "WS2812B-V5"}
244 // Returns: {success, passed, duration_ms, show_duration_us, drivers: [...],
245 // rx_validation_attempted, rx_validation_passed}
246 remote.bind("runParallelTest", [this](const fl::json& args) -> fl::json {
247 fl::json result = this->runParallelTestImpl(args);
248 if (!result.is_null()) {
249 mRemote->sendAsyncResponse("runParallelTest", result);
250 }
251 return fl::json(nullptr);
253
254 // ========================================================================
255 // Phase 4 Functions: Utility and Control
256 // ========================================================================
257
258 // Register "ping" function - health check with timestamp
259 remote.bind("ping", [](const fl::json& args) -> fl::json {
260 uint32_t now = millis();
261 const fl::ResetCause reset_cause = FastLED.watchdog().lastResetCause();
262
263 fl::json response = fl::json::object();
264 response.set("success", true);
265 response.set("message", "pong");
266 response.set("timestamp", static_cast<int64_t>(now));
267 response.set("uptimeMs", static_cast<int64_t>(now));
268 response.set("lastResetCause", fl::resetCauseName(reset_cause).data());
269 response.set("lastResetWasWatchdog", reset_cause == fl::ResetCause::WATCHDOG);
270 return response;
271 });
272
273 // TEST: Simple RPC without Serial to verify task context works
274 remote.bind("testNoSerial", [](const fl::json& args) -> fl::json {
275 fl::json response = fl::json::object();
276 response.set("success", true);
277 response.set("message", "RPC works from task context");
278 response.set("serial_safe", false);
279 return response;
280 });
281
282 remote.bind("testRpConcurrency", [this](const fl::json& args) -> fl::json {
283 (void)args;
284 mRemote->sendAsyncResponse("testRpConcurrency",
286 return fl::json(nullptr);
288
289 // FastLED#1471: starve the PIO block the way TinyUSB does, then check
290 // that the clockless driver declines instead of stealing a state machine.
291 remote.bind("testRpPioContention", [this](const fl::json& args) -> fl::json {
292 (void)args;
293 mRemote->sendAsyncResponse("testRpPioContention",
295 return fl::json(nullptr);
297
298 // FastLED#3899: PIO0+PIO1 simultaneous operation, asserted at the resource
299 // level. runParallelTest only proves show() returned for this pair.
300 remote.bind("testRpPioParallelResources", [this](const fl::json& args) -> fl::json {
301 mRemote->sendAsyncResponse("testRpPioParallelResources",
303 return fl::json(nullptr);
305
306 // "parlioMetrics" — PARLIO engine ISR/DMA counters (C6 bring-up,
307 // FastLED#3576 Phase 7). Reads the engine's debug metrics AFTER a
308 // test to see whether the TX ISRs ever fired.
309#if defined(FL_IS_ESP32) && FASTLED_ESP32_HAS_PARLIO
310 remote.bind("parlioMetrics", [](const fl::json& args) -> fl::json {
311 fl::json response = fl::json::object();
312 const auto m = fl::detail::ParlioEngine::getInstance().getDebugMetrics();
313 response.set("success", true);
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);
324 return response;
325 });
326#endif
327
328 // FastLED#3569 bench diagnostic — read up to 16 u32 words of
329 // memory-mapped register space without resetting the chip (esptool
330 // pokes force a reset, wiping the very peripheral state under
331 // investigation). Params: [{addr: <u32>, count?: 1-16}]. The caller
332 // is expected to pass valid peripheral/SRAM addresses; an unmapped
333 // address faults exactly as it would from any other code.
334 // "parlioRawTest" — minimal IDF parlio TX (FastLED#3586): 1-bit
335 // unit @ requested clock on a pin, transmit an alternating pattern.
336 // Bypasses the FastLED engine completely to split engine bugs from
337 // IDF-driver/silicon behavior.
338#if defined(FL_IS_ESP32) && FASTLED_ESP32_HAS_PARLIO
339 remote.bind("parlioRawTest", [](const fl::json& args) -> fl::json {
340 fl::json response = fl::json::object();
341 int pin = 1;
342 int hz = 8000000;
343 int width = 1;
344 int pattern = 0xAA;
345 int qnb = 0;
346 int mts = 1024;
347 int burst = 0;
348 int depth = 4;
349 int chunks = 1;
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();
360 }
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); // shift_edge where it exists, sample_edge before
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);
377 if (err != ESP_OK) {
378 response.set("success", false);
379 response.set("error", esp_err_to_name(err));
380 return response;
381 }
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 = {};
386 tcfg.idle_value = 0;
387 tcfg.flags.queue_nonblocking = qnb ? 1 : 0;
388 if (burst > 0) {
389 // dma_burst_size is a unit-config field; already applied below via cfg2 hack? No —
390 // it must be set before creation; handled via the 'burst' arg at cfg time.
391 }
392 esp_err_t terr = ESP_OK;
393 const size_t chunk_bytes = 512 / (chunks > 0 ? chunks : 1);
394 // Repeat for ~3 s so the edge-probe task (which re-arms 150 ms
395 // after the RPC) always overlaps live transmissions.
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);
399 }
400 if (terr == ESP_OK) {
401 (void)parlio_tx_unit_wait_all_done(unit, 100);
402 }
403 vTaskDelay(pdMS_TO_TICKS(10));
404 }
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);
408 response.set("success", true);
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));
412 return response;
413 });
414#endif
415
416 // "edgeProbe" / "edgeProbeRead" — ISR-based edge recorder
417 // (FastLED#3586 bring-up): arms a GPIO any-edge interrupt that
418 // timestamps up to 200 edges with the cycle counter. Arm it, run a
419 // test, then read back exact pulse widths — a 6-ns logic probe for
420 // "transmits but capture sees nothing" investigations.
421#if defined(FL_IS_ESP32)
422 remote.bind("edgeProbe", [](const fl::json& args) -> fl::json {
423 fl::json response = fl::json::object();
424 int pin = 0;
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());
428 }
429 if (args.is_object() && args.contains("passive") && args["passive"].is_bool()) {
430 passive = args["passive"].as_bool().value();
431 }
432 edgeProbeArmMode(pin, passive);
433 response.set("success", true);
434 response.set("pin", static_cast<int64_t>(pin));
435 response.set("selfTestEdges", static_cast<int64_t>(edgeProbeSelfTestEdges()));
436 return response;
437 });
438
439 remote.bind("edgeProbeRead", [](const fl::json& args) -> fl::json {
440 fl::json response = fl::json::object();
441 response.set("success", true);
442 fl::u32 count = 0;
443 const fl::u32 *stamps = edgeProbeStamps(count);
444 response.set("edges", static_cast<int64_t>(count));
445 response.set("probeState", static_cast<int64_t>(edgeProbeState()));
446 {
447 const fl::u32 *rl = edgeProbeRmtLive();
448 fl::json live = fl::json::array();
449 for (int k = 0; k < 8; ++k) live.push_back(static_cast<int64_t>(rl[k]));
450 response.set("rmtLive", live);
451 }
452 fl::json runs = fl::json::array();
453 // Convert consecutive timestamps to durations in ns. Entry i
454 // encodes (level << 31) | cycle_stamp.
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;
460 // cycles -> ns via configured CPU MHz
461 const fl::u64 ns = (static_cast<fl::u64>(d_cycles) * 1000u) / edgeProbeCpuMhz();
462 fl::json e = fl::json::object();
463 e.set("level", static_cast<int64_t>(level));
464 e.set("ns", static_cast<int64_t>(ns));
465 runs.push_back(e);
466 }
467 response.set("runs", runs);
468 return response;
469 });
470#endif
471
472 // "heapCheck" — heap integrity + stats (FastLED#3588 corruption
473 // bisect). heap_caps_check_integrity_all walks every block; with
474 // poisoning configured in the IDF libs it also validates canaries.
475#if defined(FL_IS_ESP32)
476 remote.bind("heapCheck", [](const fl::json& args) -> fl::json {
477 fl::json response = fl::json::object();
478 const bool intact = heap_caps_check_integrity_all(true);
479 response.set("success", true);
480 response.set("intact", intact);
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
486 {
487 // Per-task stack headroom (words): a near-zero value means
488 // that task is about to overflow into adjacent heap
489 // (FastLED#3588 candidate mechanism).
490 TaskStatus_t statuses[16];
491 const UBaseType_t n = uxTaskGetSystemState(statuses, 16, nullptr);
492 fl::json tasks = fl::json::array();
493 for (UBaseType_t i = 0; i < n; ++i) {
494 fl::json t = fl::json::object();
495 t.set("name", statuses[i].pcTaskName);
496 t.set("hw", static_cast<int64_t>(statuses[i].usStackHighWaterMark));
497 tasks.push_back(t);
498 }
499 response.set("tasks", tasks);
500 }
501#endif
502 return response;
503 });
504#endif
505
506 remote.bind("peekMem", [](const fl::json& args) -> fl::json {
507 fl::json response = fl::json::object();
508 if (!args.is_object() || !args.contains("addr") ||
509 !args["addr"].is_int()) {
510 response.set("success", false);
511 response.set("error", "InvalidArgs");
512 response.set("message", "Expected {addr: u32, count?: 1-16}");
513 return response;
514 }
515 const uint32_t addr =
516 static_cast<uint32_t>(args["addr"].as_int().value());
517 int count = 1;
518 if (args.contains("count") && args["count"].is_int()) {
519 count = static_cast<int>(args["count"].as_int().value());
520 }
521 if (count < 1 || count > 16 || (addr & 3u) != 0) {
522 response.set("success", false);
523 response.set("error", "InvalidArgs");
524 response.set("message", "count must be 1-16, addr 4-byte aligned");
525 return response;
526 }
527 fl::json values = fl::json::array();
528 const volatile uint32_t* p =
529 reinterpret_cast<const volatile uint32_t*>(addr); // ok reinterpret cast - caller-supplied MMIO/SRAM address, bench diagnostic
530 for (int i = 0; i < count; ++i) {
531 values.push_back(static_cast<int64_t>(p[i]));
532 }
533 response.set("success", true);
534 response.set("addr", static_cast<int64_t>(addr));
535 response.set("values", values);
536 return response;
537 });
538}
539
540#endif // FL_PLATFORM_HAS_LARGE_MEMORY
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
Definition RpcClient.ino:43
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.
Definition remote.h:42
static T & instance() FL_NO_EXCEPT
Definition singleton.h:41
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
Definition json.h:866
void push_back(const json &value) FL_NO_EXCEPT
Definition json.h:918
static json object() FL_NO_EXCEPT
Definition json.h:857
static json array() FL_NO_EXCEPT
Definition json.h:853
#define FL_EXTERN_C_BEGIN
#define FL_EXTERN_C_END
static uint32_t t
Definition Luminova.h:55
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.
Definition wifi.cpp.hpp:43
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
ResetCause
Cause of the previous boot. Call once early in setup().
Definition watchdog.h:43
u8 width
Definition blur.h:186
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
fl::string_view resetCauseName(ResetCause c) FL_NO_EXCEPT
Return a static-lifetime name for c — zero-cost, no allocation.
Definition watchdog.h:61
fl::json deviceJson()
corkscrew_args args
Definition old.h:149
Promise-based fluent API for FastLED - standalone async primitives.
Umbrella header for SIMD subsystem.
unsigned char uint8_t
Definition stdint.h:208
fl::size size_t
Definition stdint.h:222
fl::u32 uint32_t
Definition stdint.h:218
Unified cross-platform Watchdog Timer API for FastLED.