FastLED 3.10.8
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/log/log.h" // IWYU pragma: keep -- FL_WARN
9#include "fl/system/delay.h"
10#include "fl/stl/int.h"
11#include "fl/stl/cctype.h"
12#include "fl/stl/chrono.h"
13// fl/stl/cstdio.h is REQUIRED here: this header calls fl::available,
14// fl::read, fl::readLine, and fl::println, all declared in cstdio.h. PR
15// #2685 tried to drop this include and rely on a strstream.h -> ostream.h
16// -> cstdio.h transitive chain to work around zackees/zccache#619
17// (Windows PCH path-spelling drift), but strstream.h does NOT include
18// ostream.h, so the chain is broken on every non-PCH build (teensy,
19// every Arduino ARM/AVR target, etc.) — see issue #2647-style follow-up.
20// The Windows PCH dedup issue is fixed by the meson.build path
21// normalization that landed in the same PR.
22#include "fl/stl/cstdio.h"
23#include "fl/stl/cstring.h"
24#include "fl/stl/function.h"
25#include "fl/stl/optional.h"
26#include "fl/stl/pair.h"
27#include "fl/stl/string.h"
28#include "fl/stl/strstream.h"
29#include "fl/stl/string_view.h"
31#include "fl/stl/noexcept.h"
32
33namespace fl {
34
35// =============================================================================
36// Core Serialization Functions (Pure, No I/O)
37// =============================================================================
38// These functions have no I/O dependencies and are easily unit-testable
39
45fl::string formatJsonResponse(const fl::json& response, const char* prefix = "") FL_NO_EXCEPT;
46
47// =============================================================================
48// Generic I/O Functions (Templated for Testability)
49// =============================================================================
50
58template<typename SerialIn>
59fl::optional<fl::string> readSerialLine(SerialIn& serial, char delimiter = '\n', fl::optional<u32> timeoutMs = fl::nullopt) FL_NO_EXCEPT;
60
65template<typename SerialOut>
66void writeSerialLine(SerialOut& serial, const fl::string& str) FL_NO_EXCEPT;
67
68// =============================================================================
69// Serial Adapters (Using fl:: Functions)
70// =============================================================================
71
75 int available() const FL_NO_EXCEPT { return fl::available(); }
76 int read() FL_NO_EXCEPT { return fl::read(); }
77};
78
86 (void)serial; // Unused, we call fl::readLine() directly
87 return fl::readLine(delimiter, '\r', timeoutMs);
88}
89
93 void println(const char* str) FL_NO_EXCEPT { fl::println(str); }
94 void write(const char* data, fl::size len) FL_NO_EXCEPT {
95 fl::write_bytes(reinterpret_cast<const fl::u8*>(data), len); // ok reinterpret cast
96 }
97};
98
99// =============================================================================
100// Remote Callback Factories (Creates RequestSource and ResponseSink)
101// =============================================================================
102
117constexpr fl::size kDroppedRequestWarnBytes = 64;
118
120createSerialRequestSource(const char* prefix = "") FL_NO_EXCEPT {
121 return [prefix]() -> fl::optional<fl::json> {
122 // Non-blocking check - any data available?
123 int avail = fl::available();
124 if (avail <= 0) {
125 return fl::nullopt;
126 }
127
128 // Data available - read a complete line.
129 // On Arduino platforms, fl::readLine() delegates to Serial.readStringUntil()
130 // which uses yield() (immediate context switch) for fast USB CDC multi-packet
131 // assembly. On other platforms, falls back to character-by-character reading.
132 auto line = fl::readLine('\n', '\r', fl::optional<u32>(1000));
133 if (!line.has_value() || line->empty()) {
134 return fl::nullopt;
135 }
136
137 // Use string_view for zero-copy prefix stripping and trimming
138 fl::string_view view = *line;
139
140 // Strip prefix if present
141 if (prefix && prefix[0] != '\0') {
142 if (view.starts_with(prefix)) {
143 view.remove_prefix(fl::strlen(prefix));
144 }
145 }
146
147 // Trim leading whitespace
148 while (!view.empty() && fl::isspace(view.front())) {
149 view.remove_prefix(1);
150 }
151
152 // Trim trailing whitespace
153 while (!view.empty() && fl::isspace(view.back())) {
154 view.remove_suffix(1);
155 }
156
157 // Only parse if input starts with '{'
158 if (view.empty() || view[0] != '{') {
159 // A short line here is ordinary serial noise -- boot banners,
160 // stray newlines -- and warning about it would drown the log.
161 // A long one is almost certainly a request that arrived damaged,
162 // and dropping that silently is expensive to debug: the caller
163 // sees no reply and no error, indistinguishable from an idle
164 // link, while the device keeps answering everything else. That
165 // is exactly how a receive-buffer truncation presented in
166 // FastLED#3956 -- 735 bytes transmitted, nothing returned, and
167 // no layer saying anything.
168 // Measure what arrived, not what survived normalisation: the
169 // view above has had its prefix stripped and whitespace trimmed,
170 // so a 64-byte line can fall under the threshold and stay silent,
171 // and the reported count would understate what was received.
172 if (line->size() >= kDroppedRequestWarnBytes) {
173 FL_WARN("[RPC] discarded a " << (static_cast<unsigned>(line->size())) << " byte line that does not begin with '{'; a request that arrived truncated looks exactly like this");
174 }
175 return fl::nullopt;
176 }
177
178 // Single copy when parsing JSON (unavoidable - JSON needs owned string)
179 fl::string input(view);
180 return fl::json::parse(input);
181 };
182}
183
188inline fl::function<void(const fl::json&)>
189createSerialResponseSink(const char* prefix = "REMOTE: ") FL_NO_EXCEPT {
190 return [prefix](const fl::json& response) {
191 // Format and write to serial (no filtering needed - protocol uses flat structure)
192 SerialWriter serial;
193 fl::string formatted = formatJsonResponse(response, prefix);
194 writeSerialLine(serial, formatted);
195 };
196}
197
202createSerialResponseStreamSink(const char* prefix = "REMOTE: ") FL_NO_EXCEPT {
203 return [prefix](fl::JsonStreamCallback writeJson) {
204 SerialWriter serial;
205 if (prefix && prefix[0] != '\0') {
206 serial.write(prefix, fl::strlen(prefix));
207 }
208
209 fl::JsonStreamWriter writer([&serial](const char* data, fl::size len) {
210 serial.write(data, len);
211 });
212 if (writeJson) {
213 writeJson(writer);
214 }
215 writer.flush();
216 serial.write("\n", 1);
217 fl::flush();
218 };
219}
220
232createSerialTransport(const char* responsePrefix = "REMOTE: ", const char* requestPrefix = "") FL_NO_EXCEPT {
233 return {createSerialRequestSource(requestPrefix), createSerialResponseSink(responsePrefix)};
234}
235
236// =============================================================================
237// Template Implementations
238// =============================================================================
239
240template<typename SerialIn>
241fl::optional<fl::string> readSerialLine(SerialIn& serial, char delimiter, fl::optional<u32> timeoutMs) FL_NO_EXCEPT {
242 // Delegate to readSerialStringUntil with default skipChar='\r'
243 return readSerialStringUntil(serial, delimiter, '\r', timeoutMs);
244}
245
246template<typename SerialIn>
247fl::optional<fl::string> readSerialStringUntil(SerialIn& serial, char delimiter, char skipChar, fl::optional<u32> timeoutMs) FL_NO_EXCEPT {
248 // Follows Arduino Serial.readStringUntil() API - blocks until delimiter found
249 fl::sstream buffer;
250
251 u32 startTime = fl::millis();
252
253 // Read characters until we find delimiter or timeout
254 while (true) {
255 // Check timeout (only if timeout is set)
256 if (timeoutMs.has_value()) {
257 if (fl::millis() - startTime >= timeoutMs.value()) {
258 // Timeout occurred - return nullopt
259 return fl::nullopt;
260 }
261 }
262
263 // Try to read next character
264 int c = serial.read();
265
266 // Handle -1 (no data available) like Arduino's timedRead():
267 // Keep trying until timeout (or forever if no timeout set)
268 if (c == -1) {
269 // Brief 1us yield instead of 1ms delay for fast USB CDC polling.
271 continue;
272 }
273
274 // Found delimiter - complete
275 if (c == delimiter) {
276 break;
277 }
278
279 // Skip specified character (e.g., '\r' for cross-platform line endings)
280 if (c == skipChar) {
281 continue;
282 }
283
284 // Valid character - add to buffer
285 buffer << static_cast<char>(c);
286 }
287
288 // Convert to string and trim whitespace
289 fl::string result = buffer.str();
290 result.trim();
291 return result;
292}
293
294template<typename SerialOut>
295void writeSerialLine(SerialOut& serial, const fl::string& str) FL_NO_EXCEPT {
296 serial.println(str.c_str());
297}
298
299} // namespace fl
FastLED chrono implementation - duration types for time measurements.
static json parse(const fl::string &txt) FL_NO_EXCEPT
Definition json.h:842
string str() const FL_NO_EXCEPT
Definition strstream.h:43
void remove_suffix(fl::size n) FL_NO_EXCEPT
constexpr const char & back() const FL_NO_EXCEPT
Definition string_view.h:90
constexpr bool empty() const FL_NO_EXCEPT
void remove_prefix(fl::size n) FL_NO_EXCEPT
bool starts_with(string_view sv) const FL_NO_EXCEPT
constexpr const char & front() const FL_NO_EXCEPT
Definition string_view.h:86
Delay utilities for FastLED Includes nanosecond-precision delays, cycle counting, and microsecond del...
FastLED's Elegant JSON Library: fl::json
#define FL_WARN(...)
Definition log.h:437
Centralized logging categories for FastLED hardware interfaces and subsystems.
fl::pair< fl::function< fl::optional< fl::json >()>, fl::function< void(const fl::json &)> > createSerialTransport(const char *responsePrefix="REMOTE: ", const char *requestPrefix="") FL_NO_EXCEPT
Create RequestSource and ResponseSink pair for serial I/O.
Definition serial.h:232
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
fl::optional< fl::string > readLine(char delimiter, char skipChar, fl::optional< u32 > timeoutMs) FL_NO_EXCEPT
bool isspace(char c) FL_NO_EXCEPT
Check if character is whitespace (space, tab, newline, carriage return)
Definition cctype.h:18
fl::u32 millis() FL_NO_EXCEPT
Universal millisecond timer - returns milliseconds since system startup.
Optional< T > optional
Definition optional.h:16
size_t write_bytes(const u8 *buffer, size_t size) FL_NO_EXCEPT
fl::ResponseStreamSink createSerialResponseStreamSink(const char *prefix="REMOTE: ") FL_NO_EXCEPT
Create a streamed JSON-RPC ResponseSink that writes directly to serial.
Definition serial.h:202
fl::function< void(fl::JsonStreamWriter &)> JsonStreamCallback
int read() FL_NO_EXCEPT
fl::function< void(const fl::json &)> createSerialResponseSink(const char *prefix="REMOTE: ") FL_NO_EXCEPT
Create a JSON-RPC ResponseSink that writes to fl:: serial output.
Definition serial.h:189
bool flush(u32 timeoutMs) FL_NO_EXCEPT
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
fl::function< fl::optional< fl::json >()> createSerialRequestSource(const char *prefix="") FL_NO_EXCEPT
Definition serial.h:120
constexpr nullopt_t nullopt
Definition optional.h:13
fl::string formatJsonResponse(const fl::json &response, const char *prefix) FL_NO_EXCEPT
Serialize JSON response to a string.
void println(const char *str) FL_NO_EXCEPT
constexpr fl::size kDroppedRequestWarnBytes
Create a JSON-RPC RequestSource that reads from fl:: serial input.
Definition serial.h:117
fl::optional< fl::string > readSerialLine(SerialIn &serial, char delimiter='\n', fl::optional< u32 > timeoutMs=fl::nullopt) FL_NO_EXCEPT
Read a line from a serial-like input source (blocking with optional timeout)
Definition serial.h:241
fl::function< void(fl::JsonStreamCallback)> ResponseStreamSink
int available() FL_NO_EXCEPT
fl::optional< fl::string > readSerialStringUntil(SerialIn &serial, char delimiter, char skipChar, fl::optional< u32 > timeoutMs) FL_NO_EXCEPT
Definition serial.h:247
void writeSerialLine(SerialOut &serial, const fl::string &str) FL_NO_EXCEPT
Write a string with newline to a serial-like output.
Definition serial.h:295
FASTLED_FORCE_INLINE size_t strlen(const char *s) FL_NO_EXCEPT
Definition cstring.h:56
void delayMicroseconds(u32 us) FL_NO_EXCEPT
Delay for a given number of microseconds.
Base definition for an LED controller.
Definition crgb.hpp:179
int read() FL_NO_EXCEPT
Definition serial.h:76
int available() const FL_NO_EXCEPT
Definition serial.h:75
Serial adapter using fl:: input functions (fl::available, fl::read)
Definition serial.h:74
void write(const char *data, fl::size len) FL_NO_EXCEPT
Definition serial.h:94
void println(const char *str) FL_NO_EXCEPT
Definition serial.h:93
Serial adapter using fl:: output functions (fl::println)
Definition serial.h:92