NanoLux (Device) 3.0
Codebase for the open-source AudioLux device.
Loading...
Searching...
No Matches
webServer.h
1#pragma once
2
3#include <ESPmDNS.h>
4#include "BluetoothSerial.h"
5#include <WiFi.h>
6#include <AsyncTCP.h>
7#include <AsyncJson.h>
8#include <ESPAsyncWebServer.h>
9#include <WebHandlerImpl.h>
10#include <WebResponseImpl.h>
11#include "esp_wifi.h"
12
13
14//#define DEBUG_PRINTF(...) Serial.printf(__VA_ARGS__)
15#define DEBUG_PRINTF(...)
16
17#define ALWAYS_PRINTF(...) Serial.printf(__VA_ARGS__)
18
19// Uncomment to use the old LittleFS web app loader.
20//#define SD_LOADER
21
22#ifdef SD_LOADER
23 #include "FS.h"
24 #include "SD.h"
25 #include "SPI.h"
26 #define SCK 5
27 #define MISO 19
28 #define MOSI 18
29 #define CS 21
30#else
31 #include <LittleFS.h>
32#endif
33
34/*
35 * WIFI management data.
36 */
37constexpr int MAX_WIFI_CONNECT_WAIT = 100;
38constexpr int MAX_NETWORKS = 15;
39constexpr int END_OF_DATA = 9999;
40typedef struct {
41 String SSID;
42 int32_t RSSI;
43 uint8_t EncryptionType;
45WiFiNetwork available_networks[MAX_NETWORKS];
46
47typedef struct {
48 String SSID;
49 String Key;
51static CurrentWifi current_wifi;
52static CurrentWifi candidate_wifi;
53extern Config_Data config; // Currently loaded config
54
55/*
56 * Artifacts used to manage the async WiFi join process.
57 */
59 wl_status_t status;
60 const char* description;
61};
62
63const WlStatusToString wl_status_to_string[] = {
64 { WL_NO_SHIELD, "WiFi shield not present" },
65 { WL_IDLE_STATUS, "WiFi is in idle state" },
66 { WL_NO_SSID_AVAIL, "Configured SSID cannot be found" },
67 { WL_SCAN_COMPLETED, "Scan completed" },
68 { WL_CONNECTED, "Connected to network" },
69 { WL_CONNECT_FAILED, "Connection failed" },
70 { WL_CONNECTION_LOST, "Connection lost" },
71 { WL_DISCONNECTED, "Disconnected from network" }
72};
73
74const char* status_description;
75
76TimerHandle_t join_timer;
77SemaphoreHandle_t join_status_mutex;
78
79bool join_in_progress = false;
80bool join_succeeded = false;
81
82
83/*
84 * Networking params
85 */
86const char* ap_ssid = "AUDIOLUX";
87const char* ap_password = "12345678";
88
89const char* DEFAULT_HOSTNAME = "audiolux";
90static String hostname;
91
92
93
94/*
95 * Settings
96 */
97const char* SETTINGS_FILE = "/settings.json";
98static StaticJsonDocument<384> settings;
99const char* EMPTY_SETTING = "#_None_#";
100volatile bool dirty_settings = false;
101
102
103
104/*
105 * Web Server related.
106 */
107constexpr int HTTP_OK = 200;
108constexpr int HTTP_ACCEPTED = 202;
109constexpr int HTTP_BAD_REQUEST = 400;
110constexpr int HTTP_UNAUTHORIZED = 401;
111constexpr int HTTP_METHOD_NOT_ALLOWED = 405;
112constexpr int HTTP_UNPROCESSABLE = 422;
113constexpr int HTTP_INTERNAL_ERROR = 500;
114constexpr int HTTP_UNAVAILABLE = 503;
115
116const char* CONTENT_JSON = "application/json";
117const char* CONTENT_TEXT = "text/plain";
118
119static String http_response;
120const char* URL_FILE = "/assets/url.json";
121volatile bool server_unavailable = false;
122
123/*
124 * Web Server API handlers
125 */
126typedef struct {
127 String path;
128 ArRequestHandlerFunction handler;
129} APIGetHook;
130
131typedef struct {
132 String path;
133 ArJsonRequestHandlerFunction request_handler;
134} APIPutHook;
135
136
137AsyncWebServer webServer(80);
138
139/*
140 * File system
141 */
142inline void initialize_file_system() {
143
144 #ifdef SD_LOADER
145 DEBUG_PRINTF("Initializing SD FS...");
146 SPI.begin(SCK, MISO, MOSI, CS);
147 if (!SD.begin(CS))
148 DEBUG_PRINTF("Card Mount Failed");
149 DEBUG_PRINTF("Card mount successful.");
150 #else
151 DEBUG_PRINTF("Initializing FS...");
152 if (LittleFS.begin()) {
153 DEBUG_PRINTF("done.\n");
154 } else {
155 DEBUG_PRINTF("fail.\n");
156 }
157 #endif
158}
159
160/*
161 * Settings Management
162 */
163inline void save_settings() {
164 if (!settings.containsKey("wifi")) {
165 settings.createNestedObject("wifi");
166 }
167 settings["hostname"] = hostname;
168 settings["wifi"]["ssid"] = current_wifi.SSID;
169 settings["wifi"]["key"] = current_wifi.Key;
170
171 #ifdef SD_LOADER
172 File saved_settings = SD.open(SETTINGS_FILE, "w");
173 #else
174 File saved_settings = LittleFS.open(SETTINGS_FILE, "w");
175 #endif
176
177 if (saved_settings) {
178 serializeJson(settings, saved_settings);
179 DEBUG_PRINTF("Saving settings:\n");
180 serializeJsonPretty(settings, Serial);
181 DEBUG_PRINTF("\n");
182 } else {
183 DEBUG_PRINTF("Unable to save settings file.\n");
184 }
185}
186
187
188inline void load_settings() {
189 DEBUG_PRINTF("Checking if settings are available.\n");
190
191 #ifdef SD_LOADER
192 File saved_settings = SD.open(SETTINGS_FILE, "r");
193 #else
194 File saved_settings = LittleFS.open(SETTINGS_FILE, "r");
195 #endif
196
197 if (saved_settings) {
198 const DeserializationError error = deserializeJson(settings, saved_settings);
199 if (!error) {
200 DEBUG_PRINTF("Settings loaded:\n");
201 serializeJsonPretty(settings, Serial);
202 DEBUG_PRINTF("\n");
203
204 hostname = settings["hostname"].as<String>();
205 current_wifi.SSID = settings["wifi"]["ssid"].as<String>();
206 current_wifi.Key = settings["wifi"]["key"].as<String>();
207
208 return;
209 }
210
211 DEBUG_PRINTF("Error loading saved file: %s\n", error.c_str());
212 }
213
214 DEBUG_PRINTF("Unable to load settings. Will use defaults (re)create file.\n");
215
216 current_wifi.SSID = EMPTY_SETTING;
217 current_wifi.Key = EMPTY_SETTING;
218 hostname = DEFAULT_HOSTNAME;
219 save_settings();
220}
221
222
223inline const String& build_response(const bool success, const char* message, const char* details) {
224 http_response = String("{\"success\": ") + (success ? "true" : "false");
225 if (message != nullptr) {
226 http_response += String(", \"message\": \"") + message + String("\"");
227 }
228 if (details != nullptr) {
229 http_response += String(", \"details\": \"") + details + String("\"");
230 }
231 http_response += "}";
232
233 return http_response;
234}
235
236
237/*
238 * Network configuration
239 */
240inline void scan_ssids() {
241 static int long_scan_count = 0;
242 const int MAX_SCAN_ITERATIONS = 2;
243
244 // Flow: check if there was a scan happening, and get its results.
245 // If there was no scan start one and be done. If the previous scan
246 // failed, start a new one and move on. If the previous scan succeeded
247 // then stash the results and start a new one. The main consequence here
248 // is that the very first time the scan is run, the result will be
249 // and empty array. It is up to the client to handle that.
250
251 // Start with the assumption we have an empty scan.
252 available_networks[0].RSSI = END_OF_DATA;
253 const int n = WiFi.scanComplete();
254 if (n == WIFI_SCAN_FAILED) {
255 WiFi.scanNetworks(true);
256 long_scan_count = 0;
257 } else if (n == WIFI_SCAN_RUNNING) {
258 long_scan_count++;
259
260 if (long_scan_count >= MAX_SCAN_ITERATIONS) {
261 // This scan has run for a while. Cancel it and start a new one.
262 WiFi.scanDelete();
263 WiFi.scanNetworks(true);
264 long_scan_count = 0;
265 }
266 } else if (n == 0) {
267 long_scan_count = 0;
268 } else if (n > 0) {
269 const int network_count = min(n, MAX_NETWORKS);
270 for (int i = 0; i < network_count; ++i) {
271 available_networks[i].SSID = String(WiFi.SSID(i));
272 available_networks[i].RSSI = WiFi.RSSI(i);
273 available_networks[i].EncryptionType = WiFi.encryptionType(i);
274 }
275
276 if (network_count < MAX_NETWORKS) {
277 available_networks[network_count].RSSI = END_OF_DATA;
278 }
279
280 WiFi.scanDelete();
281 if (WiFi.scanComplete() == WIFI_SCAN_FAILED) {
282 WiFi.scanNetworks(true);
283 }
284 long_scan_count = 0;
285 }
286}
287
288
289inline bool initialize_wifi_connection() {
290 server_unavailable = true;
291
292 // Stop any pending WiFi scans.
293 WiFi.scanDelete();
294
295 // Drop the current connection, if any.
296 WiFi.disconnect();
297 delay(100);
298
299 WiFi.begin(current_wifi.SSID.c_str(), current_wifi.Key.c_str());
300 int wait_count = 0;
301 while (WiFi.status() != WL_CONNECTED && wait_count < MAX_WIFI_CONNECT_WAIT) {
302 delay(500);
303 ++wait_count;
304 }
305 server_unavailable = false;
306
307 if (WiFi.status() == WL_CONNECTED) {
308 return true;
309 }
310
311 WiFi.disconnect();
312 delay(100);
313 return false;
314}
315
316
317inline void initialize_mdns(bool use_user_hostname) {
318 bool success = MDNS.begin(
319 (use_user_hostname ? hostname : DEFAULT_HOSTNAME).c_str());
320
321 // The assumption is that we are connected. Setup mDNS
322 if (success) {
323 ALWAYS_PRINTF("mDNS connected. The AudioLux can be reached at %s.local\n", hostname.c_str());
324 } else {
325 ALWAYS_PRINTF("Unable to setup mDNS\n");
326 }
327}
328
329
330inline const char* get_status_description(const wl_status_t status) {
331 for (int i = 0; i < sizeof(wl_status_to_string) / sizeof(WlStatusToString); i++) {
332 if (wl_status_to_string[i].status == status) {
333 status_description = wl_status_to_string[i].description;
334 break;
335 }
336 }
337
338 return status_description;
339}
340
341
342inline void on_join_timer(TimerHandle_t timer) {
343 DEBUG_PRINTF("Timer: Checking WiFi Join Status.\n");
344 if (xSemaphoreTake(join_status_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
345 if (join_in_progress) {
346 wl_status_t status = WiFi.status();
347 if (status == WL_CONNECTED) {
348 join_in_progress = false;
349 join_succeeded = true;
350 xTimerStop(timer, 0);
351 ALWAYS_PRINTF("Timer: WiFi join succeeded.\n");
352
353 // Queue the settings for saving. Can't do it here
354 // because FreeRTOS croaks with a stack overflow.
355 // Possibly writing to flash is resource-heavy.
356 current_wifi.SSID = candidate_wifi.SSID;
357 current_wifi.Key = candidate_wifi.Key;
358 dirty_settings = true;
359
360 initialize_mdns(true);
361 } else if (status != WL_IDLE_STATUS && status != WL_CONNECT_FAILED && status != WL_NO_SHIELD) {
362 join_in_progress = false;
363 join_succeeded = false;
364 xTimerStop(timer, 0);
365 DEBUG_PRINTF("Timer: WiFi join failed. Reason: %s.\n", get_status_description(status));
366 }
367 }
368
369 xSemaphoreGive(join_status_mutex);
370 }
371}
372
373
374/*
375 * Wifi management
376 */
377inline bool join_wifi(const char* ssid, const char* key) {
378 DEBUG_PRINTF("Trying to join network %s ...\n", ssid);
379
380 // Reset any radio activity.
381 WiFi.scanDelete();
382 WiFi.disconnect();
383 delay(100);
384 WiFi.setHostname(hostname.c_str());
385
386 candidate_wifi.SSID = ssid;
387 candidate_wifi.Key = key;
388
389 // Start the connection process.
390 WiFi.begin(ssid, key);
391 if (xSemaphoreTake(join_status_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
392 join_in_progress = true;
393 join_succeeded = false;
394
395 if (xTimerStart(join_timer, 0) != pdPASS) {
396 DEBUG_PRINTF("Unable to star timer. Join unsupervised.\n");
397 return false;
398 }
399
400 xSemaphoreGive(join_status_mutex);
401 return true;
402 }
403
404 DEBUG_PRINTF("Unable to get mutex. Join unsupervised.\n");
405 return false;
406}
407
408
409/*
410 * Wifi API handling
411 */
412inline void serve_wifi_list(AsyncWebServerRequest* request) {
413 if (join_in_progress) {
414 request->send(HTTP_UNAVAILABLE);
415 return;
416 }
417
418 scan_ssids();
419
420 StaticJsonDocument<1024> json_list;
421 int wifi_number = 0;
422 while (wifi_number < MAX_NETWORKS && available_networks[wifi_number].RSSI != END_OF_DATA) {
423 JsonObject wifi = json_list.createNestedObject();
424 wifi["ssid"] = available_networks[wifi_number].SSID;
425 wifi["rssi"] = available_networks[wifi_number].RSSI;
426 wifi["lock"] = available_networks[wifi_number].EncryptionType != WIFI_AUTH_OPEN;
427 wifi_number++;
428 }
429
430 // If no results, return an empty array.
431 String wifi_list;
432 serializeJson(json_list, wifi_list);
433 if (wifi_list == "null") {
434 wifi_list = "[]";
435 }
436
437 DEBUG_PRINTF("Sending networks:\n%s\\n", wifi_list.c_str());
438 request->send(HTTP_OK, CONTENT_JSON, wifi_list);
439}
440
441
442inline void handle_wifi_put_request(AsyncWebServerRequest* request, JsonVariant& json) {
443 if (request->method() == HTTP_PUT) {
444 const JsonObject& payload = json.as<JsonObject>();
445
446 int status = HTTP_OK;
447
448 bool joined = false;
449 if (payload["ssid"] == nullptr) {
450 DEBUG_PRINTF("/api/wifi: Forgetting current network.\n");
451 WiFi.disconnect();
452 delay(100);
453
454 current_wifi.SSID = EMPTY_SETTING;
455 current_wifi.Key = EMPTY_SETTING;
456 save_settings();
457
458 joined = true;
459 } else {
460 DEBUG_PRINTF("/api/wifi: Joining network.\n");
461 joined = join_wifi(payload["ssid"], payload["key"]);
462 }
463
464 int response_status;
465 String message;
466 if (joined) {
467 response_status = HTTP_ACCEPTED;
468 message = "Operation completed.";
469 } else {
470 response_status = HTTP_INTERNAL_ERROR;
471 message = "Unable to monitor join operation: could not start timer or get mutex.";
472 DEBUG_PRINTF("%s\n", message.c_str());
473 }
474 request->send(response_status, CONTENT_JSON, build_response(joined, message.c_str(), nullptr));
475 } else {
476 request->send(HTTP_METHOD_NOT_ALLOWED);
477 }
478}
479
480
481inline void handle_wifi_get_request(AsyncWebServerRequest* request) {
482 const String wifi = current_wifi.SSID == EMPTY_SETTING ? String("null") : String("\"") + String(current_wifi.SSID) + String("\"");
483 const bool connected = current_wifi.SSID == EMPTY_SETTING ? false : (WiFi.status() == WL_CONNECTED);
484 const String response = String("{ \"ssid\": ") + String(wifi) + String(", \"connected\": ") + (connected ? "true" : "false") + String(" }");
485
486 DEBUG_PRINTF("Sending current wifi: %s\n", response.c_str());
487 request->send(HTTP_OK, CONTENT_JSON, response);
488}
489
490inline void handle_wifi_status_request(AsyncWebServerRequest* request) {
491 const String wifi = current_wifi.SSID == EMPTY_SETTING ? String("null") : String("\"") + String(current_wifi.SSID) + String("\"");
492
493 String status;
494 if (xSemaphoreTake(join_status_mutex, pdMS_TO_TICKS(50)) == pdTRUE) {
495 if (join_in_progress) {
496 status = "pending";
497 } else if (join_succeeded) {
498 status = "success";
499 } else {
500 status = "fail";
501 }
502
503 xSemaphoreGive(join_status_mutex);
504 }
505
506 const String response = String("{ \"ssid\": ") + String(wifi) + String(", \"status\": \"") + status + String("\" }");
507
508 DEBUG_PRINTF("Sending wifi status: %s\n", response.c_str());
509 request->send(HTTP_OK, CONTENT_JSON, response);
510}
511
512
513/*
514 * Hostname API handling
515 */
516inline void handle_hostname_put_request(AsyncWebServerRequest* request, JsonVariant& json) {
517 if (server_unavailable) {
518 request->send(HTTP_UNAVAILABLE);
519 return;
520 }
521
522 if (request->method() == HTTP_PUT) {
523 const JsonObject& payload = json.as<JsonObject>();
524
525 int status = HTTP_OK;
526
527 hostname = payload["hostname"].as<String>();
528 save_settings();
529 DEBUG_PRINTF("Hostname %s saved.\n", hostname.c_str());
530 request->send(HTTP_OK,
531 CONTENT_TEXT,
532 build_response(true, "New hostname saved.", nullptr));
533 }
534}
535
536inline void handle_hostname_get_request(AsyncWebServerRequest* request) {
537 if (server_unavailable) {
538 request->send(HTTP_UNAVAILABLE);
539 return;
540 }
541
542 const String response = String("{ \"hostname\": ") + "\"" + String(hostname) + String("\" }");
543
544 DEBUG_PRINTF("Sending current hostname: %s\n", response.c_str());
545 request->send(HTTP_OK, CONTENT_JSON, response);
546}
547
548
549/*
550 * Health ping.
551 */
552inline void handle_health_check(AsyncWebServerRequest* request) {
553 if (server_unavailable) {
554 request->send(HTTP_UNAVAILABLE);
555 return;
556 }
557
558 DEBUG_PRINTF("Pong.\n");
559 request->send(HTTP_OK);
560}
561
562
563/*
564 * Unknown path (404) handler
565 */
566inline void handle_unknown_url(AsyncWebServerRequest* request) {
567 // If browser sends preflight to check for CORS we tell them
568 // it's okay. NOTE: Google is stubborn about it. You will need
569 // to disable strict CORS checking using the --disable-web-security
570 // option when starting it.
571 if (request->method() == HTTP_OPTIONS) {
572 request->send(200);
573 return;
574 }
575
576 // Otherwise, we got an unknown request. Print info about it
577 // that may be useful for debugging.
578 String method;
579 switch (request->method()) {
580 case HTTP_GET:
581 method = "GET";
582 break;
583 case HTTP_POST:
584 method = "POST";
585 break;
586 case HTTP_PUT:
587 method = "PUT";
588 break;
589 default:
590 method = "UNKNOWN";
591 }
592 DEBUG_PRINTF("Not Found: %s -> http://%s%s\n", method.c_str(), request->host().c_str(), request->url().c_str());
593
594 if (request->contentLength()) {
595 DEBUG_PRINTF("_CONTENT_TYPE: %s\n", request->contentType().c_str());
596 DEBUG_PRINTF("_CONTENT_LENGTH: %u\n", request->contentLength());
597 }
598
599 const int headers = request->headers();
600 for (int i = 0; i < headers; i++) {
601 const AsyncWebHeader* header = request->getHeader(i);
602 DEBUG_PRINTF("_HEADER[%s]: %s\n", header->name().c_str(), header->value().c_str());
603 }
604
605 request->send(404);
606}
607
608
609inline void save_url(const String& url) {
610 // This is a static file that lives in the "assets" of the web app.
611 // When it starts, it will check this file to know which URL to use to talk
612 // back to the server. The reason we need this is that we don't know which
613 // route is available (AP or STA) until runtime.
614 #ifdef SD_LOADER
615 File saved_url = SD.open(URL_FILE, "w");
616 #else
617 File saved_url = LittleFS.open(URL_FILE, "w");
618 #endif
619
620 if (saved_url) {
621 StaticJsonDocument<192> data;
622
623 data["url"] = url;
624 Serial.println(url);
625
626 serializeJson(data, saved_url);
627 DEBUG_PRINTF("%s saved as Web App URL.\n", url.c_str());
628 } else {
629 DEBUG_PRINTF("Unable to save Web App URL, will default to http://192.168.4.1.\n");
630 }
631}
632
633
634inline void setup_networking(const char* password) {
635 initialize_file_system();
636
637 // Load saved settings. If we have an SSID, try to join the network.
638 load_settings();
639
640 // Prevent he radio from going to sleep.
641 WiFi.setSleep(false);
642
643 // Local WiFi connection depends on whether it has been configured
644 // by the user.
645 bool wifi_okay = false;
646 if (current_wifi.SSID != nullptr && current_wifi.SSID != EMPTY_SETTING) {
647 DEBUG_PRINTF("Attempting to connect to saved WiFi: %s\n", current_wifi.SSID.c_str());
648 wifi_okay = initialize_wifi_connection();
649 if (wifi_okay) {
650 ALWAYS_PRINTF("WiFi IP: %s\n", WiFi.localIP().toString().c_str());
651 }
652 } else {
653 ALWAYS_PRINTF("****\n");
654 ALWAYS_PRINTF("No wifi saved. AudioLux available via Access Point:\n");
655 ALWAYS_PRINTF("SSID: %s Password: %s\n", ap_ssid, password);
656 ALWAYS_PRINTF("****\n");
657 }
658
659 // AP mode is always active.
660 WiFi.mode(WIFI_MODE_APSTA);
661 if (password[0] == '\0') {
662 WiFi.softAP("AudioluxUnsecured");
663 ALWAYS_PRINTF("WIFI IS UNSECURED!!!\n");
664 initialize_mdns(false);
665 } else {
666 WiFi.softAP(hostname, password);
667 initialize_mdns(true);
668 }
669
670
671
672 delay(1000);
673 const IPAddress ap_ip = WiFi.softAPIP();
674
675 // Set up the URL that the Web App needs to talk to.
676 // We prefer user's network if available.
677 String api_url = "http://";
678 if (wifi_okay) {
679 api_url += hostname;
680 api_url += ".local";
681 } else {
682 api_url += ap_ip.toString();
683 }
684 save_url(api_url);
685 ALWAYS_PRINTF("Backend availbale at: %s", api_url.c_str());
686}
687
688
689inline void initialize_web_server(const APIGetHook api_get_hooks[], const int get_hook_count, APIPutHook api_put_hooks[], const int put_hook_count, const char* password) {
690 // Mutex to make join status globals thread safe.
691 // The timer below runs on a different context than the web server,
692 // so we need to properly marshall access between contexts.
693 join_status_mutex = xSemaphoreCreateMutex();
694 if (join_status_mutex == nullptr) {
695 // If we get here we are in serious trouble, and there is nothing the
696 // code can do. We just die unceremoniously.
697 DEBUG_PRINTF("WebServer: failed to create mutex. Process halted.\n");
698 for (;;) {
699 delay(1000);
700 }
701 }
702
703 // Software timer to monitor async WiFi joins.
704 join_timer = xTimerCreate(
705 "WiFiJoinTimer",
706 pdMS_TO_TICKS(200),
707 pdTRUE, // Auto re-trigger.
708 nullptr, // Timer ID pointer, not used.
709 on_join_timer);
710
711 setup_networking(password);
712
713 // Register the main process API handlers.
714 DEBUG_PRINTF("Registering main APIs.\n");
715 for (int i = 0; i < get_hook_count; i++) {
716 DEBUG_PRINTF("%s\n", api_get_hooks[i].path.c_str());
717 webServer.on(api_get_hooks[i].path.c_str(), HTTP_GET, api_get_hooks[i].handler);
718 }
719 for (int i = 0; i < put_hook_count; i++) {
720 DEBUG_PRINTF("%s\n", api_put_hooks[i].path.c_str());
721 webServer.addHandler(new AsyncCallbackJsonWebHandler(api_put_hooks[i].path.c_str(), api_put_hooks[i].request_handler));
722 }
723
724 // Now add internal APi endpoints (wifi and health)
725 webServer.on("/api/wifis", HTTP_GET, serve_wifi_list);
726 webServer.on("/api/wifi", HTTP_GET, handle_wifi_get_request);
727 webServer.on("/api/wifi_status", HTTP_GET, handle_wifi_status_request);
728 webServer.on("/api/hostname", HTTP_GET, handle_hostname_get_request);
729 webServer.on("/api/health", HTTP_GET, handle_health_check);
730
731 webServer.addHandler(new AsyncCallbackJsonWebHandler("/api/wifi", handle_wifi_put_request));
732 webServer.addHandler(new AsyncCallbackJsonWebHandler("/api/hostname", handle_hostname_put_request));
733
734
735 // Register the Web App
736 DEBUG_PRINTF("Registering Web App files.\n");
737
738 #ifdef SD_LOADER
739 webServer.serveStatic("/", SD, "/").setDefaultFile("index.html");
740 #else
741 webServer.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
742 #endif
743
744
745 webServer.onNotFound(handle_unknown_url);
746
747 // "Disable" CORS.
748 DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
749 DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "*");
750 DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "*");
751 esp_wifi_set_ps(WIFI_PS_NONE);
752 webServer.begin();
753 MDNS.addService("http", "tcp", 80);
754}
Config_Data config
The currently-loaded device config.
Definition main.ino:61
Definition webServer.h:126
Definition webServer.h:131
A structure holding system configuration data.
Definition storage.h:66
Definition webServer.h:47
Definition webServer.h:40
Definition webServer.h:58