FastLED 3.10.6
Loading...
Searching...
No Matches
AutoResearchRemoteMathMethods.cpp
Go to the documentation of this file.
1// Math / compute kernel RPC bindings: SIMD correctness + speed, Animartrix
2// Perlin generator bench, wave8 expansion bench, PARLIO encoder/streaming
3// validation. These exercise math/compute paths (not the perf probes which
4// measure the probe machinery itself — those live in bindBenchmarkMethods).
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 "fl/audio/fft/fft.h"
22#include "fl/math/math.h"
23#include "Common.h"
24#include "AutoResearchTest.h"
25#include "AutoResearchHelpers.h"
26#include "fl/stl/sstream.h"
27#include "fl/stl/unique_ptr.h"
28#include "fl/stl/optional.h"
29#include "fl/stl/json.h"
30#include "fl/task/task.h"
31#include "fl/task/executor.h"
33#include "fl/stl/atomic.h"
34#include "fl/task/promise.h"
35#include "fl/math/simd.h"
36#include "AutoResearchSimd.h"
42#include "AutoResearchFlexIo.h"
43#include "AutoResearchIeee754.h"
44#include "AutoResearchMathExp.h"
45#include "AutoResearchMp3.h"
46#include "fl/chipsets/spi.h"
47#include "fl/channels/config.h"
48#include <Arduino.h>
49
50#include "fl/net/ble.h"
51
52#include "fl/codec/h264.h"
53#include "fl/codec/mp4_parser.h"
55#include "fl/fx/frame.h"
56
57
59 // Register "testSimd" function - run comprehensive SIMD test suite
60 remote.bind("testSimd", [](const fl::json& args) -> fl::json {
61 fl::json response = fl::json::object();
62
63 // Run the full test suite and collect per-test results
64 using autoresearch::simd_check::SimdTestEntry;
65 const SimdTestEntry* tests = nullptr;
66 int num_tests = 0;
67 autoresearch::simd_check::getTests(&tests, &num_tests);
68
69 int passed_count = 0;
70 int failed_count = 0;
71 fl::json failures = fl::json::array();
72
73 for (int i = 0; i < num_tests; i++) {
74 bool ok = tests[i].func();
75 if (ok) {
76 passed_count++;
77 } else {
78 failed_count++;
79 failures.push_back(fl::string(tests[i].name));
80 }
81 }
82
83 response.set("success", true);
84 response.set("passed", failed_count == 0);
85 response.set("totalTests", static_cast<int64_t>(num_tests));
86 response.set("passedTests", static_cast<int64_t>(passed_count));
87 response.set("failedTests", static_cast<int64_t>(failed_count));
88 if (failed_count > 0) {
89 response.set("failures", failures);
90 }
91 return response;
92 });
93
94 // Register "testSimdBenchmark" - multiply speed benchmark
95 remote.bind("testSimdBenchmark", [](const fl::json& args) -> fl::json {
96 fl::json response = fl::json::object();
97
98 int iters = 10000;
99 fl::json config;
100 if (args.is_object()) {
101 config = args;
102 } else if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
103 config = args[0];
104 }
105 if (!config.is_null() && config.contains("iterations") && config["iterations"].is_int()) {
106 iters = static_cast<int>(config["iterations"].as_int().value());
107 if (iters < 1) iters = 1;
108 if (iters > 1000000) iters = 1000000;
109 }
110
112
113 response.set("success", true);
114 response.set("iterations", result.iterations);
115
116 fl::json add = fl::json::object();
117 add.set("float_us", result.add_float_us);
118 add.set("s8x8_us", result.add_s8x8_us);
119 add.set("s16x16_us", result.add_s16x16_us);
120 add.set("u16x16_us", result.add_u16x16_us);
121 add.set("simd_us", result.add_simd_us);
122 response.set("add", add);
123
124 fl::json sub = fl::json::object();
125 sub.set("float_us", result.sub_float_us);
126 sub.set("s8x8_us", result.sub_s8x8_us);
127 sub.set("s16x16_us", result.sub_s16x16_us);
128 sub.set("u16x16_us", result.sub_u16x16_us);
129 sub.set("simd_us", result.sub_simd_us);
130 response.set("sub", sub);
131
132 fl::json mul = fl::json::object();
133 mul.set("float_us", result.mul_float_us);
134 mul.set("s8x8_us", result.mul_s8x8_us);
135 mul.set("s16x16_us", result.mul_s16x16_us);
136 mul.set("u16x16_us", result.mul_u16x16_us);
137 mul.set("simd_us", result.mul_simd_us);
138 response.set("mul", mul);
139
140 fl::json div = fl::json::object();
141 div.set("float_us", result.div_float_us);
142 div.set("s8x8_us", result.div_s8x8_us);
143 div.set("s16x16_us", result.div_s16x16_us);
144 div.set("u16x16_us", result.div_u16x16_us);
145 response.set("div", div);
146
147 return response;
148 });
149
150 // Register "animartrixPerlinBench" - Animartrix-representative Perlin
151 // noise bench: scalar float (fl::pnoise) vs s16x16 fixed-point
152 // (fl::perlin_i16_optimized::pnoise2d). Same workload that drives every
153 // Animartrix frame — one Perlin lookup per output pixel, 16x16 grid
154 // per iteration. Args: {iterations} (optional, default 100).
155 remote.bind("animartrixPerlinBench", [](const fl::json& args) -> fl::json {
156 fl::json response = fl::json::object();
157
158 int iters = 100;
159 fl::json config;
160 if (args.is_object()) {
161 config = args;
162 } else if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
163 config = args[0];
164 }
165 if (!config.is_null() && config.contains("iterations") && config["iterations"].is_int()) {
166 iters = static_cast<int>(config["iterations"].as_int().value());
167 if (iters < 1) iters = 1;
168 if (iters > 10000) iters = 10000;
169 }
170
172
173 response.set("success", true);
174 response.set("iterations", result.iterations);
175 // Workload-per-iter is 16*16 = 256 pnoise calls. Surface this so
176 // the client can compute per-call timings without hardcoding.
177 response.set("pnoise_calls_per_iter", static_cast<int64_t>(256));
178 response.set("pnoise_float_us", result.pnoise_float_us);
179 response.set("pnoise_i16_us", result.pnoise_i16_us);
180 // Speedup expressed as float / i16 (>1 means i16 wins). Computed
181 // host-side too for cross-check; this is just convenience.
182 if (result.pnoise_i16_us > 0) {
183 double speedup = static_cast<double>(result.pnoise_float_us) /
184 static_cast<double>(result.pnoise_i16_us);
185 // Encode as basis-points (1000ths) to avoid float in the json wire fmt
186 response.set("speedup_x1000", static_cast<int64_t>(speedup * 1000.0));
187 }
188 return response;
189 });
190
191 // Register "wave8ExpandBenchmark" - PARLIO Wave8 expansion bench (#2526).
192 // Compares nibble-LUT vs byte-LUT vs batched byte-LUT, and times the full
193 // per-byte-position cost (expansion + 16-lane transpose) for both LUTs.
194 // Args: {iterations} (optional, default 30000, max 200000)
195 remote.bind("wave8ExpandBenchmark", [](const fl::json& args) -> fl::json {
196 fl::json response = fl::json::object();
197
198 int iters = 30000;
199 fl::json config;
200 if (args.is_object()) {
201 config = args;
202 } else if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
203 config = args[0];
204 }
205 if (!config.is_null() && config.contains("iterations") && config["iterations"].is_int()) {
206 iters = static_cast<int>(config["iterations"].as_int().value());
207 }
208
210
211 response.set("success", true);
212 response.set("iterations", static_cast<int64_t>(r.iters));
213 response.set("expand_nibble_us", static_cast<int64_t>(r.expand_nibble_us));
214 response.set("expand_byte_us", static_cast<int64_t>(r.expand_byte_us));
215 response.set("expand_batched_us", static_cast<int64_t>(r.expand_batched_us));
216 response.set("transpose16_nibble_us", static_cast<int64_t>(r.transpose16_nibble_us));
217 response.set("transpose16_byte_us", static_cast<int64_t>(r.transpose16_byte_us));
218 response.set("sink", static_cast<int64_t>(r.sink));
219 return response;
220 });
221
222 // Register "ieee754CodecTest" - on-device integer IEEE 754 decimal codec
223 // verification for #3039. No strtof/libm reference is used by the test.
224 remote.bind("ieee754CodecTest", [](const fl::json& args) -> fl::json {
225 (void)args;
226 const auto r = autoresearch::ieee754_check::run();
227 fl::json response = fl::json::object();
228 response.set("success", r.success);
229 response.set("tests_run", static_cast<int64_t>(r.tests_run));
230 response.set("tests_failed", static_cast<int64_t>(r.tests_failed));
231 response.set("first_failure", r.first_failure ? r.first_failure : "");
232 response.set("expected_bits", static_cast<int64_t>(r.expected_bits));
233 response.set("actual_bits", static_cast<int64_t>(r.actual_bits));
234 return response;
235 });
236
237 // Register "mathExpBenchmark" - fl::exp accuracy (ulp vs libm) and speed
238 // (fl::expf/exp vs the toolchain's expf/exp) on this core. #4288.
239 remote.bind("mathExpBenchmark", [](const fl::json& args) -> fl::json {
240 fl::u32 iters = 20000;
241 const fl::json config = args.is_array() && args.size() > 0 ? args[0] : args;
242 if (!config.is_null() && config.contains("iterations") && config["iterations"].is_int()) {
243 iters = static_cast<fl::u32>(config["iterations"].as_int().value());
244 }
245 const auto r = autoresearch::math_exp::run(iters);
246 fl::json response = fl::json::object();
247 response.set("success", r.success);
248 response.set("iterations", static_cast<int64_t>(r.iterations));
249 response.set("accuracy_points", static_cast<int64_t>(r.accuracy_points));
250 response.set("worst_ulp_float", static_cast<double>(r.worst_ulp_float));
251 response.set("worst_x_float", static_cast<double>(r.worst_x_float));
252 response.set("worst_ulp_double", r.worst_ulp_double);
253 response.set("worst_x_double", r.worst_x_double);
254 response.set("fl_expf_us", static_cast<int64_t>(r.fl_expf_us));
255 response.set("libm_expf_us", static_cast<int64_t>(r.libm_expf_us));
256 response.set("fl_exp_us", static_cast<int64_t>(r.fl_exp_us));
257 response.set("libm_exp_us", static_cast<int64_t>(r.libm_exp_us));
258 response.set("large_memory", static_cast<int64_t>(r.large_memory));
259 response.set("sink", static_cast<double>(r.sink));
260 return response;
261 });
262
263 // Register "fftCqOctaveBench" - CQ_OCTAVE per-frame cost on device
264 // (#4540). Times the first frame (cold scratch) and the average warm
265 // frame, and reports free heap before construction, after the first
266 // frame, and after all warm frames, so per-frame heap growth shows up as
267 // a difference between the last two. The input is a fixed two-tone
268 // signal so results are comparable across builds.
269 remote.bind("fftCqOctaveBench", [](const fl::json& args) -> fl::json {
270 fl::i32 samples = 512;
271 fl::i32 bands = 16;
272 fl::i32 frames = 50;
273 const fl::json config = args.is_array() && args.size() > 0 ? args[0] : args;
274 if (!config.is_null() && config.is_object()) {
275 if (config.contains("samples") && config["samples"].is_int())
276 samples = static_cast<fl::i32>(config["samples"].as_int().value());
277 if (config.contains("bands") && config["bands"].is_int())
278 bands = static_cast<fl::i32>(config["bands"].as_int().value());
279 if (config.contains("frames") && config["frames"].is_int())
280 frames = static_cast<fl::i32>(config["frames"].as_int().value());
281 }
282 fl::json response = fl::json::object();
283 response.set("samples", static_cast<int64_t>(samples));
284 response.set("bands", static_cast<int64_t>(bands));
285 response.set("frames", static_cast<int64_t>(frames));
286 if (samples < 64 || samples > 4096 || bands < 2 || bands > 512 ||
287 frames < 1 || frames > 10000) {
288 response.set("success", false);
289 response.set("error", "out_of_range");
290 return response;
291 }
292 const fl::i32 kSampleRate = 44100;
293 fl::vector<fl::i16> signal(static_cast<fl::size>(samples));
294 for (fl::i32 i = 0; i < samples; ++i) {
295 const float t = static_cast<float>(i) / static_cast<float>(kSampleRate);
296 const float v = 0.5f * fl::sinf(2.0f * static_cast<float>(FL_M_PI) * 220.0f * t) +
297 0.3f * fl::sinf(2.0f * static_cast<float>(FL_M_PI) * 1500.0f * t);
298 signal[static_cast<fl::size>(i)] = static_cast<fl::i16>(v * 20000.0f);
299 }
300 const fl::u32 heapBefore = static_cast<fl::u32>(fl::getFreeHeap().free_sram);
301 fl::audio::fft::Args fftArgs(samples, bands, 174.6f, 4698.3f, kSampleRate,
303 fl::audio::fft::Impl fft(fftArgs);
304 fl::audio::fft::Bins out(static_cast<fl::size>(bands));
305 const fl::u32 t0 = fl::micros();
306 fft.run(signal, &out);
307 const fl::u32 firstUs = fl::micros() - t0;
308 const fl::u32 heapAfterFirst = static_cast<fl::u32>(fl::getFreeHeap().free_sram);
309 const fl::u32 t1 = fl::micros();
310 for (fl::i32 f = 0; f < frames; ++f) {
311 fft.run(signal, &out);
312 }
313 const fl::u32 warmTotalUs = fl::micros() - t1;
314 const fl::u32 heapAfterWarm = static_cast<fl::u32>(fl::getFreeHeap().free_sram);
315 float checksum = 0.0f;
316 for (float b : out.raw()) checksum += b;
317 response.set("success", true);
318 response.set("first_frame_us", static_cast<int64_t>(firstUs));
319 response.set("warm_frame_us", static_cast<double>(warmTotalUs) / frames);
320 response.set("heap_before", static_cast<int64_t>(heapBefore));
321 response.set("heap_after_first", static_cast<int64_t>(heapAfterFirst));
322 response.set("heap_after_warm", static_cast<int64_t>(heapAfterWarm));
323 response.set("checksum", static_cast<double>(checksum));
324 return response;
325 });
326
327 // Register "mp3CodecTest" - on-device verification of the fixed-point MP3
328 // decoder. FastLED ships minimp3 in fixed-point mode on every embedded
329 // target, and that pipeline is where 32-bit-specific defects live: host
330 // testing on x86-64 cannot see an int64 add that costs two instructions
331 // plus carry here, signed overflow the optimiser assumed away, or a
332 // different alignment of the scratch arena. The check is therefore
333 // bit-exact -- an FNV-1a over the PCM against the value the same
334 // mp3dec_decode_frame_r produced on the host -- because the fixed-point
335 // decoder is deterministic and has no tolerance to spend.
336 remote.bind("mp3CodecTest", [](const fl::json& args) -> fl::json {
337 (void)args;
338 const auto r = autoresearch::mp3_check::run();
339 fl::json response = fl::json::object();
340 response.set("success", r.success);
341 response.set("streams_run", static_cast<int64_t>(r.streams_run));
342 response.set("streams_failed", static_cast<int64_t>(r.streams_failed));
343 response.set("first_failure", r.first_failure ? r.first_failure : "");
344 response.set("expected_fnv1a", static_cast<int64_t>(r.expected_fnv1a));
345 response.set("actual_fnv1a", static_cast<int64_t>(r.actual_fnv1a));
346 response.set("expected_samples",
347 static_cast<int64_t>(r.expected_samples));
348 response.set("actual_samples", static_cast<int64_t>(r.actual_samples));
349 response.set("decode_micros", static_cast<int64_t>(r.decode_micros));
350 response.set("scratch_bytes", static_cast<int64_t>(r.scratch_bytes));
351 response.set("shared_decoder_matched", r.shared_decoder_matched);
352 response.set("samples_decoded",
353 static_cast<int64_t>(r.samples_decoded));
354 response.set("frames_decoded",
355 static_cast<int64_t>(r.frames_decoded));
356 response.set("combined_fnv1a",
357 static_cast<int64_t>(r.combined_fnv1a));
358 response.set("verify_micros",
359 static_cast<int64_t>(r.verify_micros));
360 response.set("audio_micros",
361 static_cast<int64_t>(r.audio_micros));
362 return response;
363 });
364
365 // Register "parlioStreamValidate" - functional test of the production
366 // PARLIO ISR-chunked streaming engine (#2548). Exercises the 16-lane Wave8
367 // path which dispatches to wave8Transpose_16x4_bf1_pipe4 (BF1, #2559).
368 // Drives N back-to-back FastLED.show() cycles and verifies each completes
369 // within timeout. Returns per-iter timing so the host can diagnose stalls.
370 remote.bind("parlioStreamValidate", [this](const fl::json& args) -> fl::json {
371 fl::json response = fl::json::object();
372 auto invalidArgs = [](const char* message) -> fl::json {
373 fl::json error = fl::json::object();
374 error.set("success", false);
375 error.set("error", "InvalidArgs");
376 error.set("message", message);
377 return error;
378 };
379
380 int num_lanes = 16;
381 int num_leds = 256;
382 int iterations = 5;
383 int timeout_ms = 200;
384 int base_tx_pin = mState->pin_tx;
386 bool has_tx_pins = false;
387 bool num_lanes_provided = false;
388 for (int i = 0; i < autoresearch::parlio_stream::kMaxLanes; ++i) {
389 tx_pins[i] = -1;
390 }
391
392 fl::json config;
393 if (args.is_object()) {
394 config = args;
395 } else if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
396 config = args[0];
397 }
398 if (!config.is_null()) {
399 if (config.contains("baseTxPin") && config["baseTxPin"].is_int())
400 base_tx_pin = static_cast<int>(config["baseTxPin"].as_int().value());
401 if (config.contains("numLanes")) {
402 if (!config["numLanes"].is_int()) {
403 return invalidArgs("numLanes must be an integer");
404 }
405 num_lanes = static_cast<int>(config["numLanes"].as_int().value());
406 num_lanes_provided = true;
407 }
408 if (config.contains("numLeds") && config["numLeds"].is_int())
409 num_leds = static_cast<int>(config["numLeds"].as_int().value());
410 if (config.contains("iterations") && config["iterations"].is_int())
411 iterations = static_cast<int>(config["iterations"].as_int().value());
412 if (config.contains("timeoutMs") && config["timeoutMs"].is_int())
413 timeout_ms = static_cast<int>(config["timeoutMs"].as_int().value());
414 if (config.contains("txPins")) {
415 if (!config["txPins"].is_array()) {
416 return invalidArgs("txPins must be an integer array");
417 }
418 if (!num_lanes_provided) {
419 return invalidArgs("txPins requires numLanes");
420 }
421 if (num_lanes < 1 ||
423 return invalidArgs("numLanes out of range for txPins");
424 }
425
426 const fl::json pins = config["txPins"];
427 if (pins.size() != static_cast<size_t>(num_lanes)) {
428 return invalidArgs("txPins length must match numLanes");
429 }
430
431 for (int i = 0; i < num_lanes; ++i) {
432 if (!pins[i].is_int()) {
433 return invalidArgs("txPins entries must be integers");
434 }
435 int64_t pin_value = pins[i].as_int().value();
436 if (pin_value < 0 || pin_value >= 64) {
437 return invalidArgs("txPins entries must be in range 0..63");
438 }
439 tx_pins[i] = static_cast<int>(pin_value);
440 }
441 has_tx_pins = true;
442 base_tx_pin = tx_pins[0];
443 }
444 }
445
446 // Clamp inputs to safe ranges.
447 if (base_tx_pin < 0) base_tx_pin = 0;
448 if (num_lanes < 1) num_lanes = 1;
449 if (num_lanes > 16) num_lanes = 16;
450 if (num_leds < 1) num_leds = 1;
451 if (num_leds > 256) num_leds = 256;
452 if (iterations < 1) iterations = 1;
455 }
456 if (timeout_ms < 1) timeout_ms = 1;
457 if (timeout_ms > 5000) timeout_ms = 5000;
458
460 base_tx_pin, num_lanes, num_leds, iterations,
461 static_cast<uint32_t>(timeout_ms),
462 has_tx_pins ? tx_pins : nullptr);
463
464 response.set("success", true);
465 response.set("channelsOk", r.channels_ok);
466 response.set("completed", r.completed);
467 response.set("baseTxPin", static_cast<int64_t>(r.base_tx_pin));
468 int last_tx_pin = r.base_tx_pin + r.lanes - 1;
469 if (r.explicit_tx_pins) {
470 for (int i = r.lanes - 1; i >= 0; --i) {
471 if (r.tx_pins[i] >= 0) {
472 last_tx_pin = r.tx_pins[i];
473 break;
474 }
475 }
476 }
477 response.set("lastTxPin", static_cast<int64_t>(last_tx_pin));
478 response.set("explicitTxPins", r.explicit_tx_pins);
479 response.set("lanes", static_cast<int64_t>(r.lanes));
480 response.set("ledsPerLane", static_cast<int64_t>(r.leds_per_lane));
481 response.set("iterations", static_cast<int64_t>(r.iterations));
482 response.set("steadyAvgUs", static_cast<int64_t>(r.steady_avg_us));
483 response.set("steadyAvgShowUs", static_cast<int64_t>(r.steady_avg_show_us));
484 response.set("steadyAvgWaitUs", static_cast<int64_t>(r.steady_avg_wait_us));
485 response.set("failedIter", static_cast<int64_t>(r.failed_iter));
486 response.set("timeoutMs", static_cast<int64_t>(r.timeout_ms));
487 response.set("txDoneCount", static_cast<int64_t>(r.tx_done_count));
488 response.set("workerIsrCount", static_cast<int64_t>(r.worker_isr_count));
489 response.set("underrunCount", static_cast<int64_t>(r.underrun_count));
490 response.set("ringCount", static_cast<int64_t>(r.ring_count));
491 response.set("bytesTotal", static_cast<int64_t>(r.bytes_total));
492 response.set("bytesTransmitted", static_cast<int64_t>(r.bytes_transmitted));
493 response.set("ringError", r.ring_error);
494 response.set("hardwareIdle", r.hardware_idle);
495 fl::json response_tx_pins = fl::json::array();
496 for (int i = 0; i < r.lanes; ++i) {
497 response_tx_pins.push_back(static_cast<int64_t>(r.tx_pins[i]));
498 }
499 response.set("txPins", response_tx_pins);
500 fl::json per_iter = fl::json::array();
501 fl::json per_iter_show = fl::json::array();
502 fl::json per_iter_wait = fl::json::array();
503 for (int i = 0; i < r.iterations; ++i) {
504 per_iter.push_back(static_cast<int64_t>(r.per_iter_us[i]));
505 per_iter_show.push_back(static_cast<int64_t>(r.per_iter_show_us[i]));
506 per_iter_wait.push_back(static_cast<int64_t>(r.per_iter_wait_us[i]));
507 }
508 response.set("perIterUs", per_iter);
509 response.set("perIterShowUs", per_iter_show);
510 response.set("perIterWaitUs", per_iter_wait);
511 return response;
512 });
513
514 // Register "parlioEncodeBenchmark" - full PARLIO encode hot-loop bench with
515 // {scratch, output} in SRAM/PSRAM (4 combinations). Answers the PSRAM
516 // hypothesis + ISR-streaming feasibility on the byte-LUT path (#2526
517 // follow-up).
518 remote.bind("parlioEncodeBenchmark", [](const fl::json& args) -> fl::json {
519 fl::json response = fl::json::object();
520
521 int iters = 12000;
522 fl::json config;
523 if (args.is_object()) {
524 config = args;
525 } else if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
526 config = args[0];
527 }
528 if (!config.is_null() && config.contains("iterations") && config["iterations"].is_int()) {
529 iters = static_cast<int>(config["iterations"].as_int().value());
530 }
531
533
534 response.set("success", r.iters > 0);
535 response.set("iters", static_cast<int64_t>(r.iters));
536 response.set("lanes", static_cast<int64_t>(r.lanes));
537 response.set("leds_per_lane", static_cast<int64_t>(r.leds_per_lane));
538 response.set("scratchPsramOk", r.scratch_psram_ok);
539 response.set("outputPsramOk", r.output_psram_ok);
540 response.set("perpos_ss_us", static_cast<int64_t>(r.perpos_ss_us));
541 response.set("perpos_sp_us", static_cast<int64_t>(r.perpos_sp_us));
542 response.set("perpos_ps_us", static_cast<int64_t>(r.perpos_ps_us));
543 response.set("perpos_pp_us", static_cast<int64_t>(r.perpos_pp_us));
544 if (r.iters > 0) {
545 constexpr fl::u32 kFrameBytePositions = 256 * 3;
546 response.set("frame_ss_us", static_cast<int64_t>(
547 static_cast<fl::u64>(r.perpos_ss_us) * kFrameBytePositions / r.iters));
548 response.set("frame_sp_us", static_cast<int64_t>(
549 static_cast<fl::u64>(r.perpos_sp_us) * kFrameBytePositions / r.iters));
550 response.set("frame_ps_us", static_cast<int64_t>(
551 static_cast<fl::u64>(r.perpos_ps_us) * kFrameBytePositions / r.iters));
552 response.set("frame_pp_us", static_cast<int64_t>(
553 static_cast<fl::u64>(r.perpos_pp_us) * kFrameBytePositions / r.iters));
554 }
555 response.set("sink", static_cast<int64_t>(r.sink));
556 return response;
557 });
558
559 // Register "flexIoDeviceInfo" — introspect which concrete driver
560 // is bound to the `Bus::FLEX_IO` slot at runtime and return a
561 // device-info JSON structure the host can inspect. Peripheral-
562 // agnostic per the "Runtime driver selection is Bus::FLEX_IO"
563 // agent-doc rule (FastLED#3515 Phase B). Optional args:
564 // `{ "instance": 0 | 1 }` — currently only affects the
565 // `instance` echo in the response since the manager's public
566 // API doesn't yet expose per-(bus,instance) lookup. When
567 // IChannelDriver grows getBus/getInstance() methods, this handler
568 // will resolve to the specific driver on that slot.
569 remote.bind("flexIoDeviceInfo", [](const fl::json& args) -> fl::json {
570 fl::u8 instance = 0;
571 fl::json config;
572 if (args.is_object()) {
573 config = args;
574 } else if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
575 config = args[0];
576 }
577 if (!config.is_null() && config.contains("instance") &&
578 config["instance"].is_int()) {
579 int64_t iv = config["instance"].as_int().value();
580 if (iv >= 0 && iv <= 255) {
581 instance = static_cast<fl::u8>(iv);
582 }
583 }
585 response.set("success", true);
586 return response;
587 });
588}
589
590#endif // FL_PLATFORM_HAS_LARGE_MEMORY
Bus::FLEX_IO runtime-binding introspection RPC handler (FastLED#3515 Phase B).
On-device verification for the integer-only IEEE 754 decimal codec.
On-device accuracy and speed of fl::exp against the toolchain libm (#4288).
On-device verification of the fixed-point MP3 decoder.
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.
AudioAnalyzeFFT1024 fft
fl::unique_ptr< fl::Remote > remote
Definition RpcClient.ino:43
int pins[]
Definition Spi.ino:11
fl::net::ble — BLE GATT transport layer for JSON-RPC
SPI encoder configuration for clocked LED chipsets.
fl::shared_ptr< AutoResearchState > mState
void bindMathMethods(fl::Remote &remote)
JSON-RPC server with scheduling support.
Definition remote.h:42
bool is_object() const FL_NO_EXCEPT
Definition json.h:391
fl::optional< i64 > as_int() const FL_NO_EXCEPT
Definition json.h:398
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
Definition json.h:866
bool is_null() const FL_NO_EXCEPT
Definition json.h:381
void push_back(const json &value) FL_NO_EXCEPT
Definition json.h:918
bool contains(size_t idx) const FL_NO_EXCEPT
Definition json.h:790
static json object() FL_NO_EXCEPT
Definition json.h:857
static json array() FL_NO_EXCEPT
Definition json.h:853
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
#define FL_M_PI
Definition math.h:34
PerlinBenchResult runPerlinBenchmark(int iters)
fl::json buildDeviceInfo(fl::u8 instance) FL_NO_EXCEPT
Build the device-info JSON returned by the flexIoDeviceInfo RPC.
Result run(fl::u32 iterations)
Runs the accuracy sweep and the timed loops. iterations = calls per loop.
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.
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)
unsigned char u8
Definition stdint.h:131
HeapInfo getFreeHeap()
Query available heap memory.
Definition heap.cpp.hpp:67
float sinf(float value) FL_NO_EXCEPT
Definition math.h:352
fl::u32 micros()
Universal microsecond timer - returns microseconds since system startup.
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
float add(float &a, float &b)
corkscrew_args args
Definition old.h:149
Promise-based fluent API for FastLED - standalone async primitives.
Umbrella header for SIMD subsystem.
fl::u32 uint32_t
Definition stdint.h:218
fl::size free_sram
Free SRAM in bytes (internal fast memory)
Definition heap.h:17