FastLED 3.9.15
Loading...
Searching...
No Matches
RpcBidirectional.ino
Go to the documentation of this file.
1// @filter: (platform is native)
2
20
21#include <FastLED.h>
22#include "fl/remote/remote.h"
34#include <thread> // ok include
35#include <atomic> // ok include
36#include "fl/stl/chrono.h"
37#include "fl/stl/thread.h"
38
39#define NUM_LEDS 10
40#define DATA_PIN 3
41#define SERVER_PORT 0 // OS auto-assigns available port
42
44
45// Server-side components
48
49// Client-side components
52
53// Server background thread (needed for same-process client+server)
54std::thread serverThread;
55std::atomic<bool> serverRunning(false);
56
57// EngineEvents listener for cleanup - runs during onExit() BEFORE static destruction
58// (static destructors happen too late - thread_local storage in fastled.so is already gone)
60 void onExit() override {
61 serverRunning.store(false);
62 if (serverThread.joinable()) {
63 serverThread.join();
64 }
65 clientRemote.reset();
66 clientTransport.reset();
67 serverRemote.reset();
68 serverTransport.reset();
69 }
70};
72
73// Run server in background thread so client can connect without deadlock
75 while (serverRunning.load()) {
76 uint32_t now = millis();
77 serverTransport->acceptClients();
78 serverTransport->update(now);
79 if (serverRemote) {
80 serverRemote->update(now);
81 }
82 fl::this_thread::sleep_for(fl::chrono::milliseconds(10)); // ok sleep for
83 }
84}
85
86// Request tracking
87int requestId = 1;
88bool waitingForResponse = false;
89uint32_t lastRequestTime = 0;
90const uint32_t REQUEST_INTERVAL = 3000; // 3 seconds between requests
91
92// Test mode
94 TEST_SYNC, // Test SYNC mode (add)
95 TEST_ASYNC, // Test ASYNC mode (longTask)
96 TEST_ASYNC_STREAM // Test ASYNC_STREAM mode (streamData)
97};
99
100void setup() {
101 Serial.begin(115200);
102 while (!Serial && millis() < 3000) {
103 // Wait for serial or timeout
104 }
105
106 Serial.println("\n=== HTTP RPC Bidirectional Example ===\n");
107 Serial.println("This example demonstrates server + client in one process:");
108 Serial.println(" - Server listens on port 8080");
109 Serial.println(" - Client connects to localhost:8080");
110 Serial.println(" - All three RPC modes demonstrated");
111 Serial.println();
112
113 // Initialize LEDs
114 FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
115 FastLED.setBrightness(50);
117 FastLED.show();
118
119 // ========== SERVER SETUP ==========
120 Serial.println("Starting HTTP server on port 8080...");
121
123 serverTransport->setHeartbeatInterval(30000);
124 serverTransport->setTimeout(60000);
125
126 // Connection callbacks
127 serverTransport->setOnConnect([]() {
128 Serial.println("[SERVER] Client connected!");
129 leds[0] = CRGB::Green;
130 FastLED.show();
131 });
132
133 serverTransport->setOnDisconnect([]() {
134 Serial.println("[SERVER] Client disconnected!");
135 leds[0] = CRGB::Red;
136 FastLED.show();
137 });
138
139 // Create server Remote
141 []() { return serverTransport->readRequest(); },
142 [](const fl::json& response) { serverTransport->writeResponse(response); }
143 );
144
145 // ========== BIND SERVER METHODS ==========
146
147 // SYNC mode: add(a, b)
148 serverRemote->bind("add", [](const fl::json& params) -> fl::json {
149 int a = 0, b = 0;
150 if (params.is_array() && params.size() >= 2) {
151 auto aOpt = params[0].as_int();
152 auto bOpt = params[1].as_int();
153 if (aOpt && bOpt) {
154 a = *aOpt;
155 b = *bOpt;
156 }
157 }
158 int result = a + b;
159 fl::printf("[SERVER] add(%d, %d) = %d\n", a, b, result);
160 return fl::json(result);
161 });
162
163 // ASYNC mode: longTask(duration)
164 serverRemote->bindAsync("longTask", [](fl::ResponseSend& send, const fl::json& params) {
165 // Send ACK
167 ack.set("ack", true);
168 send.send(ack);
169
170 // Extract duration
171 int duration = 1000;
172 if (params.is_array() && params.size() > 0) {
173 auto durationOpt = params[0].as_int();
174 if (durationOpt) {
175 duration = *durationOpt;
176 }
177 }
178
179 fl::printf("[SERVER] longTask(%d) - ACK sent\n", duration);
180
181 // Simulate work
182 uint32_t startTime = millis();
183 while (millis() - startTime < static_cast<uint32_t>(duration)) {
184 delay(100);
185 }
186
187 // Send result
188 fl::json result = fl::json::object();
189 result.set("value", 42);
190 result.set("duration", duration);
191 send.send(result);
192
193 fl::printf("[SERVER] longTask(%d) - DONE\n", duration);
195
196 // ASYNC_STREAM mode: streamData(count)
197 serverRemote->bindAsync("streamData", [](fl::ResponseSend& send, const fl::json& params) {
198 // Send ACK
200 ack.set("ack", true);
201 send.send(ack);
202
203 // Extract count
204 int count = 5;
205 if (params.is_array() && params.size() > 0) {
206 auto countOpt = params[0].as_int();
207 if (countOpt) {
208 count = *countOpt;
209 }
210 }
211
212 fl::printf("[SERVER] streamData(%d) - ACK sent\n", count);
213
214 // Send updates
215 for (int i = 0; i < count; i++) {
216 fl::json update = fl::json::object();
217 update.set("update", i);
218 send.sendUpdate(update);
219 fl::printf("[SERVER] streamData - update %d/%d\n", i + 1, count);
220 delay(200);
221 }
222
223 // Send final
224 fl::json final = fl::json::object();
225 final.set("value", count);
226 send.sendFinal(final);
227
228 fl::printf("[SERVER] streamData(%d) - DONE\n", count);
230
231 // Start server
232 if (!serverTransport->connect()) {
233 Serial.println("ERROR: Failed to start server!");
234 return;
235 }
236 Serial.println("✓ Server started successfully");
237 fl::u16 actualPort = serverTransport->port();
238 Serial.print(" Listening on port: ");
239 Serial.println(actualPort);
240 Serial.println();
241
242 // Start server in background thread so client can connect without deadlock
243 serverRunning.store(true);
244 serverThread = std::thread(serverThreadFunc);
245
246 // Register cleanup listener now — must happen after thread starts so the
247 // thread is always joined during onExit(), even if setup() returns early.
248 // Without this, std::thread destructor calls std::terminate() on a joinable thread.
250
251 // ========== CLIENT SETUP ==========
252 delay(500); // Give server time to start and begin accepting connections
253
254 Serial.println("Connecting client to server...");
255
257 clientTransport->setHeartbeatInterval(30000);
258 clientTransport->setTimeout(60000);
259
260 // Connection callbacks
261 clientTransport->setOnConnect([]() {
262 Serial.println("[CLIENT] Connected to server!");
263 leds[9] = CRGB::Blue;
264 FastLED.show();
265 });
266
267 clientTransport->setOnDisconnect([]() {
268 Serial.println("[CLIENT] Disconnected from server!");
269 leds[9] = CRGB::Red;
270 FastLED.show();
271 waitingForResponse = false;
272 });
273
274 // Create client Remote
276 []() { return clientTransport->readRequest(); },
277 [](const fl::json& response) { clientTransport->writeResponse(response); }
278 );
279
280 // Connect to server
281 if (!clientTransport->connect()) {
282 Serial.println("ERROR: Failed to connect to server!");
283 return;
284 }
285
286 Serial.println("✓ Client connected successfully\n");
287 Serial.println("Starting test sequence...\n");
288}
289
291 Serial.println(">>> [CLIENT] Testing SYNC mode: add(2, 3)");
292
293 fl::json request;
294 request["jsonrpc"] = "2.0";
295 request["method"] = "add";
296 fl::json params;
297 params.push_back(2);
298 params.push_back(3);
299 request["params"] = params;
300 request["id"] = requestId++;
301
302 waitingForResponse = true;
303 clientTransport->writeResponse(request);
304}
305
307 Serial.println(">>> [CLIENT] Testing ASYNC mode: longTask(1000)");
308
309 fl::json request;
310 request["jsonrpc"] = "2.0";
311 request["method"] = "longTask";
312 fl::json params;
313 params.push_back(1000);
314 request["params"] = params;
315 request["id"] = requestId++;
316
317 waitingForResponse = true;
318 clientTransport->writeResponse(request);
319}
320
322 Serial.println(">>> [CLIENT] Testing ASYNC_STREAM mode: streamData(5)");
323
324 fl::json request;
325 request["jsonrpc"] = "2.0";
326 request["method"] = "streamData";
327 fl::json params;
328 params.push_back(5);
329 request["params"] = params;
330 request["id"] = requestId++;
331
332 waitingForResponse = true;
333 clientTransport->writeResponse(request);
334}
335
336void handleClientResponse(const fl::json& response) {
337 if (response.contains("result")) {
338 const fl::json& result = response["result"];
339
340 // ACK
341 if (result.contains("ack")) {
342 auto ackOpt = result["ack"].as_bool();
343 if (ackOpt && *ackOpt) {
344 Serial.println("<<< [CLIENT] Received ACK");
345 return;
346 }
347 }
348
349 // Update
350 if (result.contains("update")) {
351 auto updateOpt = result["update"].as_int();
352 if (updateOpt) {
353 int update = *updateOpt;
354 fl::printf("<<< [CLIENT] Received update: %d\n", update);
355 leds[update % NUM_LEDS] = CRGB::Purple;
356 FastLED.show();
357 }
358 return;
359 }
360
361 // Final
362 if (result.contains("stop")) {
363 auto stopOpt = result["stop"].as_bool();
364 if (stopOpt && *stopOpt) {
365 Serial.println("<<< [CLIENT] Received FINAL result");
366 waitingForResponse = false;
367 return;
368 }
369 }
370
371 // Regular result
372 Serial.print("<<< [CLIENT] Received result: ");
373 Serial.println(result.to_string().c_str());
374 waitingForResponse = false;
375 }
376}
377
378void loop() {
379 uint32_t now = millis();
380
381 // Server is managed by serverThread - no update needed here
382
383 // Update client
384 clientTransport->update(now);
385 clientRemote->update(now);
386
387 // Process client responses
388 fl::optional<fl::json> response = clientTransport->readRequest();
389 if (response) {
390 handleClientResponse(*response);
391 }
392
393 // Send requests periodically
395 lastRequestTime = now;
396
397 switch (currentMode) {
398 case TEST_SYNC:
401 break;
402
403 case TEST_ASYNC:
406 break;
407
411 Serial.println("\n--- Test cycle complete, starting over ---\n");
412 break;
413 }
414 }
415
416 delay(10);
417}
#define NUM_LEDS
fl::CRGB leds[NUM_LEDS]
#define DATA_PIN
Definition ClientReal.h:82
#define SERVER_PORT
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
@ TEST_ASYNC_STREAM
@ TEST_ASYNC
@ TEST_SYNC
int requestId
void setup()
const uint32_t REQUEST_INTERVAL
static ServerCleanup serverCleanup
void sendAsyncRequest()
fl::unique_ptr< fl::Remote > clientRemote
std::atomic< bool > serverRunning(false)
fl::unique_ptr< fl::net::http::HttpStreamClient > clientTransport
void sendSyncRequest()
void serverThreadFunc()
fl::unique_ptr< fl::net::http::HttpStreamServer > serverTransport
TestMode currentMode
void sendAsyncStreamRequest()
uint32_t lastRequestTime
void handleClientResponse(const fl::json &response)
std::thread serverThread
bool waitingForResponse
fl::unique_ptr< fl::Remote > serverRemote
void loop()
FastLED chrono implementation - duration types for time measurements.
static void addListener(Listener *listener, int priority=0) FL_NOEXCEPT
void send(const fl::json &result)
Send a single response (for ASYNC mode)
void sendFinal(const fl::json &result)
Send final response and mark stream as complete (for ASYNC_STREAM mode)
void sendUpdate(const fl::json &update)
Send intermediate streaming update (for ASYNC_STREAM mode)
Helper class for sending responses in async/streaming RPC methods.
void push_back(const json &value) FL_NOEXCEPT
Definition json.h:745
fl::optional< i64 > as_int() const FL_NOEXCEPT
Definition json.h:255
bool is_array() const FL_NOEXCEPT
Definition json.h:246
size_t size() const FL_NOEXCEPT
Definition json.h:633
void set(const fl::string &key, const json &value) FL_NOEXCEPT
Definition json.h:701
static json object() FL_NOEXCEPT
Definition json.h:692
void fill_solid(CRGB *targetArray, int numToFill, const CRGB &color) FL_NOEXCEPT
Fill a range of LEDs with a solid color.
Definition fill.cpp.hpp:9
constexpr EOrder GRB
Definition eorder.h:19
fl::CRGB CRGB
Definition crgb.h:25
duration< fl::i64, fl::milli > milliseconds
Milliseconds - duration with period of 1/1,000 seconds.
Definition chrono.h:103
void printf(const char *format, const Args &... args) FL_NOEXCEPT
Printf-like formatting function that prints directly to the platform output.
Definition stdio.h:635
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
Definition unique_ptr.h:261
Optional< T > optional
Definition optional.h:16
void onExit() override
@ Green
<div style='background:#008000;width:4em;height:4em;'></div>
Definition crgb.h:558
@ Red
<div style='background:#FF0000;width:4em;height:4em;'></div>
Definition crgb.h:622
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
Definition crgb.h:512
@ Purple
<div style='background:#800080;width:4em;height:4em;'></div>
Definition crgb.h:621
@ Black
<div style='background:#000000;width:4em;height:4em;'></div>
Definition crgb.h:510
#define Serial
Definition serial.h:304