FastLED 3.9.15
Loading...
Searching...
No Matches
AutoResearchNet.cpp
Go to the documentation of this file.
1// examples/AutoResearch/AutoResearchNet.cpp
2//
3// Network autoresearch implementation for ESP32.
4// Uses fl::asio::http::Server (unified HTTP API) for the HTTP server.
5// Uses ESP-IDF native APIs for WiFi Soft AP and HTTP client.
6// Guarded with FL_IS_ESP32 - no-op stubs on other platforms.
7
8#include "AutoResearchNet.h"
9#include "fl/stl/json.h"
10#include "fl/log/log.h"
11
12// Global net state
14
18
19// ============================================================================
20// ESP32 Implementation
21// ============================================================================
22
23#if defined(FL_IS_ESP32)
24
25// Unified HTTP server API (must be included before Arduino.h to avoid INADDR_NONE conflict)
27
28#include "fl/stl/cstring.h"
29#include "fl/stl/unique_ptr.h"
30#include "fl/stl/sstream.h"
31#include <Arduino.h>
32
33// ESP-IDF headers for WiFi AP and HTTP client
34// Must come after Arduino.h to get proper IPAddress definitions
35// IWYU pragma: begin_keep
36#include <esp_wifi.h>
37#include <esp_netif.h>
38#include <esp_event.h>
39#include <esp_http_client.h>
40// IWYU pragma: end_keep
41
42// Static handles
44static esp_netif_t* s_netif_ap = nullptr;
45static bool s_event_loop_initialized = false;
46static bool s_wifi_initialized = false;
47
48// ============================================================================
49// WiFi Soft AP Setup
50// ============================================================================
51
52static bool initWifiAP() {
53 if (s_net_state.wifi_ap_active) {
54 return true; // Already active
55 }
56
57 // Initialize TCP/IP stack (safe to call multiple times)
58 esp_err_t err = esp_netif_init();
59 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
60 FL_WARN("[NET] esp_netif_init failed: " << esp_err_to_name(err));
61 return false;
62 }
63
64 // Create default event loop (safe to call multiple times)
65 if (!s_event_loop_initialized) {
66 err = esp_event_loop_create_default();
67 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
68 FL_WARN("[NET] esp_event_loop_create_default failed: " << esp_err_to_name(err));
69 return false;
70 }
71 s_event_loop_initialized = true;
72 }
73
74 // Create default WiFi AP netif
75 if (!s_netif_ap) {
76 s_netif_ap = esp_netif_create_default_wifi_ap();
77 if (!s_netif_ap) {
78 FL_WARN("[NET] esp_netif_create_default_wifi_ap failed");
79 return false;
80 }
81 }
82
83 // Initialize WiFi with default config
84 if (!s_wifi_initialized) {
85 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
86 err = esp_wifi_init(&cfg);
87 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
88 FL_WARN("[NET] esp_wifi_init failed: " << esp_err_to_name(err));
89 return false;
90 }
91 s_wifi_initialized = true;
92 }
93
94 // Configure AP
95 wifi_config_t wifi_config = {};
97 wifi_config.ap.ssid_len = strlen(AUTORESEARCH_NET_SSID);
99 wifi_config.ap.max_connection = AUTORESEARCH_NET_MAX_CONNECTIONS;
100 wifi_config.ap.authmode = WIFI_AUTH_WPA2_PSK;
101 wifi_config.ap.channel = 1;
102
103 err = esp_wifi_set_mode(WIFI_MODE_AP);
104 if (err != ESP_OK) {
105 FL_WARN("[NET] esp_wifi_set_mode failed: " << esp_err_to_name(err));
106 return false;
107 }
108
109 err = esp_wifi_set_config(WIFI_IF_AP, &wifi_config);
110 if (err != ESP_OK) {
111 FL_WARN("[NET] esp_wifi_set_config failed: " << esp_err_to_name(err));
112 return false;
113 }
114
115 err = esp_wifi_start();
116 if (err != ESP_OK) {
117 FL_WARN("[NET] esp_wifi_start failed: " << esp_err_to_name(err));
118 return false;
119 }
120
121 s_net_state.wifi_ap_active = true;
122 FL_WARN("[NET] WiFi AP started: SSID=" << AUTORESEARCH_NET_SSID << " IP=" << AUTORESEARCH_NET_AP_IP);
123 return true;
124}
125
126// ============================================================================
127// HTTP Server Setup (using unified fl::asio::http::Server)
128// ============================================================================
129
130static bool startHttpServer() {
131 if (s_http_server.get()) {
132 return true; // Already running
133 }
134
136
137 // Register routes using the unified API
138 s_http_server->get("/ping", [](const fl::asio::http::Request&) {
139 return fl::asio::http::Response::ok("pong");
140 });
141
142 s_http_server->get("/status", [](const fl::asio::http::Request&) {
143 fl::json json = fl::json::object();
144 json.set("uptime_ms", static_cast<int64_t>(millis()));
145 json.set("free_heap", static_cast<int64_t>(ESP.getFreeHeap()));
146#if defined(FL_IS_ESP_32S3)
147 json.set("chip", "esp32s3");
148#elif defined(FL_IS_ESP_32C6)
149 json.set("chip", "esp32c6");
150#elif defined(FL_IS_ESP_32C3)
151 json.set("chip", "esp32c3");
152#else
153 json.set("chip", "esp32");
154#endif
156 resp.json(json);
157 return resp;
158 });
159
160 s_http_server->post("/echo", [](const fl::asio::http::Request& req) {
161 if (!req.has_body()) {
163 }
165 });
166
167 s_http_server->get("/leds", [](const fl::asio::http::Request&) {
168 fl::json json = fl::json::object();
169 json.set("num_leds", static_cast<int64_t>(10));
170 json.set("brightness", static_cast<int64_t>(64));
172 resp.json(json);
173 return resp;
174 });
175
176 if (!s_http_server->start(s_net_state.server_port)) {
177 FL_WARN("[NET] HTTP server failed to start: " << s_http_server->last_error().c_str());
178 s_http_server.reset();
179 return false;
180 }
181
182 s_net_state.http_server_active = true;
183 FL_WARN("[NET] HTTP server started on port " << s_net_state.server_port);
184 return true;
185}
186
187// ============================================================================
188// HTTP Client Tests (using esp_http_client - no unified client for ESP32 yet)
189// ============================================================================
190
192static bool initNetifForLoopback() {
193 esp_err_t err = esp_netif_init();
194 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
195 FL_WARN("[NET] esp_netif_init failed: " << esp_err_to_name(err));
196 return false;
197 }
198
199 if (!s_event_loop_initialized) {
200 err = esp_event_loop_create_default();
201 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
202 FL_WARN("[NET] esp_event_loop_create_default failed: "
203 << esp_err_to_name(err));
204 return false;
205 }
206 s_event_loop_initialized = true;
207 }
208 return true;
209}
210
212static fl::json runHttpGetTest(const char* url, const char* test_name) {
214 result.set("test", test_name);
215
216 esp_http_client_config_t config = {};
217 config.url = url;
218 config.timeout_ms = 5000;
219
220 esp_http_client_handle_t client = esp_http_client_init(&config);
221 if (!client) {
222 result.set("passed", false);
223 result.set("error", "Failed to init HTTP client");
224 return result;
225 }
226
227 esp_err_t err = esp_http_client_perform(client);
228 if (err != ESP_OK) {
229 result.set("passed", false);
230 fl::sstream ss;
231 ss << "HTTP request failed: " << esp_err_to_name(err);
232 result.set("error", ss.str().c_str());
233 esp_http_client_cleanup(client);
234 return result;
235 }
236
237 int status = esp_http_client_get_status_code(client);
238 result.set("status_code", static_cast<int64_t>(status));
239
240 if (status != 200) {
241 result.set("passed", false);
242 fl::sstream ss;
243 ss << "Expected status 200, got " << status;
244 result.set("error", ss.str().c_str());
245 } else {
246 result.set("passed", true);
247 }
248
249 esp_http_client_cleanup(client);
250 return result;
251}
252
253// ============================================================================
254// Public API
255// ============================================================================
256
259
260 if (!initWifiAP()) {
261 response.set("success", false);
262 response.set("error", "Failed to start WiFi AP");
263 return response;
264 }
265
266 if (!startHttpServer()) {
267 response.set("success", false);
268 response.set("error", "Failed to start HTTP server");
269 return response;
270 }
271
272 response.set("success", true);
273 response.set("ssid", AUTORESEARCH_NET_SSID);
274 response.set("password", AUTORESEARCH_NET_PASSWORD);
276 response.set("port", static_cast<int64_t>(s_net_state.server_port));
277 return response;
278}
279
282
283 if (!initWifiAP()) {
284 response.set("success", false);
285 response.set("error", "Failed to start WiFi AP");
286 return response;
287 }
288
289 response.set("success", true);
290 response.set("ssid", AUTORESEARCH_NET_SSID);
291 response.set("password", AUTORESEARCH_NET_PASSWORD);
292 response.set("gateway_ip", AUTORESEARCH_NET_AP_IP);
293 return response;
294}
295
296fl::json runNetClientTest(const char* host_ip, uint16_t port) {
298 int tests_passed = 0;
299 int tests_failed = 0;
300 fl::json results = fl::json::array();
301
302 // Build URLs
303 char url_ping[128];
304 char url_data[128];
305 snprintf(url_ping, sizeof(url_ping), "http://%s:%u/ping", host_ip, port);
306 snprintf(url_data, sizeof(url_data), "http://%s:%u/data", host_ip, port);
307
308 // Test 1: GET /ping
309 {
310 fl::json r = runHttpGetTest(url_ping, "GET /ping");
311 auto passed = r[fl::string("passed")].as_bool();
312 if (passed.has_value() && passed.value()) {
313 tests_passed++;
314 } else {
315 tests_failed++;
316 }
317 results.push_back(r);
318 }
319
320 // Test 2: GET /data
321 {
322 fl::json r = runHttpGetTest(url_data, "GET /data");
323 auto passed = r[fl::string("passed")].as_bool();
324 if (passed.has_value() && passed.value()) {
325 tests_passed++;
326 } else {
327 tests_failed++;
328 }
329 results.push_back(r);
330 }
331
332 response.set("success", tests_failed == 0);
333 response.set("tests_passed", static_cast<int64_t>(tests_passed));
334 response.set("tests_failed", static_cast<int64_t>(tests_failed));
335 response.set("results", results);
336 return response;
337}
338
341 int tests_passed = 0;
342 int tests_failed = 0;
343 fl::json results = fl::json::array();
344
345 // Initialize TCP/IP stack for loopback (no WiFi needed)
346 if (!initNetifForLoopback()) {
347 response.set("success", false);
348 response.set("error", "Failed to initialize network stack for loopback");
349 return response;
350 }
351
352 // Start the HTTP server on localhost
353 if (!startHttpServer()) {
354 response.set("success", false);
355 response.set("error", "Failed to start HTTP server for loopback test");
356 return response;
357 }
358
359 FL_WARN("[NET] Loopback test: server running on port " << s_net_state.server_port);
360
361 // Small delay to let server settle
362 delay(100);
363
364 // Build loopback URLs using 127.0.0.1
365 char url_ping[128];
366 char url_status[128];
367 char url_leds[128];
368 snprintf(url_ping, sizeof(url_ping), "http://127.0.0.1:%u/ping",
369 s_net_state.server_port);
370 snprintf(url_status, sizeof(url_status), "http://127.0.0.1:%u/status",
371 s_net_state.server_port);
372 snprintf(url_leds, sizeof(url_leds), "http://127.0.0.1:%u/leds",
373 s_net_state.server_port);
374
375 // Test 1: GET /ping
376 {
377 fl::json r = runHttpGetTest(url_ping, "GET /ping (loopback)");
378 auto passed = r[fl::string("passed")].as_bool();
379 if (passed.has_value() && passed.value()) {
380 tests_passed++;
381 } else {
382 tests_failed++;
383 }
384 results.push_back(r);
385 }
386
387 // Test 2: GET /status
388 {
389 fl::json r = runHttpGetTest(url_status, "GET /status (loopback)");
390 auto passed = r[fl::string("passed")].as_bool();
391 if (passed.has_value() && passed.value()) {
392 tests_passed++;
393 } else {
394 tests_failed++;
395 }
396 results.push_back(r);
397 }
398
399 // Test 3: GET /leds
400 {
401 fl::json r = runHttpGetTest(url_leds, "GET /leds (loopback)");
402 auto passed = r[fl::string("passed")].as_bool();
403 if (passed.has_value() && passed.value()) {
404 tests_passed++;
405 } else {
406 tests_failed++;
407 }
408 results.push_back(r);
409 }
410
411 response.set("success", tests_failed == 0);
412 response.set("tests_passed", static_cast<int64_t>(tests_passed));
413 response.set("tests_failed", static_cast<int64_t>(tests_failed));
414 response.set("results", results);
415 return response;
416}
417
420
421 // Stop HTTP server (unified API)
422 if (s_http_server.get()) {
423 s_http_server->stop();
424 s_http_server.reset();
425 s_net_state.http_server_active = false;
426 FL_WARN("[NET] HTTP server stopped");
427 }
428
429 // Stop WiFi
430 if (s_net_state.wifi_ap_active) {
431 esp_wifi_stop();
432 s_net_state.wifi_ap_active = false;
433 FL_WARN("[NET] WiFi AP stopped");
434 }
435
436 response.set("success", true);
437 return response;
438}
439
440#else // !FL_IS_ESP32
441
442// ============================================================================
443// Stub Implementation for Non-ESP32 Platforms
444// ============================================================================
445
447 fl::json response = fl::json::object();
448 response.set("success", false);
449 response.set("error", "Net autoresearch only supported on ESP32");
450 return response;
451}
452
454 fl::json response = fl::json::object();
455 response.set("success", false);
456 response.set("error", "Net autoresearch only supported on ESP32");
457 return response;
458}
459
460fl::json runNetClientTest(const char* host_ip, uint16_t port) {
461 (void)host_ip;
462 (void)port;
463 fl::json response = fl::json::object();
464 response.set("success", false);
465 response.set("error", "Net autoresearch only supported on ESP32");
466 return response;
467}
468
470 fl::json response = fl::json::object();
471 response.set("success", false);
472 response.set("error", "Net loopback autoresearch only supported on ESP32");
473 return response;
474}
475
477 fl::json response = fl::json::object();
478 response.set("success", true);
479 return response;
480}
481
482#endif // FL_IS_ESP32
fl::json runNetLoopback()
Run self-contained loopback test: start HTTP server, client GETs localhost.
fl::json startNetClient()
Start WiFi Soft AP only (for net-client mode).
AutoResearchNetState & getNetState()
Get current network autoresearch state.
fl::json runNetClientTest(const char *host_ip, uint16_t port)
Run HTTP client tests against a host server.
static AutoResearchNetState s_net_state
fl::json startNetServer()
Start WiFi Soft AP and HTTP server with test endpoints.
fl::json stopNet()
Stop WiFi AP and HTTP server/client, release all resources.
#define AUTORESEARCH_NET_SSID
#define AUTORESEARCH_NET_PASSWORD
#define AUTORESEARCH_NET_MAX_CONNECTIONS
#define AUTORESEARCH_NET_AP_IP
State for network autoresearch.
int tests_passed
int tests_failed
const string & body() const
Get request body (for POST/PUT requests)
Definition server.h:59
bool has_body() const
Check if request has a body.
Definition server.h:81
HTTP request object (immutable, passed by const reference)
Definition server.h:48
Response & json(const class json &data)
Set JSON response body with automatic Content-Type header.
static Response ok(const string &body="")
Factory method for 200 OK response.
static Response bad_request(const string &message)
Factory method for 400 Bad Request response.
HTTP response builder (fluent interface)
Definition server.h:95
const char * c_str() const FL_NOEXCEPT
void push_back(const json &value) FL_NOEXCEPT
Definition json.h:745
fl::optional< bool > as_bool() const FL_NOEXCEPT
Definition json.h:254
void set(const fl::string &key, const json &value) FL_NOEXCEPT
Definition json.h:701
static json object() FL_NOEXCEPT
Definition json.h:692
static json array() FL_NOEXCEPT
Definition json.h:688
string str() const FL_NOEXCEPT
Definition strstream.h:43
pointer get() const FL_NOEXCEPT
Definition unique_ptr.h:97
void reset(pointer p=nullptr) FL_NOEXCEPT
Definition unique_ptr.h:113
FastLED's Elegant JSON Library: fl::json
#define FL_WARN(X)
Definition log.h:276
Centralized logging categories for FastLED hardware interfaces and subsystems.
void * memcpy(void *dest, const void *src, size_t n) FL_NOEXCEPT
size_t strlen(const char *s) FL_NOEXCEPT
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
Definition unique_ptr.h:261
void delay(u32 ms, bool run_async=true) FL_NOEXCEPT
Public delay wrapper that keeps bare Arduino delay() preferred after using fl::delay; while still all...
Definition delay.h:98
int snprintf(char *buffer, fl::size size, const char *format, const Args &... args) FL_NOEXCEPT
Snprintf-like formatting function that writes to a buffer.
Definition stdio.h:666
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31