diff --git a/README.md b/README.md
index 9d72631..46c914e 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,49 @@
# WiFiManager
-Espressif ESPx WiFi connection manager with a fallback web configuration portal.
+This repository is a **breaking fork** of upstream [`tzapu/WiFiManager`](https://github.com/tzapu/WiFiManager).
+It is not a drop-in replacement for upstream behavior, APIs, templates, or portal customization patterns.
-## Fork notice
+If you are evaluating this fork, assume that core web-portal architecture has changed and review the code before adopting it in an existing upstream-based project.
-**This is a fork with breaking changes.** The web portal is built with **DFTE** ([Device Framework Template Engine](https://github.com/alexhopeoconnor/DFTE)): composable templates and chunked HTML responses instead of upstream’s monolithic string assembly.
+**This repository:** [alexhopeoconnor/WiFiManager](https://github.com/alexhopeoconnor/WiFiManager)
+**Upstream:** [tzapu/WiFiManager](https://github.com/tzapu/WiFiManager)
-**This repository:** [alexhopeoconnor/WiFiManager](https://github.com/alexhopeoconnor/WiFiManager)
+## Breaking Changes
-**Upstream (original):** [tzapu/WiFiManager](https://github.com/tzapu/WiFiManager)
+This fork intentionally modernizes and restructures the configuration portal.
+Notable differences from upstream include:
+
+- The portal now serves a **single HTML shell** from `GET /`.
+- Client navigation is handled as a **SPA with hash routing**.
+- Interactive behavior is exposed through **JSON APIs under `/api/...`** plus firmware upload at **`POST /u`**.
+- Legacy multi-page portal routes and legacy root-template override paths have been removed.
+- The portal rendering pipeline is built around **DFTE** instead of upstream's monolithic HTML string assembly.
+- JSON endpoints are expected to be **data-first**, not derived from generated HTML fragments.
+
+## Current Improvements And Modernization
+
+This fork currently includes the following architectural improvements:
+
+- A **single-shell portal architecture** with embedded bootstrap JSON and embedded application JS.
+- A **SPA-based configuration UI** for WiFi setup, parameters, info views, device actions, and OTA flow.
+- A **clean API surface** for WiFi scanning, WiFi save, parameters, info, status, restart, erase, portal exit, and captive-portal close behavior.
+- **Captive portal redirect handling** retained while removing duplicate legacy UI architecture.
+- **Data-first JSON generation** for portal APIs, including info/device/about data, instead of HTML-to-JSON parsing.
+- **Capability-driven UI flags** in bootstrap/API payloads so features like info, update, erase, and action visibility can be controlled by backend state.
+- **SPA-native feedback UX** using in-DOM dialog/toast behavior rather than page-based action flows.
+- A **shell-scoped placeholder model** centered on `%PAGE_TITLE%`, `%STYLES%`, `%SCRIPTS%`, `%BOOTSTRAP_JSON%`, and `%PORTAL_APP_JS%`.
+- A clearer separation between:
+ - shell rendering
+ - API responses
+ - captive portal behavior
+ - OTA handling
+- Updated tests focused on the **shell contract**, **bootstrap payloads**, and **API JSON shapes** rather than removed legacy portal pages.
## Dependencies
-**DFTE** is required and is declared in `library.json` (PlatformIO / `lib_deps` pick it up automatically). For **this repo’s** `platformio.ini` tests, a **sibling checkout** is expected:
+This fork depends on **DFTE** ([Device Framework Template Engine](https://github.com/alexhopeoconnor/DFTE)) and **ESP32Async/ESPAsyncWebServer**.
+
+For this repo's `platformio.ini` test setup, DFTE is expected as a sibling checkout:
```ini
lib_deps =
@@ -20,11 +51,7 @@ lib_deps =
symlink://../DFTE
```
-Clone `DFTE` next to `WiFiManager` (same parent directory), or point `lib_deps` at the Git URL used in `library.json`.
-
-Other pinned dependency: **ESP32Async/ESPAsyncWebServer** (see `library.json`).
-
-## Installation (PlatformIO)
+## Installation
```ini
[env:your_environment]
@@ -42,46 +69,9 @@ lib_deps =
file:///path/to/WiFiManager
```
-## HTML customization (DFTE)
+## AI Assistance Notice
-- **Page shell and routes** stream through DFTE (`TemplateRenderer` + chunked responses). Per-request bundles use their own `PlaceholderRegistry` for dynamic sections; the server also keeps a **shared** registry for defaults (`%STYLES%`, `%SCRIPTS%`, `%PAGE_TITLE%`, `%SUBTITLE%`, `%MENU%`, `%STATUS%`, …).
-- **`WM_TEMPLATE_REGISTRY_CAPACITY`** (default in `WiFiManagerServer.h`) sizes the shared registry; raise it if you register many custom placeholders.
-- **Override the root document template:** define **`WM_CUSTOM_ROOT_TEMPLATE_HEADER`** to a **quoted** include path (see `lib/WiFiManager/include/templates/RootSelector.h`). By default it includes `Root.h` (`WM_ROOT_TEMPLATE`).
-- **Advanced / tests:** `WiFiManagerServer::instance()` exposes the same hooks after the server exists; prefer the `WiFiManager` methods below for typical apps.
-
-### `WiFiManager` API
-
-```cpp
-wm.registerTemplateSetupCallback([](PlaceholderRegistry& reg) {
- // reg.registerProgmemData(...), registerRamData, registerDynamicTemplate, etc.
-});
-wm.rebuildPlaceholderRegistry(); // optional: after changing callback while portal runs
-```
-
-`registerTemplateSetupCallback` may be called **before** `startConfigPortal` / `startWebPortal` (stored until the HTTP server is created). If the portal is **already** running, the callback is applied and the shared registry is **rebuilt** so changes take effect.
-
-`rebuildPlaceholderRegistry(customizer)` forwards to the server when present: rebuilds defaults, then runs your optional one-shot customizer, or the registered setup callback if `customizer` is null.
-
-### Content built as `String` / PROGMEM fragments
-
-Some small HTML snippets (status messages, OTA blurb, help table, etc.) are still assembled in RAM or pulled from `HTML.h` and then injected into templates. That is **intentional**: not every line of markup needs its own placeholder or registry churn. Customize those via DFTE where it pays off (shell, branding, layout); keep trivial blobs as strings when appropriate.
-
-## Debug: WiFiManager logging and DFTE
-
-### How DFTE logging is designed
-
-DFTE keeps a **single global pointer** `deviceFrameworkTemplateEngineLogger` (see DFTE’s `DeviceFrameworkTemplateEngineDebug.h`). By default it is **`nullptr`**: every `DFTE_LOG_*` macro expands to a cheap null check and does nothing—no virtual calls, no strings, no flash cost. A host (sketch or library) implements `DeviceFrameworkTemplateEngineLogger` (virtual `error` / `warn` / `info` / `debug`) and calls `deviceFrameworkTemplateEngineEnableLogging(&myLogger)` once. Until then, DFTE is silent on the happy path; internal code mostly uses `DFTE_LOG_ERROR` and `DFTE_LOG_WARN` when something is wrong.
-
-### WiFiManager today
-
-- **`WM_LOG_LEVEL`**: compile-time default is **`0`** (silent). Set **`1`–`5`** for `Error` … `Trace` (see `WiFiManagerLogLevel` in `WiFiManagerLogLevel.h`). Example: `-DWM_LOG_LEVEL=5` in `platformio.ini` for full verbosity. Strip all log **call sites** from the binary with **`WM_NO_LOG`** (or legacy **`WM_NODEBUG`**). At runtime, **`setLogEnabled`**, **`setLogOutput(enabled, maxLevel)`**, **`_logPrefix`**, and **`_logPort`** gate output; use **`WM_DEBUG_PORT`** to redirect the default `Print` target. **`WiFiManagerLogSink`** receives structured **`WiFiManagerLogMessage`** (level, subsystem, full line).
-- **`WM_DFTE_LOGGING`** (build flag, **off by default**): when defined, `WiFiManagerServer::setupTemplateEngine()` registers **`WiFiManagerDfteLogger`**, which forwards DFTE into **`WiFiManager::log(..., "WM/DFTE", ...)`**. It uses **`deviceFrameworkTemplateEngineEnableLogging(..., this)`** so **`disableLoggingForOwner(this)`** on shutdown does not clobber another owner. If DFTE logging was already enabled, WiFiManager does not replace it.
-
-Use **`pio test -e esp8266_dfte_log --without-uploading --without-testing`** in this repo to compile the test firmware with **`WM_DFTE_LOGGING`** enabled.
-
-### Design note
-
-DFTE uses a small optional logger pointer; WiFiManager uses **`#ifndef WM_NO_LOG`** around log call sites so release builds can omit strings entirely. **`WiFiManagerLogSink`** lets hosts (e.g. **DeviceFramework**) map levels without parsing ad hoc prefixes.
+Parts of this codebase have been developed and refactored with the aid of AI coding agents under human direction and review.
## License
diff --git a/lib/WiFiManager/include/WiFiManager.h b/lib/WiFiManager/include/WiFiManager.h
index 7f623e6..f73474d 100644
--- a/lib/WiFiManager/include/WiFiManager.h
+++ b/lib/WiFiManager/include/WiFiManager.h
@@ -6,7 +6,7 @@
*
* @author Creator tzapu
* @author tablatronix
- * @author Alex Hope-O'Connor
+ * @author alexhopeoconnor
* @license MIT
*/
@@ -139,8 +139,6 @@
#include
#include
#include
-#include
-
// Include utility functions
#include "WiFiManagerUtils.h"
@@ -437,6 +435,9 @@ class WiFiManager
// show OTA upload button on info page
void setShowInfoUpdate(boolean enabled);
+ // show Info nav / info view (portal SPA); default true
+ void setShowInfo(boolean enabled);
+
// set ap channel
void setWiFiAPChannel(int32_t channel);
@@ -449,13 +450,6 @@ class WiFiManager
// set the webapp title, default WiFiManager
void setTitle(String title);
- // DFTE (portal HTML): register extra placeholders or override defaults (%STYLES%, %PAGE_TITLE%, …).
- // Call before startConfigPortal / startWebPortal to have it applied when the server starts; if the
- // portal is already running, the shared registry is rebuilt immediately.
- void registerTemplateSetupCallback(std::function cb);
- // Rebuild the server’s shared PlaceholderRegistry (defaults + registerTemplateSetupCallback, or customizer if passed).
- void rebuildPlaceholderRegistry(std::function customizer = nullptr);
-
// add params to its own menu page and remove from wifi, NOT TO BE COMBINED WITH setMenu!
void setParamsPage(bool enable);
@@ -589,7 +583,6 @@ class WiFiManager
protected:
// vars
std::unique_ptr _serverManager;
- std::function _templateSetupCallback;
// ip configs @todo struct ?
IPAddress _ap_static_ip;
@@ -684,6 +677,7 @@ class WiFiManager
boolean _webClientCheck = true; // keep cp alive if web have client
boolean _scanDispOptions = false; // show percentage in scans not icons
boolean _paramsInWifi = true; // show custom parameters on wifi page
+ boolean _showInfo = true; // show Info in portal SPA
boolean _showInfoErase = true; // info page erase button
boolean _showInfoUpdate = true; // info page update button
boolean _showBack = false; // show back button
diff --git a/lib/WiFiManager/include/WiFiManagerDfteLogger.h b/lib/WiFiManager/include/WiFiManagerDfteLogger.h
index e3ac5a1..904f1d8 100644
--- a/lib/WiFiManager/include/WiFiManagerDfteLogger.h
+++ b/lib/WiFiManager/include/WiFiManagerDfteLogger.h
@@ -1,6 +1,9 @@
/**
* WiFiManagerDfteLogger.h
*
+ * @author alexhopeoconnor
+ * @license MIT
+ *
* Optional bridge from DFTE's logging hooks to WiFiManager::log.
* Enabled only when the sketch / library build defines WM_DFTE_LOGGING.
*/
diff --git a/lib/WiFiManager/include/WiFiManagerHandlers.h b/lib/WiFiManager/include/WiFiManagerHandlers.h
index 4d8dfc9..1677a3c 100644
--- a/lib/WiFiManager/include/WiFiManagerHandlers.h
+++ b/lib/WiFiManager/include/WiFiManagerHandlers.h
@@ -1,11 +1,9 @@
/**
* WiFiManagerHandlers.h
- *
- * HTTP request handlers and rendering logic for WiFiManager
- * Handles all web portal page generation and request processing
- *
- * @author tablatronix
- * @author Alex Hope-O'Connor
+ *
+ * HTTP request handlers for WiFiManager: single HTML shell + JSON APIs.
+ *
+ * @author alexhopeoconnor
* @license MIT
*/
@@ -16,20 +14,7 @@
#include
#include
-#include "WiFiManager.h" // Need full definition for WiFiManagerRequestArgs
-
-// -----------------------------------------------------------------------------------------------
-// CSS CLASSES (for body class attribute)
-
-const char C_root[] PROGMEM = "home";
-const char C_wifi[] PROGMEM = "wifi";
-const char C_info[] PROGMEM = "info";
-const char C_param[] PROGMEM = "param";
-const char C_close[] PROGMEM = "close";
-const char C_restart[] PROGMEM = "restart";
-const char C_exit[] PROGMEM = "exit";
-const char C_erase[] PROGMEM = "erase";
-const char C_update[] PROGMEM = "update";
+#include "WiFiManager.h"
// -----------------------------------------------------------------------------------------------
// FORM FIELD NAMES (for IP configuration forms)
@@ -50,27 +35,13 @@ const char HTTP_HEAD_CORS_ALLOW_ALL[] PROGMEM = "*";
class WiFiManagerHandlers {
public:
WiFiManagerHandlers(WiFiManager* wm);
-
- // HTTP Request Handlers
+
void handleRoot(AsyncWebServerRequest *request);
- void handleWifi(AsyncWebServerRequest *request, boolean scan);
- void handleWifiSave(AsyncWebServerRequest *request);
- void handleParam(AsyncWebServerRequest *request);
- void handleParamSave(AsyncWebServerRequest *request);
- void handleInfo(AsyncWebServerRequest *request);
- void handleReset(AsyncWebServerRequest *request);
- void handleExit(AsyncWebServerRequest *request);
- void handleClose(AsyncWebServerRequest *request);
- void handleErase(AsyncWebServerRequest *request, boolean opt);
void handleNotFound(AsyncWebServerRequest *request);
void handleRequest(AsyncWebServerRequest *request);
- void handleWiFiScanRequest(AsyncWebServerRequest *request);
- void handleWiFiScanStatus(AsyncWebServerRequest *request);
- void handleUpdate(AsyncWebServerRequest *request);
void handleUpdating(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
void handleUpdateDone(AsyncWebServerRequest *request);
- // JSON / SPA API (portal is a single-page app; only GET / serves HTML)
void handleApiBootstrap(AsyncWebServerRequest *request);
void handleApiWifiScanStatus(AsyncWebServerRequest *request);
void handleApiWifiScan(AsyncWebServerRequest *request);
@@ -81,39 +52,58 @@ class WiFiManagerHandlers {
void handleApiInfo(AsyncWebServerRequest *request);
void handleApiStatus(AsyncWebServerRequest *request);
void handleApiDeviceRestart(AsyncWebServerRequest *request);
- void handleApiDeviceErase(AsyncWebServerRequest *request);
+ void handleApiDeviceErase(AsyncWebServerRequest *request, boolean optionalErase = false);
void handleApiPortalClose(AsyncWebServerRequest *request);
void handleApiPortalExit(AsyncWebServerRequest *request);
-
- // Captive Portal
+
boolean captivePortal(AsyncWebServerRequest *request);
void stopCaptivePortal();
-
- // Rendering Methods
- String getMenuOut();
- String getMenuOut(String* outOpt);
- String getScanItemOut();
- String getParamOut();
- String getStaticOut();
- String getIpForm(String id, String title, String value);
- String getInfoData(String id);
- void reportStatus(String &page);
-
- // Internal helper (needed by handlers)
+
+ /** True when the HTTP Host header does not match the portal’s canonical host:port (captive redirect needed). */
+ static bool shouldRedirectCaptiveForHost(const String& requestHost, const String& serverLocWithPort);
+
void doParamSave(WiFiManager::WiFiManagerRequestArgs requestArgs);
-
+
+ /** Portal bootstrap JSON (same payload as GET /api/bootstrap). Public for tests and host integration. */
+ String buildPortalBootstrapJson();
+ /** Same JSON body as GET /api/wifi/meta (tests + embedding). */
+ String buildApiWifiMetaJson();
+ /** Same JSON body as GET /api/info (tests + embedding). */
+ String buildApiInfoJson();
+ /** Same JSON body as GET /api/params (tests + embedding). */
+ String buildApiParamsGetJson();
+ /** Same JSON body as GET /api/status (tests + embedding). */
+ String buildApiStatusJson();
+
+ /** Fixed JSON bodies for POST action endpoints (single source for handlers + tests). */
+ static String jsonApiDeviceRestartScheduled();
+ static String jsonApiParamsSaveOk();
+ static String jsonApiPortalCloseOk();
+ static String jsonApiPortalExitOk();
+ static String jsonApiPortalExitForbidden();
+ static String jsonApiOtaUpdateSuccess();
+ static String jsonApiEraseResponse(boolean success);
+
private:
WiFiManager* _wm;
void collectVisibleScanResults(std::vector& networks);
void appendVisibleScanResultsJson(String& json, const std::vector& networks);
- String buildPortalBootstrapJson();
void applyWifiAndParamsFromRequest(AsyncWebServerRequest *request);
void buildPlainStatusSummary(String& out);
+ void appendPortalJsonStaticFields(String& json, bool& first);
+ void appendPortalJsonCustomParams(String& json, bool& first);
+
+ void appendJsonKvItem(String& json, bool& first, const char* key, const String& label, const String& value);
+ void appendOneInfoItemForId(String& json, bool& first, const char* id);
+ void appendInfoSectionFromIds(String& json, const char* const* ids, size_t count, bool& first);
+
+ void appendPortalUiFeatureFlagsJson(String& json);
+ void appendApiInfoActionsJson(String& json);
+
static void sendApiJson(AsyncWebServerRequest *request, int code, const String& json);
};
#endif // defined(ESP8266) || defined(ESP32)
#endif // WiFiManagerHandlers_h
-
diff --git a/lib/WiFiManager/include/WiFiManagerLogLevel.h b/lib/WiFiManager/include/WiFiManagerLogLevel.h
index f755f13..5dd15b7 100644
--- a/lib/WiFiManager/include/WiFiManagerLogLevel.h
+++ b/lib/WiFiManager/include/WiFiManagerLogLevel.h
@@ -1,6 +1,9 @@
/**
* WiFiManagerLogLevel.h
*
+ * @author alexhopeoconnor
+ * @license MIT
+ *
* Compile-time and runtime log levels for WiFiManager. Higher numeric values
* mean more verbose output. Matches the old 0..5 tier model for build flags.
*/
diff --git a/lib/WiFiManager/include/WiFiManagerLogSink.h b/lib/WiFiManager/include/WiFiManagerLogSink.h
index 7374e39..42c82dd 100644
--- a/lib/WiFiManager/include/WiFiManagerLogSink.h
+++ b/lib/WiFiManager/include/WiFiManagerLogSink.h
@@ -1,6 +1,9 @@
/**
* WiFiManagerLogSink.h
*
+ * @author alexhopeoconnor
+ * @license MIT
+ *
* Optional host-provided sink for WiFiManager log output. When set, logs are
* delivered here instead of the configured Print stream (usually Serial).
*/
diff --git a/lib/WiFiManager/include/WiFiManagerLogTemplates.h b/lib/WiFiManager/include/WiFiManagerLogTemplates.h
index 14e5e11..964988f 100644
--- a/lib/WiFiManager/include/WiFiManagerLogTemplates.h
+++ b/lib/WiFiManager/include/WiFiManagerLogTemplates.h
@@ -1,6 +1,9 @@
/**
* WiFiManagerLogTemplates.h
*
+ * @author alexhopeoconnor
+ * @license MIT
+ *
* Template implementations for WiFiManager::log(...). Included at the end of WiFiManager.h.
*/
diff --git a/lib/WiFiManager/include/WiFiManagerParameter.h b/lib/WiFiManager/include/WiFiManagerParameter.h
index ba6fcbd..a49862a 100644
--- a/lib/WiFiManager/include/WiFiManagerParameter.h
+++ b/lib/WiFiManager/include/WiFiManagerParameter.h
@@ -1,3 +1,10 @@
+/**
+ * WiFiManagerParameter.h
+ *
+ * @author alexhopeoconnor
+ * @license MIT
+ */
+
#ifndef WiFiManagerParameter_h
#define WiFiManagerParameter_h
diff --git a/lib/WiFiManager/include/WiFiManagerServer.h b/lib/WiFiManager/include/WiFiManagerServer.h
index 066896c..698c00d 100644
--- a/lib/WiFiManager/include/WiFiManagerServer.h
+++ b/lib/WiFiManager/include/WiFiManagerServer.h
@@ -1,12 +1,29 @@
/**
* WiFiManagerServer.h
- *
- * Server lifecycle management for WiFiManager
- * Handles AsyncWebServer creation, route registration, and shutdown
- *
- * @author tablatronix
- * @author Alex Hope-O'Connor
+ *
+ * @author alexhopeoconnor
* @license MIT
+ *
+ * HTTP server for the config portal: single HTML shell (GET /), JSON under /api/..., OTA POST /u,
+ * captive-portal redirects, and 404 handling. No legacy page routes.
+ *
+ * Supported surface (authoritative; registerRoutes mirrors this):
+ * GET /
+ * GET /api/bootstrap
+ * GET /api/wifi/scan-status
+ * POST /api/wifi/scan
+ * GET /api/wifi/meta
+ * POST /api/wifi/save
+ * GET /api/params
+ * POST /api/params/save
+ * GET /api/info
+ * GET /api/status
+ * POST /api/device/restart
+ * POST /api/device/erase
+ * POST /api/portal/close
+ * POST /api/portal/exit
+ * POST /u (multipart firmware upload; JSON completion response)
+ * notFound -> captive redirect or 404
*/
#ifndef WiFiManagerServer_h
@@ -19,17 +36,13 @@
#include
#include
-// DFTE (template engine)
#include
#ifndef WM_TEMPLATE_REGISTRY_CAPACITY
-#define WM_TEMPLATE_REGISTRY_CAPACITY 24
+#define WM_TEMPLATE_REGISTRY_CAPACITY 16
#endif
-// Forward declarations
class WiFiManager;
-
-// Forward declaration - full definition needed in .cpp file
class WiFiManagerHandlers;
#ifdef WM_DFTE_LOGGING
@@ -37,23 +50,9 @@ class WiFiManagerDfteLogger;
#endif
// -----------------------------------------------------------------------------------------------
-// HTTP ROUTES
+// HTTP routes (portal API only; no legacy /wifi, /info, … page URLs)
const char R_root[] PROGMEM = "/";
-const char R_wifi[] PROGMEM = "/wifi";
-const char R_wifinoscan[] PROGMEM = "/0wifi";
-const char R_wifisave[] PROGMEM = "/wifisave";
-const char R_info[] PROGMEM = "/info";
-const char R_param[] PROGMEM = "/param";
-const char R_paramsave[] PROGMEM = "/paramsave";
-const char R_restart[] PROGMEM = "/restart";
-const char R_exit[] PROGMEM = "/exit";
-const char R_close[] PROGMEM = "/close";
-const char R_erase[] PROGMEM = "/erase";
-const char R_scanstatus[] PROGMEM = "/wifistatus";
-const char R_scan[] PROGMEM = "/wifi/scan";
-const char R_update[] PROGMEM = "/update";
-const char R_updatedone[] PROGMEM = "/u";
const char R_api_bootstrap[] PROGMEM = "/api/bootstrap";
const char R_api_wifi_scan_status[] PROGMEM = "/api/wifi/scan-status";
@@ -69,86 +68,50 @@ const char R_api_device_erase[] PROGMEM = "/api/device/erase";
const char R_api_portal_close[] PROGMEM = "/api/portal/close";
const char R_api_portal_exit[] PROGMEM = "/api/portal/exit";
+const char R_updatedone[] PROGMEM = "/u";
+
class WiFiManagerServer {
public:
- // Singleton access
static WiFiManagerServer* instance();
-
+
WiFiManagerServer(WiFiManager* wm);
~WiFiManagerServer();
-
- // Template engine setup and access
+
void setupTemplateEngine();
PlaceholderRegistry* getPlaceholderRegistry() { return _tplRegistry.get(); }
-
- // Static zero-arg getters for DFTE RAM placeholders
+
static const char* tplGetPageTitle();
- static const char* tplGetSubtitle();
- static const char* tplGetMenu();
- static const char* tplGetStatus();
-
- // Template customization API
- // Consumers can register a callback to customize placeholders
- void registerTemplateSetupCallback(std::function cb) { _tplSetupCallback = cb; }
-
- // Convenience helpers to register our defaults (granular + all)
+
void registerDefaultStyles(PlaceholderRegistry& reg);
void registerDefaultScripts(PlaceholderRegistry& reg);
void registerDefaultPageTitle(PlaceholderRegistry& reg);
- void registerDefaultSubtitle(PlaceholderRegistry& reg);
- void registerDefaultMenu(PlaceholderRegistry& reg);
- void registerDefaultStatus(PlaceholderRegistry& reg);
+ /** Shell placeholders only: %STYLES%, %SCRIPTS%, %PAGE_TITLE%. */
void registerDefaultPlaceholders(PlaceholderRegistry& reg);
- void applyTemplateSetupCallback(PlaceholderRegistry& reg);
-
- // Rebuild registry with defaults and optional customizer
- void rebuildPlaceholderRegistry(std::function customizer = nullptr);
-
- // Create server instance
+
void createServer(uint16_t port);
-
- // Register all routes (takes handler callbacks)
void registerRoutes();
-
- // Setup DNS server for captive portal
void setupDNSD();
-
- // Process DNS requests (call periodically)
void processDNS();
-
- // Shutdown and cleanup server and DNS
void shutdownServer();
-
- // Get server instance
AsyncWebServer* getServer() { return server.get(); }
-
- // Get DNS server instance (for testing)
DNSServer* getDNSServer() { return dnsServer.get(); }
-
+
private:
WiFiManager* _wm;
std::unique_ptr _handlers;
std::unique_ptr server;
std::unique_ptr dnsServer;
-
- // Shared DFTE placeholder registry
+
std::unique_ptr _tplRegistry;
- std::function _tplSetupCallback;
#ifdef WM_DFTE_LOGGING
- // DFTE -> WiFiManager::log bridge (only installed when WM_DFTE_LOGGING and no other logger registered)
std::unique_ptr _dfteLogger;
bool _wmOwnsDfteLogSink = false;
#endif
-
- // Singleton instance
+
static WiFiManagerServer* s_instance;
};
-// Include WiFiManagerHandlers.h after class definition to provide full definition
-// needed for std::unique_ptr destructor
-#include "WiFiManagerHandlers.h"
-
#endif // defined(ESP8266) || defined(ESP32)
#endif // WiFiManagerServer_h
diff --git a/lib/WiFiManager/include/WiFiManagerUtils.h b/lib/WiFiManager/include/WiFiManagerUtils.h
index cae5383..687f8d8 100644
--- a/lib/WiFiManager/include/WiFiManagerUtils.h
+++ b/lib/WiFiManager/include/WiFiManagerUtils.h
@@ -4,7 +4,7 @@
* String conversions, HTML encoding, and helper calculations
* These functions have no dependency on WiFiManager instance state
*
- * @author Alex Hope-O'Connor
+ * @author alexhopeoconnor
* @license MIT
*/
diff --git a/lib/WiFiManager/include/templates/CSS.h b/lib/WiFiManager/include/templates/CSS.h
index 9fdd365..3345e5e 100644
--- a/lib/WiFiManager/include/templates/CSS.h
+++ b/lib/WiFiManager/include/templates/CSS.h
@@ -1,7 +1,11 @@
/**
* CSS.h
- * CSS stylesheet for WiFiManager web interface
- * Embedded styles to avoid external file dependencies
+ *
+ * @author alexhopeoconnor
+ * @license MIT
+ *
+ * CSS stylesheet for WiFiManager web interface.
+ * Embedded styles to avoid external file dependencies.
*/
#ifndef _CSS_TEMPLATES_H_
@@ -39,6 +43,21 @@ const char CSS_STYLE[] PROGMEM = "";
#endif // _CSS_TEMPLATES_H_
diff --git a/lib/WiFiManager/include/templates/Fragments.h b/lib/WiFiManager/include/templates/Fragments.h
deleted file mode 100644
index 3c9208b..0000000
--- a/lib/WiFiManager/include/templates/Fragments.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Fragments.h
- * Reusable DFTE fragment templates for WiFiManager page composition.
- */
-
-#ifndef _WM_FRAGMENTS_TEMPLATE_H_
-#define _WM_FRAGMENTS_TEMPLATE_H_
-
-#include
-
-const char WM_ACTION_FORM_TEMPLATE[] PROGMEM =
-"%ACTION_PREFIX%%ACTION_SUFFIX%";
-
-const char WM_SUBMIT_BUTTON_TEMPLATE[] PROGMEM =
-"
";
-
-const char WM_CENTERED_BUTTON_TEMPLATE[] PROGMEM =
-"
";
-
-const char WM_SECTION_BREAK_TEMPLATE[] PROGMEM =
-"
%SECTION_CONTENT%";
-
-const char WM_SCAN_MESSAGE_TEMPLATE[] PROGMEM =
-"%SCAN_MESSAGE%
";
-
-const char WM_SCAN_RESULT_ROW_TEMPLATE[] PROGMEM =
-"";
-
-const char WM_FIELD_LABEL_BEFORE_TEMPLATE[] PROGMEM =
-"
";
-
-const char WM_FIELD_LABEL_AFTER_TEMPLATE[] PROGMEM =
-"
"
-"";
-
-const char WM_FIELD_INPUT_ONLY_TEMPLATE[] PROGMEM =
-"
";
-
-const char WM_INFO_ROW_TEMPLATE[] PROGMEM =
-"%INFO_LABEL%%INFO_VALUE%";
-
-const char WM_INFO_SECTION_TEMPLATE[] PROGMEM =
-"%SECTION_PREFIX%%SECTION_TITLE%
%SECTION_ROWS%
%SECTION_SUFFIX%";
-
-const char WM_STATUS_MESSAGE_TEMPLATE[] PROGMEM =
-"%STATUS_TITLE%%STATUS_BODY%
";
-
-const char WM_PAGE_HEADING_TEMPLATE[] PROGMEM =
-"%HEADER_TITLE%
%HEADER_SUBTITLE%
";
-
-#endif // _WM_FRAGMENTS_TEMPLATE_H_
diff --git a/lib/WiFiManager/include/templates/HTML.h b/lib/WiFiManager/include/templates/HTML.h
deleted file mode 100644
index 3d244fc..0000000
--- a/lib/WiFiManager/include/templates/HTML.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * HTML.h
- * HTML template fragments for WiFiManager web interface
- * Static PROGMEM strings to reduce string concatenations
- */
-
-#ifndef _HTML_TEMPLATES_H_
-#define _HTML_TEMPLATES_H_
-
-#include
-
-// Status messages
-const char HTML_STATUS_OFFPW[] PROGMEM = "
Authentication failure";
-
-const char HTML_STATUS_OFFNOAP[] PROGMEM = "
AP not found";
-
-const char HTML_STATUS_OFFFAIL[] PROGMEM = "
Could not connect";
-
-const char HTML_STATUS_NONE[] PROGMEM = "No AP set
";
-
-const char HTML_SAVED[] PROGMEM = "Saving Credentials
Trying to connect ESP to network.
If it fails reconnect to AP to try again
";
-
-const char HTML_PARAMSAVED[] PROGMEM = "Saved
";
-
-// Update page
-const char HTML_UPDATE[] PROGMEM = "Upload new firmware
* May not function inside captive portal, open in browser http://192.168.4.1";
-
-const char HTML_UPDATE_FAIL[] PROGMEM = "Update failed!
Reboot device and try again
";
-
-const char HTML_UPDATE_SUCCESS[] PROGMEM = "Update successful.
Device rebooting now...
";
-
-// Help page
-#ifndef WM_NOHELP
-const char HTML_HELP[] PROGMEM =
- "
Available pages
"
- ""
- "| Page | Function |
"
- "| / | "
- "Menu page. |
"
- "| /wifi | "
- "Show WiFi scan results and enter WiFi configuration. |
"
- "| /0wifi | "
- "Show WiFi configuration without loading scan results until refresh is requested. |
"
- "| /wifisave | "
- "Save WiFi configuration information and configure device. Needs variables supplied. |
"
- "| /param | "
- "Parameter page |
"
- "| /info | "
- "Information page |
"
- "| /update | "
- "OTA update page. Firmware upload posts to /u. |
"
- "| /close | "
- "Close the captiveportal popup, config portal will remain active |
"
- "| /exit | "
- "Exit Config portal, config portal will close |
"
- "| /restart | "
- "Reboot the device |
"
- "| /erase | "
- "Erase WiFi configuration and reboot device. Device will not reconnect to a network until new WiFi configuration data is entered. |
"
- "
"
- "Fork https://github.com/alexhopeoconnor/WiFiManager.";
-#else
-const char HTML_HELP[] PROGMEM = "";
-#endif
-
-#endif // _HTML_TEMPLATES_H_
-
diff --git a/lib/WiFiManager/include/templates/Info.h b/lib/WiFiManager/include/templates/Info.h
deleted file mode 100644
index 66921c5..0000000
--- a/lib/WiFiManager/include/templates/Info.h
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Info.h
- * Streamed info page content template for WiFiManager.
- */
-
-#ifndef _WM_INFO_TEMPLATE_H_
-#define _WM_INFO_TEMPLATE_H_
-
-#include
-
-const char WM_INFO_CONTENT_TEMPLATE[] PROGMEM =
-"%INFO_STATUS%"
-""
-""
-""
-"%INFO_FOOTER%";
-
-#endif // _WM_INFO_TEMPLATE_H_
diff --git a/lib/WiFiManager/include/templates/JS.h b/lib/WiFiManager/include/templates/JS.h
index 26e9785..e560a0f 100644
--- a/lib/WiFiManager/include/templates/JS.h
+++ b/lib/WiFiManager/include/templates/JS.h
@@ -1,7 +1,11 @@
/**
* JS.h
- * JavaScript code for WiFiManager web interface
- * Client-side functionality for WiFi configuration
+ *
+ * @author alexhopeoconnor
+ * @license MIT
+ *
+ * JavaScript code for WiFiManager web interface.
+ * Client-side functionality for WiFi configuration.
*/
#ifndef _JS_TEMPLATES_H_
diff --git a/lib/WiFiManager/include/templates/Message.h b/lib/WiFiManager/include/templates/Message.h
deleted file mode 100644
index 9284ffa..0000000
--- a/lib/WiFiManager/include/templates/Message.h
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Message.h
- * Streamed message page content template for WiFiManager.
- */
-
-#ifndef _WM_MESSAGE_TEMPLATE_H_
-#define _WM_MESSAGE_TEMPLATE_H_
-
-#include
-
-const char WM_MESSAGE_CONTENT_TEMPLATE[] PROGMEM =
-"%MESSAGE_BODY%"
-"%MESSAGE_ACTIONS%";
-
-#endif // _WM_MESSAGE_TEMPLATE_H_
diff --git a/lib/WiFiManager/include/templates/PageShell.h b/lib/WiFiManager/include/templates/PageShell.h
deleted file mode 100644
index b3f7070..0000000
--- a/lib/WiFiManager/include/templates/PageShell.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * PageShell.h
- * Generic whole-document shell template for streamed WiFiManager pages.
- */
-
-#ifndef _WM_PAGE_SHELL_TEMPLATE_H_
-#define _WM_PAGE_SHELL_TEMPLATE_H_
-
-#include
-
-const char WM_PAGE_SHELL_TEMPLATE[] PROGMEM =
-""
-""
-""
-""
-""
-""
-"%DOC_TITLE%"
-"%SCRIPTS%"
-"%PAGE_SCRIPTS%"
-"%STYLES%"
-"%PAGE_STYLES%"
-""
-""
-""
-"%PAGE_CONTENT%"
-"
"
-""
-"";
-
-#endif // _WM_PAGE_SHELL_TEMPLATE_H_
diff --git a/lib/WiFiManager/include/templates/Param.h b/lib/WiFiManager/include/templates/Param.h
deleted file mode 100644
index 1dfef89..0000000
--- a/lib/WiFiManager/include/templates/Param.h
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * Param.h
- * Streamed setup page content template for WiFiManager.
- */
-
-#ifndef _WM_PARAM_TEMPLATE_H_
-#define _WM_PARAM_TEMPLATE_H_
-
-#include
-
-const char WM_PARAM_CONTENT_TEMPLATE[] PROGMEM =
-""
-"%PARAM_PAGE_ACTIONS%"
-"%PARAM_STATUS%";
-
-#endif // _WM_PARAM_TEMPLATE_H_
diff --git a/lib/WiFiManager/include/templates/PortalAppJS.h b/lib/WiFiManager/include/templates/PortalAppJS.h
index 3416968..15e315c 100644
--- a/lib/WiFiManager/include/templates/PortalAppJS.h
+++ b/lib/WiFiManager/include/templates/PortalAppJS.h
@@ -1,5 +1,9 @@
/**
* PortalAppJS.h
+ *
+ * @author alexhopeoconnor
+ * @license MIT
+ *
* Embedded SPA for WiFiManager config portal (hash routing + JSON APIs).
*/
@@ -17,30 +21,96 @@ try{var el=document.getElementById('wm-bootstrap');if(el&&el.textContent)boot=JS
function $(id){return document.getElementById(id);}
function esc(t){if(!t)return'';return String(t).replace(/[&<>"']/g,function(c){return({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]);});}
+var _wmToastTimer=null;
+function showToast(msg,isErr){
+ var el=$('wm-toast');
+ if(!el)return;
+ el.textContent=msg||'';
+ el.className='wm-toast '+(isErr?'err':'ok');
+ el.style.display='block';
+ if(_wmToastTimer)clearTimeout(_wmToastTimer);
+ _wmToastTimer=setTimeout(function(){el.style.display='none';el.textContent='';},4500);
+}
+function showConfirm(msg,onOk){
+ var dlg=$('wm-dialog'),dmsg=$('wm-dialog-msg'),bk=$('wm-dialog-backdrop'),ok=$('wm-dialog-ok'),cancel=$('wm-dialog-cancel');
+ if(!dlg||!dmsg||!ok||!cancel){if(onOk)onOk();return;}
+ dmsg.textContent=msg||'';
+ dlg.style.display='flex';
+ dlg.setAttribute('aria-hidden','false');
+ function hide(){
+ dlg.style.display='none';
+ dlg.setAttribute('aria-hidden','true');
+ ok.onclick=null;
+ cancel.onclick=null;
+ if(bk)bk.onclick=null;
+ }
+ function onCancel(){hide();}
+ cancel.onclick=onCancel;
+ if(bk)bk.onclick=onCancel;
+ ok.onclick=function(){hide();if(onOk)onOk();};
+}
+
function setView(html){
var a=$('app');
if(!a)return;
a.innerHTML=""+html+"
";
}
+function renderMetaField(f){
+ if(!f)return'';
+ if(f.html)return f.html;
+ var id=f.id||f.name||'';
+ var nm=f.name||f.id||'';
+ var domId=(id==='s')?'wm-s':(id==='p')?'wm-p':('wm-f-'+id);
+ var t=f.type||'text';
+ var h='';
+ h+='';
+ return h;
+}
+
+function renderFieldList(arr){
+ if(!arr||!arr.length)return'';
+ var h='',i;
+ for(i=0;iHome · ";
h+="WiFi";
if(!f.paramsInWifi){h+=" · Setup";}
- h+=" · Info";
- h+=" · Update";
+ if(f.showInfo!==false){h+=" · Info";}
+ if(f.showUpdate){h+=" · Update";}
+ if(boot.showBack){h+=" · Back";}
h+="
";
return h;
}
+function deviceActionsHtml(){
+ var f=boot.features||{};
+ var h='', ok=false;
+ if(f.showRestart){h+="";ok=true;}
+ if(f.showExitPortal){h+="";ok=true;}
+ if(f.showCloseCaptive){h+="";ok=true;}
+ if(!ok)return'';
+ return "Device actions
"+h+"";
+}
+
function viewHome(){
var sub=esc(boot.subtitle||'');
var t=esc(boot.title||'WiFiManager');
var html=navBar();
html+=""+t+"
"+sub+"
";
+ if(boot.initialStatus)html+=""+esc(boot.initialStatus)+"
";
html+="Use the links above to configure WiFi, view device info, or update firmware.
";
+ html+=deviceActionsHtml();
setView(html);
}
@@ -99,11 +169,16 @@ function viewWifi(){
var html=navBar()+"WiFi
";
html+="";
html+="";
html+="
";
html+="";
@@ -143,22 +218,52 @@ function viewInfo(){
h+="";
return h;
}
- html+=section('Status',d.status||[]);
+ var st=d.status;
+ if(st && typeof st==='object' && !Array.isArray(st)){
+ html+="Status
";
+ html+="- Connected
- "+esc(String(st.connected))+"
";
+ if(st.ssid!==undefined)html+="- SSID
- "+esc(st.ssid)+"
";
+ if(st.stationIp)html+="- Station IP
- "+esc(st.stationIp)+"
";
+ if(st.apIp)html+="- AP IP
- "+esc(st.apIp)+"
";
+ if(st.summary)html+="- Summary
- "+esc(st.summary)+"
";
+ html+="
";
+ } else {
+ html+=section('Status',d.status||[]);
+ }
html+=section('Device',d.device||[]);
html+=section('WiFi',d.wifi||[]);
html+=section('About',d.about||[]);
var act=d.actions||{};
+ html+="Actions
";
+ if(act.showRestart)html+="";
+ if(act.showExitPortal)html+="";
+ if(act.showCloseCaptive)html+="";
if(act.showUpdate)html+="Firmware update
";
if(act.showErase)html+="";
setView(html);
});
}
+function portalConfirmPost(url, promptMsg){
+ showConfirm(promptMsg||'Continue?',function(){
+ api(url,{method:'POST'}).then(function(res){
+ try{var j=JSON.parse(res.body);}catch(e){j={};}
+ var m=(j&&j.message)?j.message:(res.ok?'OK':'Error');
+ showToast(m,!res.ok);
+ });
+ });
+}
+
+window.portalRestart=function(){portalConfirmPost('/api/device/restart','Restart the device?');};
+window.portalExit=function(){portalConfirmPost('/api/portal/exit','Exit the configuration portal?');};
+window.portalCloseCaptive=function(){portalConfirmPost('/api/portal/close','Stop redirecting clients to this portal (captive detection off)?');};
+
window.portalErase=function(){
- if(!confirm('Erase WiFi configuration?'))return;
- api('/api/device/erase',{method:'POST'}).then(function(res){
- try{var j=JSON.parse(res.body);}catch(e){j={};}
- alert((j&&j.message)?j.message:'Done');
+ showConfirm('Erase WiFi configuration?',function(){
+ api('/api/device/erase',{method:'POST'}).then(function(res){
+ try{var j=JSON.parse(res.body);}catch(e){j={};}
+ showToast((j&&j.message)?j.message:(res.ok?'Done':'Error'),!res.ok);
+ });
});
};
@@ -167,7 +272,9 @@ function viewSetup(){
api('/api/params').then(function(res){
try{var d=JSON.parse(res.body);}catch(e){d={};}
var html=navBar()+"Parameters
";
- html+="";
+ html+="";
html+="";
setView(html);
});
@@ -186,17 +293,59 @@ window.portalParamSave=function(ev){
};
function viewUpdate(){
+ var f=boot.features||{};
var html=navBar()+"Firmware update
";
- html+="";
- html+="Upload a .bin firmware. Device may restart after update.
";
+ if(!f.showUpdate){html+="Firmware update is disabled.
";setView(html);return;}
+ html+="";
+ html+="Upload a .bin firmware. The device restarts after a successful update.
";
+ html+="";
setView(html);
}
+window.portalOtaSubmit=function(ev){
+ ev.preventDefault();
+ var fi=$('wm-ota-file');
+ var logEl=$('wm-ota-log');
+ if(!fi||!fi.files||!fi.files[0]){
+ if(logEl)logEl.textContent='Choose a firmware file first.';
+ return false;
+ }
+ if(logEl)logEl.textContent='Uploading…';
+ var xhr=new XMLHttpRequest();
+ xhr.upload.onprogress=function(e){
+ if(!logEl||!e.lengthComputable)return;
+ logEl.textContent='Uploading… '+Math.round(100*e.loaded/e.total)+'%';
+ };
+ xhr.onreadystatechange=function(){
+ if(xhr.readyState!==4)return;
+ var t=xhr.responseText||'';
+ if(logEl)logEl.textContent=t;
+ var httpOk=xhr.status>=200&&xhr.status<300;
+ try{
+ var j=JSON.parse(t);
+ if(j&&j.message){
+ if(logEl)logEl.textContent=j.message;
+ showToast(j.message,j.ok===false);
+ return;
+ }
+ }catch(e){}
+ showToast(httpOk?'Upload finished.':('HTTP '+xhr.status),!httpOk);
+ };
+ xhr.open('POST','/u');
+ var fd=new FormData();
+ fd.append('update',fi.files[0]);
+ xhr.send(fd);
+ return false;
+};
+
function route(){
var h=location.hash||'#/';
if(h.indexOf('#/')!==0)h='#/';
+ var f=boot.features||{};
+ if(h==='#/info'&&f.showInfo===false){viewHome();return;}
+ if(h==='#/update'&&f.showUpdate===false){viewHome();return;}
if(h==='#/'||h==='#'){viewHome();return;}
if(h==='#/wifi'){viewWifi();return;}
if(h==='#/info'){viewInfo();return;}
diff --git a/lib/WiFiManager/include/templates/Root.h b/lib/WiFiManager/include/templates/Root.h
deleted file mode 100644
index 5a2b198..0000000
--- a/lib/WiFiManager/include/templates/Root.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * Root.h
- * Whole-document root template for WiFiManager using DFTE placeholders
- */
-
-#ifndef _WM_ROOT_TEMPLATE_H_
-#define _WM_ROOT_TEMPLATE_H_
-
-#include
-
-// Whole document template:
-// - Injects %SCRIPTS% and %STYLES% (these already include "
"
-)rawliteral";
-
-#endif // _WIFI_POLLING_JS_H_
-
diff --git a/lib/WiFiManager/src/WiFiManager.cpp b/lib/WiFiManager/src/WiFiManager.cpp
index db7eecd..eb5b9ee 100644
--- a/lib/WiFiManager/src/WiFiManager.cpp
+++ b/lib/WiFiManager/src/WiFiManager.cpp
@@ -6,13 +6,13 @@
*
* @author Creator tzapu
* @author tablatronix
- * @author Alex Hope-O'Connor
+ * @author alexhopeoconnor
* @license MIT
*/
#include "WiFiManager.h"
#include "WiFiManagerLogSink.h"
-#include "WiFiManagerServer.h" // Need menu tokens
+#include "WiFiManagerServer.h"
#if defined(ESP8266) || defined(ESP32)
@@ -508,7 +508,6 @@ void WiFiManager::setupConfigPortal() {
// Lazy initialization: only create server manager when config portal is actually needed
if (!_serverManager) {
_serverManager = std::make_unique(this);
- _serverManager->registerTemplateSetupCallback(_templateSetupCallback);
}
_serverManager->createServer(_httpPort);
_serverManager->registerRoutes();
@@ -2013,6 +2012,10 @@ void WiFiManager::setShowInfoUpdate(boolean enabled){
_showInfoUpdate = enabled;
}
+void WiFiManager::setShowInfo(boolean enabled){
+ _showInfo = enabled;
+}
+
/**
* check if the config portal is running
* @return bool true if active
@@ -2054,20 +2057,6 @@ void WiFiManager::setTitle(String title){
_title = title;
}
-void WiFiManager::registerTemplateSetupCallback(std::function cb) {
- _templateSetupCallback = std::move(cb);
- if (_serverManager) {
- _serverManager->registerTemplateSetupCallback(_templateSetupCallback);
- _serverManager->rebuildPlaceholderRegistry();
- }
-}
-
-void WiFiManager::rebuildPlaceholderRegistry(std::function customizer) {
- if (_serverManager) {
- _serverManager->rebuildPlaceholderRegistry(std::move(customizer));
- }
-}
-
/**
* Set params as separate page not in wifi
* NOT COMPATIBLE WITH setMenu!
diff --git a/lib/WiFiManager/src/WiFiManagerHandlers.cpp b/lib/WiFiManager/src/WiFiManagerHandlers.cpp
index 3dd8686..826ec89 100644
--- a/lib/WiFiManager/src/WiFiManagerHandlers.cpp
+++ b/lib/WiFiManager/src/WiFiManagerHandlers.cpp
@@ -3,24 +3,15 @@
*
* HTTP request handlers and rendering logic implementation
*
- * @author tablatronix
- * @author Alex Hope-O'Connor
+ * @author alexhopeoconnor
* @license MIT
*/
#include "WiFiManagerHandlers.h"
-#include "WiFiManagerServer.h" // Need menu tokens and routes
-#include "templates/HTML.h"
+#include "WiFiManagerServer.h"
#include "templates/CSS.h"
#include "templates/JS.h"
-#include "templates/WiFiPollingJS.h"
-#include "templates/PageShell.h"
-#include "templates/Fragments.h"
-#include "templates/Info.h"
-#include "templates/Message.h"
-#include "templates/Param.h"
-#include "templates/WiFi.h"
-#include "templates/RootSelector.h"
+#include "templates/RootShell.h"
#include "templates/PortalAppJS.h"
#include
#include
@@ -48,58 +39,6 @@ static void jsonAppendEscaped(String& out, const String& s) {
}
}
-/** Parse a single `......` row from `getInfoData` HTML. */
-static bool parseInfoRowDtDd(const String& html, String& label, String& value) {
- const int dtOpen = html.indexOf(F(""));
- if (dtOpen < 0) {
- return false;
- }
- const int dtClose = html.indexOf(F(""), dtOpen);
- if (dtClose < 0) {
- return false;
- }
- label = html.substring(dtOpen + 4, dtClose);
- const int ddOpen = html.indexOf(F(""), dtClose);
- if (ddOpen < 0) {
- return false;
- }
- const int ddClose = html.indexOf(F(""), ddOpen);
- if (ddClose < 0) {
- return false;
- }
- value = html.substring(ddOpen + 4, ddClose);
- return true;
-}
-
-static void appendJsonInfoArrayFromIds(WiFiManagerHandlers* handlers,
- String& json,
- const char* const* ids,
- size_t count,
- bool& first) {
- for (size_t i = 0; i < count; i++) {
- const String row = handlers->getInfoData(ids[i]);
- if (row.length() == 0) {
- continue;
- }
- String label;
- String value;
- if (!parseInfoRowDtDd(row, label, value)) {
- continue;
- }
- if (!first) {
- json += F(",");
- }
- first = false;
- json += F("{\"key\":\"");
- json += ids[i];
- json += F("\",\"label\":\"");
- jsonAppendEscaped(json, label);
- json += F("\",\"value\":\"");
- jsonAppendEscaped(json, value);
- json += F("\"}");
- }
-}
-
namespace {
const char kEmptyTemplateChunk[] PROGMEM = "";
@@ -142,7 +81,6 @@ void registerSharedShellPlaceholders(WiFiManagerServer* server, PlaceholderRegis
if (server) {
server->registerDefaultStyles(registry);
server->registerDefaultScripts(registry);
- server->applyTemplateSetupCallback(registry);
return;
}
@@ -150,479 +88,6 @@ void registerSharedShellPlaceholders(WiFiManagerServer* server, PlaceholderRegis
registry.registerProgmemData("%SCRIPTS%", JS_SCRIPT);
}
-void registerShellTemplate(PlaceholderRegistry& registry,
- DynamicTemplateDescriptor& docTitleDescriptor,
- String& docTitle,
- const char* bodyClass,
- const char* pageContentTemplate,
- const char* pageScripts = kEmptyTemplateChunk,
- const char* pageStyles = kEmptyTemplateChunk) {
- configureDynamicStringDescriptor(docTitleDescriptor, docTitle);
- registry.registerDynamicTemplate("%DOC_TITLE%", &docTitleDescriptor);
- registry.registerProgmemData("%BODY_CLASS%", bodyClass);
- registry.registerProgmemData("%PAGE_SCRIPTS%", pageScripts);
- registry.registerProgmemData("%PAGE_STYLES%", pageStyles);
- registry.registerProgmemTemplate("%PAGE_CONTENT%", pageContentTemplate);
-}
-
-struct MessagePageState {
- String docTitle;
- const char* bodyClass;
- String messageBody;
- String actions;
-
- MessagePageState() : bodyClass(C_root) {}
-};
-
-struct InfoPageState {
- String docTitle;
- String status;
- String deviceSection;
- String wifiSection;
- String aboutSection;
- String footer;
-};
-
-struct WiFiPageState {
- String docTitle;
- String scanContent;
- String ssidPlaceholder;
- String passwordPlaceholder;
- String staticFields;
- String paramSection;
- String formActions;
- String pageActions;
- String status;
-};
-
-struct ParamPageState {
- String docTitle;
- String fields;
- String formActions;
- String pageActions;
- String status;
-};
-
-void escapePercentsForTemplate(String& value) {
- if (value.indexOf('%') < 0) return;
-
- String escaped;
- reservePage(escaped, value.length() + 32);
- for (size_t i = 0; i < value.length(); i++) {
- if (value[i] == '%') {
- escaped += F("%");
- } else {
- escaped += value[i];
- }
- }
- value = escaped;
-}
-
-String renderActionForm(const String& action,
- const String& method,
- const String& label,
- const String& buttonClassAttr = String(),
- const String& prefix = String(),
- const String& suffix = String()) {
- String output;
- reservePage(output, prefix.length() + suffix.length() + action.length() + method.length() +
- label.length() + buttonClassAttr.length() + 48);
- output += prefix;
- output += F("");
- output += suffix;
- return output;
-}
-
-String renderSubmitButton(const String& label) {
- String output;
- reservePage(output, label.length() + 40);
- output += F("
");
- return output;
-}
-
-String renderCenteredButton(const String& id,
- const String& type,
- const String& onClick,
- const String& label,
- const String& extraAttrs = String()) {
- String output;
- reservePage(output, id.length() + type.length() + onClick.length() + label.length() +
- extraAttrs.length() + 72);
- output += F("
");
- return output;
-}
-
-String renderSectionBreak(const String& content) {
- if (content.length() == 0) {
- return String();
- }
-
- String output;
- reservePage(output, content.length() + 16);
- output += F("
");
- output += content;
- return output;
-}
-
-String renderScanMessage(const String& message) {
- String output;
- reservePage(output, message.length() + 16);
- output += message;
- output += F("
");
- return output;
-}
-
-String renderScanRow(const String& ssidAttr,
- const String& ssidText,
- const String& qualityLabel,
- const String& qualityIcon,
- const String& lockClass,
- const String& iconVisibilityClass,
- const String& valueVisibilityClass,
- const String& qualityValue) {
- String output;
- reservePage(output, ssidAttr.length() + ssidText.length() + qualityLabel.length() +
- qualityIcon.length() + lockClass.length() +
- iconVisibilityClass.length() + valueVisibilityClass.length() +
- qualityValue.length() + 160);
- output += F("");
- return output;
-}
-
-String renderFieldTemplate(const char* templateData,
- const String& id,
- const String& label,
- const String& maxLength,
- const String& value,
- const String& extraAttrs = String()) {
- String input;
- reservePage(input, id.length() + label.length() + maxLength.length() + value.length() +
- extraAttrs.length() + 96);
- input += F("");
-
- if (templateData == WM_FIELD_LABEL_BEFORE_TEMPLATE) {
- String output;
- reservePage(output, label.length() + input.length() + 32);
- output += F("
");
- output += input;
- return output;
- }
-
- if (templateData == WM_FIELD_LABEL_AFTER_TEMPLATE) {
- String output;
- reservePage(output, label.length() + input.length() + 32);
- output += F("
");
- output += input;
- output += F("");
- return output;
- }
-
- String output;
- reservePage(output, input.length() + 8);
- output += F("
");
- output += input;
- return output;
-}
-
-String renderInfoRow(const String& label, const String& value) {
- String output;
- reservePage(output, label.length() + value.length() + 24);
- output += F("");
- output += label;
- output += F("");
- output += value;
- output += F("");
- return output;
-}
-
-String renderInfoSection(const String& title,
- const String& rows,
- const String& prefix = String(),
- const String& suffix = String()) {
- String output;
- reservePage(output, prefix.length() + suffix.length() + title.length() + rows.length() + 40);
- output += prefix;
- output += F("");
- output += title;
- output += F("
");
- output += rows;
- output += F("
");
- output += suffix;
- return output;
-}
-
-String renderStatusMessage(const String& title,
- const String& body,
- const String& statusClassSuffix = String()) {
- String output;
- reservePage(output, title.length() + body.length() + statusClassSuffix.length() + 48);
- output += F("");
- output += title;
- output += F("");
- output += body;
- output += F("
");
- return output;
-}
-
-String renderPageHeading(const String& title, const String& subtitle) {
- String output;
- reservePage(output, title.length() + subtitle.length() + 20);
- output += F("");
- output += title;
- output += F("
");
- output += subtitle;
- output += F("
");
- return output;
-}
-
-void buildMessagePageState(MessagePageState& state,
- const String& title,
- const char* bodyClass,
- const String& messageBodyHtml,
- const String& actionsHtml) {
- state.docTitle = title;
- state.bodyClass = bodyClass;
- state.messageBody = messageBodyHtml;
- state.actions = actionsHtml;
- escapePercentsForTemplate(state.messageBody);
- escapePercentsForTemplate(state.actions);
-}
-
-String buildUpdatePanelContent(const String& title, const String& subtitle) {
- String content;
- reservePage(content, 2048);
- content += renderPageHeading(title, subtitle);
- content += FPSTR(HTML_UPDATE);
- return content;
-}
-
-String buildUpdateResultContent(const String& title, const String& subtitle) {
- String content;
- reservePage(content, 2048);
- content += renderPageHeading(title, subtitle);
-
- if (Update.hasError()) {
- content += FPSTR(HTML_UPDATE_FAIL);
- #ifdef ESP32
- content += "OTA Error: " + String(Update.errorString());
- #else
- content += "OTA Error: " + String(Update.getError());
- #endif
- } else {
- content += FPSTR(HTML_UPDATE_SUCCESS);
- }
-
- return content;
-}
-
-void appendInfoEntries(WiFiManagerHandlers* handlers,
- String& section,
- const char* const* ids,
- size_t count) {
- for (size_t i = 0; i < count; i++) {
- section += handlers->getInfoData(ids[i]);
- }
-}
-
-void buildInfoPageState(WiFiManagerHandlers* handlers,
- InfoPageState& state,
- bool showInfoUpdate,
- bool showInfoErase,
- bool showBack) {
- reservePage(state.status, 512);
- reservePage(state.deviceSection, 4096);
- reservePage(state.wifiSection, 4096);
- reservePage(state.aboutSection, 1536);
- reservePage(state.footer, 1024);
-
- state.docTitle = F("Info");
- handlers->reportStatus(state.status);
-
-#ifdef ESP8266
- static const char* const deviceIds[] = {
- "uptime", "chipid", "fchipid", "idesize", "flashsize", "corever",
- "bootver", "cpufreq", "freeheap", "memsketch", "memsmeter", "lastreset"
- };
- static const char* const wifiIds[] = {
- "conx", "stassid", "staip", "stagw", "stasub", "dnss", "host",
- "stamac", "autoconx", "apssid", "apip", "apbssid", "apmac"
- };
-#elif defined(ESP32)
- static const char* const deviceIds[] = {
- "uptime", "chipid", "chiprev", "idesize", "flashsize",
- "cpufreq", "freeheap", "memsketch", "memsmeter", "lastreset", "temp"
- };
- static const char* const wifiIds[] = {
- "conx", "stassid", "staip", "stagw", "stasub", "dnss", "host",
- "stamac", "apssid", "apip", "apmac", "aphost", "apbssid"
- };
-#endif
-
- appendInfoEntries(handlers, state.deviceSection, deviceIds, sizeof(deviceIds) / sizeof(deviceIds[0]));
- appendInfoEntries(handlers, state.wifiSection, wifiIds, sizeof(wifiIds) / sizeof(wifiIds[0]));
- state.aboutSection += handlers->getInfoData("aboutver");
- state.aboutSection += handlers->getInfoData("aboutarduinover");
- state.aboutSection += handlers->getInfoData("aboutsdkver");
- state.aboutSection += handlers->getInfoData("aboutdate");
-
- if (showInfoUpdate) {
- state.footer += renderActionForm(F("/update"), F("get"), F("Update"), String(), F("
"), F("
\n"));
- }
- if (showInfoErase) {
- state.footer += renderActionForm(F("/erase"), F("get"), F("Erase WiFi config"), F(" class='D'"), showInfoUpdate ? String() : String(F("
")), F("
\n"));
- }
- if (showBack) {
- state.footer += renderActionForm(F("/"), F("get"), F("Back"), String(), (showInfoUpdate || showInfoErase) ? String() : String(F("
")));
- }
- state.footer += FPSTR(HTML_HELP);
-
- state.deviceSection = renderInfoSection(
-#ifdef ESP32
- F("ESP32"),
-#else
- F("ESP8266"),
-#endif
- state.deviceSection
- );
- state.wifiSection = renderInfoSection(F("WiFi"), state.wifiSection, F("
"));
- state.aboutSection = renderInfoSection(F("About"), state.aboutSection, F("
"));
-
- escapePercentsForTemplate(state.status);
- escapePercentsForTemplate(state.deviceSection);
- escapePercentsForTemplate(state.wifiSection);
- escapePercentsForTemplate(state.aboutSection);
- escapePercentsForTemplate(state.footer);
-}
-
-void buildWiFiPageState(WiFiManagerHandlers* handlers,
- WiFiPageState& state,
- bool includeScanResults,
- bool showBack,
- const String& ssidPlaceholder,
- const String& passwordPlaceholder,
- bool paramsInWifi) {
- reservePage(state.scanContent, includeScanResults ? 4096 : 64);
- reservePage(state.staticFields, 2048);
- reservePage(state.paramSection, 4096);
- reservePage(state.formActions, 128);
- reservePage(state.pageActions, 512);
- reservePage(state.status, 512);
-
- state.docTitle = F("Config ESP");
- state.ssidPlaceholder = ssidPlaceholder;
- state.passwordPlaceholder = passwordPlaceholder;
-
- if (includeScanResults) {
- state.scanContent += handlers->getScanItemOut();
- }
-
- state.staticFields += handlers->getStaticOut();
- if (paramsInWifi) {
- state.paramSection += renderSectionBreak(handlers->getParamOut());
- }
-
- state.formActions += renderSubmitButton(F("Save"));
- state.pageActions += renderCenteredButton(
- F("refresh-btn"),
- F("button"),
- F("return refreshScan()"),
- F("Refresh"),
- includeScanResults ? String() : String(F(" data-skip-initial-scan='true'"))
- );
- if (showBack) state.pageActions += renderActionForm(F("/"), F("get"), F("Back"), String(), F("
"));
-
- handlers->reportStatus(state.status);
-
- escapePercentsForTemplate(state.scanContent);
- escapePercentsForTemplate(state.ssidPlaceholder);
- escapePercentsForTemplate(state.passwordPlaceholder);
- escapePercentsForTemplate(state.staticFields);
- escapePercentsForTemplate(state.paramSection);
- escapePercentsForTemplate(state.formActions);
- escapePercentsForTemplate(state.pageActions);
- escapePercentsForTemplate(state.status);
-}
-
-void buildParamPageState(WiFiManagerHandlers* handlers,
- ParamPageState& state,
- bool showBack) {
- reservePage(state.fields, 4096);
- reservePage(state.formActions, 128);
- reservePage(state.pageActions, 256);
- reservePage(state.status, 512);
-
- state.docTitle = F("Setup");
- state.fields += handlers->getParamOut();
- state.formActions += renderSubmitButton(F("Save"));
-
- if (showBack) state.pageActions += renderActionForm(F("/"), F("get"), F("Back"), String(), F("
"));
-
- handlers->reportStatus(state.status);
-
- escapePercentsForTemplate(state.fields);
- escapePercentsForTemplate(state.formActions);
- escapePercentsForTemplate(state.pageActions);
- escapePercentsForTemplate(state.status);
-}
-
struct ShellRenderBundle {
String bootstrapJson;
String pageTitleStatic;
@@ -635,142 +100,10 @@ struct ShellRenderBundle {
: registry(WM_TEMPLATE_REGISTRY_CAPACITY), bootstrapDescriptor{}, pageTitleDescriptor{} {}
};
-struct MessageRenderBundle {
- MessagePageState state;
- PlaceholderRegistry registry;
- TemplateContext context;
- DynamicTemplateDescriptor docTitleDescriptor;
- DynamicTemplateDescriptor messageBodyDescriptor;
- DynamicTemplateDescriptor actionsDescriptor;
-
- MessageRenderBundle()
- : registry(WM_TEMPLATE_REGISTRY_CAPACITY),
- docTitleDescriptor{},
- messageBodyDescriptor{},
- actionsDescriptor{} {}
-};
-
-struct InfoRenderBundle {
- InfoPageState state;
- PlaceholderRegistry registry;
- TemplateContext context;
- DynamicTemplateDescriptor docTitleDescriptor;
- DynamicTemplateDescriptor statusDescriptor;
- DynamicTemplateDescriptor deviceDescriptor;
- DynamicTemplateDescriptor wifiDescriptor;
- DynamicTemplateDescriptor aboutDescriptor;
- DynamicTemplateDescriptor footerDescriptor;
-
- InfoRenderBundle()
- : registry(WM_TEMPLATE_REGISTRY_CAPACITY),
- docTitleDescriptor{},
- statusDescriptor{},
- deviceDescriptor{},
- wifiDescriptor{},
- aboutDescriptor{},
- footerDescriptor{} {}
-};
-
-struct WiFiRenderBundle {
- WiFiPageState state;
- PlaceholderRegistry registry;
- TemplateContext context;
- DynamicTemplateDescriptor docTitleDescriptor;
- DynamicTemplateDescriptor scanContentDescriptor;
- DynamicTemplateDescriptor ssidPlaceholderDescriptor;
- DynamicTemplateDescriptor passwordPlaceholderDescriptor;
- DynamicTemplateDescriptor staticFieldsDescriptor;
- DynamicTemplateDescriptor paramSectionDescriptor;
- DynamicTemplateDescriptor formActionsDescriptor;
- DynamicTemplateDescriptor pageActionsDescriptor;
- DynamicTemplateDescriptor statusDescriptor;
-
- WiFiRenderBundle()
- : registry(WM_TEMPLATE_REGISTRY_CAPACITY),
- docTitleDescriptor{},
- scanContentDescriptor{},
- ssidPlaceholderDescriptor{},
- passwordPlaceholderDescriptor{},
- staticFieldsDescriptor{},
- paramSectionDescriptor{},
- formActionsDescriptor{},
- pageActionsDescriptor{},
- statusDescriptor{} {}
-};
-
-struct ParamRenderBundle {
- ParamPageState state;
- PlaceholderRegistry registry;
- TemplateContext context;
- DynamicTemplateDescriptor docTitleDescriptor;
- DynamicTemplateDescriptor fieldsDescriptor;
- DynamicTemplateDescriptor formActionsDescriptor;
- DynamicTemplateDescriptor pageActionsDescriptor;
- DynamicTemplateDescriptor statusDescriptor;
-
- ParamRenderBundle()
- : registry(WM_TEMPLATE_REGISTRY_CAPACITY),
- docTitleDescriptor{},
- fieldsDescriptor{},
- formActionsDescriptor{},
- pageActionsDescriptor{},
- statusDescriptor{} {}
-};
-
-void sendMessageTemplateResponse(WiFiManagerServer* server,
- const std::shared_ptr& bundle,
- const String& title,
- const char* bodyClass,
- const String& messageBody,
- const String& actions = String()) {
- buildMessagePageState(bundle->state, title, bodyClass, messageBody, actions);
-
- registerSharedShellPlaceholders(server, bundle->registry);
- registerShellTemplate(bundle->registry,
- bundle->docTitleDescriptor,
- bundle->state.docTitle,
- bundle->state.bodyClass,
- WM_MESSAGE_CONTENT_TEMPLATE);
- configureDynamicStringDescriptor(bundle->messageBodyDescriptor, bundle->state.messageBody);
- configureDynamicStringDescriptor(bundle->actionsDescriptor, bundle->state.actions);
-
- bundle->registry.registerDynamicTemplate("%MESSAGE_BODY%", &bundle->messageBodyDescriptor);
- bundle->registry.registerDynamicTemplate("%MESSAGE_ACTIONS%", &bundle->actionsDescriptor);
-}
-
} // namespace
WiFiManagerHandlers::WiFiManagerHandlers(WiFiManager* wm) : _wm(wm) {}
-// Rendering Methods
-
-String WiFiManagerHandlers::getMenuOut(){
- return getMenuOut(nullptr);
-}
-
-String WiFiManagerHandlers::getMenuOut(String* outOpt){
- String local;
- String &out = outOpt ? *outOpt : local;
- out += renderActionForm(F("/wifi"), F("get"), F("Configure WiFi"), String(), String(), F("
\n"));
-
- // Show PARAM (Setup) when params are on their own page
- if(!_wm->_paramsInWifi && _wm->_paramsCount > 0){
- out += renderActionForm(F("/param"), F("get"), F("Setup"), String(), String(), F("
\n"));
- }
-
- out += renderActionForm(F("/info"), F("get"), F("Info"), String(), String(), F("
\n"));
-
- // When captive/config portal is active, offer Close (keeps portal running)
- if(_wm->configPortalActive){
- out += renderActionForm(F("/close"), F("get"), F("Close"), String(), String(), F("
\n"));
- }
-
- out += renderActionForm(F("/exit"), F("get"), F("Exit"), String(), String(), F("
\n"));
- out += F("
");
- out += renderActionForm(F("/update"), F("get"), F("Update"), String(), String(), F("
\n"));
- return out;
-}
-
void WiFiManagerHandlers::collectVisibleScanResults(std::vector& networks) {
networks.clear();
networks.reserve(_wm->_scanResultsCache.size());
@@ -816,375 +149,345 @@ void WiFiManagerHandlers::collectVisibleScanResults(std::vector networks;
- collectVisibleScanResults(networks);
- const int n = static_cast(networks.size());
- reservePage(page, 256 + (n > 0 ? static_cast(n) * 96 : 96));
-
- if (_wm->_scan.state == WiFiManager::WM_SCAN_RUNNING || _wm->_scan.state == WiFiManager::WM_SCAN_QUEUED) {
- page += renderScanMessage(F("Scanning for networks..."));
- return page;
- }
-
- if (!_wm->_scan.resultsValid || n == 0) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("No networks found"));
- #endif
- if (_wm->_scan.state == WiFiManager::WM_SCAN_TIMEOUT) {
- page += renderScanMessage(F("Scan timed out. Refresh to try again."));
- } else if (_wm->_scan.state == WiFiManager::WM_SCAN_FAILED) {
- page += renderScanMessage(F("Scan failed. Refresh to try again."));
- } else {
- page += renderScanMessage(F("No networks found. Refresh to scan again."));
+void WiFiManagerHandlers::appendPortalJsonStaticFields(String& json, bool& first) {
+ auto emitField = [&](const char* fieldId, const __FlashStringHelper* label, const String& val) {
+ if (!first) {
+ json += F(",");
}
- return page;
+ first = false;
+ json += F("{\"id\":\"");
+ json += fieldId;
+ json += F("\",\"name\":\"");
+ json += fieldId;
+ json += F("\",\"label\":\"");
+ json += label;
+ json += F("\",\"type\":\"text\",\"maxlength\":15,\"value\":\"");
+ jsonAppendEscaped(json, val);
+ json += F("\"}");
+ };
+
+ if ((_wm->_staShowStaticFields || _wm->_sta_static_ip) && _wm->_staShowStaticFields >= 0) {
+ emitField("ip", F("Static IP"), (_wm->_sta_static_ip ? _wm->_sta_static_ip.toString() : ""));
+ emitField("gw", F("Static gateway"), (_wm->_sta_static_gw ? _wm->_sta_static_gw.toString() : ""));
+ emitField("sn", F("Subnet"), (_wm->_sta_static_sn ? _wm->_sta_static_sn.toString() : ""));
}
+ if ((_wm->_staShowDns || _wm->_sta_static_dns) && _wm->_staShowDns >= 0) {
+ emitField("dns", F("Static DNS"), (_wm->_sta_static_dns ? _wm->_sta_static_dns.toString() : ""));
+ }
+}
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, n,F("networks found"));
- #endif
-
- String hiddenClass = _wm->_scanDispOptions ? "" : "h";
- String visibleClass = _wm->_scanDispOptions ? "h" : "";
-
+void WiFiManagerHandlers::appendPortalJsonCustomParams(String& json, bool& first) {
+ const int n = _wm->getParametersCount();
+ if (n <= 0) {
+ return;
+ }
+ WiFiManagerParameter** params = _wm->getParameters();
for (int i = 0; i < n; i++) {
- const auto* network = networks[static_cast(i)];
-
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem,F("AP: "),(String)network->rssi + " " + network->ssid);
- #endif
-
- int rssiperc = _wm->getRSSIasQuality(network->rssi);
- String ssid_encoded = _wm->htmlEntities(network->ssid);
- String ssid_display = _wm->htmlEntities(network->ssid, true);
- int quality = int(round(map(rssiperc,0,100,1,4)));
- String lockIcon = (network->encType != WM_WIFIOPEN) ? "l" : "";
- String qualityPercent = String(rssiperc) + "%";
-
- page += renderScanRow(
- ssid_encoded,
- ssid_display,
- qualityPercent,
- String(quality),
- lockIcon,
- hiddenClass,
- visibleClass,
- qualityPercent
- );
-
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("Added network item"));
- #endif
- delay(0);
- }
-
- page += F("
");
- return page;
-}
-
-String WiFiManagerHandlers::getIpForm(String id, String title, String value){
- return renderFieldTemplate(WM_FIELD_LABEL_BEFORE_TEMPLATE, id, title, F("15"), value);
-}
-
-String WiFiManagerHandlers::getStaticOut(){
- String page;
- reservePage(page, 384);
- String fields;
- reservePage(fields, 384);
- if ((_wm->_staShowStaticFields || _wm->_sta_static_ip) && _wm->_staShowStaticFields>=0) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem,F("_staShowStaticFields"));
- #endif
- fields += getIpForm(FPSTR(S_ip),F("Static IP"),(_wm->_sta_static_ip ? _wm->_sta_static_ip.toString() : ""));
- fields += getIpForm(FPSTR(S_gw),F("Static gateway"),(_wm->_sta_static_gw ? _wm->_sta_static_gw.toString() : ""));
- fields += getIpForm(FPSTR(S_sn),F("Subnet"),(_wm->_sta_static_sn ? _wm->_sta_static_sn.toString() : ""));
- }
-
- if((_wm->_staShowDns || _wm->_sta_static_dns) && _wm->_staShowDns>=0){
- fields += getIpForm(FPSTR(S_dns),F("Static DNS"),(_wm->_sta_static_dns ? _wm->_sta_static_dns.toString() : ""));
- }
-
- if(fields != ""){
- page += renderSectionBreak(fields);
- page += F("
");
- }
-
- return page;
-}
-
-String WiFiManagerHandlers::getParamOut(){
- String page;
- reservePage(page, 256 + (_wm->_paramsCount > 0 ? static_cast(_wm->_paramsCount) * 128 : 64));
-
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem,F("getParamOut"),_wm->_paramsCount);
- #endif
-
- if(_wm->_paramsCount > 0){
-
- char valLength[5];
-
- for (int i = 0; i < _wm->_paramsCount; i++) {
- if (_wm->_params[i] == NULL || _wm->_params[i]->getValueLength() > 99999) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Error, kWiFiMgrLogSubsystem,F("[ERROR] WiFiManagerParameter is out of scope"));
- #endif
- return "";
- }
+ WiFiManagerParameter* p = params[i];
+ if (p == NULL || p->getValueLength() > 99999) {
+ continue;
}
-
- // add the extra parameters to the form
- for (int i = 0; i < _wm->_paramsCount; i++) {
- String pitem;
-
- if (_wm->_params[i]->getID() != NULL) {
- String paramId = _wm->_params[i]->getID();
- String paramLabel = _wm->htmlEntities(_wm->_params[i]->getLabel(), true);
- String paramValue = _wm->htmlEntities(_wm->_params[i]->getValue(), true);
- String customHTML = _wm->_params[i]->getCustomHTML();
- snprintf(valLength, 5, "%d", _wm->_params[i]->getValueLength());
- String extraAttrs = customHTML.length() > 0 ? String(F(" ")) + customHTML : String();
-
- switch (_wm->_params[i]->getLabelPlacement()) {
- case WFM_LABEL_BEFORE:
- pitem = renderFieldTemplate(WM_FIELD_LABEL_BEFORE_TEMPLATE, paramId, paramLabel, String(valLength), paramValue, extraAttrs);
- break;
- case WFM_LABEL_AFTER:
- pitem = renderFieldTemplate(WM_FIELD_LABEL_AFTER_TEMPLATE, paramId, paramLabel, String(valLength), paramValue, extraAttrs);
- break;
- default:
- pitem = renderFieldTemplate(WM_FIELD_INPUT_ONLY_TEMPLATE, paramId, paramLabel, String(valLength), paramValue, extraAttrs);
- break;
- }
- } else {
- // Custom HTML only (no ID)
- pitem = _wm->_params[i]->getCustomHTML();
- }
-
- page += pitem;
+ if (!first) {
+ json += F(",");
}
- }
-
- return page;
-}
-
-String WiFiManagerHandlers::getInfoData(String id){
- String p;
- if(id==F("uptime")){
- p = renderInfoRow(F("Uptime"), String(millis() / 1000 / 60) + F(" mins ") + String((millis() / 1000) % 60) + F(" secs"));
- }
- else if(id==F("chipid")){
- #ifdef ESP8266
- p = renderInfoRow(F("Chip ID"), String(ESP.getChipId(),HEX));
- #elif defined(ESP32)
- p = renderInfoRow(F("Chip ID"), String((uint32_t)ESP.getEfuseMac(),HEX));
- #endif
- }
- #ifdef ESP32
- else if(id==F("chiprev")){
- String rev = (String)ESP.getChipRevision();
- #ifdef _SOC_EFUSE_REG_H_
- String revb = (String)(REG_READ(EFUSE_BLK0_RDATA3_REG) >> (EFUSE_RD_CHIP_VER_RESERVE_S)&&EFUSE_RD_CHIP_VER_RESERVE_V);
- p = renderInfoRow(F("Chip rev"), rev + F("
") + revb);
- #else
- p = renderInfoRow(F("Chip rev"), rev);
- #endif
- }
- #endif
- #ifdef ESP8266
- else if(id==F("fchipid")){
- p = renderInfoRow(F("Flash chip ID"), String(ESP.getFlashChipId()));
- }
- #endif
- else if(id==F("idesize")){
- p = renderInfoRow(F("Flash size"), String(ESP.getFlashChipSize()) + F(" bytes"));
- }
- else if(id==F("flashsize")){
- #ifdef ESP8266
- p = renderInfoRow(F("Real flash size"), String(ESP.getFlashChipRealSize()) + F(" bytes"));
- #elif defined ESP32
- p = renderInfoRow(F("PSRAM Size"), String(ESP.getPsramSize()) + F(" bytes"));
- #endif
- }
- else if(id==F("corever")){
- #ifdef ESP8266
- p = renderInfoRow(F("Core version"), String(ESP.getCoreVersion()));
- #endif
- }
- #ifdef ESP8266
- else if(id==F("bootver")){
- p = renderInfoRow(F("Boot version"), String(system_get_boot_version()));
- }
- #endif
- else if(id==F("cpufreq")){
- p = renderInfoRow(F("CPU frequency"), String(ESP.getCpuFreqMHz()) + F("MHz"));
- }
- else if(id==F("freeheap")){
- p = renderInfoRow(F("Memory - Free heap"), String(ESP.getFreeHeap()) + F(" bytes available"));
- }
- else if(id==F("memsketch")){
- p = renderInfoRow(F("Memory - Sketch size"), String(F("Used / Total bytes
")) + String(ESP.getSketchSize()) + F(" / ") + String(ESP.getSketchSize()+ESP.getFreeSketchSpace()));
- }
- else if(id==F("memsmeter")){
- p = F("
");
- }
- else if(id==F("lastreset")){
- #ifdef ESP8266
- p = renderInfoRow(F("Last reset reason"), String(ESP.getResetReason()));
- #elif defined(ESP32) && defined(_ROM_RTC_H_)
- String reasons[2];
- for(int i=0;i<2;i++){
- int reason = rtc_get_reset_reason(i);
- switch (reason)
- {
- case 1 : reasons[i] = F("Vbat power on reset");break;
- case 3 : reasons[i] = F("Software reset digital core");break;
- case 4 : reasons[i] = F("Legacy watch dog reset digital core");break;
- case 5 : reasons[i] = F("Deep Sleep reset digital core");break;
- case 6 : reasons[i] = F("Reset by SLC module, reset digital core");break;
- case 7 : reasons[i] = F("Timer Group0 Watch dog reset digital core");break;
- case 8 : reasons[i] = F("Timer Group1 Watch dog reset digital core");break;
- case 9 : reasons[i] = F("RTC Watch dog Reset digital core");break;
- case 10 : reasons[i] = F("Instrusion tested to reset CPU");break;
- case 11 : reasons[i] = F("Time Group reset CPU");break;
- case 12 : reasons[i] = F("Software reset CPU");break;
- case 13 : reasons[i] = F("RTC Watch dog Reset CPU");break;
- case 14 : reasons[i] = F("for APP CPU, reseted by PRO CPU");break;
- case 15 : reasons[i] = F("Reset when the vdd voltage is not stable");break;
- case 16 : reasons[i] = F("RTC Watch dog reset digital core and rtc module");break;
- default : reasons[i] = F("NO_MEAN");
+ first = false;
+ if (p->getID() != nullptr) {
+ String pid = String(p->getID());
+ json += F("{\"name\":\"");
+ jsonAppendEscaped(json, pid);
+ json += F("\",\"id\":\"");
+ jsonAppendEscaped(json, pid);
+ json += F("\",\"label\":\"");
+ jsonAppendEscaped(json, String(p->getLabel()));
+ json += F("\",\"type\":\"text\",\"maxlength\":");
+ json += String(p->getValueLength());
+ json += F(",\"value\":\"");
+ jsonAppendEscaped(json, String(p->getValue()));
+ json += F("\",\"labelPlacement\":");
+ json += String(p->getLabelPlacement());
+ {
+ String c = p->getCustomHTML();
+ if (c.length() > 0) {
+ json += F(",\"customAttrs\":\"");
+ jsonAppendEscaped(json, c);
+ json += F("\"");
}
}
- p = renderInfoRow(F("Last reset reason"), String(F("CPU0: ")) + reasons[0] + F("
CPU1: ") + reasons[1]);
- #endif
- }
- else if(id==F("apip")){
- p = renderInfoRow(F("Access point IP"), WiFi.softAPIP().toString());
- }
- else if(id==F("apmac")){
- p = renderInfoRow(F("Access point MAC"), WiFi.softAPmacAddress());
- }
- #ifdef ESP32
- else if(id==F("aphost")){
- p = renderInfoRow(F("Access point hostname"), String(WiFi.softAPgetHostname()));
- }
- #endif
- #ifndef WM_NOSOFTAPSSID
- #ifdef ESP8266
- else if(id==F("apssid")){
- p = renderInfoRow(F("Access point SSID"), _wm->htmlEntities(WiFi.softAPSSID(), true));
- }
- #endif
- #endif
- else if(id==F("apbssid")){
- p = renderInfoRow(F("BSSID"), WiFi.BSSIDstr());
- }
- else if(id==F("stassid")){
- p = renderInfoRow(F("Station SSID"), _wm->htmlEntities((String)_wm->WiFi_SSID(), true));
- }
- else if(id==F("staip")){
- p = renderInfoRow(F("Station IP"), WiFi.localIP().toString());
- }
- else if(id==F("stagw")){
- p = renderInfoRow(F("Station gateway"), WiFi.gatewayIP().toString());
- }
- else if(id==F("stasub")){
- p = renderInfoRow(F("Station subnet"), WiFi.subnetMask().toString());
- }
- else if(id==F("dnss")){
- p = renderInfoRow(F("DNS Server"), WiFi.dnsIP().toString());
- }
- else if(id==F("host")){
- #ifdef ESP32
- p = renderInfoRow(F("Hostname"), String(WiFi.getHostname()));
- #else
- p = renderInfoRow(F("Hostname"), String(WiFi.hostname()));
- #endif
- }
- else if(id==F("stamac")){
- p = renderInfoRow(F("Station MAC"), WiFi.macAddress());
- }
- else if(id==F("conx")){
- p = renderInfoRow(F("Connected"), WiFi.isConnected() ? F("Yes") : F("No"));
- }
- #ifdef ESP8266
- else if(id==F("autoconx")){
- p = renderInfoRow(F("Autoconnect"), WiFi.getAutoConnect() ? F("Enabled") : F("Disabled"));
- }
- #endif
- #if defined(ESP32) && !defined(WM_NOTEMP)
- else if(id==F("temp")){
- p = renderInfoRow(F("Temperature"), String(temperatureRead()) + F(" C° / ") + String((temperatureRead()+32)*1.8f) + F(" F°"));
- }
- #endif
- else if(id==F("aboutver")){
- p = renderInfoRow(F("WiFiManager"), FPSTR(WM_VERSION_STR));
- }
- else if(id==F("aboutarduinover")){
- #ifdef VER_ARDUINO_STR
- p = renderInfoRow(F("Arduino"), String(VER_ARDUINO_STR));
- #endif
- }
- else if(id==F("aboutsdkver")){
- #ifdef ESP32
- p = renderInfoRow(F("SDK version"), String(esp_get_idf_version()));
- #else
- p = renderInfoRow(F("SDK version"), String(system_get_sdk_version()));
- #endif
- }
- else if(id==F("aboutdate")){
- p = renderInfoRow(F("Build date"), String(__DATE__ " " __TIME__));
- }
- return p;
-}
-
-void WiFiManagerHandlers::reportStatus(String &page){
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem,F("[WIFI] reportStatus prev:"),_wm->getWLStatusString(_wm->_lastconxresult));
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem,F("[WIFI] reportStatus current:"),_wm->getWLStatusString(WiFi.status()));
- String str;
- if (_wm->WiFi_SSID() != ""){
- if (WiFi.status()==WL_CONNECTED){
- str = renderStatusMessage(
- F("Connected"),
- String(F(" to ")) + _wm->htmlEntities(_wm->WiFi_SSID(), true) + F("
with IP ") + WiFi.localIP().toString() + F(""),
- F(" S")
- );
- }
- else {
- String ssidEncoded = _wm->htmlEntities(_wm->WiFi_SSID(), true);
- String statusClass = " D";
- String statusMsg = "";
-
- if(_wm->_lastconxresult == _wm->WL_STATION_WRONG_PASSWORD){
- statusMsg = FPSTR(HTML_STATUS_OFFPW);
- }
- else if(_wm->_lastconxresult == WL_NO_SSID_AVAIL){
- statusMsg = FPSTR(HTML_STATUS_OFFNOAP);
- }
- else if(_wm->_lastconxresult == WL_CONNECT_FAILED || _wm->_lastconxresult == WL_CONNECTION_LOST){
- statusMsg = FPSTR(HTML_STATUS_OFFFAIL);
- }
- else{
- statusClass = "";
- }
-
- str = renderStatusMessage(F("Not connected"), String(F(" to ")) + ssidEncoded + statusMsg, statusClass);
+ json += F("}");
+ } else {
+ json += F("{\"html\":\"");
+ jsonAppendEscaped(json, String(p->getCustomHTML()));
+ json += F("}");
}
}
- else {
- str = FPSTR(HTML_STATUS_NONE);
- }
- page += str;
}
+void WiFiManagerHandlers::appendJsonKvItem(String& json, bool& first, const char* key, const String& label,
+ const String& value) {
+ if (!first) {
+ json += F(",");
+ }
+ first = false;
+ json += F("{\"key\":\"");
+ json += key;
+ json += F("\",\"label\":\"");
+ jsonAppendEscaped(json, label);
+ json += F("\",\"value\":\"");
+ jsonAppendEscaped(json, value);
+ json += F("}");
+}
+
+void WiFiManagerHandlers::appendInfoSectionFromIds(String& json, const char* const* ids, size_t count, bool& first) {
+ for (size_t i = 0; i < count; i++) {
+ appendOneInfoItemForId(json, first, ids[i]);
+ }
+}
+
+void WiFiManagerHandlers::appendOneInfoItemForId(String& json, bool& first, const char* id) {
+ if (strcmp(id, "uptime") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Uptime")),
+ String(millis() / 1000 / 60) + F(" mins ") + String((millis() / 1000) % 60) + F(" secs"));
+ return;
+ }
+ if (strcmp(id, "chipid") == 0) {
+#ifdef ESP8266
+ appendJsonKvItem(json, first, id, String(F("Chip ID")), String(ESP.getChipId(), HEX));
+#elif defined(ESP32)
+ appendJsonKvItem(json, first, id, String(F("Chip ID")), String((uint32_t)ESP.getEfuseMac(), HEX));
+#endif
+ return;
+ }
+#ifdef ESP32
+ if (strcmp(id, "chiprev") == 0) {
+ String rev = (String)ESP.getChipRevision();
+#ifdef _SOC_EFUSE_REG_H_
+ String revb =
+ (String)(REG_READ(EFUSE_BLK0_RDATA3_REG) >> (EFUSE_RD_CHIP_VER_RESERVE_S)&EFUSE_RD_CHIP_VER_RESERVE_V);
+ appendJsonKvItem(json, first, id, String(F("Chip rev")), rev + F(" / ") + revb);
+#else
+ appendJsonKvItem(json, first, id, String(F("Chip rev")), rev);
+#endif
+ return;
+ }
+#endif
+#ifdef ESP8266
+ if (strcmp(id, "fchipid") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Flash chip ID")), String(ESP.getFlashChipId()));
+ return;
+ }
+#endif
+ if (strcmp(id, "idesize") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Flash size")), String(ESP.getFlashChipSize()) + F(" bytes"));
+ return;
+ }
+ if (strcmp(id, "flashsize") == 0) {
+#ifdef ESP8266
+ appendJsonKvItem(json, first, id, String(F("Real flash size")), String(ESP.getFlashChipRealSize()) + F(" bytes"));
+#elif defined(ESP32)
+ appendJsonKvItem(json, first, id, String(F("PSRAM Size")), String(ESP.getPsramSize()) + F(" bytes"));
+#endif
+ return;
+ }
+ if (strcmp(id, "corever") == 0) {
+#ifdef ESP8266
+ appendJsonKvItem(json, first, id, String(F("Core version")), String(ESP.getCoreVersion()));
+#endif
+ return;
+ }
+#ifdef ESP8266
+ if (strcmp(id, "bootver") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Boot version")), String(system_get_boot_version()));
+ return;
+ }
+#endif
+ if (strcmp(id, "cpufreq") == 0) {
+ appendJsonKvItem(json, first, id, String(F("CPU frequency")), String(ESP.getCpuFreqMHz()) + F("MHz"));
+ return;
+ }
+ if (strcmp(id, "freeheap") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Memory - Free heap")), String(ESP.getFreeHeap()) + F(" bytes available"));
+ return;
+ }
+ if (strcmp(id, "memsketch") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Memory - Sketch size")),
+ String(ESP.getSketchSize()) + F(" / ") + String(ESP.getSketchSize() + ESP.getFreeSketchSpace()) +
+ F(" bytes"));
+ return;
+ }
+ if (strcmp(id, "memsmeter") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Sketch usage (used / max)")),
+ String(ESP.getSketchSize()) + F(" / ") + String(ESP.getSketchSize() + ESP.getFreeSketchSpace()));
+ return;
+ }
+ if (strcmp(id, "lastreset") == 0) {
+#ifdef ESP8266
+ appendJsonKvItem(json, first, id, String(F("Last reset reason")), String(ESP.getResetReason()));
+#elif defined(ESP32) && defined(_ROM_RTC_H_)
+ String reasons[2];
+ for (int i = 0; i < 2; i++) {
+ int reason = rtc_get_reset_reason(i);
+ switch (reason) {
+ case 1:
+ reasons[i] = F("Vbat power on reset");
+ break;
+ case 3:
+ reasons[i] = F("Software reset digital core");
+ break;
+ case 4:
+ reasons[i] = F("Legacy watch dog reset digital core");
+ break;
+ case 5:
+ reasons[i] = F("Deep Sleep reset digital core");
+ break;
+ case 6:
+ reasons[i] = F("Reset by SLC module, reset digital core");
+ break;
+ case 7:
+ reasons[i] = F("Timer Group0 Watch dog reset digital core");
+ break;
+ case 8:
+ reasons[i] = F("Timer Group1 Watch dog reset digital core");
+ break;
+ case 9:
+ reasons[i] = F("RTC Watch dog Reset digital core");
+ break;
+ case 10:
+ reasons[i] = F("Instrusion tested to reset CPU");
+ break;
+ case 11:
+ reasons[i] = F("Time Group reset CPU");
+ break;
+ case 12:
+ reasons[i] = F("Software reset CPU");
+ break;
+ case 13:
+ reasons[i] = F("RTC Watch dog Reset CPU");
+ break;
+ case 14:
+ reasons[i] = F("for APP CPU, reseted by PRO CPU");
+ break;
+ case 15:
+ reasons[i] = F("Reset when the vdd voltage is not stable");
+ break;
+ case 16:
+ reasons[i] = F("RTC Watch dog reset digital core and rtc module");
+ break;
+ default:
+ reasons[i] = F("NO_MEAN");
+ }
+ }
+ appendJsonKvItem(json, first, id, String(F("Last reset reason")),
+ String(F("CPU0: ")) + reasons[0] + F(" — CPU1: ") + reasons[1]);
+#endif
+ return;
+ }
+ if (strcmp(id, "apip") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Access point IP")), WiFi.softAPIP().toString());
+ return;
+ }
+ if (strcmp(id, "apmac") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Access point MAC")), WiFi.softAPmacAddress());
+ return;
+ }
+#ifdef ESP32
+ if (strcmp(id, "aphost") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Access point hostname")), String(WiFi.softAPgetHostname()));
+ return;
+ }
+#endif
+#ifndef WM_NOSOFTAPSSID
+#ifdef ESP8266
+ if (strcmp(id, "apssid") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Access point SSID")), WiFi.softAPSSID());
+ return;
+ }
+#endif
+#endif
+ if (strcmp(id, "apbssid") == 0) {
+ appendJsonKvItem(json, first, id, String(F("BSSID")), WiFi.BSSIDstr());
+ return;
+ }
+ if (strcmp(id, "stassid") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Station SSID")), (String)_wm->WiFi_SSID());
+ return;
+ }
+ if (strcmp(id, "staip") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Station IP")), WiFi.localIP().toString());
+ return;
+ }
+ if (strcmp(id, "stagw") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Station gateway")), WiFi.gatewayIP().toString());
+ return;
+ }
+ if (strcmp(id, "stasub") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Station subnet")), WiFi.subnetMask().toString());
+ return;
+ }
+ if (strcmp(id, "dnss") == 0) {
+ appendJsonKvItem(json, first, id, String(F("DNS Server")), WiFi.dnsIP().toString());
+ return;
+ }
+ if (strcmp(id, "host") == 0) {
+#ifdef ESP32
+ appendJsonKvItem(json, first, id, String(F("Hostname")), String(WiFi.getHostname()));
+#else
+ appendJsonKvItem(json, first, id, String(F("Hostname")), String(WiFi.hostname()));
+#endif
+ return;
+ }
+ if (strcmp(id, "stamac") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Station MAC")), WiFi.macAddress());
+ return;
+ }
+ if (strcmp(id, "conx") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Connected")), WiFi.isConnected() ? String(F("Yes")) : String(F("No")));
+ return;
+ }
+#ifdef ESP8266
+ if (strcmp(id, "autoconx") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Autoconnect")),
+ WiFi.getAutoConnect() ? String(F("Enabled")) : String(F("Disabled")));
+ return;
+ }
+#endif
+#if defined(ESP32) && !defined(WM_NOTEMP)
+ if (strcmp(id, "temp") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Temperature")),
+ String(temperatureRead()) + F(" C / ") + String((temperatureRead() + 32) * 1.8f) + F(" F"));
+ return;
+ }
+#endif
+ if (strcmp(id, "aboutver") == 0) {
+ appendJsonKvItem(json, first, id, String(F("WiFiManager")), String(FPSTR(WM_VERSION_STR)));
+ return;
+ }
+ if (strcmp(id, "aboutarduinover") == 0) {
+#ifdef VER_ARDUINO_STR
+ appendJsonKvItem(json, first, id, String(F("Arduino")), String(VER_ARDUINO_STR));
+#endif
+ return;
+ }
+ if (strcmp(id, "aboutsdkver") == 0) {
+#ifdef ESP32
+ appendJsonKvItem(json, first, id, String(F("SDK version")), String(esp_get_idf_version()));
+#else
+ appendJsonKvItem(json, first, id, String(F("SDK version")), String(system_get_sdk_version()));
+#endif
+ return;
+ }
+ if (strcmp(id, "aboutdate") == 0) {
+ appendJsonKvItem(json, first, id, String(F("Build date")), String(__DATE__ " " __TIME__));
+ return;
+ }
+}
+
+
// Captive Portal
+bool WiFiManagerHandlers::shouldRedirectCaptiveForHost(const String& requestHost, const String& serverLocWithPort) {
+ return serverLocWithPort.length() > 0 && requestHost != serverLocWithPort;
+}
+
boolean WiFiManagerHandlers::captivePortal(AsyncWebServerRequest *request) {
if(!_wm->_enableCaptivePortal || !_wm->configPortalActive) return false;
@@ -1205,7 +508,7 @@ boolean WiFiManagerHandlers::captivePortal(AsyncWebServerRequest *request) {
}
if(_wm->_httpPort != 80) serverLoc += ":" + (String)_wm->_httpPort;
- bool doredirect = serverLoc != request->host();
+ bool doredirect = shouldRedirectCaptiveForHost(request->host(), serverLoc);
if (doredirect) {
#ifndef WM_NO_LOG
@@ -1254,7 +557,6 @@ void WiFiManagerHandlers::handleRoot(AsyncWebServerRequest *request) {
_wm->_serverManager->registerDefaultStyles(bundle->registry);
_wm->_serverManager->registerDefaultScripts(bundle->registry);
_wm->_serverManager->registerDefaultPageTitle(bundle->registry);
- _wm->_serverManager->applyTemplateSetupCallback(bundle->registry);
} else {
registerSharedShellPlaceholders(nullptr, bundle->registry);
bundle->pageTitleStatic = _wm->_title;
@@ -1269,30 +571,6 @@ void WiFiManagerHandlers::handleRoot(AsyncWebServerRequest *request) {
request->send(beginTemplateResponse(request, bundle, WM_ROOT_SHELL_TEMPLATE));
}
-void WiFiManagerHandlers::handleWifi(AsyncWebServerRequest *request, boolean scan) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP Wifi"));
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("handleWifi called, scan="), scan ? "true" : "false");
- #endif
- (void)scan;
- if (captivePortal(request)) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("Captive portal redirect"));
- #endif
- return;
- }
- handleRequest(request);
- request->redirect(String(F("/#/wifi")));
-}
-
-void WiFiManagerHandlers::handleParam(AsyncWebServerRequest *request){
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP Param"));
- #endif
- handleRequest(request);
- request->redirect(String(F("/#/setup")));
-}
-
void WiFiManagerHandlers::applyWifiAndParamsFromRequest(AsyncWebServerRequest *request) {
WiFiManager::WiFiManagerRequestArgs requestArgs(request);
@@ -1365,79 +643,6 @@ void WiFiManagerHandlers::applyWifiAndParamsFromRequest(AsyncWebServerRequest *r
}
}
-void WiFiManagerHandlers::handleWifiSave(AsyncWebServerRequest *request) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP WiFi save "));
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("Method:"), request->method() == HTTP_GET ? F("GET") : F("POST"));
- #endif
- handleRequest(request);
-
- applyWifiAndParamsFromRequest(request);
-
- auto bundle = std::make_shared();
- String messageBody;
- String actions;
- reservePage(messageBody, 1024);
-
- if(_wm->_ssid == ""){
- messageBody += FPSTR(HTML_PARAMSAVED);
- }
- else {
- messageBody += FPSTR(HTML_SAVED);
- }
-
- if(_wm->_showBack) actions += renderActionForm(F("/"), F("get"), F("Back"), String(), F("
"));
-
- sendMessageTemplateResponse(_wm->_serverManager.get(),
- bundle,
- _wm->_ssid == "" ? F("Settings saved") : F("Credentials saved"),
- C_wifi,
- messageBody,
- actions);
-
- AsyncWebServerResponse *response = beginTemplateResponse(request, bundle, WM_PAGE_SHELL_TEMPLATE);
- response->addHeader(FPSTR(HTTP_HEAD_CORS), FPSTR(HTTP_HEAD_CORS_ALLOW_ALL));
- request->send(response);
-
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("Sent wifi save page"));
- #endif
-
- _wm->connect = true;
-}
-
-void WiFiManagerHandlers::handleParamSave(AsyncWebServerRequest *request) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP Param save "));
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("Method:"), request->method() == HTTP_GET ? F("GET") : F("POST"));
- #endif
- handleRequest(request);
-
- WiFiManager::WiFiManagerRequestArgs requestArgs(request);
-
- doParamSave(requestArgs);
-
- auto bundle = std::make_shared();
- String messageBody;
- String actions;
- reservePage(messageBody, 1024);
- messageBody += FPSTR(HTML_PARAMSAVED);
- if(_wm->_showBack) actions += renderActionForm(F("/"), F("get"), F("Back"), String(), F("
"));
-
- sendMessageTemplateResponse(_wm->_serverManager.get(),
- bundle,
- F("Setup saved"),
- C_param,
- messageBody,
- actions);
-
- request->send(beginTemplateResponse(request, bundle, WM_PAGE_SHELL_TEMPLATE));
-
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("Sent param save page"));
- #endif
-}
-
void WiFiManagerHandlers::doParamSave(WiFiManager::WiFiManagerRequestArgs requestArgs){
if ( _wm->_presaveparamscallback != NULL) {
_wm->_presaveparamscallback();
@@ -1492,107 +697,6 @@ void WiFiManagerHandlers::doParamSave(WiFiManager::WiFiManagerRequestArgs reques
}
-void WiFiManagerHandlers::handleInfo(AsyncWebServerRequest *request) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP Info"));
- #endif
- handleRequest(request);
- request->redirect(String(F("/#/info")));
-}
-
-void WiFiManagerHandlers::handleExit(AsyncWebServerRequest *request) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP Exit"));
- #endif
- handleRequest(request);
- auto bundle = std::make_shared();
- sendMessageTemplateResponse(_wm->_serverManager.get(),
- bundle,
- F("Exit"),
- C_exit,
- String(F("Exiting")));
-
- AsyncWebServerResponse *response = beginTemplateResponse(request, bundle, WM_PAGE_SHELL_TEMPLATE);
- response->addHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate"));
- request->send(response);
-
- _wm->_abortScheduled = true;
- _wm->_abortTime = millis() + _wm->EXIT_DELAY_MS;
-}
-
-void WiFiManagerHandlers::handleReset(AsyncWebServerRequest *request) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP Reset"));
- #endif
- handleRequest(request);
- auto bundle = std::make_shared();
- sendMessageTemplateResponse(_wm->_serverManager.get(),
- bundle,
- F("Reset"),
- C_restart,
- String(F("Module will reset in a few seconds.")));
-
- request->send(beginTemplateResponse(request, bundle, WM_PAGE_SHELL_TEMPLATE));
-
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("RESETTING ESP"));
- #endif
- _wm->_rebootScheduled = true;
- _wm->_rebootTime = millis() + _wm->REBOOT_DELAY_MS;
-}
-
-void WiFiManagerHandlers::handleErase(AsyncWebServerRequest *request, boolean opt) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("<- HTTP Erase"));
- #endif
- handleRequest(request);
- bool ret = _wm->erase(opt);
- auto bundle = std::make_shared();
- String messageBody;
- reservePage(messageBody, 256);
-
- if(ret) messageBody += F("Module will reset in a few seconds.");
- else {
- messageBody += F("An error occured");
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Error, kWiFiMgrLogSubsystem, F("[ERROR] WiFi EraseConfig failed"));
- #endif
- }
-
- sendMessageTemplateResponse(_wm->_serverManager.get(),
- bundle,
- F("Erase"),
- C_erase,
- messageBody);
-
- request->send(beginTemplateResponse(request, bundle, WM_PAGE_SHELL_TEMPLATE));
-
- if(ret){
- _wm->_rebootScheduled = true;
- _wm->_rebootTime = millis() + _wm->ERASE_REBOOT_DELAY_MS;
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("RESETTING ESP"));
- #endif
- }
-}
-
-void WiFiManagerHandlers::handleClose(AsyncWebServerRequest *request){
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("Disabling Captive Portal"));
- stopCaptivePortal();
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP close"));
- #endif
- handleRequest(request);
- auto bundle = std::make_shared();
- sendMessageTemplateResponse(_wm->_serverManager.get(),
- bundle,
- F("Close"),
- C_close,
- String(F("You can close the page, portal will continue to run")));
-
- request->send(beginTemplateResponse(request, bundle, WM_PAGE_SHELL_TEMPLATE));
-}
-
void WiFiManagerHandlers::handleNotFound(AsyncWebServerRequest *request) {
if (captivePortal(request)) return;
handleRequest(request);
@@ -1604,49 +708,6 @@ void WiFiManagerHandlers::handleNotFound(AsyncWebServerRequest *request) {
request->send(response);
}
-void WiFiManagerHandlers::handleWiFiScanStatus(AsyncWebServerRequest *request){
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP WiFi scan status"));
- #endif
- handleRequest(request);
-
- std::vector networks;
- collectVisibleScanResults(networks);
-
- String json = "{";
- reservePage(json, 160 + (networks.size() > 0 ? networks.size() * 96 : 64));
- json += "\"state\":\"";
- switch (_wm->_scan.state) {
- case WiFiManager::WM_SCAN_IDLE: json += "idle"; break;
- case WiFiManager::WM_SCAN_QUEUED: json += "queued"; break;
- case WiFiManager::WM_SCAN_RUNNING: json += "running"; break;
- case WiFiManager::WM_SCAN_COMPLETE: json += "complete"; break;
- case WiFiManager::WM_SCAN_FAILED: json += "failed"; break;
- case WiFiManager::WM_SCAN_TIMEOUT: json += "timeout"; break;
- }
- json += "\",\"scanning\":";
- json += (_wm->_scan.state == WiFiManager::WM_SCAN_RUNNING || _wm->_scan.state == WiFiManager::WM_SCAN_QUEUED) ? "true" : "false";
- json += ",\"results_valid\":";
- json += _wm->_scan.resultsValid ? "true" : "false";
- json += ",\"count\":";
- json += String(networks.size());
- json += ",\"lastscan\":";
- json += String(_wm->_lastscan);
- json += ",\"error\":\"";
- if (_wm->_scan.state == WiFiManager::WM_SCAN_TIMEOUT) {
- json += "timeout";
- } else if (_wm->_scan.state == WiFiManager::WM_SCAN_FAILED) {
- json += "failed";
- }
- json += "\",\"networks\":[";
- appendVisibleScanResultsJson(json, networks);
- json += "]}";
-
- AsyncWebServerResponse *response = request->beginResponse(200, "application/json", json);
- response->addHeader(F("Cache-Control"), F("no-cache"));
- request->send(response);
-}
-
void WiFiManagerHandlers::appendVisibleScanResultsJson(String& json, const std::vector& networks) {
for (size_t i = 0; i < networks.size(); i++) {
const auto* network = networks[i];
@@ -1655,7 +716,7 @@ void WiFiManagerHandlers::appendVisibleScanResultsJson(String& json, const std::
}
json += "{";
json += "\"ssid\":\"";
- json += _wm->htmlEntities(network->ssid, true);
+ jsonAppendEscaped(json, network->ssid);
json += "\",\"rssi\":";
json += String(network->rssi);
json += ",\"quality\":";
@@ -1668,28 +729,6 @@ void WiFiManagerHandlers::appendVisibleScanResultsJson(String& json, const std::
}
}
-void WiFiManagerHandlers::handleWiFiScanRequest(AsyncWebServerRequest *request){
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- HTTP WiFi scan request"));
- #endif
- handleRequest(request);
- _wm->requestAsyncScan(true);
-
- AsyncWebServerResponse *response = request->beginResponse(202, "application/json",
- "{\"accepted\":true,\"state\":\"queued\"}");
- response->addHeader(F("Cache-Control"), F("no-cache"));
- request->send(response);
-}
-
-void WiFiManagerHandlers::handleUpdate(AsyncWebServerRequest *request) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- Handle update"));
- #endif
- if (captivePortal(request)) return;
- handleRequest(request);
- request->redirect(String(F("/#/update")));
-}
-
void WiFiManagerHandlers::handleUpdating(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
static unsigned long _configPortalTimeoutSAV = 0;
static bool timeoutSaved = false;
@@ -1757,25 +796,26 @@ void WiFiManagerHandlers::handleUpdating(AsyncWebServerRequest *request, String
}
void WiFiManagerHandlers::handleUpdateDone(AsyncWebServerRequest *request) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- Handle update done"));
- #endif
+#ifndef WM_NO_LOG
+ _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- Handle update done (POST /u)"));
+#endif
handleRequest(request);
- auto bundle = std::make_shared();
- const String subtitle = _wm->configPortalActive ? _wm->_apName : WiFi.localIP().toString();
- sendMessageTemplateResponse(_wm->_serverManager.get(),
- bundle,
- F("Update"),
- C_update,
- buildUpdateResultContent(_wm->_title, subtitle));
-
- request->send(beginTemplateResponse(request, bundle, WM_PAGE_SHELL_TEMPLATE));
-
- if (!Update.hasError()) {
- delay(1000);
- ESP.restart();
+ if (Update.hasError()) {
+ String json = F("{\"ok\":false,\"message\":\"Update failed\",\"detail\":\"");
+#ifdef ESP32
+ jsonAppendEscaped(json, String(Update.errorString()));
+#else
+ jsonAppendEscaped(json, String(Update.getError()));
+#endif
+ json += F("\"}");
+ sendApiJson(request, 500, json);
+ return;
}
+
+ sendApiJson(request, 200, jsonApiOtaUpdateSuccess());
+ delay(1000);
+ ESP.restart();
}
void WiFiManagerHandlers::sendApiJson(AsyncWebServerRequest *request, int code, const String& json) {
@@ -1785,6 +825,40 @@ void WiFiManagerHandlers::sendApiJson(AsyncWebServerRequest *request, int code,
request->send(response);
}
+void WiFiManagerHandlers::appendPortalUiFeatureFlagsJson(String& json) {
+ const bool portalRunning = _wm->configPortalActive || _wm->webPortalActive;
+ json += F("\"showInfo\":");
+ json += _wm->_showInfo ? F("true") : F("false");
+ json += F(",\"showUpdate\":");
+ json += _wm->_showInfoUpdate ? F("true") : F("false");
+ json += F(",\"showErase\":");
+ json += _wm->_showInfoErase ? F("true") : F("false");
+ json += F(",\"paramsInWifi\":");
+ json += _wm->_paramsInWifi ? F("true") : F("false");
+ json += F(",\"showRestart\":");
+ json += portalRunning ? F("true") : F("false");
+ json += F(",\"showExitPortal\":");
+ json += (_wm->_allowExit && portalRunning) ? F("true") : F("false");
+ json += F(",\"showCloseCaptive\":");
+ json += (_wm->_enableCaptivePortal && _wm->configPortalActive) ? F("true") : F("false");
+}
+
+void WiFiManagerHandlers::appendApiInfoActionsJson(String& json) {
+ const bool portalRunning = _wm->configPortalActive || _wm->webPortalActive;
+ json += F("\"showUpdate\":");
+ json += _wm->_showInfoUpdate ? F("true") : F("false");
+ json += F(",\"showErase\":");
+ json += _wm->_showInfoErase ? F("true") : F("false");
+ json += F(",\"showBack\":");
+ json += _wm->_showBack ? F("true") : F("false");
+ json += F(",\"showRestart\":");
+ json += portalRunning ? F("true") : F("false");
+ json += F(",\"showExitPortal\":");
+ json += (_wm->_allowExit && portalRunning) ? F("true") : F("false");
+ json += F(",\"showCloseCaptive\":");
+ json += (_wm->_enableCaptivePortal && _wm->configPortalActive) ? F("true") : F("false");
+}
+
String WiFiManagerHandlers::buildPortalBootstrapJson() {
std::vector networks;
collectVisibleScanResults(networks);
@@ -1830,19 +904,20 @@ String WiFiManagerHandlers::buildPortalBootstrapJson() {
json += F("\",\"portalActive\":");
json += _wm->configPortalActive ? F("true") : F("false");
json += F(",\"features\":{");
- json += F("\"showInfo\":true,\"showUpdate\":");
- json += _wm->_showInfoUpdate ? F("true") : F("false");
- json += F(",\"showErase\":");
- json += _wm->_showInfoErase ? F("true") : F("false");
- json += F(",\"paramsInWifi\":");
- json += _wm->_paramsInWifi ? F("true") : F("false");
+ appendPortalUiFeatureFlagsJson(json);
json += F("},\"showBack\":");
json += _wm->_showBack ? F("true") : F("false");
json += F(",\"scan\":{\"state\":\"");
json += stateStr;
json += F("\",\"count\":");
json += String((unsigned int)networks.size());
- json += F("}}");
+ json += F("},\"initialStatus\":\"");
+ {
+ String st;
+ buildPlainStatusSummary(st);
+ jsonAppendEscaped(json, st);
+ }
+ json += F("\"}");
return json;
}
@@ -1876,15 +951,76 @@ void WiFiManagerHandlers::handleApiBootstrap(AsyncWebServerRequest *request) {
}
void WiFiManagerHandlers::handleApiWifiScanStatus(AsyncWebServerRequest *request) {
- handleWiFiScanStatus(request);
+#ifndef WM_NO_LOG
+ _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- API WiFi scan status"));
+#endif
+ handleRequest(request);
+
+ std::vector networks;
+ collectVisibleScanResults(networks);
+
+ String json = "{";
+ reservePage(json, 160 + (networks.size() > 0 ? networks.size() * 96 : 64));
+ json += "\"state\":\"";
+ switch (_wm->_scan.state) {
+ case WiFiManager::WM_SCAN_IDLE:
+ json += "idle";
+ break;
+ case WiFiManager::WM_SCAN_QUEUED:
+ json += "queued";
+ break;
+ case WiFiManager::WM_SCAN_RUNNING:
+ json += "running";
+ break;
+ case WiFiManager::WM_SCAN_COMPLETE:
+ json += "complete";
+ break;
+ case WiFiManager::WM_SCAN_FAILED:
+ json += "failed";
+ break;
+ case WiFiManager::WM_SCAN_TIMEOUT:
+ json += "timeout";
+ break;
+ }
+ json += "\",\"scanning\":";
+ json += (_wm->_scan.state == WiFiManager::WM_SCAN_RUNNING || _wm->_scan.state == WiFiManager::WM_SCAN_QUEUED)
+ ? "true"
+ : "false";
+ json += ",\"results_valid\":";
+ json += _wm->_scan.resultsValid ? "true" : "false";
+ json += ",\"count\":";
+ json += String(networks.size());
+ json += ",\"lastscan\":";
+ json += String(_wm->_lastscan);
+ json += ",\"error\":\"";
+ if (_wm->_scan.state == WiFiManager::WM_SCAN_TIMEOUT) {
+ json += "timeout";
+ } else if (_wm->_scan.state == WiFiManager::WM_SCAN_FAILED) {
+ json += "failed";
+ }
+ json += "\",\"networks\":[";
+ appendVisibleScanResultsJson(json, networks);
+ json += "]}";
+
+ AsyncWebServerResponse *response = request->beginResponse(200, "application/json", json);
+ response->addHeader(F("Cache-Control"), F("no-cache"));
+ request->send(response);
}
void WiFiManagerHandlers::handleApiWifiScan(AsyncWebServerRequest *request) {
- handleWiFiScanRequest(request);
+#ifndef WM_NO_LOG
+ _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- API WiFi scan"));
+#endif
+ handleRequest(request);
+ _wm->requestAsyncScan(true);
+
+ AsyncWebServerResponse *response =
+ request->beginResponse(202, "application/json", "{\"accepted\":true,\"state\":\"queued\"}");
+ response->addHeader(F("Cache-Control"), F("no-cache"));
+ request->send(response);
}
-void WiFiManagerHandlers::handleApiWifiMeta(AsyncWebServerRequest *request) {
- handleRequest(request);
+String WiFiManagerHandlers::buildApiWifiMetaJson() {
String ssidPlaceholder = _wm->WiFi_SSID();
String passwordPlaceholder = "";
if (_wm->_showPassword) {
@@ -1892,26 +1028,34 @@ void WiFiManagerHandlers::handleApiWifiMeta(AsyncWebServerRequest *request) {
} else if (_wm->WiFi_psk() != "") {
passwordPlaceholder = F("********");
}
- String staticHtml = getStaticOut();
- String paramsHtml;
- if (_wm->_paramsInWifi && _wm->_paramsCount > 0) {
- paramsHtml = renderSectionBreak(getParamOut());
- }
- String json = F("{\"ssidPlaceholder\":\"");
+
+ String json = F("{\"wifiFields\":[");
+ json += F("{\"id\":\"s\",\"name\":\"s\",\"label\":\"SSID\",\"type\":\"text\",\"maxlength\":32,\"value\":\"");
jsonAppendEscaped(json, ssidPlaceholder);
- json += F("\",\"passwordPlaceholder\":\"");
+ json += F("\"},{\"id\":\"p\",\"name\":\"p\",\"label\":\"Password\",\"type\":\"password\",\"maxlength\":64,\"placeholder\":\"");
jsonAppendEscaped(json, passwordPlaceholder);
- json += F("\",\"staticFieldsHtml\":\"");
- jsonAppendEscaped(json, staticHtml);
- json += F("\",\"paramsHtml\":\"");
- jsonAppendEscaped(json, paramsHtml);
- json += F("\"}");
- sendApiJson(request, 200, json);
+ json += F("\"}],\"staticFields\":[");
+ bool first = true;
+ appendPortalJsonStaticFields(json, first);
+ json += F("],\"params\":[");
+ first = true;
+ if (_wm->_paramsInWifi && _wm->getParametersCount() > 0) {
+ appendPortalJsonCustomParams(json, first);
+ }
+ json += F("],\"actions\":{\"canRefreshScan\":true,\"showBack\":");
+ json += _wm->_showBack ? F("true") : F("false");
+ json += F("}}");
+ return json;
+}
+
+void WiFiManagerHandlers::handleApiWifiMeta(AsyncWebServerRequest *request) {
+ handleRequest(request);
+ sendApiJson(request, 200, buildApiWifiMetaJson());
}
void WiFiManagerHandlers::handleApiWifiSave(AsyncWebServerRequest *request) {
#ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- API WiFi save"));
+ _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("<- WiFi save"));
#endif
handleRequest(request);
applyWifiAndParamsFromRequest(request);
@@ -1926,13 +1070,19 @@ void WiFiManagerHandlers::handleApiWifiSave(AsyncWebServerRequest *request) {
_wm->connect = true;
}
+String WiFiManagerHandlers::buildApiParamsGetJson() {
+ String json = F("{\"params\":[");
+ bool first = true;
+ appendPortalJsonCustomParams(json, first);
+ json += F("],\"actions\":{\"showBack\":");
+ json += _wm->_showBack ? F("true") : F("false");
+ json += F("}}");
+ return json;
+}
+
void WiFiManagerHandlers::handleApiParamsGet(AsyncWebServerRequest *request) {
handleRequest(request);
- String fields = getParamOut();
- String json = F("{\"paramsHtml\":\"");
- jsonAppendEscaped(json, fields);
- json += F("\"}");
- sendApiJson(request, 200, json);
+ sendApiJson(request, 200, buildApiParamsGetJson());
}
void WiFiManagerHandlers::handleApiParamsSave(AsyncWebServerRequest *request) {
@@ -1942,16 +1092,25 @@ void WiFiManagerHandlers::handleApiParamsSave(AsyncWebServerRequest *request) {
handleRequest(request);
WiFiManager::WiFiManagerRequestArgs requestArgs(request);
doParamSave(requestArgs);
- sendApiJson(request, 200, F("{\"ok\":true,\"message\":\"Setup saved\"}"));
+ sendApiJson(request, 200, jsonApiParamsSaveOk());
}
-void WiFiManagerHandlers::handleApiInfo(AsyncWebServerRequest *request) {
- handleRequest(request);
- String summary;
- buildPlainStatusSummary(summary);
- String json = F("{\"status\":[{\"key\":\"summary\",\"label\":\"Status\",\"value\":\"");
- jsonAppendEscaped(json, summary);
- json += F("\"}],\"device\":[");
+String WiFiManagerHandlers::buildApiInfoJson() {
+ String json = F("{\"status\":{\"connected\":");
+ json += WiFi.isConnected() ? F("true") : F("false");
+ json += F(",\"ssid\":\"");
+ jsonAppendEscaped(json, _wm->WiFi_SSID());
+ json += F("\",\"stationIp\":\"");
+ jsonAppendEscaped(json, WiFi.localIP().toString());
+ json += F("\",\"apIp\":\"");
+ jsonAppendEscaped(json, WiFi.softAPIP().toString());
+ json += F("\",\"summary\":\"");
+ {
+ String summary;
+ buildPlainStatusSummary(summary);
+ jsonAppendEscaped(json, summary);
+ }
+ json += F("\"},\"device\":[");
bool first = true;
#ifdef ESP8266
static const char *const deviceIds[] = {"uptime", "chipid", "fchipid", "idesize", "flashsize",
@@ -1961,7 +1120,7 @@ void WiFiManagerHandlers::handleApiInfo(AsyncWebServerRequest *request) {
static const char *const deviceIds[] = {"uptime", "chipid", "chiprev", "idesize", "flashsize",
"cpufreq", "freeheap", "memsketch", "memsmeter", "lastreset", "temp"};
#endif
- appendJsonInfoArrayFromIds(this, json, deviceIds, sizeof(deviceIds) / sizeof(deviceIds[0]), first);
+ appendInfoSectionFromIds(json, deviceIds, sizeof(deviceIds) / sizeof(deviceIds[0]), first);
json += F("],\"wifi\":[");
first = true;
#ifdef ESP8266
@@ -1971,30 +1130,65 @@ void WiFiManagerHandlers::handleApiInfo(AsyncWebServerRequest *request) {
static const char *const wifiIds[] = {"conx", "stassid", "staip", "stagw", "stasub", "dnss", "host",
"stamac", "apssid", "apip", "apmac", "aphost", "apbssid"};
#endif
- appendJsonInfoArrayFromIds(this, json, wifiIds, sizeof(wifiIds) / sizeof(wifiIds[0]), first);
+ appendInfoSectionFromIds(json, wifiIds, sizeof(wifiIds) / sizeof(wifiIds[0]), first);
json += F("],\"about\":[");
first = true;
static const char *const aboutIds[] = {"aboutver", "aboutarduinover", "aboutsdkver", "aboutdate"};
- appendJsonInfoArrayFromIds(this, json, aboutIds, sizeof(aboutIds) / sizeof(aboutIds[0]), first);
+ appendInfoSectionFromIds(json, aboutIds, sizeof(aboutIds) / sizeof(aboutIds[0]), first);
json += F("],\"actions\":{");
- json += F("\"showUpdate\":");
- json += _wm->_showInfoUpdate ? F("true") : F("false");
- json += F(",\"showErase\":");
- json += _wm->_showInfoErase ? F("true") : F("false");
- json += F(",\"showBack\":");
- json += _wm->_showBack ? F("true") : F("false");
+ appendApiInfoActionsJson(json);
json += F("}}");
- sendApiJson(request, 200, json);
+ return json;
}
-void WiFiManagerHandlers::handleApiStatus(AsyncWebServerRequest *request) {
+void WiFiManagerHandlers::handleApiInfo(AsyncWebServerRequest *request) {
handleRequest(request);
+ sendApiJson(request, 200, buildApiInfoJson());
+}
+
+String WiFiManagerHandlers::buildApiStatusJson() {
String summary;
buildPlainStatusSummary(summary);
String json = F("{\"text\":\"");
jsonAppendEscaped(json, summary);
json += F("\"}");
- sendApiJson(request, 200, json);
+ return json;
+}
+
+void WiFiManagerHandlers::handleApiStatus(AsyncWebServerRequest *request) {
+ handleRequest(request);
+ sendApiJson(request, 200, buildApiStatusJson());
+}
+
+String WiFiManagerHandlers::jsonApiDeviceRestartScheduled() {
+ return F("{\"ok\":true,\"message\":\"Restart scheduled\"}");
+}
+
+String WiFiManagerHandlers::jsonApiParamsSaveOk() {
+ return F("{\"ok\":true,\"message\":\"Setup saved\"}");
+}
+
+String WiFiManagerHandlers::jsonApiPortalCloseOk() {
+ return F("{\"ok\":true,\"message\":\"Captive portal detection disabled\"}");
+}
+
+String WiFiManagerHandlers::jsonApiPortalExitOk() {
+ return F("{\"ok\":true,\"message\":\"Exiting portal\"}");
+}
+
+String WiFiManagerHandlers::jsonApiPortalExitForbidden() {
+ return F("{\"ok\":false,\"message\":\"Exit not allowed\"}");
+}
+
+String WiFiManagerHandlers::jsonApiOtaUpdateSuccess() {
+ return F("{\"ok\":true,\"message\":\"Firmware updated. Restarting...\"}");
+}
+
+String WiFiManagerHandlers::jsonApiEraseResponse(boolean success) {
+ if (success) {
+ return F("{\"ok\":true,\"message\":\"WiFi configuration erased. Device will restart shortly.\"}");
+ }
+ return F("{\"ok\":false,\"message\":\"Erase failed\"}");
}
void WiFiManagerHandlers::handleApiDeviceRestart(AsyncWebServerRequest *request) {
@@ -2002,27 +1196,18 @@ void WiFiManagerHandlers::handleApiDeviceRestart(AsyncWebServerRequest *request)
_wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("API device restart"));
#endif
handleRequest(request);
- sendApiJson(request, 200, F("{\"ok\":true,\"message\":\"Restart scheduled\"}"));
+ sendApiJson(request, 200, jsonApiDeviceRestartScheduled());
_wm->_rebootScheduled = true;
_wm->_rebootTime = millis() + _wm->REBOOT_DELAY_MS;
}
-void WiFiManagerHandlers::handleApiDeviceErase(AsyncWebServerRequest *request) {
+void WiFiManagerHandlers::handleApiDeviceErase(AsyncWebServerRequest *request, boolean optionalErase) {
#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("API device erase"));
#endif
handleRequest(request);
- bool ret = _wm->erase(false);
- String json = F("{\"ok\":");
- json += ret ? F("true") : F("false");
- json += F(",\"message\":\"");
- if (ret) {
- json += F("WiFi configuration erased. Device will restart shortly.");
- } else {
- json += F("Erase failed");
- }
- json += F("\"}");
- sendApiJson(request, ret ? 200 : 500, json);
+ bool ret = _wm->erase(optionalErase);
+ sendApiJson(request, ret ? 200 : 500, jsonApiEraseResponse(ret));
if (ret) {
_wm->_rebootScheduled = true;
_wm->_rebootTime = millis() + _wm->ERASE_REBOOT_DELAY_MS;
@@ -2035,7 +1220,7 @@ void WiFiManagerHandlers::handleApiPortalClose(AsyncWebServerRequest *request) {
#endif
stopCaptivePortal();
handleRequest(request);
- sendApiJson(request, 200, F("{\"ok\":true,\"message\":\"Captive portal detection disabled\"}"));
+ sendApiJson(request, 200, jsonApiPortalCloseOk());
}
void WiFiManagerHandlers::handleApiPortalExit(AsyncWebServerRequest *request) {
@@ -2043,7 +1228,11 @@ void WiFiManagerHandlers::handleApiPortalExit(AsyncWebServerRequest *request) {
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("API portal exit"));
#endif
handleRequest(request);
- sendApiJson(request, 200, F("{\"ok\":true,\"message\":\"Exiting portal\"}"));
+ if (!_wm->_allowExit) {
+ sendApiJson(request, 403, jsonApiPortalExitForbidden());
+ return;
+ }
+ sendApiJson(request, 200, jsonApiPortalExitOk());
_wm->_abortScheduled = true;
_wm->_abortTime = millis() + _wm->EXIT_DELAY_MS;
}
diff --git a/lib/WiFiManager/src/WiFiManagerParameter.cpp b/lib/WiFiManager/src/WiFiManagerParameter.cpp
index 328969f..eab1ab1 100644
--- a/lib/WiFiManager/src/WiFiManagerParameter.cpp
+++ b/lib/WiFiManager/src/WiFiManagerParameter.cpp
@@ -1,3 +1,10 @@
+/**
+ * WiFiManagerParameter.cpp
+ *
+ * @author alexhopeoconnor
+ * @license MIT
+ */
+
#include "WiFiManagerParameter.h"
#include
diff --git a/lib/WiFiManager/src/WiFiManagerServer.cpp b/lib/WiFiManager/src/WiFiManagerServer.cpp
index 5e9d52a..c988b9a 100644
--- a/lib/WiFiManager/src/WiFiManagerServer.cpp
+++ b/lib/WiFiManager/src/WiFiManagerServer.cpp
@@ -1,16 +1,16 @@
/**
* WiFiManagerServer.cpp
- *
- * Server lifecycle management implementation
- *
- * @author tablatronix
- * @author Alex Hope-O'Connor
+ *
+ * @author alexhopeoconnor
* @license MIT
+ *
+ * Server lifecycle: single shell + JSON APIs + OTA + captive portal.
+ * Route inventory matches WiFiManagerServer.h (GET /, /api/..., POST /u, onNotFound).
*/
#include "WiFiManagerServer.h"
#include "WiFiManager.h"
-#include "WiFiManagerHandlers.h" // Full definition needed for unique_ptr
+#include "WiFiManagerHandlers.h"
#ifdef WM_DFTE_LOGGING
#include "WiFiManagerDfteLogger.h"
#include
@@ -26,51 +26,19 @@ WiFiManagerServer* WiFiManagerServer::instance() {
return s_instance;
}
-WiFiManagerServer::WiFiManagerServer(WiFiManager* wm)
- : _wm(wm), _handlers(std::make_unique(wm)) {
+WiFiManagerServer::WiFiManagerServer(WiFiManager* wm)
+ : _wm(wm), _handlers(std::make_unique(wm)) {
s_instance = this;
}
WiFiManagerServer::~WiFiManagerServer() = default;
-// Static getters for RAM placeholders (registry requires zero-arg functions) using singleton
const char* WiFiManagerServer::tplGetPageTitle() {
auto srv = WiFiManagerServer::instance();
if (!srv || !srv->_wm) return "";
return srv->_wm->_title.c_str();
}
-const char* WiFiManagerServer::tplGetSubtitle() {
- static String buf;
- auto srv = WiFiManagerServer::instance();
- if (!srv || !srv->_wm) { buf = ""; return buf.c_str(); }
- WiFiManager* wm = srv->_wm;
- if (wm->configPortalActive) {
- buf = wm->_apName;
- } else {
- buf = wm->getWiFiHostname() + " - " + WiFi.localIP().toString();
- }
- return buf.c_str();
-}
-
-const char* WiFiManagerServer::tplGetMenu() {
- static String buf;
- auto srv = WiFiManagerServer::instance();
- if (!srv || !srv->_handlers) { buf = ""; return buf.c_str(); }
- buf = "";
- srv->_handlers->getMenuOut(&buf);
- return buf.c_str();
-}
-
-const char* WiFiManagerServer::tplGetStatus() {
- static String buf;
- auto srv = WiFiManagerServer::instance();
- if (!srv || !srv->_handlers) { buf = ""; return buf.c_str(); }
- buf = "";
- srv->_handlers->reportStatus(buf);
- return buf.c_str();
-}
-
void WiFiManagerServer::registerDefaultStyles(PlaceholderRegistry& reg) {
reg.registerProgmemData("%STYLES%", CSS_STYLE);
}
@@ -83,59 +51,21 @@ void WiFiManagerServer::registerDefaultPageTitle(PlaceholderRegistry& reg) {
reg.registerRamData("%PAGE_TITLE%", &WiFiManagerServer::tplGetPageTitle);
}
-void WiFiManagerServer::registerDefaultSubtitle(PlaceholderRegistry& reg) {
- reg.registerRamData("%SUBTITLE%", &WiFiManagerServer::tplGetSubtitle);
-}
-
-void WiFiManagerServer::registerDefaultMenu(PlaceholderRegistry& reg) {
- reg.registerRamData("%MENU%", &WiFiManagerServer::tplGetMenu);
-}
-
-void WiFiManagerServer::registerDefaultStatus(PlaceholderRegistry& reg) {
- reg.registerRamData("%STATUS%", &WiFiManagerServer::tplGetStatus);
-}
-
void WiFiManagerServer::registerDefaultPlaceholders(PlaceholderRegistry& reg) {
registerDefaultStyles(reg);
registerDefaultScripts(reg);
registerDefaultPageTitle(reg);
- registerDefaultSubtitle(reg);
- registerDefaultMenu(reg);
- registerDefaultStatus(reg);
-}
-
-void WiFiManagerServer::applyTemplateSetupCallback(PlaceholderRegistry& reg) {
- if (_tplSetupCallback) {
- _tplSetupCallback(reg);
- }
-}
-
-void WiFiManagerServer::rebuildPlaceholderRegistry(std::function customizer) {
- _tplRegistry = std::unique_ptr(new PlaceholderRegistry(WM_TEMPLATE_REGISTRY_CAPACITY));
- registerDefaultPlaceholders(*_tplRegistry);
- if (customizer) {
- customizer(*_tplRegistry);
- } else {
- applyTemplateSetupCallback(*_tplRegistry);
- }
}
void WiFiManagerServer::setupTemplateEngine() {
- // Initialize shared placeholder registry once
if (!_tplRegistry) {
_tplRegistry = std::unique_ptr(new PlaceholderRegistry(WM_TEMPLATE_REGISTRY_CAPACITY));
} else {
_tplRegistry->clear();
}
-
- // Register all default placeholders
registerDefaultPlaceholders(*_tplRegistry);
-
- // Allow consumers to customize placeholders
- applyTemplateSetupCallback(*_tplRegistry);
#ifdef WM_DFTE_LOGGING
- // Install DFTE log sink only if nothing else registered yet (e.g. DeviceFramework's bridge).
if (!deviceFrameworkTemplateEngineIsLoggingEnabled()) {
if (!_dfteLogger) {
_dfteLogger = std::make_unique(_wm);
@@ -147,53 +77,48 @@ void WiFiManagerServer::setupTemplateEngine() {
}
void WiFiManagerServer::createServer(uint16_t port) {
- // If server already exists, shutdown first to prevent leaks and connection drops
if (server) {
- #ifndef WM_NO_LOG
+#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("Server already exists, shutting down first"));
- #endif
+#endif
shutdownServer();
}
- #ifndef WM_NO_LOG
+#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("Starting Web Portal"));
- #endif
+#endif
- if(port != 80) {
- #ifndef WM_NO_LOG
+ if (port != 80) {
+#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("http server started with custom port: "), port);
- #endif
+#endif
}
server.reset(new AsyncWebServer(port));
-
- // Ensure template engine is ready whenever a server is (re)created
setupTemplateEngine();
}
void WiFiManagerServer::registerRoutes() {
if (!server) {
- #ifndef WM_NO_LOG
+#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Error, kWiFiMgrLogSubsystem, F("[ERROR] Server not created, call createServer() first"));
- #endif
+#endif
return;
}
- // Ensure template engine is initialized before routes use it
if (!_tplRegistry) {
setupTemplateEngine();
}
-
- if (_wm->_webservercallback != NULL) {
- #ifndef WM_NO_LOG
- _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("[CB] _webservercallback calling"));
- #endif
- _wm->_webservercallback(); // @CALLBACK
- }
-
- /* Setup httpd callbacks, web pages: root, wifi config pages, SO captive portal detectors and not found. */
- // Register routes with lambdas
+ if (_wm->_webservercallback != NULL) {
+#ifndef WM_NO_LOG
+ _wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("[CB] _webservercallback calling"));
+#endif
+ _wm->_webservercallback();
+ }
+
+ // Shell: only HTML document. JSON + actions + OTA below (no legacy /wifi, /info, … routes).
+
server->on(WM_G(R_root), HTTP_GET, [this](AsyncWebServerRequest *request) {
this->_handlers->handleRoot(request);
});
@@ -237,88 +162,32 @@ void WiFiManagerServer::registerRoutes() {
server->on(WM_G(R_api_portal_exit), HTTP_POST, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiPortalExit(request);
});
-
- server->on(WM_G(R_wifi), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleWifi(request, true);
- });
-
- server->on(WM_G(R_wifinoscan), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleWifi(request, false);
- });
-
- server->on(WM_G(R_wifisave), HTTP_POST, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleWifiSave(request);
- });
-
- server->on(WM_G(R_info), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleInfo(request);
- });
-
- server->on(WM_G(R_param), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleParam(request);
- });
-
- server->on(WM_G(R_paramsave), HTTP_POST, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleParamSave(request);
- });
-
- server->on(WM_G(R_restart), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleReset(request);
- });
-
- server->on(WM_G(R_exit), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleExit(request);
- });
-
- server->on(WM_G(R_close), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleClose(request);
- });
-
- server->on(WM_G(R_erase), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleErase(request, false);
- });
-
- server->on(WM_G(R_scanstatus), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleWiFiScanStatus(request);
- });
- server->on(WM_G(R_scan), HTTP_POST, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleWiFiScanRequest(request);
- });
-
- // OTA Update routes
- server->on(WM_G(R_update), HTTP_GET, [this](AsyncWebServerRequest *request) {
- this->_handlers->handleUpdate(request);
- });
-
- server->on(WM_G(R_updatedone), HTTP_POST,
- [this](AsyncWebServerRequest *request) {
- this->_handlers->handleUpdateDone(request);
- },
- [this](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
- this->_handlers->handleUpdating(request, filename, index, data, len, final);
- }
- );
-
- // 404 handler
+ server->on(WM_G(R_updatedone), HTTP_POST,
+ [this](AsyncWebServerRequest *request) {
+ this->_handlers->handleUpdateDone(request);
+ },
+ [this](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len,
+ bool final) {
+ this->_handlers->handleUpdating(request, filename, index, data, len, final);
+ });
+
server->onNotFound([this](AsyncWebServerRequest *request) {
this->_handlers->handleNotFound(request);
});
-
- server->begin(); // Web server start
- #ifndef WM_NO_LOG
+
+ server->begin();
+#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("HTTP server started"));
- #endif
+#endif
}
void WiFiManagerServer::setupDNSD() {
dnsServer.reset(new DNSServer());
-
- /* Setup the DNS server redirecting all the domains to the apIP */
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
- #ifndef WM_NO_LOG
+#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("dns server started with ip: "), WiFi.softAPIP());
- #endif
+#endif
const uint8_t DNS_PORT = 53;
dnsServer->start(DNS_PORT, F("*"), WiFi.softAPIP());
}
@@ -338,20 +207,16 @@ void WiFiManagerServer::shutdownServer() {
}
#endif
- // AsyncWebServer doesn't have stop(), just reset the unique_ptr to free resources
if (server) {
server.reset();
}
-
- // Shutdown DNS server
+
if (dnsServer) {
- dnsServer->stop(); // free heap ?
+ dnsServer->stop();
dnsServer.reset();
}
-
- // Clear singleton on shutdown
+
if (s_instance == this) s_instance = nullptr;
}
#endif
-
diff --git a/lib/WiFiManager/src/WiFiManagerUtils.cpp b/lib/WiFiManager/src/WiFiManagerUtils.cpp
index 6d5c3de..b3421e8 100644
--- a/lib/WiFiManager/src/WiFiManagerUtils.cpp
+++ b/lib/WiFiManager/src/WiFiManagerUtils.cpp
@@ -2,7 +2,7 @@
* WiFiManagerUtils.cpp
* Implementation of pure utility functions for WiFiManager
*
- * @author Alex Hope-O'Connor
+ * @author alexhopeoconnor
* @license MIT
*/
diff --git a/test/test_wifimanager/test_main.cpp b/test/test_wifimanager/test_main.cpp
index 489167c..4896c6c 100644
--- a/test/test_wifimanager/test_main.cpp
+++ b/test/test_wifimanager/test_main.cpp
@@ -30,8 +30,8 @@ TestCase tests[] = {
TEST_ENTRY(test_config_portal_multiple_start_stop),
TEST_ENTRY(test_config_portal_already_active),
TEST_ENTRY(test_get_config_portal_ssid),
- TEST_ENTRY(test_root_render_menu_state_transitions),
- TEST_ENTRY(test_root_render_snapshot_consistency),
+ TEST_ENTRY(test_bootstrap_json_portal_feature_flags),
+ TEST_ENTRY(test_bootstrap_json_snapshot_consistency),
TEST_ENTRY(test_root_render_interleaved_context_isolation),
// Non-blocking tests
@@ -113,15 +113,15 @@ TestCase tests[] = {
TEST_ENTRY(test_scan_generation_invalidated_on_reset),
// Template rendering tests
- TEST_ENTRY(test_page_shell_supports_page_assets),
- TEST_ENTRY(test_root_template_renders_core_placeholders),
- TEST_ENTRY(test_wifi_template_renders_scan_container_and_form_fields),
- TEST_ENTRY(test_message_template_renders_body_and_actions),
- TEST_ENTRY(test_param_template_renders_shared_actions),
- TEST_ENTRY(test_info_template_renders_sections_and_footer),
- TEST_ENTRY(test_fragment_action_form_renders_shared_button_markup),
- TEST_ENTRY(test_fragment_info_row_and_section_render),
- TEST_ENTRY(test_fragment_centered_button_supports_extra_attrs),
+ TEST_ENTRY(test_shell_template_renders_core_placeholders),
+
+ TEST_ENTRY(test_captive_redirect_host_rule),
+ TEST_ENTRY(test_api_wifi_meta_json_shape),
+ TEST_ENTRY(test_api_info_json_shape),
+ TEST_ENTRY(test_api_params_json_shape),
+ TEST_ENTRY(test_api_status_json_shape),
+ TEST_ENTRY(test_api_action_response_json_literals),
+ TEST_ENTRY(test_api_ota_done_success_json_literal),
// State transition tests
TEST_ENTRY(test_portal_to_connected_transition),
diff --git a/test/test_wifimanager/test_main.h b/test/test_wifimanager/test_main.h
index 43ae1b4..8d7f64c 100644
--- a/test/test_wifimanager/test_main.h
+++ b/test/test_wifimanager/test_main.h
@@ -40,8 +40,8 @@ void test_stop_web_portal();
void test_config_portal_multiple_start_stop();
void test_config_portal_already_active();
void test_get_config_portal_ssid();
-void test_root_render_menu_state_transitions();
-void test_root_render_snapshot_consistency();
+void test_bootstrap_json_portal_feature_flags();
+void test_bootstrap_json_snapshot_consistency();
void test_root_render_interleaved_context_isolation();
// Non-blocking tests
@@ -123,15 +123,16 @@ void test_scan_cancels_when_lifecycle_blocked();
void test_scan_generation_invalidated_on_reset();
// Template rendering tests
-void test_page_shell_supports_page_assets();
-void test_root_template_renders_core_placeholders();
-void test_wifi_template_renders_scan_container_and_form_fields();
-void test_message_template_renders_body_and_actions();
-void test_param_template_renders_shared_actions();
-void test_info_template_renders_sections_and_footer();
-void test_fragment_action_form_renders_shared_button_markup();
-void test_fragment_info_row_and_section_render();
-void test_fragment_centered_button_supports_extra_attrs();
+void test_shell_template_renders_core_placeholders();
+
+// API JSON + captive helper tests
+void test_captive_redirect_host_rule();
+void test_api_wifi_meta_json_shape();
+void test_api_info_json_shape();
+void test_api_params_json_shape();
+void test_api_status_json_shape();
+void test_api_action_response_json_literals();
+void test_api_ota_done_success_json_literal();
// State transition tests
void test_portal_to_connected_transition();
diff --git a/test/test_wifimanager/tests/test_api_json_and_captive.cpp b/test/test_wifimanager/tests/test_api_json_and_captive.cpp
new file mode 100644
index 0000000..f043445
--- /dev/null
+++ b/test/test_wifimanager/tests/test_api_json_and_captive.cpp
@@ -0,0 +1,117 @@
+#include
+#include
+#include
+#include
+#include "../test_main.h"
+
+void test_captive_redirect_host_rule() {
+ Serial.println("[TEST] Testing captive redirect host comparison...");
+
+ TEST_ASSERT_TRUE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("captive.apple.com", "192.168.4.1"));
+ TEST_ASSERT_TRUE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("192.168.4.2", "192.168.4.1"));
+ TEST_ASSERT_FALSE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("192.168.4.1", "192.168.4.1"));
+ TEST_ASSERT_FALSE(WiFiManagerHandlers::shouldRedirectCaptiveForHost("example.com", ""));
+
+ Serial.println("[TEST] Captive redirect host rule test completed successfully");
+}
+
+void test_api_wifi_meta_json_shape() {
+ Serial.println("[TEST] Testing /api/wifi/meta JSON shape...");
+
+ WiFiManager wm;
+ WiFiManagerHandlers handlers(&wm);
+ String j = handlers.buildApiWifiMetaJson();
+
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"wifiFields\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"staticFields\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"canRefreshScan\":true")));
+
+ Serial.println("[TEST] WiFi meta JSON shape test completed successfully");
+}
+
+void test_api_info_json_shape() {
+ Serial.println("[TEST] Testing /api/info JSON shape...");
+
+ WiFiManager wm;
+ WiFiManagerHandlers handlers(&wm);
+ String j = handlers.buildApiInfoJson();
+
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"status\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"connected\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"device\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"wifi\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"about\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"showUpdate\"")));
+
+ Serial.println("[TEST] API info JSON shape test completed successfully");
+}
+
+void test_api_params_json_shape() {
+ Serial.println("[TEST] Testing /api/params JSON shape...");
+
+ WiFiManager wm;
+ WiFiManagerHandlers handlers(&wm);
+ String j = handlers.buildApiParamsGetJson();
+
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"actions\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"showBack\"")));
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"params\":[")));
+
+ Serial.println("[TEST] API params JSON shape test completed successfully");
+}
+
+void test_api_status_json_shape() {
+ Serial.println("[TEST] Testing /api/status JSON shape...");
+
+ WiFiManager wm;
+ WiFiManagerHandlers handlers(&wm);
+ String j = handlers.buildApiStatusJson();
+
+ TEST_ASSERT_NOT_EQUAL(-1, j.indexOf(F("\"text\"")));
+ TEST_ASSERT_EQUAL('{', j.charAt(0));
+ TEST_ASSERT_EQUAL('}', j.charAt(j.length() - 1));
+
+ Serial.println("[TEST] API status JSON shape test completed successfully");
+}
+
+void test_api_action_response_json_literals() {
+ Serial.println("[TEST] Testing POST /api action JSON payloads...");
+
+ TEST_ASSERT_EQUAL_STRING(
+ "{\"ok\":true,\"message\":\"Restart scheduled\"}",
+ WiFiManagerHandlers::jsonApiDeviceRestartScheduled().c_str());
+ TEST_ASSERT_EQUAL_STRING(
+ "{\"ok\":true,\"message\":\"Setup saved\"}",
+ WiFiManagerHandlers::jsonApiParamsSaveOk().c_str());
+ TEST_ASSERT_EQUAL_STRING(
+ "{\"ok\":true,\"message\":\"Captive portal detection disabled\"}",
+ WiFiManagerHandlers::jsonApiPortalCloseOk().c_str());
+ TEST_ASSERT_EQUAL_STRING(
+ "{\"ok\":true,\"message\":\"Exiting portal\"}",
+ WiFiManagerHandlers::jsonApiPortalExitOk().c_str());
+ TEST_ASSERT_EQUAL_STRING(
+ "{\"ok\":false,\"message\":\"Exit not allowed\"}",
+ WiFiManagerHandlers::jsonApiPortalExitForbidden().c_str());
+ TEST_ASSERT_EQUAL_STRING(
+ "{\"ok\":true,\"message\":\"WiFi configuration erased. Device will restart shortly.\"}",
+ WiFiManagerHandlers::jsonApiEraseResponse(true).c_str());
+ TEST_ASSERT_EQUAL_STRING(
+ "{\"ok\":false,\"message\":\"Erase failed\"}",
+ WiFiManagerHandlers::jsonApiEraseResponse(false).c_str());
+
+ Serial.println("[TEST] Action response JSON literals test completed successfully");
+}
+
+void test_api_ota_done_success_json_literal() {
+ Serial.println("[TEST] Testing POST /u completion success JSON...");
+
+ TEST_ASSERT_EQUAL_STRING(
+ "{\"ok\":true,\"message\":\"Firmware updated. Restarting...\"}",
+ WiFiManagerHandlers::jsonApiOtaUpdateSuccess().c_str());
+
+ Serial.println("[TEST] OTA success JSON literal test completed successfully");
+}
diff --git a/test/test_wifimanager/tests/test_root_render_lifecycle.cpp b/test/test_wifimanager/tests/test_root_render_lifecycle.cpp
index 8c0df02..3296087 100644
--- a/test/test_wifimanager/tests/test_root_render_lifecycle.cpp
+++ b/test/test_wifimanager/tests/test_root_render_lifecycle.cpp
@@ -3,13 +3,12 @@
#include
#include
#include
-#include "templates/RootSelector.h"
+#include "templates/RootShell.h"
namespace {
const char kEmptyTemplateChunk[] PROGMEM = "";
const char kTestTitle[] PROGMEM = "WiFiManager";
-const char kTestSubtitle[] PROGMEM = "Root Test";
const char* dynamicStringGetter(void* userData) {
const auto* value = static_cast(userData);
@@ -56,37 +55,29 @@ void renderContextsInterleaved(TemplateContext& first, String& firstOutput,
} // namespace
-void test_root_render_menu_state_transitions() {
- Serial.println("[TEST] Testing root menu state transitions...");
+void test_bootstrap_json_portal_feature_flags() {
+ Serial.println("[TEST] Testing bootstrap JSON portal feature flags...");
WiFiManager wm;
WiFiManagerHandlers handlers(&wm);
- String menuBefore;
- handlers.getMenuOut(&menuBefore);
- TEST_ASSERT_EQUAL(-1, menuBefore.indexOf("/close"));
+ String idleBootstrap = handlers.buildPortalBootstrapJson();
+ TEST_ASSERT_EQUAL(-1, idleBootstrap.indexOf(F("\"showCloseCaptive\":true")));
- wm.setConfigPortalTimeout(5);
- wm.startConfigPortal("RootStateAP");
- wm.process();
- delay(100);
+ wm.wmTestSetPortalActive(true);
+ wm.setCaptivePortalEnable(true);
- String menuActive;
- handlers.getMenuOut(&menuActive);
- TEST_ASSERT_NOT_EQUAL(-1, menuActive.indexOf("/close"));
+ String apBootstrap = handlers.buildPortalBootstrapJson();
+ TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"showCloseCaptive\":true")));
+ TEST_ASSERT_NOT_EQUAL(-1, apBootstrap.indexOf(F("\"showExitPortal\":true")));
- wm.stopConfigPortal();
- delay(100);
+ wm.wmTestSetPortalActive(false);
- String menuAfter;
- handlers.getMenuOut(&menuAfter);
- TEST_ASSERT_EQUAL(-1, menuAfter.indexOf("/close"));
-
- Serial.println("[TEST] Root menu state transitions test completed successfully");
+ Serial.println("[TEST] Bootstrap portal feature flags test completed successfully");
}
-void test_root_render_snapshot_consistency() {
- Serial.println("[TEST] Testing root render snapshot consistency...");
+void test_bootstrap_json_snapshot_consistency() {
+ Serial.println("[TEST] Testing bootstrap JSON snapshot consistency...");
WiFiManager wm;
WiFiManagerHandlers handlers(&wm);
@@ -96,24 +87,15 @@ void test_root_render_snapshot_consistency() {
wm.process();
delay(100);
- String menuSnapshot1;
- String statusSnapshot1;
- handlers.getMenuOut(&menuSnapshot1);
- handlers.reportStatus(statusSnapshot1);
+ String snap1 = handlers.buildPortalBootstrapJson();
+ String snap2 = handlers.buildPortalBootstrapJson();
- String menuSnapshot2;
- String statusSnapshot2;
- handlers.getMenuOut(&menuSnapshot2);
- handlers.reportStatus(statusSnapshot2);
-
- TEST_ASSERT_GREATER_THAN(0, menuSnapshot1.length());
- TEST_ASSERT_GREATER_THAN(0, statusSnapshot1.length());
- TEST_ASSERT_EQUAL_STRING(menuSnapshot1.c_str(), menuSnapshot2.c_str());
- TEST_ASSERT_EQUAL_STRING(statusSnapshot1.c_str(), statusSnapshot2.c_str());
+ TEST_ASSERT_GREATER_THAN(32, snap1.length());
+ TEST_ASSERT_EQUAL_STRING(snap1.c_str(), snap2.c_str());
wm.stopConfigPortal();
- Serial.println("[TEST] Root render snapshot consistency test completed successfully");
+ Serial.println("[TEST] Bootstrap JSON snapshot consistency test completed successfully");
}
void test_root_render_interleaved_context_isolation() {
@@ -125,58 +107,56 @@ void test_root_render_interleaved_context_isolation() {
registryA.registerProgmemData("%SCRIPTS%", kEmptyTemplateChunk);
registryA.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
registryA.registerProgmemData("%PAGE_TITLE%", kTestTitle);
- registryA.registerProgmemData("%SUBTITLE%", kTestSubtitle);
registryB.registerProgmemData("%SCRIPTS%", kEmptyTemplateChunk);
registryB.registerProgmemData("%STYLES%", kEmptyTemplateChunk);
registryB.registerProgmemData("%PAGE_TITLE%", kTestTitle);
- registryB.registerProgmemData("%SUBTITLE%", kTestSubtitle);
- String menuA = F("Menu A
");
- String statusA = F("Status A
");
- String menuB = F("Menu B
");
- String statusB = F("Status B
");
+ String bootstrapA = F("{\"ctx\":\"A\"}");
+ String appJsA = F("// shell A");
+ String bootstrapB = F("{\"ctx\":\"B\"}");
+ String appJsB = F("// shell B");
- DynamicTemplateDescriptor menuDescriptorA{};
- DynamicTemplateDescriptor statusDescriptorA{};
- DynamicTemplateDescriptor menuDescriptorB{};
- DynamicTemplateDescriptor statusDescriptorB{};
+ DynamicTemplateDescriptor bootstrapDescriptorA{};
+ DynamicTemplateDescriptor appJsDescriptorA{};
+ DynamicTemplateDescriptor bootstrapDescriptorB{};
+ DynamicTemplateDescriptor appJsDescriptorB{};
- configureDescriptor(menuDescriptorA, menuA);
- configureDescriptor(statusDescriptorA, statusA);
- configureDescriptor(menuDescriptorB, menuB);
- configureDescriptor(statusDescriptorB, statusB);
+ configureDescriptor(bootstrapDescriptorA, bootstrapA);
+ configureDescriptor(appJsDescriptorA, appJsA);
+ configureDescriptor(bootstrapDescriptorB, bootstrapB);
+ configureDescriptor(appJsDescriptorB, appJsB);
- TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%MENU%", &menuDescriptorA),
- "Registry A menu placeholder should register");
- TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%STATUS%", &statusDescriptorA),
- "Registry A status placeholder should register");
- TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%MENU%", &menuDescriptorB),
- "Registry B menu placeholder should register");
- TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%STATUS%", &statusDescriptorB),
- "Registry B status placeholder should register");
+ TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%BOOTSTRAP_JSON%", &bootstrapDescriptorA),
+ "Registry A bootstrap placeholder should register");
+ TEST_ASSERT_TRUE_MESSAGE(registryA.registerDynamicTemplate("%PORTAL_APP_JS%", &appJsDescriptorA),
+ "Registry A app JS placeholder should register");
+ TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%BOOTSTRAP_JSON%", &bootstrapDescriptorB),
+ "Registry B bootstrap placeholder should register");
+ TEST_ASSERT_TRUE_MESSAGE(registryB.registerDynamicTemplate("%PORTAL_APP_JS%", &appJsDescriptorB),
+ "Registry B app JS placeholder should register");
TemplateContext contextA;
TemplateContext contextB;
contextA.setRegistry(®istryA);
contextB.setRegistry(®istryB);
- TemplateRenderer::initializeContext(contextA, WM_ROOT_TEMPLATE);
- TemplateRenderer::initializeContext(contextB, WM_ROOT_TEMPLATE);
+ TemplateRenderer::initializeContext(contextA, WM_ROOT_SHELL_TEMPLATE);
+ TemplateRenderer::initializeContext(contextB, WM_ROOT_SHELL_TEMPLATE);
String outputA;
String outputB;
renderContextsInterleaved(contextA, outputA, contextB, outputB);
- TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("Menu A"));
- TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("Status A"));
- TEST_ASSERT_EQUAL(-1, outputA.indexOf("Menu B"));
- TEST_ASSERT_EQUAL(-1, outputA.indexOf("Status B"));
+ TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("{\"ctx\":\"A\"}"));
+ TEST_ASSERT_NOT_EQUAL(-1, outputA.indexOf("// shell A"));
+ TEST_ASSERT_EQUAL(-1, outputA.indexOf("{\"ctx\":\"B\"}"));
+ TEST_ASSERT_EQUAL(-1, outputA.indexOf("// shell B"));
- TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("Menu B"));
- TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("Status B"));
- TEST_ASSERT_EQUAL(-1, outputB.indexOf("Menu A"));
- TEST_ASSERT_EQUAL(-1, outputB.indexOf("Status A"));
+ TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("{\"ctx\":\"B\"}"));
+ TEST_ASSERT_NOT_EQUAL(-1, outputB.indexOf("// shell B"));
+ TEST_ASSERT_EQUAL(-1, outputB.indexOf("{\"ctx\":\"A\"}"));
+ TEST_ASSERT_EQUAL(-1, outputB.indexOf("// shell A"));
Serial.println("[TEST] Root render interleaved context isolation test completed successfully");
}
diff --git a/test/test_wifimanager/tests/test_template_rendering.cpp b/test/test_wifimanager/tests/test_template_rendering.cpp
index de068d3..b85733f 100644
--- a/test/test_wifimanager/tests/test_template_rendering.cpp
+++ b/test/test_wifimanager/tests/test_template_rendering.cpp
@@ -1,13 +1,7 @@
#include
#include
#include
-#include "templates/Fragments.h"
-#include "templates/Info.h"
-#include "templates/Param.h"
-#include "templates/PageShell.h"
-#include "templates/Root.h"
-#include "templates/WiFi.h"
-#include "templates/Message.h"
+#include "templates/RootShell.h"
#include "../test_main.h"
namespace {
@@ -31,221 +25,30 @@ String renderTemplate(const char* templateData, PlaceholderRegistry& registry) {
const char kEmpty[] PROGMEM = "";
const char kDocTitle[] PROGMEM = "Config ESP";
-const char kBodyClass[] PROGMEM = "wifi";
-const char kPageScripts[] PROGMEM = "";
-const char kPageStyles[] PROGMEM = "";
-const char kPageContent[] PROGMEM = "Body
";
-const char kScanContent[] PROGMEM = "Scanning for networks...
";
-const char kSsidPlaceholder[] PROGMEM = "Office";
-const char kPasswordPlaceholder[] PROGMEM = "********";
-const char kStaticFields[] PROGMEM = "
";
-const char kParamSection[] PROGMEM = "
";
-const char kFormActions[] PROGMEM = "
";
-const char kPageActions[] PROGMEM = "
";
-const char kMessageActions[] PROGMEM = "
";
-const char kStatus[] PROGMEM = "Status
";
-const char kMessageBody[] PROGMEM = "Saved
";
-const char kInfoSection[] PROGMEM = "About
- Build date
- today
";
-const char kInfoFooter[] PROGMEM = "
";
-const char kPageTitle[] PROGMEM = "WiFiManager";
-const char kSubtitle[] PROGMEM = "Setup";
-const char kMenu[] PROGMEM = "";
-const char kActionPrefix[] PROGMEM = "
";
-const char kActionPath[] PROGMEM = "/update";
-const char kActionMethod[] PROGMEM = "get";
-const char kActionLabel[] PROGMEM = "Update";
-const char kActionClassAttr[] PROGMEM = " class='D'";
-const char kActionSuffix[] PROGMEM = "
\n";
-const char kButtonId[] PROGMEM = "refresh-btn";
-const char kButtonType[] PROGMEM = "button";
-const char kButtonOnClick[] PROGMEM = "return refreshScan()";
-const char kButtonExtraAttrs[] PROGMEM = " data-skip-initial-scan='true'";
-const char kInfoLabel[] PROGMEM = "SDK version";
-const char kInfoValue[] PROGMEM = "3.1.2";
-const char kSectionTitle[] PROGMEM = "Device";
-const char kSectionRows[] PROGMEM = "Chip IDabc";
+const char kBootstrapJson[] PROGMEM = "{\"title\":\"Test\"}";
+const char kPortalAppJs[] PROGMEM = "console.log('portal');";
} // namespace
-void test_page_shell_supports_page_assets() {
- Serial.println("[TEST] Testing page shell page-scoped assets...");
-
- PlaceholderRegistry registry(12);
- registry.registerProgmemData("%SCRIPTS%", kEmpty);
- registry.registerProgmemData("%PAGE_SCRIPTS%", kPageScripts);
- registry.registerProgmemData("%STYLES%", kEmpty);
- registry.registerProgmemData("%PAGE_STYLES%", kPageStyles);
- registry.registerProgmemData("%DOC_TITLE%", kDocTitle);
- registry.registerProgmemData("%BODY_CLASS%", kBodyClass);
- registry.registerProgmemData("%PAGE_CONTENT%", kPageContent);
-
- String output = renderTemplate(WM_PAGE_SHELL_TEMPLATE, registry);
-
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("wifi page"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf(".wifi{color:#000;}"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Body
"));
-
- Serial.println("[TEST] Page shell page-scoped assets test completed successfully");
-}
-
-void test_wifi_template_renders_scan_container_and_form_fields() {
- Serial.println("[TEST] Testing WiFi page template rendering...");
-
- PlaceholderRegistry registry(16);
- registry.registerProgmemData("%WIFI_SCAN_CONTENT%", kScanContent);
- registry.registerProgmemData("%WIFI_SSID_PLACEHOLDER%", kSsidPlaceholder);
- registry.registerProgmemData("%WIFI_PASSWORD_PLACEHOLDER%", kPasswordPlaceholder);
- registry.registerProgmemData("%WIFI_STATIC_FIELDS%", kStaticFields);
- registry.registerProgmemData("%WIFI_PARAM_SECTION%", kParamSection);
- registry.registerProgmemData("%WIFI_FORM_ACTIONS%", kFormActions);
- registry.registerProgmemData("%WIFI_PAGE_ACTIONS%", kPageActions);
- registry.registerProgmemData("%WIFI_STATUS%", kStatus);
-
- String output = renderTemplate(WM_WIFI_CONTENT_TEMPLATE, registry);
-
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='scan-results'"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Scanning for networks..."));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("placeholder='Office'"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='refresh-btn'"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Token"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Save"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Back"));
-
- Serial.println("[TEST] WiFi page template rendering test completed successfully");
-}
-
-void test_message_template_renders_body_and_actions() {
- Serial.println("[TEST] Testing message template rendering...");
-
- PlaceholderRegistry registry(8);
- registry.registerProgmemData("%MESSAGE_BODY%", kMessageBody);
- registry.registerProgmemData("%MESSAGE_ACTIONS%", kMessageActions);
-
- String output = renderTemplate(WM_MESSAGE_CONTENT_TEMPLATE, registry);
-
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Saved"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Back"));
-
- Serial.println("[TEST] Message template rendering test completed successfully");
-}
-
-void test_param_template_renders_shared_actions() {
- Serial.println("[TEST] Testing param page template rendering...");
-
- PlaceholderRegistry registry(8);
- registry.registerProgmemData("%PARAM_FIELDS%", kParamSection);
- registry.registerProgmemData("%PARAM_FORM_ACTIONS%", kFormActions);
- registry.registerProgmemData("%PARAM_PAGE_ACTIONS%", kMessageActions);
- registry.registerProgmemData("%PARAM_STATUS%", kStatus);
-
- String output = renderTemplate(WM_PARAM_CONTENT_TEMPLATE, registry);
-
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Token"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Save"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Back"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Status"));
-
- Serial.println("[TEST] Param page template rendering test completed successfully");
-}
-
-void test_info_template_renders_sections_and_footer() {
- Serial.println("[TEST] Testing info page template rendering...");
-
- PlaceholderRegistry registry(8);
- registry.registerProgmemData("%INFO_STATUS%", kStatus);
- registry.registerProgmemData("%INFO_DEVICE_SECTION%", kInfoSection);
- registry.registerProgmemData("%INFO_WIFI_SECTION%", kInfoSection);
- registry.registerProgmemData("%INFO_ABOUT_SECTION%", kInfoSection);
- registry.registerProgmemData("%INFO_FOOTER%", kInfoFooter);
-
- String output = renderTemplate(WM_INFO_CONTENT_TEMPLATE, registry);
-
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Status"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Build date"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Update"));
-
- Serial.println("[TEST] Info page template rendering test completed successfully");
-}
-
-void test_fragment_action_form_renders_shared_button_markup() {
- Serial.println("[TEST] Testing shared action fragment rendering...");
-
- PlaceholderRegistry registry(8);
- registry.registerProgmemData("%ACTION_PREFIX%", kActionPrefix);
- registry.registerProgmemData("%ACTION%", kActionPath);
- registry.registerProgmemData("%METHOD%", kActionMethod);
- registry.registerProgmemData("%BUTTON_LABEL%", kActionLabel);
- registry.registerProgmemData("%BUTTON_CLASS_ATTR%", kActionClassAttr);
- registry.registerProgmemData("%ACTION_SUFFIX%", kActionSuffix);
-
- String output = renderTemplate(WM_ACTION_FORM_TEMPLATE, registry);
-
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("action='/update'"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("class='D'"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf(">Update"));
-
- Serial.println("[TEST] Shared action fragment rendering test completed successfully");
-}
-
-void test_fragment_info_row_and_section_render() {
- Serial.println("[TEST] Testing shared info fragment rendering...");
-
- PlaceholderRegistry rowRegistry(4);
- rowRegistry.registerProgmemData("%INFO_LABEL%", kInfoLabel);
- rowRegistry.registerProgmemData("%INFO_VALUE%", kInfoValue);
-
- String rowOutput = renderTemplate(WM_INFO_ROW_TEMPLATE, rowRegistry);
- TEST_ASSERT_NOT_EQUAL(-1, rowOutput.indexOf("SDK version3.1.2"));
-
- PlaceholderRegistry sectionRegistry(6);
- sectionRegistry.registerProgmemData("%SECTION_PREFIX%", kEmpty);
- sectionRegistry.registerProgmemData("%SECTION_TITLE%", kSectionTitle);
- sectionRegistry.registerProgmemData("%SECTION_ROWS%", kSectionRows);
- sectionRegistry.registerProgmemData("%SECTION_SUFFIX%", kActionSuffix);
-
- String sectionOutput = renderTemplate(WM_INFO_SECTION_TEMPLATE, sectionRegistry);
- TEST_ASSERT_NOT_EQUAL(-1, sectionOutput.indexOf("Device
"));
- TEST_ASSERT_NOT_EQUAL(-1, sectionOutput.indexOf("Chip IDabc"));
-
- Serial.println("[TEST] Shared info fragment rendering test completed successfully");
-}
-
-void test_fragment_centered_button_supports_extra_attrs() {
- Serial.println("[TEST] Testing centered button fragment extra attrs...");
-
- PlaceholderRegistry registry(8);
- registry.registerProgmemData("%BUTTON_ID%", kButtonId);
- registry.registerProgmemData("%BUTTON_TYPE%", kButtonType);
- registry.registerProgmemData("%BUTTON_ONCLICK%", kButtonOnClick);
- registry.registerProgmemData("%BUTTON_LABEL%", kActionLabel);
- registry.registerProgmemData("%BUTTON_EXTRA_ATTRS%", kButtonExtraAttrs);
-
- String output = renderTemplate(WM_CENTERED_BUTTON_TEMPLATE, registry);
-
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='refresh-btn'"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("data-skip-initial-scan='true'"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf(">Update"));
-
- Serial.println("[TEST] Centered button fragment extra attrs test completed successfully");
-}
-
-void test_root_template_renders_core_placeholders() {
- Serial.println("[TEST] Testing root template rendering...");
+void test_shell_template_renders_core_placeholders() {
+ Serial.println("[TEST] Testing portal shell template rendering...");
PlaceholderRegistry registry(8);
registry.registerProgmemData("%SCRIPTS%", kEmpty);
registry.registerProgmemData("%STYLES%", kEmpty);
- registry.registerProgmemData("%PAGE_TITLE%", kPageTitle);
- registry.registerProgmemData("%SUBTITLE%", kSubtitle);
- registry.registerProgmemData("%MENU%", kMenu);
- registry.registerProgmemData("%STATUS%", kStatus);
+ registry.registerProgmemData("%PAGE_TITLE%", kDocTitle);
+ registry.registerProgmemData("%BOOTSTRAP_JSON%", kBootstrapJson);
+ registry.registerProgmemData("%PORTAL_APP_JS%", kPortalAppJs);
- String output = renderTemplate(WM_ROOT_TEMPLATE, registry);
+ String output = renderTemplate(WM_ROOT_SHELL_TEMPLATE, registry);
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("WiFiManager"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Setup"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Configure WiFi"));
- TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Status"));
+ TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("Config ESP"));
+ TEST_ASSERT_NOT_EQUAL(-1, output.indexOf(""));
+ TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-toast'"));
+ TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-dialog'"));
+ TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("id='wm-bootstrap'"));
+ TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("{\"title\":\"Test\"}"));
+ TEST_ASSERT_NOT_EQUAL(-1, output.indexOf("console.log('portal')"));
- Serial.println("[TEST] Root template rendering test completed successfully");
+ Serial.println("[TEST] Portal shell template rendering test completed successfully");
}