FastLED 3.10.8
Loading...
Searching...
No Matches
AutoResearchRemote.cpp
Go to the documentation of this file.
1// examples/AutoResearch/AutoResearchRemote.cpp
2//
3// Remote RPC control system implementation for AutoResearch sketch.
4//
5// ARCHITECTURE:
6// - RPC responses use printJsonRaw()/printStreamRaw() which bypass fl::println
7// - Test execution wrapped in fl::ScopedLogDisable to suppress debug noise
8// - This provides clean, parseable JSON output without FL_DBG/FL_PRINT spam
9
10// Gate out under low-memory mode -- the LowMemory bring-up surface
11// (AutoResearchLowMemory.h) binds its own minimal pinToggleRx / echo /
12// ws2812SctTest handlers directly; the full remote-control wiring here
13// references the AutoResearchTest / AutoResearchBle helpers and pulls in
14// the entire RPC dispatch surface which doesn't fit Low-memory budgets.
15// Matches the conditional structure in AutoResearch.ino itself.
17#if FL_PLATFORM_HAS_LARGE_MEMORY
18
19// Legacy debug macros (no-ops, kept for debugTest RPC function)
20#define DEBUG_PRINT(x) do {} while(0)
21#define DEBUG_PRINTLN(x) do {} while(0)
22
23#include "AutoResearchRemote.h"
24#include "AutoResearchBle.h"
25#include "AutoResearchNet.h"
26#include "AutoResearchOta.h"
28#include "fl/system/heap.h"
29#include "Common.h"
30#include "AutoResearchTest.h"
31#include "AutoResearchHelpers.h"
32#include "fl/stl/sstream.h"
33#include "fl/stl/unique_ptr.h"
34#include "fl/stl/optional.h"
35#include "fl/stl/json.h"
36#include "fl/stl/cstring.h"
37#include "fl/task/task.h"
38#include "fl/task/executor.h"
39#include <Arduino.h>
41#include "fl/stl/atomic.h"
42#include "fl/task/promise.h"
43
44#include "fl/system/pin_probe.h" // pin discovery skip rules (#3446)
45#include "fl/math/simd.h"
46#include "AutoResearchSimd.h"
47#include "AutoResearchAnimartrixBench.h" // animartrixPerlinBench RPC (#2628 follow-up)
48#include "AutoResearchWave8Expand.h" // #2526 wave8ExpandBenchmark RPC
49#include "AutoResearchParlioEncode.h" // parlioEncodeBenchmark RPC (#2526 follow-up)
50#include "AutoResearchTimingDrift.h" // timingDriftTest RPC (#2994 repro)
51#include "AutoResearchParlioStream.h" // parlioStreamValidate RPC (#2548 follow-up)
52#include "AutoResearchFlexIo.h" // flexIoDeviceInfo RPC (#3515 Phase B)
53#include "fl/system/heap.h"
54#include "fl/chipsets/spi.h"
55#include "fl/channels/config.h"
56#include <Arduino.h>
57
58#include "fl/net/ble.h"
59
60// Codec headers for decodeFile RPC
61#include "fl/codec/h264.h"
62#include "fl/codec/mp4_parser.h"
64#include "fl/fx/frame.h"
65
66
67// ============================================================================
68// Raw Serial Output Functions (bypass fl::println and ScopedLogDisable)
69// ============================================================================
70
71void printJsonRaw(const fl::json& json, const char* prefix) {
72 // Serialize and print response
73 fl::string formatted = fl::formatJsonResponse(json, prefix);
74 Serial.println(formatted.c_str()); // ok serial - ok autoresearch rpc serial - RPC response boundary
75 Serial.flush(); // ok serial - ok autoresearch rpc serial - host waits for RPC boundary
76}
77
78namespace {
79
80void printRemoteResponseRaw(const fl::json& response) {
81 fl::string formatted = fl::formatJsonResponse(response, "REMOTE: ");
82 Serial.println(formatted.c_str()); // ok serial - ok autoresearch rpc serial - RPC response boundary
83 Serial.flush(); // ok serial - ok autoresearch rpc serial - host waits for RPC boundary
84}
85
86void printRemoteStreamRaw(fl::JsonStreamCallback writeJson) {
87 Serial.print("REMOTE: "); // ok serial - ok autoresearch rpc serial - RPC response boundary
88
89 fl::JsonStreamWriter writer([](const char* data, fl::size len) {
90 Serial.write(reinterpret_cast<const uint8_t*>(data), static_cast<size_t>(len)); // ok serial - ok autoresearch rpc serial - streamed JSON bytes
91 });
92 if (writeJson) {
93 writeJson(writer);
94 }
95 writer.flush();
96 Serial.println(); // ok serial - ok autoresearch rpc serial - RPC response boundary
97 Serial.flush(); // ok serial - ok autoresearch rpc serial - host waits for RPC boundary
98}
99
100} // namespace
101
102void printStreamRaw(const char* messageType, const fl::json& data) {
103 // Build pure JSONL message: RESULT: {"type":"...", ...data}
104 fl::json output = fl::json::object();
105 output.set("type", messageType);
106
107 // Copy all fields from data into output
108 if (data.is_object()) {
109 auto keys = data.keys();
110 for (fl::size i = 0; i < keys.size(); i++) {
111 output.set(keys[i].c_str(), data[keys[i]]);
112 }
113 }
114
115 // Use fl:: serial transport for consistent formatting
116 fl::string formatted = fl::formatJsonResponse(output, "RESULT: ");
117 Serial.println(formatted.c_str()); // ok serial - ok autoresearch rpc serial - stream response boundary
118}
119
120// ============================================================================
121// Standard JSON-RPC Response Format (Phase 4 Refactoring)
122// ============================================================================
123// Return codes:
124// 0 = SUCCESS
125// 1 = TEST_FAILED
126// 2 = HARDWARE_ERROR (GPIO not connected)
127// 3 = INVALID_ARGS
128
129enum class ReturnCode : int {
130 SUCCESS = 0,
131 TEST_FAILED = 1,
132 HARDWARE_ERROR = 2,
133 INVALID_ARGS = 3
134};
135
136fl::json makeResponse(bool success, ReturnCode returnCode, const char* message,
137 const fl::json& data = fl::json()) {
139 r.set("success", success);
140 r.set("returnCode", static_cast<int64_t>(static_cast<int>(returnCode)));
141 r.set("message", message);
142 if (!data.is_null() && data.has_value()) {
143 r.set("data", data);
144 }
145 return r;
146}
147
148// No forward declarations needed - using one-test-per-RPC architecture
149
151 fl::json response = fl::json::object();
152
153 // Parse optional arguments: [{startPin: int, endPin: int, autoApply: bool}]
154 //
155 // FastLED #3446: default range stays at the historically-safe 0-8
156 // window so an unparameterised call cannot accidentally drive a
157 // boot/strap/USB pin into reset. Callers that need to sweep higher
158 // pins (e.g. the user-reported (33, 34) short) should issue
159 // multiple `findConnectedPins` calls with overlapping 8-pin windows
160 // (0-8, 8-16, 16-24, ...) from the host side. The Python wrapper
161 // `run_pin_discovery_segmented` in `ci/autoresearch/gpio.py` does
162 // exactly that.
163 int start_pin = 0;
164 int end_pin = 8;
165 bool auto_apply = true; // If true, automatically apply found pins
166
167 if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
168 fl::json config = args[0];
169 if (config.contains("startPin") && config["startPin"].is_int()) {
170 start_pin = static_cast<int>(config["startPin"].as_int().value());
171 }
172 if (config.contains("endPin") && config["endPin"].is_int()) {
173 end_pin = static_cast<int>(config["endPin"].as_int().value());
174 }
175 if (config.contains("autoApply") && config["autoApply"].is_bool()) {
176 auto_apply = config["autoApply"].as_bool().value();
177 }
178 }
179
180 // Validate range
181 if (start_pin < 0 || start_pin > 48 || end_pin < 0 || end_pin > 48 || start_pin >= end_pin) {
182 response.set("error", "InvalidRange");
183 response.set("message", "Pin range must be 0-48 with startPin < endPin");
184 return response;
185 }
186 // FastLED #3446: the platform owns the per-chip list of pins a probe
187 // must not touch (SPI flash / PSRAM pads, flash power, USB-JTAG, the
188 // console UART) and of pins it may read but not drive (input-only).
189 // See fl/system/pin_probe.h; platforms without such pins report none.
190 //
191 // We surface skipped pins in the response so the host log says
192 // exactly which pins were forbidden and why, turning silent
193 // "no jumper found" into actionable "pins 6-11 are flash, 20 is
194 // USB-JTAG, 34-39 are input-only".
195
196 fl::json skipped_pins = fl::json::array();
197 // Each pin is recorded once: the all-pairs scan below visits a pin many
198 // times, and an unbounded array exhausts heap on ESP32.
199 fl::u64 skipped_mask = 0;
200 auto recordSkip = [&](int p, const char* reason) {
201 const fl::u64 bit = fl::pinMaskBit(p);
202 if (skipped_mask & bit) {
203 return;
204 }
205 skipped_mask |= bit;
206 fl::json entry = fl::json::object();
207 entry.set("pin", static_cast<int64_t>(p));
208 entry.set("reason", reason);
209 skipped_pins.push_back(entry);
210 };
211
212 FL_DBG("[PIN PROBE] Searching for connected pin pairs in range " << start_pin << "-" << end_pin);
213
214 // Helper lambda to test if two pins are connected
215 auto testPinPair = [](int tx, int rx) -> bool {
216 // Test 1: TX drives LOW, RX has pullup → RX should read LOW if connected
217 pinMode(tx, OUTPUT);
218 pinMode(rx, INPUT_PULLUP);
219 digitalWrite(tx, LOW);
220 delay(2); // Allow signal to settle
221 int rx_when_tx_low = digitalRead(rx);
222
223 // Test 2: TX drives HIGH → RX should read HIGH if connected
224 digitalWrite(tx, HIGH);
225 delay(2); // Allow signal to settle
226 int rx_when_tx_high = digitalRead(rx);
227
228 // Restore pins to safe state
229 pinMode(tx, INPUT);
230 pinMode(rx, INPUT);
231
232 return (rx_when_tx_low == LOW) && (rx_when_tx_high == HIGH);
233 };
234
235 // Search every pin pair (a, b) in the range, both directions. Bench
236 // jumpers are not always on neighbouring pins (a Teensy 4.0 wired across
237 // the board was invisible to the old adjacent-only (n, n+1) scan).
238 // Cost: 2 probes x ~4 ms per pair; a 0-40 range is ~6 s.
239 int found_tx = -1;
240 int found_rx = -1;
241
242 // Drive `tx`, read `rx`; skips (and records) a driver that can't output.
243 auto tryDirection = [&](int tx, int rx) -> bool {
244 if (const char* why = fl::pinProbeDriveSkipReason(tx)) {
245 recordSkip(tx, why);
246 return false;
247 }
248 return testPinPair(tx, rx);
249 };
250
251 for (int a = start_pin; a <= end_pin && found_tx < 0; a++) {
252 // FastLED #3446: hands off the platform's reserved pads (SPI
253 // flash 6-11, USB-JTAG, ...) -- driving them locks the chip up or
254 // corrupts the live flash transaction -- and off this board's
255 // console and in-use PSRAM pins (fl::pinProbeSkipReason).
256 if (const char* why = fl::pinProbeSkipReason(a)) {
257 recordSkip(a, why);
258 continue;
259 }
260 for (int b = a + 1; b <= end_pin; b++) {
261 if (const char* why = fl::pinProbeSkipReason(b)) {
262 recordSkip(b, why);
263 continue;
264 }
265 if (tryDirection(a, b)) {
266 found_tx = a;
267 found_rx = b;
268 FL_DBG("[PIN PROBE] Found connected pair: TX=" << found_tx << " -> RX=" << found_rx);
269 break;
270 }
271 if (tryDirection(b, a)) {
272 found_tx = b; // Reversed: b drives a
273 found_rx = a;
274 FL_DBG("[PIN PROBE] Found connected pair (reversed): TX=" << found_tx << " -> RX=" << found_rx);
275 break;
276 }
277 }
278 }
279
280 fl::json search_range = fl::json::array();
281 search_range.push_back(static_cast<int64_t>(start_pin));
282 search_range.push_back(static_cast<int64_t>(end_pin));
283 response.set("searchRange", search_range);
284
285 // FastLED #3446: surface every pin we declined to drive (and why)
286 // so the host log can render "Skipped GPIO 6/7/8/9/10/11 (SPI flash),
287 // 20 (USB-JTAG), 34-39 (input-only)". A small list — at most the
288 // bad-pin count for the platform.
289 response.set("skippedPins", skipped_pins);
290
291 if (found_tx >= 0 && found_rx >= 0) {
292 response.set("success", true);
293 response.set("found", true);
294 response.set("txPin", static_cast<int64_t>(found_tx));
295 response.set("rxPin", static_cast<int64_t>(found_rx));
296
297 // Auto-apply the found pins if requested
298 if (auto_apply) {
299 int old_tx = mState->pin_tx;
300 int old_rx = mState->pin_rx;
301 bool tx_changed = (found_tx != old_tx);
302 bool rx_changed = (found_rx != old_rx);
303
304 fl::shared_ptr<fl::RxChannel> replacement_rx;
305 if (rx_changed) {
306 if (mState->rx_factory) {
307 replacement_rx = mState->rx_factory(found_rx);
308 }
309 if (!replacement_rx) {
310 response.set("success", false);
311 response.set("error", "RxChannelCreationFailed");
312 response.set("message", "Failed to recreate RX channel on discovered pin");
313 response.set("autoApplied", false);
314 return response;
315 }
316 }
317
318 // Commit only after the replacement channel is ready, preserving
319 // the prior channel and validation state on factory failure.
320 if (tx_changed || rx_changed) {
321 mState->invalidateLedRxValidation();
322 }
323 mState->pin_tx = found_tx;
324 mState->pin_rx = found_rx;
325 if (rx_changed) {
326 mState->rx_channel = replacement_rx;
327 }
328
329 FL_DBG("[PIN PROBE] Auto-applied pins: TX=" << found_tx << ", RX=" << found_rx);
330 response.set("autoApplied", true);
331 response.set("previousTxPin", static_cast<int64_t>(old_tx));
332 response.set("previousRxPin", static_cast<int64_t>(old_rx));
333 } else {
334 response.set("autoApplied", false);
335 response.set("message", "Use setPins to apply the found pins");
336 }
337 } else {
338 response.set("success", true); // Function succeeded, just no pins found
339 response.set("found", false);
340 response.set("message", "No connected pin pairs found. Please connect a jumper wire between two GPIO pins.");
341 }
342
343 return response;
344}
345
347 : mRemote(fl::make_unique<fl::Remote>(
349 printRemoteResponseRaw,
350 printRemoteStreamRaw
351 )) {
352 // mState will be set by registerFunctions()
353}
354
356
357
359 // Store shared state
360 mState = state;
361
362 // NOTE: All RPC callbacks use const fl::json& for efficient parameter passing.
363 // The RPC system strips const/reference qualifiers and stores values in the tuple,
364 // then passes them as references to the function. This avoids copies while
365 // maintaining clean const-correct API.
366 //
367 // Bindings are partitioned across sibling .cpp files (#3132 / meta #3127) so
368 // each file stays under 1 K LOC.
377}
378
379
380void AutoResearchRemoteControl::tick(uint32_t current_millis) {
381 if (mRemote) {
382 // Remote::update() does pull + tick + push
383 mRemote->update(current_millis);
384 }
385 if (mBleRemote) {
386 mBleRemote->update(current_millis);
387 }
388 // Deferred BLE teardown: stopBle RPC sets this flag so the response
389 // is sent (via push() above) before we call ble::destroyTransport().
390 if (mPendingBleStop) {
391 mPendingBleStop = false;
392 mBleRemote.reset(); // destroy lambdas before freeing state they capture
394 mBleState = nullptr;
395 mState->ble_server_active = false;
397 }
398}
399
401 // Register the core methods that BLE remote needs.
402 // This registers a subset of methods — enough for ping/pong PoC.
403
404 // Register "ping" function - health check with timestamp
405 remote->bind("ping", [](const fl::json& args) -> fl::json {
406 uint32_t now = millis();
407 fl::json response = fl::json::object();
408 response.set("success", true);
409 response.set("message", "pong");
410 response.set("timestamp", static_cast<int64_t>(now));
411 response.set("uptimeMs", static_cast<int64_t>(now));
412 response.set("transport", "ble");
413 return response;
414 });
415
416 // Register "status" function - device readiness check
417 remote->bind("status", [this](const fl::json& args) -> fl::json {
418 fl::json status = fl::json::object();
419 status.set("ready", true);
420 status.set("pinTx", static_cast<int64_t>(mState->pin_tx));
421 status.set("pinRx", static_cast<int64_t>(mState->pin_rx));
422 status.set("transport", "ble");
423 return status;
424 });
425}
426
428 if (mBleRemote) {
429 fl::json response = fl::json::object();
430 response.set("success", true);
431 response.set("message", "BLE remote already active");
432 response.set("device_name", AUTORESEARCH_BLE_DEVICE_NAME);
433 return response;
434 }
435
436 // Create BLE GATT server (heap-allocates transport state)
437 // On stub platforms, createTransport returns nullptr and logs FL_ERROR.
439 if (!mBleState) {
440 fl::json response = fl::json::object();
441 response.set("success", false);
442 response.set("error", "BLE not available on this platform");
443 return response;
444 }
445
446 // Get transport lambdas that capture mBleState
448
449 // Create BLE Remote instance with BLE transport
450 mBleRemote = fl::make_unique<fl::Remote>(callbacks.first, callbacks.second);
451
452 // Register RPC methods on the BLE remote
454
455 mState->ble_server_active = true;
457
458 fl::json response = fl::json::object();
459 response.set("success", true);
460 response.set("device_name", AUTORESEARCH_BLE_DEVICE_NAME);
461 response.set("service_uuid", FL_BLE_SERVICE_UUID);
462 response.set("rx_uuid", FL_BLE_CHAR_RX_UUID);
463 response.set("tx_uuid", FL_BLE_CHAR_TX_UUID);
464 return response;
465}
466
468 // Defer actual BLE teardown to tick() so the RPC response is sent first.
469 // BLEDevice::deinit(true) blocks long enough to prevent the response
470 // from being transmitted over serial before the device resets BLE state.
471 mPendingBleStop = true;
472 fl::json response = fl::json::object();
473 response.set("success", true);
474 return response;
475}
476
477#endif // FL_PLATFORM_HAS_LARGE_MEMORY
AutoResearchBleState & getBleState()
Get current BLE autoresearch state.
#define AUTORESEARCH_BLE_DEVICE_NAME
Bus::FLEX_IO runtime-binding introspection RPC handler (FastLED#3515 Phase B).
Full PARLIO encode bench for ESP32-P4 (post-byte-LUT, #2526 landed).
PARLIO ISR-streaming functional validation (#2548 deep-dive follow-up).
void printStreamRaw(const char *messageType, const fl::json &data)
Print JSONL stream message directly to Serial, bypassing fl::println.
void printJsonRaw(const fl::json &json, const char *prefix="REMOTE: ")
Print JSON directly to Serial, bypassing fl::println and ScopedLogDisable This ensures RPC responses ...
Issue #2994 repro: compounded per-sequence timing drift vs 3.10.3.
Wave8 expansion micro-benchmark for #2526.
TestState state
fl::unique_ptr< fl::Remote > remote
Definition RpcClient.ino:43
#define FL_BLE_CHAR_TX_UUID
Definition ble.h:45
#define FL_BLE_CHAR_RX_UUID
Definition ble.h:44
#define FL_BLE_SERVICE_UUID
Definition ble.h:43
fl::net::ble — BLE GATT transport layer for JSON-RPC
SPI encoder configuration for clocked LED chipsets.
void bindNetworkMethods(fl::Remote &remote)
void bindRpSpiMethods(fl::Remote &remote)
fl::json stopBleRemote()
Stop BLE remote (destroys BLE Remote + GATT server)
AutoResearchRemoteControl()
Constructor.
void tick(uint32_t current_millis)
Process RPC system (pull + tick + push)
fl::net::ble::TransportState * mBleState
void bindBenchmarkMethods(fl::Remote &remote)
fl::unique_ptr< fl::Remote > mBleRemote
void bindSystemMethods(fl::Remote &remote)
fl::shared_ptr< AutoResearchState > mState
fl::json findConnectedPinsImpl(const fl::json &args)
void bindDriverMethods(fl::Remote &remote)
void registerFunctions(fl::shared_ptr< AutoResearchState > state)
Register all RPC functions with shared autoresearch state.
~AutoResearchRemoteControl()
Destructor.
void bindPinMethods(fl::Remote &remote)
fl::json startBleRemote()
Start BLE remote (creates BLE GATT server + second Remote instance)
void bindAsyncMethods(fl::Remote &remote)
void registerAllMethods(fl::Remote *remote)
Register all RPC methods on a given Remote instance.
fl::unique_ptr< fl::Remote > mRemote
void bindMathMethods(fl::Remote &remote)
void bind(const Config< Callable > &config) FL_NO_EXCEPT
Register method with config (name, function, optional metadata)
Definition remote.h:92
JSON-RPC server with scheduling support.
Definition remote.h:42
const char * c_str() const FL_NO_EXCEPT
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 has_value() const FL_NO_EXCEPT
Definition json.h:819
fl::vector< fl::string > keys() const FL_NO_EXCEPT
Definition json.h:683
bool is_null() const FL_NO_EXCEPT
Definition json.h:381
fl::optional< bool > as_bool() const FL_NO_EXCEPT
Definition json.h:397
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
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_DBG
Definition log.h:553
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
Definition unique_ptr.h:263
void destroyTransport(TransportState *) FL_NO_EXCEPT
Deinitialize BLE stack and free heap state.
Definition ble.cpp.hpp:26
fl::pair< fl::function< fl::optional< fl::json >()>, fl::function< void(const fl::json &)> > getTransportCallbacks(TransportState *) FL_NO_EXCEPT
Get RequestSource and ResponseSink lambdas for fl::Remote.
Definition ble.cpp.hpp:36
TransportState * createTransport(const char *) FL_NO_EXCEPT
Create BLE GATT server, heap-allocate transport state.
Definition ble.cpp.hpp:21
Status status() FL_NO_EXCEPT
Current connection state.
Definition wifi.cpp.hpp:43
void delay(u32 ms, bool run_async=true) FL_NO_EXCEPT
Public delay wrapper that keeps bare Arduino delay() preferred after using fl::delay; while still all...
Definition delay.h:98
fl::u32 millis() FL_NO_EXCEPT
Universal millisecond timer - returns milliseconds since system startup.
constexpr u64 pinMaskBit(int pin) FL_NO_EXCEPT
Bit for pin in a 64-bit pin mask; 0 when pin is outside [0, 64).
Definition pin_probe.h:20
fl::function< void(fl::JsonStreamWriter &)> JsonStreamCallback
const char * pinProbeDriveSkipReason(int pin) FL_NO_EXCEPT
Why a probe may read pin but must not drive it, or nullptr when it may drive it.
fl::function< fl::optional< fl::json >()> createSerialRequestSource(const char *prefix="") FL_NO_EXCEPT
Definition serial.h:120
fl::string formatJsonResponse(const fl::json &response, const char *prefix) FL_NO_EXCEPT
Serialize JSON response to a string.
@ SUCCESS
Operation completed successfully.
Definition rx.h:153
const char * pinProbeSkipReason(int pin) FL_NO_EXCEPT
Why a probe must not touch pin at all (no pinMode, read or write), or nullptr when it may.
void digitalWrite(int pin, PinValue val) FL_NO_EXCEPT
Write digital value to pin.
Definition pin.cpp.hpp:51
PinValue digitalRead(int pin) FL_NO_EXCEPT
Read digital value from pin.
Definition pin.cpp.hpp:55
void pinMode(int pin, PinMode mode) FL_NO_EXCEPT
Set pin mode (input, output, pull-up, pull-down)
Definition pin.cpp.hpp:394
Base definition for an LED controller.
Definition crgb.hpp:179
corkscrew_args args
Definition old.h:149
Platform-neutral "may a pin probe touch this pin?" API.
Promise-based fluent API for FastLED - standalone async primitives.
Umbrella header for SIMD subsystem.
unsigned char uint8_t
Definition stdint.h:208
fl::u32 uint32_t
Definition stdint.h:218
#define Serial
Definition serial.h:309