FastLED 3.9.15
Loading...
Searching...
No Matches
serial.h
Go to the documentation of this file.
1// src/fl/remote/transport/serial.h
2// Cross-platform serial transport layer for JSON-RPC
3// Provides factory functions for creating RequestSource and ResponseSink callbacks
4
5#pragma once
6
7#include "fl/stl/json.h"
8#include "fl/system/delay.h"
9#include "fl/stl/int.h"
10#include "fl/stl/cctype.h"
11#include "fl/stl/chrono.h"
12// fl/stl/cstdio.h is REQUIRED here: this header calls fl::available,
13// fl::read, fl::readLine, and fl::println, all declared in cstdio.h. PR
14// #2685 tried to drop this include and rely on a strstream.h -> ostream.h
15// -> cstdio.h transitive chain to work around zackees/zccache#619
16// (Windows PCH path-spelling drift), but strstream.h does NOT include
17// ostream.h, so the chain is broken on every non-PCH build (teensy,
18// every Arduino ARM/AVR target, etc.) — see issue #2647-style follow-up.
19// The Windows PCH dedup issue is fixed by the meson.build path
20// normalization that landed in the same PR.
21#include "fl/stl/cstdio.h"
22#include "fl/stl/cstring.h"
23#include "fl/stl/function.h"
24#include "fl/stl/optional.h"
25#include "fl/stl/pair.h"
26#include "fl/stl/string.h"
27#include "fl/stl/strstream.h"
28#include "fl/stl/string_view.h"
29
30namespace fl {
31
32// =============================================================================
33// Core Serialization Functions (Pure, No I/O)
34// =============================================================================
35// These functions have no I/O dependencies and are easily unit-testable
36
42fl::string formatJsonResponse(const fl::json& response, const char* prefix = "");
43
44// =============================================================================
45// Generic I/O Functions (Templated for Testability)
46// =============================================================================
47
55template<typename SerialIn>
56fl::optional<fl::string> readSerialLine(SerialIn& serial, char delimiter = '\n', fl::optional<u32> timeoutMs = fl::nullopt);
57
62template<typename SerialOut>
63void writeSerialLine(SerialOut& serial, const fl::string& str);
64
65// =============================================================================
66// Serial Adapters (Using fl:: Functions)
67// =============================================================================
68
72 int available() const { return fl::available(); }
73 int read() { return fl::read(); }
74};
75
82inline fl::optional<fl::string> readSerialLine(SerialReader& serial, char delimiter = '\n', fl::optional<u32> timeoutMs = fl::nullopt) {
83 (void)serial; // Unused, we call fl::readLine() directly
84 return fl::readLine(delimiter, '\r', timeoutMs);
85}
86
90 void println(const char* str) { fl::println(str); }
91};
92
93// =============================================================================
94// Remote Callback Factories (Creates RequestSource and ResponseSink)
95// =============================================================================
96
109createSerialRequestSource(const char* prefix = "") {
110 return [prefix]() -> fl::optional<fl::json> {
111 // Non-blocking check - any data available?
112 int avail = fl::available();
113 if (avail <= 0) {
114 return fl::nullopt;
115 }
116
117 // Data available - read a complete line.
118 // On Arduino platforms, fl::readLine() delegates to Serial.readStringUntil()
119 // which uses yield() (immediate context switch) for fast USB CDC multi-packet
120 // assembly. On other platforms, falls back to character-by-character reading.
121 auto line = fl::readLine('\n', '\r', fl::optional<u32>(1000));
122 if (!line.has_value() || line->empty()) {
123 return fl::nullopt;
124 }
125
126 // Use string_view for zero-copy prefix stripping and trimming
127 fl::string_view view = *line;
128
129 // Strip prefix if present
130 if (prefix && prefix[0] != '\0') {
131 if (view.starts_with(prefix)) {
132 view.remove_prefix(fl::strlen(prefix));
133 }
134 }
135
136 // Trim leading whitespace
137 while (!view.empty() && fl::isspace(view.front())) {
138 view.remove_prefix(1);
139 }
140
141 // Trim trailing whitespace
142 while (!view.empty() && fl::isspace(view.back())) {
143 view.remove_suffix(1);
144 }
145
146 // Only parse if input starts with '{'
147 if (view.empty() || view[0] != '{') {
148 return fl::nullopt;
149 }
150
151 // Single copy when parsing JSON (unavoidable - JSON needs owned string)
152 fl::string input(view);
153 return fl::json::parse(input);
154 };
155}
156
161inline fl::function<void(const fl::json&)>
162createSerialResponseSink(const char* prefix = "REMOTE: ") {
163 return [prefix](const fl::json& response) {
164 // Format and write to serial (no filtering needed - protocol uses flat structure)
165 SerialWriter serial;
166 fl::string formatted = formatJsonResponse(response, prefix);
167 writeSerialLine(serial, formatted);
168 };
169}
170
182createSerialTransport(const char* responsePrefix = "REMOTE: ", const char* requestPrefix = "") {
183 return {createSerialRequestSource(requestPrefix), createSerialResponseSink(responsePrefix)};
184}
185
186// =============================================================================
187// Template Implementations
188// =============================================================================
189
190template<typename SerialIn>
191fl::optional<fl::string> readSerialLine(SerialIn& serial, char delimiter, fl::optional<u32> timeoutMs) {
192 // Delegate to readSerialStringUntil with default skipChar='\r'
193 return readSerialStringUntil(serial, delimiter, '\r', timeoutMs);
194}
195
196template<typename SerialIn>
197fl::optional<fl::string> readSerialStringUntil(SerialIn& serial, char delimiter, char skipChar, fl::optional<u32> timeoutMs) {
198 // Follows Arduino Serial.readStringUntil() API - blocks until delimiter found
199 fl::sstream buffer;
200
201 u32 startTime = fl::millis();
202
203 // Read characters until we find delimiter or timeout
204 while (true) {
205 // Check timeout (only if timeout is set)
206 if (timeoutMs.has_value()) {
207 if (fl::millis() - startTime >= timeoutMs.value()) {
208 // Timeout occurred - return nullopt
209 return fl::nullopt;
210 }
211 }
212
213 // Try to read next character
214 int c = serial.read();
215
216 // Handle -1 (no data available) like Arduino's timedRead():
217 // Keep trying until timeout (or forever if no timeout set)
218 if (c == -1) {
219 // Brief 1us yield instead of 1ms delay for fast USB CDC polling.
221 continue;
222 }
223
224 // Found delimiter - complete
225 if (c == delimiter) {
226 break;
227 }
228
229 // Skip specified character (e.g., '\r' for cross-platform line endings)
230 if (c == skipChar) {
231 continue;
232 }
233
234 // Valid character - add to buffer
235 buffer << static_cast<char>(c);
236 }
237
238 // Convert to string and trim whitespace
239 fl::string result = buffer.str();
240 result.trim();
241 return result;
242}
243
244template<typename SerialOut>
245void writeSerialLine(SerialOut& serial, const fl::string& str) {
246 serial.println(str.c_str());
247}
248
249} // namespace fl
FastLED chrono implementation - duration types for time measurements.
T & value() FL_NOEXCEPT
Definition optional.h:112
bool has_value() const FL_NOEXCEPT
Definition optional.h:42
const char * c_str() const FL_NOEXCEPT
static json parse(const fl::string &txt) FL_NOEXCEPT
Definition json.h:677
string str() const FL_NOEXCEPT
Definition strstream.h:43
constexpr bool empty() const FL_NOEXCEPT
void remove_suffix(fl::size n) FL_NOEXCEPT
constexpr const char & front() const FL_NOEXCEPT
Definition string_view.h:86
bool starts_with(string_view sv) const FL_NOEXCEPT
constexpr const char & back() const FL_NOEXCEPT
Definition string_view.h:90
void remove_prefix(fl::size n) FL_NOEXCEPT
Delay utilities for FastLED Includes nanosecond-precision delays, cycle counting, and microsecond del...
FastLED's Elegant JSON Library: fl::json
int available()
fl::optional< fl::string > readSerialStringUntil(SerialIn &serial, char delimiter, char skipChar, fl::optional< u32 > timeoutMs)
Definition serial.h:197
int read()
size_t strlen(const char *s) FL_NOEXCEPT
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
Optional< T > optional
Definition optional.h:16
fl::string formatJsonResponse(const fl::json &response, const char *prefix)
Serialize JSON response to a string.
fl::optional< fl::string > readSerialLine(SerialIn &serial, char delimiter='\n', fl::optional< u32 > timeoutMs=fl::nullopt)
Read a line from a serial-like input source (blocking with optional timeout)
Definition serial.h:191
fl::function< fl::optional< fl::json >()> createSerialRequestSource(const char *prefix="")
Create a JSON-RPC RequestSource that reads from fl:: serial input.
Definition serial.h:109
fl::pair< fl::function< fl::optional< fl::json >()>, fl::function< void(const fl::json &)> > createSerialTransport(const char *responsePrefix="REMOTE: ", const char *requestPrefix="")
Create RequestSource and ResponseSink pair for serial I/O.
Definition serial.h:182
void writeSerialLine(SerialOut &serial, const fl::string &str)
Write a string with newline to a serial-like output.
Definition serial.h:245
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
constexpr nullopt_t nullopt
Definition optional.h:13
fl::function< void(const fl::json &)> createSerialResponseSink(const char *prefix="REMOTE: ")
Create a JSON-RPC ResponseSink that writes to fl:: serial output.
Definition serial.h:162
void println(const char *str) FL_NOEXCEPT
void delayMicroseconds(u32 us)
Delay for a given number of microseconds.
bool isspace(char c) FL_NOEXCEPT
Check if character is whitespace (space, tab, newline, carriage return)
Definition cctype.h:18
fl::optional< fl::string > readLine(char delimiter, char skipChar, fl::optional< u32 > timeoutMs)
Base definition for an LED controller.
Definition crgb.hpp:179
int available() const
Definition serial.h:72
Serial adapter using fl:: input functions (fl::available, fl::read)
Definition serial.h:71
void println(const char *str)
Definition serial.h:90
Serial adapter using fl:: output functions (fl::println)
Definition serial.h:89