FastLED 3.10.6
Loading...
Searching...
No Matches
AutoResearchRemoteRunParallelTest.cpp
Go to the documentation of this file.
1// runParallelTestImpl. Extracted from AutoResearchRemote.cpp as part of #3132 / meta #3127.
2
4#if FL_PLATFORM_HAS_LARGE_MEMORY
5
6// Legacy debug macros (no-ops, kept for debugTest RPC function)
7#define DEBUG_PRINT(x) do {} while(0)
8#define DEBUG_PRINTLN(x) do {} while(0)
9
10#include "AutoResearchRemote.h"
11#include "AutoResearchBle.h"
12#include "AutoResearchNet.h"
13#include "AutoResearchOta.h"
15#include "fl/system/heap.h"
16#include "Common.h"
17#include "AutoResearchTest.h"
18#include "AutoResearchHelpers.h"
19#include "fl/stl/sstream.h"
20#include "fl/stl/unique_ptr.h"
21#include "fl/stl/optional.h"
22#include "fl/stl/json.h"
23#include "fl/task/task.h"
24#include "fl/task/executor.h"
26#include "fl/stl/atomic.h"
27#include "fl/task/promise.h"
28#include "fl/math/simd.h"
29#include "AutoResearchSimd.h"
35#include "fl/chipsets/spi.h"
36#include "fl/channels/config.h"
37#include <Arduino.h>
38
39#include "fl/net/ble.h"
40
41#include "fl/codec/h264.h"
42#include "fl/codec/mp4_parser.h"
44#include "fl/fx/frame.h"
45
47 fl::json response = fl::json::object();
48
49 // Expects: {drivers: [{driver: "PARLIO", laneSizes: [100]}, {driver: "LCD_RGB", laneSizes: [100]}],
50 // pattern?: "MSB_LSB_A", iterations?: 1, timing?: "WS2812B-V5"}
51 if (!args.is_object()) {
52 response.set("success", false);
53 response.set("error", "InvalidArgs");
54 response.set("message", "Expected {drivers: [{driver, laneSizes}, ...]}");
55 return response;
56 }
57
58 fl::json config = args;
59
60 // 1. Extract drivers array (required)
61 if (!config.contains("drivers") || !config["drivers"].is_array()) {
62 response.set("success", false);
63 response.set("error", "MissingDrivers");
64 response.set("message", "Required field 'drivers' (array of {driver, laneSizes}) missing");
65 return response;
66 }
67
68 fl::json drivers_json = config["drivers"];
69 if (drivers_json.size() < 2) {
70 response.set("success", false);
71 response.set("error", "TooFewDrivers");
72 response.set("message", "Parallel test requires at least 2 drivers");
73 return response;
74 }
75
76 // 2. Extract shared optional parameters
77 fl::string pattern = "MSB_LSB_A";
78 if (config.contains("pattern") && config["pattern"].is_string()) {
79 pattern = config["pattern"].as_string().value();
80 }
81
82 int iterations = 1;
83 if (config.contains("iterations") && config["iterations"].is_int()) {
84 iterations = static_cast<int>(config["iterations"].as_int().value());
85 if (iterations < 1) iterations = 1;
86 }
87
88 fl::string timing_name = "WS2812B-V5";
89 if (config.contains("timing") && config["timing"].is_string()) {
90 timing_name = config["timing"].as_string().value();
91 }
92
93 // Get timing configuration
94 fl::ChipsetTimingConfig resolved_timing;
96 if (timing_name == "UCS7604-800KHZ") {
99 } else {
101 resolved_encoder = fl::encoder_for<fl::TIMING_WS2812B_V5>();
102 }
103 fl::NamedTimingConfig timing_config(resolved_timing, timing_name.c_str(), resolved_encoder);
104
105 // 3. Parse each driver entry and validate
106 struct DriverEntry {
107 fl::string name;
108 fl::vector<int> lane_sizes;
109 int pin_tx;
110 };
111 fl::vector<DriverEntry> driver_entries;
112
113 int next_pin = mState->pin_tx; // Start from the configured TX pin
114 for (fl::size i = 0; i < drivers_json.size(); i++) {
115 if (!drivers_json[i].is_object()) {
116 response.set("success", false);
117 response.set("error", "InvalidDriverEntry");
118 fl::sstream msg;
119 msg << "drivers[" << i << "] must be an object {driver, laneSizes}";
120 response.set("message", msg.str().c_str());
121 return response;
122 }
123
124 fl::json entry = drivers_json[i];
125
126 // Validate driver name
127 if (!entry.contains("driver") || !entry["driver"].is_string()) {
128 response.set("success", false);
129 response.set("error", "MissingDriverName");
130 fl::sstream msg;
131 msg << "drivers[" << i << "] missing 'driver' (string) field";
132 response.set("message", msg.str().c_str());
133 return response;
134 }
135 fl::string driver_name = entry["driver"].as_string().value();
136
137 // Validate driver exists
138 bool driver_found = false;
139 for (fl::size j = 0; j < mState->drivers_available.size(); j++) {
140 if (mState->drivers_available[j].name == driver_name) {
141 driver_found = true;
142 break;
143 }
144 }
145 if (!driver_found) {
146 response.set("success", false);
147 response.set("error", "UnknownDriver");
148 fl::sstream msg;
149 msg << "Driver '" << driver_name.c_str() << "' not available";
150 response.set("message", msg.str().c_str());
151 return response;
152 }
153
154 // Validate laneSizes
155 if (!entry.contains("laneSizes") || !entry["laneSizes"].is_array()) {
156 response.set("success", false);
157 response.set("error", "MissingLaneSizes");
158 fl::sstream msg;
159 msg << "drivers[" << i << "] missing 'laneSizes' (array) field";
160 response.set("message", msg.str().c_str());
161 return response;
162 }
163
164 fl::json lane_sizes_json = entry["laneSizes"];
165 fl::vector<int> lane_sizes;
166 for (fl::size li = 0; li < lane_sizes_json.size(); li++) {
167 if (!lane_sizes_json[li].is_int()) {
168 response.set("success", false);
169 response.set("error", "InvalidLaneSizeType");
170 return response;
171 }
172 int size = static_cast<int>(lane_sizes_json[li].as_int().value());
173 if (size <= 0) {
174 response.set("success", false);
175 response.set("error", "InvalidLaneSize");
176 return response;
177 }
178 lane_sizes.push_back(size);
179 }
180
181 // Extract optional pinTx per driver (default: auto-assign consecutive pins)
182 int pin_tx = next_pin;
183 if (entry.contains("pinTx") && entry["pinTx"].is_int()) {
184 pin_tx = static_cast<int>(entry["pinTx"].as_int().value());
185 }
186
187 DriverEntry de;
188 de.name = driver_name;
189 de.lane_sizes = lane_sizes;
190 de.pin_tx = pin_tx;
191 driver_entries.push_back(de);
192
193 // Advance pin for next driver (skip past this driver's lanes)
194 next_pin = pin_tx + (int)lane_sizes.size();
195 }
196
197 // ========== EXECUTION ==========
198 uint32_t start_ms = millis();
199
200 // Step 1: Enable all requested drivers (not exclusive)
201 // First disable all, then enable only the ones we want
202 for (fl::size i = 0; i < mState->drivers_available.size(); i++) {
203 FastLED.setDriverEnabled(mState->drivers_available[i].name.c_str(), false);
204 }
205 for (fl::size i = 0; i < driver_entries.size(); i++) {
206 FastLED.setDriverEnabled(driver_entries[i].name.c_str(), true);
207 }
208
209 // Step 2: Create channels for each driver using affinity binding
210 fl::vector<fl::unique_ptr<fl::vector<CRGB>>> all_led_arrays;
211 fl::vector<fl::ChannelPtr> all_channels;
212
213 for (fl::size di = 0; di < driver_entries.size(); di++) {
214 const auto& de = driver_entries[di];
215 for (fl::size li = 0; li < de.lane_sizes.size(); li++) {
216 auto leds = fl::make_unique<fl::vector<CRGB>>(de.lane_sizes[li]);
217
218 // Set up channel with typed driver selection (#2459).
219 // The pre-#2459 string `mAffinity` field is gone — translate the
220 // discovered driver name back to a `fl::Bus` enum value.
221 fl::ChannelOptions opts;
222 {
223 const fl::string& n = de.name;
224 if (n == "RMT") opts.mBus = fl::Bus::RMT;
225 else if (n == "PARLIO") opts.mBus = fl::Bus::FLEX_IO;
226 else if (n == "SPI") opts.mBus = fl::Bus::SPI;
227 else if (n == "I2S") opts.mBus = fl::Bus::FLEX_IO;
228 else if (n == "I2S_SPI") opts.mBus = fl::Bus::FLEX_IO;
229 else if (n == "LCD_RGB") { opts.mBus = fl::Bus::FLEX_IO; opts.mBusWhich = 1; }
230 else if (n == "LCD_SPI") opts.mBus = fl::Bus::FLEX_IO;
231 else if (n == "LCD_CLOCKLESS") opts.mBus = fl::Bus::FLEX_IO;
232 else if (n == "UART") opts.mBus = fl::Bus::UART;
233 else if (n == "UART0") { opts.mBus = fl::Bus::UART; opts.mBusWhich = 0; }
234 else if (n == "UART1") { opts.mBus = fl::Bus::UART; opts.mBusWhich = 1; }
235 else if (n == "FLEX_IO") { opts.mBus = fl::Bus::FLEX_IO; opts.mBusWhich = 1; }
236 else if (n == "PIO0") { opts.mBus = fl::Bus::FLEX_IO; opts.mBusWhich = 0; }
237 else if (n == "PIO1") { opts.mBus = fl::Bus::FLEX_IO; opts.mBusWhich = 1; }
238 else if (n == "PIO2") { opts.mBus = fl::Bus::FLEX_IO; opts.mBusWhich = 2; }
239 else if (n == "OBJECT_FLED") opts.mBus = fl::Bus::FLEX_IO;
240 else if (n == "LPUART") opts.mBus = fl::Bus::UART;
241 else if (n == "BIT_BANG") opts.mBus = fl::Bus::BIT_BANG;
242 else if (n == "STUB") opts.mBus = fl::Bus::BIT_BANG;
243 // else: leave Bus::AUTO; priority dispatch will pick.
244 }
245
246 fl::ChannelConfig channel_config(
247 de.pin_tx + (int)li, // Consecutive pins per lane
248 timing_config.timing,
249 fl::span<CRGB>(leds->data(), leds->size()),
250 RGB,
251 opts
252 );
253
254 auto channel = FastLED.add(channel_config);
255 if (!channel) {
256 // Clean up already-created channels
258 response.set("success", false);
259 response.set("error", "ChannelCreationFailed");
260 fl::sstream msg;
261 msg << "Failed to create channel for driver '" << de.name.c_str()
262 << "' lane " << li;
263 response.set("message", msg.str().c_str());
264 return response;
265 }
266
267 all_channels.push_back(channel);
268 all_led_arrays.push_back(fl::move(leds));
269 }
270 }
271
272 // Step 3: Set LED data patterns and call show()
273 // Use a simple pattern: fill each driver's LEDs with a known color
274 int array_idx = 0;
275 for (fl::size di = 0; di < driver_entries.size(); di++) {
276 const auto& de = driver_entries[di];
277 for (fl::size li = 0; li < de.lane_sizes.size(); li++) {
278 auto& leds = *all_led_arrays[array_idx];
279 for (int led = 0; led < de.lane_sizes[li]; led++) {
280 // Pattern: alternate colors per driver for visual distinction
281 if (di == 0) {
282 leds[led] = CRGB(0xFF, 0x00, 0x00); // Red for first driver
283 } else {
284 leds[led] = CRGB(0x00, 0xFF, 0x00); // Green for second driver
285 }
286 }
287 array_idx++;
288 }
289 }
290
291 // Step 4: Call show() - both drivers transmit simultaneously
292 bool show_success = true;
293 uint32_t show_start = micros();
294 for (int iter = 0; iter < iterations; iter++) {
295 FastLED.show();
296 show_success = FastLED.wait(5000) && show_success; // 5 second timeout for DMA completion
297 }
298 uint32_t show_duration_us = micros() - show_start;
299
300 // Step 5: Validate first driver's output via RX loopback (if available)
301 // Only the first driver (PARLIO) is typically connected to the RX pin
302 bool rx_validation_passed = true;
303 bool rx_validation_attempted = false;
304 fl::vector<fl::RunResult> run_results;
305 const bool is_rp_pio_pair = driver_entries.size() == 2 &&
306 ((driver_entries[0].name == "PIO0" && driver_entries[1].name == "PIO1") ||
307 (driver_entries[0].name == "PIO1" && driver_entries[1].name == "PIO0"));
308
309 // PIO0+PIO1 is a resource-concurrency test. Its two transmitters have
310 // already been exact-byte loopback-validated independently; re-entering
311 // capture after their shared show() creates another channel while both
312 // engines remain configured and tests a different resource topology.
313 if (mState->rx_channel && driver_entries.size() > 0 && !is_rp_pio_pair) {
314 const auto& primary_driver = driver_entries[0];
315
316 // Only attempt RX validation if the primary driver's pin matches our TX pin
317 if (primary_driver.pin_tx == mState->pin_tx) {
318 rx_validation_attempted = true;
319
320 // Create validation config for the primary driver
321 fl::vector<fl::ChannelConfig> tx_configs;
322 int led_array_offset = 0;
323 for (fl::size li = 0; li < primary_driver.lane_sizes.size(); li++) {
324 tx_configs.push_back(fl::ChannelConfig(
325 primary_driver.pin_tx + (int)li,
326 timing_config.timing,
327 fl::span<CRGB>(all_led_arrays[led_array_offset + li]->data(),
328 all_led_arrays[led_array_offset + li]->size()),
329 RGB
330 ));
331 }
332
333 fl::AutoResearchConfig autoresearch_config(
334 timing_config.timing,
335 timing_config.name,
336 tx_configs,
337 primary_driver.name.c_str(),
338 mState->rx_channel,
339 mState->rx_buffer,
340 primary_driver.lane_sizes[0],
342 timing_config.encoder
343 );
344
345 int total_tests = 0;
346 int passed_tests = 0;
347 uint32_t val_show_duration_ms = 0;
348
350 val_show_duration_ms, &run_results);
351
352 rx_validation_passed = (total_tests > 0) && (passed_tests == total_tests);
353 }
354 }
355
356 // Step 6: Clean up channels
358
359 uint32_t duration_ms = millis() - start_ms;
360 int total_tests = 1;
361 int passed_tests = show_success ? 1 : 0;
362 const int primary_tx_pin = driver_entries.empty()
363 ? mState->pin_tx
364 : driver_entries[0].pin_tx;
365 int capture_evidence_bytes = 0;
366 int capture_evidence_raw_edges = 0;
367 for (fl::size ri = 0; ri < run_results.size(); ri++) {
368 const auto& rr = run_results[ri];
369 if (rr.capturedBytes > 0) {
370 capture_evidence_bytes += rr.capturedBytes;
371 }
372 if (rr.rawEdgesAfterWait > capture_evidence_raw_edges) {
373 capture_evidence_raw_edges = rr.rawEdgesAfterWait;
374 }
375 }
376 if (rx_validation_attempted) {
377 total_tests++;
378 if (rx_validation_passed) {
379 passed_tests++;
380 }
381 }
382
383 // ========== RESPONSE ==========
384 response.set("success", true);
385 response.set("passed", show_success && rx_validation_passed);
386 response.set("totalTests", static_cast<int64_t>(total_tests));
387 response.set("passedTests", static_cast<int64_t>(passed_tests));
388 response.set("failedTests", static_cast<int64_t>(total_tests - passed_tests));
389 response.set("requestedTxPin", static_cast<int64_t>(primary_tx_pin));
390 response.set("requestedRxPin", static_cast<int64_t>(mState->pin_rx));
391 response.set("actualTxPin", static_cast<int64_t>(primary_tx_pin));
392 response.set("actualRxPin", static_cast<int64_t>(mState->pin_rx));
393 fl::string capture_backend = mState->rx_channel
394 ? mState->rx_channel->getEngineName()
395 : fl::string("none");
396 response.set("captureBackend", capture_backend.c_str());
397 response.set("captureEvidenceExpected", rx_validation_attempted);
398 response.set("captureEvidenceBytes",
399 static_cast<int64_t>(capture_evidence_bytes));
400 response.set("captureEvidenceRawEdges",
401 static_cast<int64_t>(capture_evidence_raw_edges));
402 response.set("duration_ms", static_cast<int64_t>(duration_ms));
403 response.set("show_duration_us", static_cast<int64_t>(show_duration_us));
404 response.set("show_success", show_success);
405 response.set("concurrent_resource_test", is_rp_pio_pair);
406 response.set("iterations", static_cast<int64_t>(iterations));
407 response.set("rx_validation_attempted", rx_validation_attempted);
408 response.set("rx_validation_passed", rx_validation_passed);
409
410 // List drivers tested
411 fl::json drivers_tested = fl::json::array();
412 for (fl::size i = 0; i < driver_entries.size(); i++) {
413 fl::json drv = fl::json::object();
414 drv.set("driver", driver_entries[i].name.c_str());
415 drv.set("pinTx", static_cast<int64_t>(driver_entries[i].pin_tx));
416 fl::json sizes = fl::json::array();
417 for (int s : driver_entries[i].lane_sizes) {
418 sizes.push_back(static_cast<int64_t>(s));
419 }
420 drv.set("laneSizes", sizes);
421 drv.set("laneCount", static_cast<int64_t>(driver_entries[i].lane_sizes.size()));
422 drivers_tested.push_back(drv);
423 }
424 response.set("drivers", drivers_tested);
425 response.set("pattern", pattern.c_str());
426
427 return response;
428}
429
430#endif // FL_PLATFORM_HAS_LARGE_MEMORY
CRGB leds[1]
Full PARLIO encode bench for ESP32-P4 (post-byte-LUT, #2526 landed).
PARLIO ISR-streaming functional validation (#2548 deep-dive follow-up).
void autoResearchChipsetTiming(fl::AutoResearchConfig &config, int &total, int &passed, uint32_t &out_show_duration_ms, fl::vector< fl::RunResult > *out_results=nullptr, int num_runs_per_pattern=1)
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.
int passed_tests
Definition SIMD.ino:75
int total_tests
Definition SIMD.ino:74
fl::net::ble — BLE GATT transport layer for JSON-RPC
SPI encoder configuration for clocked LED chipsets.
fl::json runParallelTestImpl(const fl::json &args)
fl::shared_ptr< AutoResearchState > mState
void setDriverEnabled(const char *name, bool enabled)
Enable or disable a channel driver by name at runtime.
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.
const char * c_str() const FL_NO_EXCEPT
size_t size() const FL_NO_EXCEPT
Definition json.h:798
fl::optional< fl::string > as_string() const FL_NO_EXCEPT
Definition json.h:443
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
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
string str() const FL_NO_EXCEPT
Definition strstream.h:43
fl::size size() const FL_NO_EXCEPT
bool empty() const FL_NO_EXCEPT
void push_back(const T &value) FL_NO_EXCEPT
Definition vector.h:624
constexpr EOrder RGB
Definition eorder.h:17
Task executor — runs registered task runners and manages the run loop.
@ CHANNELS
Remove all channels from controller list.
Definition FastLED.h:668
fl::CRGB CRGB
Definition crgb.h:25
Platform-abstracted heap memory query functions.
FastLED's Elegant JSON Library: fl::json
constexpr remove_reference< T >::type && move(T &&t) FL_NO_EXCEPT
Definition move.h:28
ClocklessEncoder
Identifies which encoder to use for clockless chipsets in the Channel API.
@ CLOCKLESS_ENCODER_WS2812
Default, no preamble (WS2812 and compatible)
constexpr ChipsetTimingConfig makeTimingConfig() FL_NO_EXCEPT
Convert compile-time CHIPSET type to runtime timing config.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
Definition unique_ptr.h:261
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
fl::u32 micros()
Universal microsecond timer - returns microseconds since system startup.
@ FLEX_IO
Definition bus.h:36
@ SPI
Definition bus.h:31
@ BIT_BANG
Definition bus.h:29
@ RMT
Definition bus.h:35
@ UART
Definition bus.h:30
constexpr ClocklessEncoder encoder_for() FL_NO_EXCEPT
Extract the encoder selector from a compile-time TIMING type.
@ RMT
RMT-based receiver (ESP32)
Definition rx.h:167
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