FastLED 3.9.15
Loading...
Searching...
No Matches
ClientValidationReal.h
Go to the documentation of this file.
1
17
18#pragma once
19
20// Disable fast exit - test needs time to complete
21#undef FASTLED_STUB_MAIN_FAST_EXIT
22
24#include "fl/net/http/fetch.h"
25#include "fl/task/executor.h"
26#include <FastLED.h>
27
28// For signaling test completion
29#ifdef FASTLED_STUB
30#include "platforms/stub_main.hpp"
31#endif
32
33#define NUM_LEDS 10
34#define DATA_PIN 2
35#define SERVER_PORT 8081
36
39
48
52uint32_t test_start_time = 0;
53bool done = false; // Track completion to print results once
54
55void updateLEDs() {
56 switch (state) {
57 case SERVER_STARTING:
58 fill_solid(leds, NUM_LEDS, fl::CRGB(0, 0, beatsin8(60))); // Blue pulse
59 break;
60 case TEST_JSON:
61 case TEST_GET:
62 case TEST_PING:
63 fill_solid(leds, NUM_LEDS, fl::CRGB(64, 64, 0)); // Yellow - testing
64 break;
65 case ALL_PASSED:
66 fill_solid(leds, NUM_LEDS, fl::CRGB(0, 64, 0)); // Green - success
67 break;
68 case FAILED:
69 fill_solid(leds, NUM_LEDS, fl::CRGB(64, 0, 0)); // Red - failure
70 break;
71 }
72}
73
75 FL_WARN("\n=== Test 1: GET /json (Slideshow Data) ===");
76
77 fl::task::Promise<fl::net::http::Response> promise = fl::net::http::fetch_get("http://localhost:8081/json");
79
80 if (!result.ok()) {
81 FL_WARN("✗ FAILED: " << result.error_message());
83 state = FAILED;
84 return;
85 }
86
87 const fl::net::http::Response& resp = result.value();
88 if (resp.status() != 200) {
89 FL_WARN("✗ FAILED: Status " << resp.status() << " " << resp.status_text());
91 state = FAILED;
92 return;
93 }
94
95 if (!resp.is_json()) {
96 FL_WARN("✗ FAILED: Response is not JSON");
98 state = FAILED;
99 return;
100 }
101
102 fl::json data = resp.json();
103 fl::string author = data["slideshow"]["author"] | fl::string("unknown");
104 fl::string title = data["slideshow"]["title"] | fl::string("untitled");
105 int slide_count = data["slideshow"]["slides"].size();
106
107 if (author == "unknown" || title == "untitled" || slide_count == 0) {
108 FL_WARN("✗ FAILED: Invalid JSON structure");
109 FL_WARN(" Author: " << author);
110 FL_WARN(" Title: " << title);
111 FL_WARN(" Slides: " << slide_count);
112 tests_failed++;
113 state = FAILED;
114 return;
115 }
116
117 FL_WARN("✓ PASSED");
118 FL_WARN(" Author: " << author);
119 FL_WARN(" Title: " << title);
120 FL_WARN(" Slides: " << slide_count);
121 tests_passed++;
122}
123
125 FL_WARN("\n=== Test 2: GET /get (Request Echo) ===");
126
127 fl::task::Promise<fl::net::http::Response> promise = fl::net::http::fetch_get("http://localhost:8081/get");
129
130 if (!result.ok()) {
131 FL_WARN("✗ FAILED: " << result.error_message());
132 tests_failed++;
133 state = FAILED;
134 return;
135 }
136
137 const fl::net::http::Response& resp = result.value();
138 if (resp.status() != 200) {
139 FL_WARN("✗ FAILED: Status " << resp.status() << " " << resp.status_text());
140 tests_failed++;
141 state = FAILED;
142 return;
143 }
144
145 if (!resp.is_json()) {
146 FL_WARN("✗ FAILED: Response is not JSON");
147 tests_failed++;
148 state = FAILED;
149 return;
150 }
151
152 fl::json data = resp.json();
153 fl::string origin = data["origin"] | fl::string("unknown");
154 fl::string url = data["url"] | fl::string("unknown");
155
156 if (origin == "unknown" || url == "unknown") {
157 FL_WARN("✗ FAILED: Invalid response structure");
158 tests_failed++;
159 state = FAILED;
160 return;
161 }
162
163 FL_WARN("✓ PASSED");
164 FL_WARN(" Origin: " << origin);
165 FL_WARN(" URL: " << url);
166 tests_passed++;
167}
168
170 FL_WARN("\n=== Test 3: GET /ping (Health Check) ===");
171
172 fl::task::Promise<fl::net::http::Response> promise = fl::net::http::fetch_get("http://localhost:8081/ping");
174
175 if (!result.ok()) {
176 FL_WARN("✗ FAILED: " << result.error_message());
177 tests_failed++;
178 state = FAILED;
179 return;
180 }
181
182 const fl::net::http::Response& resp = result.value();
183 if (resp.status() != 200) {
184 FL_WARN("✗ FAILED: Status " << resp.status() << " " << resp.status_text());
185 tests_failed++;
186 state = FAILED;
187 return;
188 }
189
190 fl::string body = resp.text();
191 if (body != "pong\n") {
192 FL_WARN("✗ FAILED: Expected 'pong\\n', got '" << body << "'");
193 tests_failed++;
194 state = FAILED;
195 return;
196 }
197
198 FL_WARN("✓ PASSED");
199 FL_WARN(" Response: " << body.substr(0, body.length() - 1)); // Strip newline for display
200 tests_passed++;
201}
202
203void setup() {
204 Serial.begin(115200);
205 Serial.println("\nHTTP Client Validation Suite (Loopback Mode)");
206 Serial.println("Starting self-contained server + client test\n");
207
208 FastLED.addLeds<WS2812, DATA_PIN, GRB>(leds, NUM_LEDS);
209 FastLED.setBrightness(64);
210
211 // Register HTTP server routes that mimic httpbin.org
212
213 // ROUTE 1: GET /json - Sample JSON slideshow data
214 server.get("/json", [](const fl::http_request& req) {
215 // Return JSON as raw string with proper Content-Type header
216 const char* json_response = R"({
217 "slideshow": {
218 "author": "FastLED Community",
219 "title": "FastLED Tutorial",
220 "slides": [
221 {"title": "Introduction to FastLED", "type": "tutorial"},
222 {"title": "LED Basics", "type": "lesson"},
223 {"title": "HTTP Fetch API", "type": "demo"}
224 ]
225 }
226})";
227 fl::http_response response;
228 response.status(200)
229 .header("Content-Type", "application/json")
230 .body(json_response);
231 return response;
232 });
233
234 // ROUTE 2: GET /get - Echo request information
235 server.get("/get", [](const fl::http_request& req) {
236 const char* json_response = R"({
237 "origin": "127.0.0.1",
238 "url": "http://localhost:8081/get"
239})";
240 fl::http_response response;
241 response.status(200)
242 .header("Content-Type", "application/json")
243 .body(json_response);
244 return response;
245 });
246
247 // ROUTE 3: GET /ping - Health check
248 server.get("/ping", [](const fl::http_request& req) {
249 return fl::http_response::ok("pong\n");
250 });
251
252 // Start server (automatically integrates with FastLED async system)
253 if (server.start(SERVER_PORT)) {
254 Serial.print("Server started on http://localhost:");
255 Serial.println(SERVER_PORT);
257 } else {
258 Serial.println("ERROR: Failed to start server");
259 Serial.print("Error: ");
260 Serial.println(server.last_error().c_str());
261 state = FAILED;
262 }
263
264 updateLEDs();
265 FastLED.show();
267}
268
269void loop() {
270 // Server updates are now handled by the task (every 1ms)
271 // No need for manual server.update() here
272
273 // Wait 1 second after startup before starting tests
274 if (state == SERVER_STARTING && (fl::millis() - test_start_time > 1000)) {
275 FL_WARN("=================================");
276 FL_WARN("Starting HTTP Client Tests");
277 FL_WARN("=================================");
279 }
280
281 // Run tests sequentially
282 if (state == TEST_JSON) {
284 if (state != FAILED) state = TEST_GET;
285 delay(500);
286 }
287 else if (state == TEST_GET) {
289 if (state != FAILED) state = TEST_PING;
290 delay(500);
291 }
292 else if (state == TEST_PING) {
294 if (state != FAILED) {
295 // All tests passed!
297
298 FL_WARN("\n=================================");
299 FL_WARN("Test Results");
300 FL_WARN("=================================");
301 FL_WARN("Passed: " << tests_passed);
302 FL_WARN("Failed: " << tests_failed);
303 FL_WARN("Total: " << (tests_passed + tests_failed));
304 FL_WARN("=================================");
305 FL_WARN("✓ All tests PASSED");
306 }
307 delay(500);
308 }
309 else if (state == FAILED && !done) {
310 FL_WARN("\n=================================");
311 FL_WARN("Test Results");
312 FL_WARN("=================================");
313 FL_WARN("Passed: " << tests_passed);
314 FL_WARN("Failed: " << tests_failed);
315 FL_WARN("Total: " << (tests_passed + tests_failed));
316 FL_WARN("=================================");
317 FL_WARN("✗ Some tests FAILED");
318 done = true;
319 // Signal completion - cleanup happens automatically via ScopedEngineCleanup
320 #ifdef FASTLED_STUB
321 fl::stub_main::stop_loop();
322 #endif
323 }
324 else if (state == ALL_PASSED && !done) {
325 done = true;
326 // Signal completion - cleanup happens automatically via ScopedEngineCleanup
327 #ifdef FASTLED_STUB
328 fl::stub_main::stop_loop();
329 #endif
330 }
331 // Keep updating LEDs on hardware
332
333 updateLEDs();
334 FastLED.show();
335 delay(100);
336}
#define NUM_LEDS
fl::UITitle title("Animartrix")
fl::CRGB leds[NUM_LEDS]
#define DATA_PIN
Definition ClientReal.h:82
bool done
void test_get_endpoint()
void test_ping_endpoint()
void setup()
fl::HttpServer server
uint32_t test_start_time
@ SERVER_STARTING
int tests_passed
int tests_failed
#define SERVER_PORT
void test_json_endpoint()
void updateLEDs()
TestState state
void loop()
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
static Response ok(const string &body="")
Factory method for 200 OK response.
fl::size length() const FL_NOEXCEPT
size_t size() const FL_NOEXCEPT
Definition json.h:633
const fl::string & text() const
Response body as text (like JavaScript response.text())
Definition fetch.h:95
bool is_json() const
Check if response appears to contain JSON content.
Definition fetch.h:122
const fl::string & status_text() const
HTTP status text (like JavaScript response.statusText)
Definition fetch.h:89
int status() const
HTTP status code (like JavaScript response.status)
Definition fetch.h:86
fl::json json() const
Response body parsed as JSON (JavaScript-like API)
HTTP response class (unified interface)
Definition fetch.h:78
string substr(fl::size start, fl::size length) const FL_NOEXCEPT
Promise class that provides fluent .then() and .catch_() semantics This is a lightweight wrapper arou...
Definition promise.h:58
Result type for promise operations.
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
Task executor — runs registered task runners and manages the run loop.
Unified HTTP fetch API for FastLED (cross-platform)
LIB8STATIC u8 beatsin8(accum88 beats_per_minute, u8 lowest=0, u8 highest=255, u32 timebase=0, u8 phase_offset=0) FL_NOEXCEPT
Generates an 8-bit sine wave at a given BPM that oscillates within a given range.
Definition beat.h:105
#define FL_WARN(X)
Definition log.h:276
fl::task::Promise< Response > fetch_get(const fl::string &url, const FetchOptions &request)
HTTP GET request.
PromiseResult< T > await_top_level(Promise< T > p)
Synchronously wait for a promise to complete (ONLY safe in top-level contexts)
Definition executor.h:186
asio::http::Request http_request
Definition server.h:280
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
asio::http::Response http_response
Definition server.h:281
asio::http::Server HttpServer
Definition server.h:279
Representation of an 8-bit RGB pixel (Red, Green, Blue)
Definition crgb.h:38
#define Serial
Definition serial.h:304