FastLED 3.9.15
Loading...
Searching...
No Matches
rpc.cpp.hpp
Go to the documentation of this file.
1#include "fl/remote/rpc/rpc.h"
2#include "fl/stl/int.h"
3#include "fl/stl/json.h"
4#include "fl/log/log.h"
5#include "fl/system/sketch_macros.h" // FL_PLATFORM_HAS_LARGE_MEMORY -- gates rpc.discover
10#include "fl/stl/optional.h"
11#include "fl/stl/shared_ptr.h"
12#include "fl/stl/string.h"
13#include "fl/stl/strstream.h"
14#include "fl/stl/tuple.h"
16#include "fl/stl/vector.h"
17
18namespace fl {
19
20// =============================================================================
21// Rpc::setResponseSink() - Set response sink for async ACKs
22// =============================================================================
23
24void Rpc::setResponseSink(fl::function<void(const fl::json&)> sink) {
25 mResponseSink = fl::move(sink);
26}
27
28// =============================================================================
29// Rpc::bindAsync() - Bind async method with ResponseSend parameter
30// =============================================================================
31
32void Rpc::bindAsync(const char* name,
33 fl::function<void(ResponseSend&, const json&)> fn,
34 fl::RpcMode mode) {
35 fl::string key(name);
36
37 detail::RpcEntry entry;
38 entry.mTypeTag = detail::TypeTag<void(const json&)>::id();
39 entry.mMode = mode;
40 entry.mIsResponseAware = true;
41 entry.mResponseAwareFn = fl::move(fn);
42
43#if FL_PLATFORM_HAS_LARGE_MEMORY
44 // Create schema generator for void(json) signature (params not decomposed).
45 // Gated on Low-memory per FastLED #3081 / #3079 (rpc.discover unreachable).
47#endif
48 entry.mDescription = "";
49 entry.mTags = {};
50
51 // Create a placeholder invoker (actual invocation handled in handle())
52 struct PlaceholderInvoker : public detail::ErasedInvoker {
54 // Should not be called - handle() will call mResponseAwareFn directly
56 }
57 };
59
60 mRegistry[key] = fl::move(entry);
61}
62
63// =============================================================================
64// Rpc::handle() - Process JSON-RPC requests
65// =============================================================================
66
67json Rpc::handle(const json& request) {
68 // Extract method name
69 if (!request.contains("method")) {
70 FL_ERROR("RPC: Invalid Request - missing 'method' field");
71 return detail::makeJsonRpcError(-32600, "Invalid Request: missing 'method'", request["id"]);
72 }
73
74 auto methodOpt = request["method"].as_string();
75 if (!methodOpt.has_value()) {
76 FL_ERROR("RPC: Invalid Request - 'method' must be a string");
77 return detail::makeJsonRpcError(-32600, "Invalid Request: 'method' must be a string", request["id"]);
78 }
79 fl::string methodName = methodOpt.value();
80
81#if FL_PLATFORM_HAS_LARGE_MEMORY
82 // Handle built-in rpc.discover method. Gated on Low-memory targets because
83 // it transitively anchors `TypedSchemaGenerator<Sig>::params()` for every
84 // registered method (each 800+ B per signature) plus the schema-building
85 // JSON tree. The LPC8xx / AVR-class JSON-RPC bring-up surface treats the
86 // RPC catalog as a known constant -- callers don't introspect at runtime.
87 // See FastLED #3081.
88 if (methodName == "rpc.discover") {
89 json response = json::object();
90 response.set("jsonrpc", "2.0");
91 response.set("result", schema());
92 if (request.contains("id")) {
93 response.set("id", request["id"]);
94 }
95 return response;
96 }
97#endif
98
99 // Look up the method
100 auto it = mRegistry.find(methodName);
101 if (it == mRegistry.end()) {
102 FL_WARN("RPC: Method not found: " << methodName.c_str());
103 return detail::makeJsonRpcError(-32601, "Method not found: " + methodName, request["id"]);
104 }
105
106 // Extract params (default to empty array)
107 json params = request.contains("params") ? request["params"] : json::parse("[]");
108 if (!params.is_array()) {
109 FL_ERROR("RPC: Invalid params - must be an array for method: " << methodName.c_str());
110 return detail::makeJsonRpcError(-32602, "Invalid params: must be an array", request["id"]);
111 }
112
113 // Check if this is an async function
114 const detail::RpcEntry& entry = it->second;
115 bool isAsync = (entry.mMode == RpcMode::ASYNC || entry.mMode == RpcMode::ASYNC_STREAM);
116
117 // Check if this is a response-aware function (uses ResponseSend&)
118 bool isResponseAware = entry.mIsResponseAware;
119
120 // For async functions, send ACK immediately
121 if (isAsync && mResponseSink && request.contains("id")) {
122 json ack = json::object();
123 ack.set("jsonrpc", "2.0");
124 ack.set("id", request["id"]);
125
126 json ackResult = json::object();
127 ackResult.set("acknowledged", true);
128 ack.set("result", ackResult);
129
130 mResponseSink(ack);
131 FL_DBG("RPC: Sent ACK for async method: " << methodName.c_str());
132 }
133
135
136 // Handle response-aware methods (with ResponseSend& parameter)
137 if (isResponseAware) {
138 // Create ResponseSend instance
139 fl::json requestId = request.contains("id") ? request["id"] : json(nullptr);
140 ResponseSend responseSend(requestId, mResponseSink);
141
142 // Invoke user function with ResponseSend& and raw JSON params
143 entry.mResponseAwareFn(responseSend, params);
144
145 // Return success with null result (actual responses sent via ResponseSend)
146 resultTuple = fl::make_tuple(TypeConversionResult::success(), json(nullptr));
147 } else {
148 // Regular invocation
149 resultTuple = entry.mInvoker->invoke(params);
150 }
151
152 TypeConversionResult convResult = fl::get<0>(resultTuple);
153 json returnVal = fl::get<1>(resultTuple);
154
155 // Check for conversion errors
156 if (!convResult.ok()) {
157 FL_ERROR("RPC: Invalid params for method '" << methodName.c_str() << "': " << convResult.errorMessage().c_str());
158 return detail::makeJsonRpcError(-32602, "Invalid params: " + convResult.errorMessage(), request["id"]);
159 }
160
161 // Build success response
162 json response = json::object();
163 response.set("jsonrpc", "2.0");
164 response.set("result", returnVal);
165
166 // Include id if present (for request/response correlation)
167 if (request.contains("id")) {
168 response.set("id", request["id"]);
169 }
170
171 // Include warnings if any
172 if (convResult.hasWarning()) {
173 json warnings = json::array();
174 for (fl::size i = 0; i < convResult.warnings().size(); ++i) {
175 warnings.push_back(json(convResult.warnings()[i]));
176 }
177 response.set("warnings", warnings);
178 }
179
180 // For async functions, mark response to not queue it (ACK already sent)
181 if (isAsync) {
182 response.set("__async", true); // Internal marker
183 }
184
185 return response;
186}
187
188// =============================================================================
189// Rpc::handle_maybe() - Process notifications (no id returns nullopt)
190// =============================================================================
191
193 // If no id, this is a notification - process but don't return response
194 if (!request.contains("id")) {
195 // Still need to execute the method
196 if (request.contains("method")) {
197 auto methodOpt = request["method"].as_string();
198 if (methodOpt.has_value()) {
199 fl::string methodName = methodOpt.value();
200 auto it = mRegistry.find(methodName);
201 if (it != mRegistry.end()) {
202 json params = request.contains("params") ? request["params"] : json::parse("[]");
203 if (params.is_array()) {
204 it->second.mInvoker->invoke(params);
205 }
206 }
207 }
208 }
209 return fl::nullopt;
210 }
211
212 return handle(request);
213}
214
215// =============================================================================
216// Rpc::tags() - Returns list of unique tags
217// =============================================================================
218
221 for (auto it = mRegistry.begin(); it != mRegistry.end(); ++it) {
222 for (fl::size i = 0; i < it->second.mTags.size(); ++i) {
223 bool found = false;
224 for (fl::size j = 0; j < result.size(); ++j) {
225 if (result[j] == it->second.mTags[i]) {
226 found = true;
227 break;
228 }
229 }
230 if (!found) {
231 result.push_back(it->second.mTags[i]);
232 }
233 }
234 }
235 return result;
236}
237
238// =============================================================================
239// Rpc::methods() - Returns flat method array
240// =============================================================================
241
243 json arr = json::array();
244 for (auto it = mRegistry.begin(); it != mRegistry.end(); ++it) {
245 // Format: ["methodName", "returnType", [["param1", "type1"], ["param2", "type2"]], "mode"]
246 json methodTuple = json::array();
247 methodTuple.push_back(it->first.c_str()); // Method name
248 methodTuple.push_back(it->second.mSchemaGenerator->resultTypeName()); // Return type
249 methodTuple.push_back(it->second.mSchemaGenerator->params()); // Params array
250
251 // Add mode (sync or async)
252 const char* modeStr = (it->second.mMode == RpcMode::ASYNC) ? "async" : "sync";
253 methodTuple.push_back(modeStr);
254
255 arr.push_back(methodTuple);
256 }
257 return arr;
258}
259
260// =============================================================================
261// Rpc::schema() - Returns flat schema
262// =============================================================================
263
265 json doc = json::object();
266 doc.set("schema", methods());
267 return doc;
268}
269
270} // namespace fl
int requestId
Helper class for sending responses in async/streaming RPC methods.
void setResponseSink(fl::function< void(const fl::json &)> sink)
Set response sink for sending ACK responses (used by async functions)
Definition rpc.cpp.hpp:24
fl::unordered_map< fl::string, detail::RpcEntry > mRegistry
Definition rpc.h:387
json handle(const json &request)
Process a JSON-RPC request.
Definition rpc.cpp.hpp:67
fl::vector< fl::string > tags() const
Returns list of unique tags used across all methods.
Definition rpc.cpp.hpp:219
fl::function< void(const fl::json &)> mResponseSink
Definition rpc.h:388
json schema() const
Returns flat schema document.
Definition rpc.cpp.hpp:264
fl::optional< json > handle_maybe(const json &request)
For notifications (no id), returns nullopt.
Definition rpc.cpp.hpp:192
json methods() const
Returns flat method array: [["name", "returnType", [["param1", "type1"], ...]], .....
Definition rpc.cpp.hpp:242
void bindAsync(const char *name, fl::function< void(ResponseSend &, const json &)> fn, fl::RpcMode mode=fl::RpcMode::ASYNC)
Bind async method with ResponseSend& parameter (for ASYNC/ASYNC_STREAM modes) Signature: void(Respons...
Definition rpc.cpp.hpp:32
const fl::string & errorMessage() const
const fl::vector< fl::string > & warnings() const
static TypeConversionResult success()
const char * c_str() const FL_NOEXCEPT
void push_back(const json &value) FL_NOEXCEPT
Definition json.h:745
bool is_array() const FL_NOEXCEPT
Definition json.h:246
bool contains(size_t idx) const FL_NOEXCEPT
Definition json.h:625
fl::optional< fl::string > as_string() const FL_NOEXCEPT
Definition json.h:282
void set(const fl::string &key, const json &value) FL_NOEXCEPT
Definition json.h:701
static json parse(const fl::string &txt) FL_NOEXCEPT
Definition json.h:677
static json object() FL_NOEXCEPT
Definition json.h:692
static json array() FL_NOEXCEPT
Definition json.h:688
FastLED's Elegant JSON Library: fl::json
#define FL_WARN(X)
Definition log.h:276
#define FL_ERROR(X)
Definition log.h:219
#define FL_DBG
Definition log.h:388
Centralized logging categories for FastLED hardware interfaces and subsystems.
json makeJsonRpcError(int code, const fl::string &message, const json &id)
fl::shared_ptr< ErasedInvoker > mInvoker
fl::function< void(ResponseSend &, const json &)> mResponseAwareFn
fl::shared_ptr< ErasedSchemaGenerator > mSchemaGenerator
fl::string mDescription
fl::vector< fl::string > mTags
const void * mTypeTag
constexpr remove_reference< T >::type && move(T &&t) FL_NOEXCEPT
Definition s16x16x4.h:28
Optional< T > optional
Definition optional.h:16
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
tuple< typename fl::decay< Ts >::type... > make_tuple(Ts &&... args) FL_NOEXCEPT
Definition tuple.h:104
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
constexpr nullopt_t nullopt
Definition optional.h:13
auto invoke(F &&f, T1 &&t1, Args &&... args) FL_NOEXCEPT -> enable_if_t< is_member_function_pointer< typename remove_reference< F >::type >::value &&!detail::use_pointer_syntax< T1 >::value, decltype((fl::forward< T1 >(t1).*f)(fl::forward< Args >(args)...))>
Definition functional.h:43
pair_element< I, T1, T2 >::type & get(pair< T1, T2 > &p) FL_NOEXCEPT
Definition pair.h:115
RpcMode
Definition rpc_mode.h:9
Base definition for an LED controller.
Definition crgb.hpp:179