FastLED 3.10.9
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/stl/cstdio.h"
38#include "fl/stl/string_view.h"
39#include "fl/task/task.h"
40#include "fl/task/executor.h"
41#include <Arduino.h>
43#include "fl/stl/atomic.h"
44#include "fl/task/promise.h"
45
46#include "fl/system/pin_probe.h" // pin discovery skip rules (#3446)
47#include "fl/math/simd.h"
48#include "AutoResearchSimd.h"
49#include "AutoResearchAnimartrixBench.h" // animartrixPerlinBench RPC (#2628 follow-up)
50#include "AutoResearchWave8Expand.h" // #2526 wave8ExpandBenchmark RPC
51#include "AutoResearchParlioEncode.h" // parlioEncodeBenchmark RPC (#2526 follow-up)
52#include "AutoResearchTimingDrift.h" // timingDriftTest RPC (#2994 repro)
53#include "AutoResearchParlioStream.h" // parlioStreamValidate RPC (#2548 follow-up)
54#include "AutoResearchFlexIo.h" // flexIoDeviceInfo RPC (#3515 Phase B)
55#include "fl/system/heap.h"
56#include "fl/chipsets/spi.h"
57#include "fl/channels/config.h"
58#include <Arduino.h>
59
60#include "fl/net/ble.h"
61
62// Codec headers for decodeFile RPC
63#include "fl/codec/h264.h"
64#include "fl/codec/mp4_parser.h"
66#include "fl/fx/frame.h"
67
68
69// ============================================================================
70// Raw Serial Output Functions (bypass fl::println and ScopedLogDisable)
71// ============================================================================
72
73namespace {
74
75// RPC output must use the selected console and bypass log-level filtering.
76void writeRaw(fl::string_view text) {
77 fl::write_bytes(reinterpret_cast<const fl::u8*>(text.data()), text.size()); // ok autoresearch rpc serial - selected console RPC transport boundary
78}
79
80// Stream a JSON line without a heap buffer proportional to the document;
81// large responses such as rpc.discover exceed the S3 largest free block
82// (FastLED #4899).
83void writeJsonLine(const fl::json& json, const char* prefix) {
84 if (prefix && prefix[0] != '\0') {
85 writeRaw(prefix);
86 }
87 json.write_to(
88 [](void*, const char* data, fl::size len) {
89 writeRaw(fl::string_view(data, len));
90 },
91 nullptr);
92 writeRaw("\r\n");
93}
94
95} // namespace
96
97void printJsonRaw(const fl::json& json, const char* prefix) {
98 writeJsonLine(json, prefix);
99 fl::flush(); // ok autoresearch rpc serial - host waits for RPC response completion
100}
101
102namespace {
103
104void printRemoteResponseRaw(const fl::json& response) {
105 writeJsonLine(response, "REMOTE: ");
106 fl::flush(); // ok autoresearch rpc serial - host waits for RPC response completion
107}
108
109void printRemoteStreamRaw(fl::JsonStreamCallback writeJson) {
110 writeRaw("REMOTE: ");
111
112 fl::JsonStreamWriter writer([](const char* data, fl::size len) {
113 writeRaw(fl::string_view(data, len));
114 });
115 if (writeJson) {
116 writeJson(writer);
117 }
118 writer.flush();
119 writeRaw("\r\n");
120 fl::flush(); // ok autoresearch rpc serial - host waits for RPC response completion
121}
122
123} // namespace
124
125void printStreamRaw(const char* messageType, const fl::json& data) {
126 // Build pure JSONL message: RESULT: {"type":"...", ...data}
127 fl::json output = fl::json::object();
128 output.set("type", messageType);
129
130 // Copy all fields from data into output
131 if (data.is_object()) {
132 auto keys = data.keys();
133 for (fl::size i = 0; i < keys.size(); i++) {
134 output.set(keys[i].c_str(), data[keys[i]]);
135 }
136 }
137
138 writeJsonLine(output, "RESULT: ");
139}
140
141// ============================================================================
142// Standard JSON-RPC Response Format (Phase 4 Refactoring)
143// ============================================================================
144// Return codes:
145// 0 = SUCCESS
146// 1 = TEST_FAILED
147// 2 = HARDWARE_ERROR (GPIO not connected)
148// 3 = INVALID_ARGS
149
150enum class ReturnCode : int {
151 SUCCESS = 0,
152 TEST_FAILED = 1,
153 HARDWARE_ERROR = 2,
154 INVALID_ARGS = 3
155};
156
157fl::json makeResponse(bool success, ReturnCode returnCode, const char* message,
158 const fl::json& data = fl::json()) {
160 r.set("success", success);
161 r.set("returnCode", static_cast<int64_t>(static_cast<int>(returnCode)));
162 r.set("message", message);
163 if (!data.is_null() && data.has_value()) {
164 r.set("data", data);
165 }
166 return r;
167}
168
169// No forward declarations needed - using one-test-per-RPC architecture
170
172 fl::json response = fl::json::object();
173
174 // Parse optional arguments: [{startPin: int, endPin: int, autoApply: bool}]
175 //
176 // FastLED #3446: default range stays at the historically-safe 0-8
177 // window so an unparameterised call cannot accidentally drive a
178 // boot/strap/USB pin into reset. Callers that need to sweep higher
179 // pins (e.g. the user-reported (33, 34) short) should issue
180 // multiple `findConnectedPins` calls with overlapping 8-pin windows
181 // (0-8, 8-16, 16-24, ...) from the host side. The Python wrapper
182 // `run_pin_discovery_segmented` in `ci/autoresearch/gpio.py` does
183 // exactly that.
184 int start_pin = 0;
185 int end_pin = 8;
186 bool auto_apply = true; // If true, automatically apply found pins
187
188 if (args.is_array() && args.size() >= 1 && args[0].is_object()) {
189 fl::json config = args[0];
190 if (config.contains("startPin") && config["startPin"].is_int()) {
191 start_pin = static_cast<int>(config["startPin"].as_int().value());
192 }
193 if (config.contains("endPin") && config["endPin"].is_int()) {
194 end_pin = static_cast<int>(config["endPin"].as_int().value());
195 }
196 if (config.contains("autoApply") && config["autoApply"].is_bool()) {
197 auto_apply = config["autoApply"].as_bool().value();
198 }
199 }
200
201 // Validate range
202 if (start_pin < 0 || start_pin > 48 || end_pin < 0 || end_pin > 48 || start_pin >= end_pin) {
203 response.set("error", "InvalidRange");
204 response.set("message", "Pin range must be 0-48 with startPin < endPin");
205 return response;
206 }
207 // FastLED #3446: the platform owns the per-chip list of pins a probe
208 // must not touch (SPI flash / PSRAM pads, flash power, USB-JTAG, the
209 // console UART) and of pins it may read but not drive (input-only).
210 // See fl/system/pin_probe.h; platforms without such pins report none.
211 //
212 // We surface skipped pins in the response so the host log says
213 // exactly which pins were forbidden and why, turning silent
214 // "no jumper found" into actionable "pins 6-11 are flash, 20 is
215 // USB-JTAG, 34-39 are input-only".
216
217 fl::json skipped_pins = fl::json::array();
218 // Each pin is recorded once: the all-pairs scan below visits a pin many
219 // times, and an unbounded array exhausts heap on ESP32.
220 fl::u64 skipped_mask = 0;
221 auto recordSkip = [&](int p, const char* reason) {
222 const fl::u64 bit = fl::pinMaskBit(p);
223 if (skipped_mask & bit) {
224 return;
225 }
226 skipped_mask |= bit;
227 fl::json entry = fl::json::object();
228 entry.set("pin", static_cast<int64_t>(p));
229 entry.set("reason", reason);
230 skipped_pins.push_back(entry);
231 };
232
233 FL_DBG("[PIN PROBE] Searching for connected pin pairs in range " << start_pin << "-" << end_pin);
234
235 // Helper lambda to test if two pins are connected
236 auto testPinPair = [](int tx, int rx) -> bool {
237 // Test 1: TX drives LOW, RX has pullup → RX should read LOW if connected
238 pinMode(tx, OUTPUT);
239 pinMode(rx, INPUT_PULLUP);
240 digitalWrite(tx, LOW);
241 delay(2); // Allow signal to settle
242 int rx_when_tx_low = digitalRead(rx);
243
244 // Test 2: TX drives HIGH → RX should read HIGH if connected
245 digitalWrite(tx, HIGH);
246 delay(2); // Allow signal to settle
247 int rx_when_tx_high = digitalRead(rx);
248
249 // Restore pins to safe state
250 pinMode(tx, INPUT);
251 pinMode(rx, INPUT);
252
253 return (rx_when_tx_low == LOW) && (rx_when_tx_high == HIGH);
254 };
255
256 // Search every pin pair (a, b) in the range, both directions. Bench
257 // jumpers are not always on neighbouring pins (a Teensy 4.0 wired across
258 // the board was invisible to the old adjacent-only (n, n+1) scan).
259 // Cost: 2 probes x ~4 ms per pair; a 0-40 range is ~6 s.
260 int found_tx = -1;
261 int found_rx = -1;
262
263 // Drive `tx`, read `rx`; skips (and records) a driver that can't output.
264 auto tryDirection = [&](int tx, int rx) -> bool {
265 if (const char* why = fl::pinProbeDriveSkipReason(tx)) {
266 recordSkip(tx, why);
267 return false;
268 }
269 return testPinPair(tx, rx);
270 };
271
272 for (int a = start_pin; a <= end_pin && found_tx < 0; a++) {
273 // FastLED #3446: hands off the platform's reserved pads (SPI
274 // flash 6-11, USB-JTAG, ...) -- driving them locks the chip up or
275 // corrupts the live flash transaction -- and off this board's
276 // console and in-use PSRAM pins (fl::pinProbeSkipReason).
277 if (const char* why = fl::pinProbeSkipReason(a)) {
278 recordSkip(a, why);
279 continue;
280 }
281 for (int b = a + 1; b <= end_pin; b++) {
282 if (const char* why = fl::pinProbeSkipReason(b)) {
283 recordSkip(b, why);
284 continue;
285 }
286 if (tryDirection(a, b)) {
287 found_tx = a;
288 found_rx = b;
289 FL_DBG("[PIN PROBE] Found connected pair: TX=" << found_tx << " -> RX=" << found_rx);
290 break;
291 }
292 if (tryDirection(b, a)) {
293 found_tx = b; // Reversed: b drives a
294 found_rx = a;
295 FL_DBG("[PIN PROBE] Found connected pair (reversed): TX=" << found_tx << " -> RX=" << found_rx);
296 break;
297 }
298 }
299 }
300
301 fl::json search_range = fl::json::array();
302 search_range.push_back(static_cast<int64_t>(start_pin));
303 search_range.push_back(static_cast<int64_t>(end_pin));
304 response.set("searchRange", search_range);
305
306 // FastLED #3446: surface every pin we declined to drive (and why)
307 // so the host log can render "Skipped GPIO 6/7/8/9/10/11 (SPI flash),
308 // 20 (USB-JTAG), 34-39 (input-only)". A small list — at most the
309 // bad-pin count for the platform.
310 response.set("skippedPins", skipped_pins);
311
312 if (found_tx >= 0 && found_rx >= 0) {
313 response.set("success", true);
314 response.set("found", true);
315 response.set("txPin", static_cast<int64_t>(found_tx));
316 response.set("rxPin", static_cast<int64_t>(found_rx));
317
318 // Auto-apply the found pins if requested
319 if (auto_apply) {
320 int old_tx = mState->pin_tx;
321 int old_rx = mState->pin_rx;
322 bool tx_changed = (found_tx != old_tx);
323 bool rx_changed = (found_rx != old_rx);
324
325 fl::shared_ptr<fl::RxChannel> replacement_rx;
326 if (rx_changed) {
327 if (mState->rx_factory) {
328 replacement_rx = mState->rx_factory(found_rx);
329 }
330 if (!replacement_rx) {
331 response.set("success", false);
332 response.set("error", "RxChannelCreationFailed");
333 response.set("message", "Failed to recreate RX channel on discovered pin");
334 response.set("autoApplied", false);
335 return response;
336 }
337 }
338
339 // Commit only after the replacement channel is ready, preserving
340 // the prior channel and validation state on factory failure.
341 if (tx_changed || rx_changed) {
342 mState->invalidateLedRxValidation();
343 }
344 mState->pin_tx = found_tx;
345 mState->pin_rx = found_rx;
346 if (rx_changed) {
347 mState->rx_channel = replacement_rx;
348 }
349
350 FL_DBG("[PIN PROBE] Auto-applied pins: TX=" << found_tx << ", RX=" << found_rx);
351 response.set("autoApplied", true);
352 response.set("previousTxPin", static_cast<int64_t>(old_tx));
353 response.set("previousRxPin", static_cast<int64_t>(old_rx));
354 } else {
355 response.set("autoApplied", false);
356 response.set("message", "Use setPins to apply the found pins");
357 }
358 } else {
359 response.set("success", true); // Function succeeded, just no pins found
360 response.set("found", false);
361 response.set("message", "No connected pin pairs found. Please connect a jumper wire between two GPIO pins.");
362 }
363
364 return response;
365}
366
368 : mRemote(fl::make_unique<fl::Remote>(
370 printRemoteResponseRaw,
371 printRemoteStreamRaw
372 )) {
373 // mState will be set by registerFunctions()
374}
375
377
378
380 // Store shared state
381 mState = state;
382
383 // NOTE: All RPC callbacks use const fl::json& for efficient parameter passing.
384 // The RPC system strips const/reference qualifiers and stores values in the tuple,
385 // then passes them as references to the function. This avoids copies while
386 // maintaining clean const-correct API.
387 //
388 // Bindings are partitioned across sibling .cpp files (#3132 / meta #3127) so
389 // each file stays under 1 K LOC.
398}
399
400
401void AutoResearchRemoteControl::tick(uint32_t current_millis) {
402 if (mRemote) {
403 // Remote::update() does pull + tick + push
404 mRemote->update(current_millis);
405 }
406 if (mBleRemote) {
407 mBleRemote->update(current_millis);
408 }
409 // Deferred BLE teardown: stopBle RPC sets this flag so the response
410 // is sent (via push() above) before we call ble::destroyTransport().
411 if (mPendingBleStop) {
412 mPendingBleStop = false;
413 mBleRemote.reset(); // destroy lambdas before freeing state they capture
415 mBleState = nullptr;
416 mState->ble_server_active = false;
418 }
419}
420
422 // Register the core methods that BLE remote needs.
423 // This registers a subset of methods — enough for ping/pong PoC.
424
425 // Register "ping" function - health check with timestamp
426 remote->bind("ping", [](const fl::json& args) -> fl::json {
427 uint32_t now = millis();
428 fl::json response = fl::json::object();
429 response.set("success", true);
430 response.set("message", "pong");
431 response.set("timestamp", static_cast<int64_t>(now));
432 response.set("uptimeMs", static_cast<int64_t>(now));
433 response.set("transport", "ble");
434 return response;
435 });
436
437 // Register "status" function - device readiness check
438 remote->bind("status", [this](const fl::json& args) -> fl::json {
439 fl::json status = fl::json::object();
440 status.set("ready", true);
441 status.set("pinTx", static_cast<int64_t>(mState->pin_tx));
442 status.set("pinRx", static_cast<int64_t>(mState->pin_rx));
443 status.set("transport", "ble");
444 return status;
445 });
446}
447
449 if (mBleRemote) {
450 fl::json response = fl::json::object();
451 response.set("success", true);
452 response.set("message", "BLE remote already active");
453 response.set("device_name", AUTORESEARCH_BLE_DEVICE_NAME);
454 return response;
455 }
456
457 // Create BLE GATT server (heap-allocates transport state)
458 // On stub platforms, createTransport returns nullptr and logs FL_ERROR.
460 if (!mBleState) {
461 fl::json response = fl::json::object();
462 response.set("success", false);
463 response.set("error", "BLE not available on this platform");
464 return response;
465 }
466
467 // Get transport lambdas that capture mBleState
469
470 // Create BLE Remote instance with BLE transport
471 mBleRemote = fl::make_unique<fl::Remote>(callbacks.first, callbacks.second);
472
473 // Register RPC methods on the BLE remote
475
476 mState->ble_server_active = true;
478
479 fl::json response = fl::json::object();
480 response.set("success", true);
481 response.set("device_name", AUTORESEARCH_BLE_DEVICE_NAME);
482 response.set("service_uuid", FL_BLE_SERVICE_UUID);
483 response.set("rx_uuid", FL_BLE_CHAR_RX_UUID);
484 response.set("tx_uuid", FL_BLE_CHAR_TX_UUID);
485 return response;
486}
487
489 // Defer actual BLE teardown to tick() so the RPC response is sent first.
490 // BLEDevice::deinit(true) blocks long enough to prevent the response
491 // from being transmitted over serial before the device resets BLE state.
492 mPendingBleStop = true;
493 fl::json response = fl::json::object();
494 response.set("success", true);
495 return response;
496}
497
498#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
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 write_to(WriteFn fn, void *ctx) const FL_NO_EXCEPT
void set(const fl::string &key, const json &value) FL_NO_EXCEPT
Definition json.h:873
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:925
bool contains(size_t idx) const FL_NO_EXCEPT
Definition json.h:790
static json object() FL_NO_EXCEPT
Definition json.h:864
static json array() FL_NO_EXCEPT
Definition json.h:860
constexpr fl::size size() const FL_NO_EXCEPT
Definition string_view.h:99
constexpr const char * data() const FL_NO_EXCEPT
Definition string_view.h:94
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
unsigned char u8
Definition stdint.h:131
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 uint32_t
Definition s16x16x4.h:219
fl::u32 millis() FL_NO_EXCEPT
Universal millisecond timer - returns milliseconds since system startup.
size_t write_bytes(const u8 *buffer, size_t size) FL_NO_EXCEPT
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.
bool flush(u32 timeoutMs) FL_NO_EXCEPT
fl::function< fl::optional< fl::json >()> createSerialRequestSource(const char *prefix="") FL_NO_EXCEPT
Definition serial.h:120
@ 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.
fl::u64 u64
Definition s16x16x4.h:221
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:398
Base definition for an LED controller.
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.