WiFiManager: structured logging, SPA portal, DFTE bridge.

Replace DEBUG_WM with WiFiManagerLogLevel/log(), optional WiFiManagerLogSink,
and WM_NO_LOG stripping. Add JSON API handlers and shell templates (RootShell,
PortalAppJS). Bridge DFTE logging with owner-scoped teardown; add migrate_debug_wm
helper; document and set WM_LOG_LEVEL in platformio.
This commit is contained in:
2026-04-20 23:28:57 +10:00
parent a7d1755bcf
commit ac42d9a342
17 changed files with 1574 additions and 943 deletions
+5 -7
View File
@@ -66,7 +66,7 @@ wm.rebuildPlaceholderRegistry(); // optional: after changing callback while por
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 (`DEBUG_WM`) and DFTE logging
## Debug: WiFiManager logging and DFTE
### How DFTE logging is designed
@@ -74,16 +74,14 @@ DFTE keeps a **single global pointer** `deviceFrameworkTemplateEngineLogger` (se
### WiFiManager today
- **`DEBUG_WM`**: compile-time gated with **`WM_DEBUG_LEVEL`** (see commented `#define WM_DFTE_LOGGING` / `WM_DEBUG_LEVEL` in `WiFiManager.h`). At runtime, `_debug`, `_debugLevel`, `_debugPrefix`, and `_debugPort` control volume and destination (`Serial` unless **`WM_DEBUG_PORT`** is set). This is tuned for **firmware size** (strip verbose strings when the macro is absent) and familiar serial prefixes (`*wm:`).
- **`WM_DFTE_LOGGING`** (build flag, **off by default**): when defined, `WiFiManagerServer::setupTemplateEngine()` registers **`WiFiManagerDfteLogger`**, which forwards DFTE messages into **`DEBUG_WM`** with a `[DFTE]` prefix and WM levels (error → `WM_DEBUG_ERROR`, warn → `WM_DEBUG_NOTIFY`, info → `WM_DEBUG_VERBOSE`, debug → `WM_DEBUG_DEV`). If **`deviceFrameworkTemplateEngineIsLoggingEnabled()`** is already true (for example **DeviceFramework** installed `WebInterfaceTemplateEngineLogger` first), WiFiManager **does not replace** that sink; it only owns teardown when it installed its own. **`shutdownServer()`** disables logging and drops the adapter only when WiFiManager installed it—avoiding clobbering a host logger on portal shutdown.
- **`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.
### Should WiFiManager’s logging be refactored to match DFTE’s hook style?
### Design note
**Not as a big-bang rewrite.** DFTE’s model fits a **small library** with one pipeline: optional sink, zero cost when disabled. WiFiManager has **hundreds** of `DEBUG_WM` sites and relies on **`#ifdef WM_DEBUG_LEVEL`** to remove entire print paths from the binary. Replacing that with only a runtime pointer would either **regain flash/RAM** for the strings or require keeping both mechanisms.
A practical middle path (what we did here) is: **keep `DEBUG_WM` + `WM_DEBUG_LEVEL` for WM-native traces**, and **optionally bridge DFTE into the same stream** with `WM_DFTE_LOGGING`. If you later want a unified sink for *both*, a small internal `WiFiManagerLogSink` interface *behind* `DEBUG_WM` could forward to `Serial` or a custom `Print`—that is incremental and does not force every call site through a virtual function in release builds.
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.
## License
+42 -53
View File
@@ -24,8 +24,19 @@
#include <functional>
#include "WiFiManagerParameter.h"
#include "WiFiManagerLogLevel.h"
// #define WM_DFTE_LOGGING // opt-in: register a DFTE log sink that forwards to DEBUG_WM (see README)
#ifdef WM_NODEBUG
#ifndef WM_NO_LOG
#define WM_NO_LOG
#endif
#endif
#ifndef WM_LOG_LEVEL
#define WM_LOG_LEVEL 0
#endif
// #define WM_DFTE_LOGGING // opt-in: bridge DFTE logging into WiFiManager (see README)
// #define WM_MDNS // includes MDNS, also set MDNS with sethostname
// #define WM_FIXERASECONFIG // use erase flash fix
// #define WM_ERASE_NVS // esp32 erase(true) will erase NVS
@@ -185,20 +196,11 @@ const char WM_VERSION_STR[] PROGMEM = "v2.0.18";
// WiFiManagerRequestArgs is defined as a nested class inside WiFiManager (after WM_WebServer is defined)
// debugging
typedef enum {
WM_DEBUG_SILENT = 0, // debug OFF but still compiled for runtime
WM_DEBUG_ERROR = 1, // error only
WM_DEBUG_NOTIFY = 2, // default stable,INFO
WM_DEBUG_VERBOSE = 3, // move verbose info
WM_DEBUG_DEV = 4, // development useful debugging info
WM_DEBUG_MAX = 5 // MAX extra dev auditing, var dumps etc (MAX+1 will print timing,mem and frag info)
} wm_debuglevel_t;
// Forward declarations
class WiFiManagerServer;
class WiFiManagerHandlers;
class WiFiManagerDfteLogger;
class WiFiManagerLogSink;
class WiFiManager
{
@@ -357,10 +359,21 @@ class WiFiManager
// lets you disable automatically connecting after save from webportal
void setSaveConnect(bool connect = true);
// toggle debug output
void setDebugOutput(boolean debug);
void setDebugOutput(boolean debug, String prefix); // log line prefix, default "*wm:"
void setDebugOutput(boolean debug, wm_debuglevel_t level ); // log line prefix, default "*wm:"
void setLogEnabled(boolean enabled);
void setLogPrefix(String prefix);
void setLogOutput(boolean enabled, WiFiManagerLogLevel maxLevel);
/** When non-null, log lines go here instead of the Print stream. */
void setLogSink(WiFiManagerLogSink* sink);
WiFiManagerLogSink* getLogSink();
template<typename T>
void log(WiFiManagerLogLevel level, const char* subsystem, T&& text);
template<typename T, typename U>
void log(WiFiManagerLogLevel level, const char* subsystem, T&& a, U&& b);
void log(WiFiManagerLogLevel level, const char* subsystem, const String& text);
//set min quality percentage to include in scan, defaults to 8% if not specified
void setMinimumSignalQuality(int quality = 8);
@@ -839,8 +852,6 @@ protected:
#endif
protected:
friend class WiFiManagerDfteLogger; // WM_DFTE_LOGGING: bridge calls protected DEBUG_WM
//helpers (rendering methods moved to WiFiManagerHandlers)
boolean isIp(String str);
String toStringIp(IPAddress ip);
@@ -867,46 +878,24 @@ protected:
int _max_params;
WiFiManagerParameter** _params = NULL;
boolean _debug = true;
String _debugPrefix = "*wm:"; // default debug prefix
boolean _logEnabled = true;
String _logPrefix = "*wm:";
WiFiManagerLogLevel _logTagMinLevel = WiFiManagerLogLevel::Debug;
wm_debuglevel_t debugLvlShow = WM_DEBUG_VERBOSE; // at which level start showing [n] level tags
// build debuglevel support
// @todo use DEBUG_ESP_x?
// Set default debug level
#ifndef WM_DEBUG_LEVEL
#define WM_DEBUG_LEVEL WM_DEBUG_NOTIFY
#endif
// override debug level OFF
#ifdef WM_NODEBUG
#undef WM_DEBUG_LEVEL
#endif
#ifdef WM_DEBUG_LEVEL
uint8_t _debugLevel = (uint8_t)WM_DEBUG_LEVEL;
#else
uint8_t _debugLevel = 0; // default debug level
#endif
#ifndef WM_NO_LOG
WiFiManagerLogLevel _runtimeMaxLevel = static_cast<WiFiManagerLogLevel>((uint8_t)WM_LOG_LEVEL);
#else
WiFiManagerLogLevel _runtimeMaxLevel = WiFiManagerLogLevel::Silent;
#endif
// @todo use DEBUG_ESP_PORT ?
#ifdef WM_DEBUG_PORT
Print& _debugPort = WM_DEBUG_PORT;
Print& _logPort = WM_DEBUG_PORT;
#else
Print& _debugPort = Serial; // debug output stream ref
Print& _logPort = Serial;
#endif
template <typename Generic>
void DEBUG_WM(Generic text);
template <typename Generic>
void DEBUG_WM(wm_debuglevel_t level,Generic text);
template <typename Generic, typename Genericb>
void DEBUG_WM(Generic text,Genericb textb);
template <typename Generic, typename Genericb>
void DEBUG_WM(wm_debuglevel_t level, Generic text,Genericb textb);
void emitLogImpl(WiFiManagerLogLevel level, const char* subsystem, const String& textA, const String& textB);
// callbacks
// @todo use cb list (vector) maybe event ids, allow no return value
@@ -920,19 +909,19 @@ protected:
std::function<void()> _preotaupdatecallback;
std::function<void()> _configportaltimeoutcallback;
WiFiManagerLogSink* _logSink = nullptr;
template <class T>
auto optionalIPFromString(T *obj, const char *s) -> decltype( obj->fromString(s) ) {
return obj->fromString(s);
}
auto optionalIPFromString(...) -> bool {
// DEBUG_WM("NO fromString METHOD ON IPAddress, you need ESP8266 core 2.1.0 or newer for Custom IP configuration to work.");
return false;
}
};
// Include template implementations for DEBUG_WM
#include "WiFiManagerDebug.h"
#include "WiFiManagerLogTemplates.h"
#endif
@@ -1,74 +0,0 @@
/**
* WiFiManagerDebug.h
*
* Debug output macros and templates for WiFiManager
*
* @author tablatronix
* @author Alex Hope-O'Connor
* @license MIT
*/
#ifndef WiFiManagerDebug_h
#define WiFiManagerDebug_h
#if defined(ESP8266) || defined(ESP32)
// Template implementations for DEBUG_WM - must be in header for template instantiation
// This file is included at the end of WiFiManager.h after the class definition
template <typename Generic>
void WiFiManager::DEBUG_WM(Generic text) {
DEBUG_WM(WM_DEBUG_NOTIFY,text,"");
}
template <typename Generic>
void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text) {
if(_debugLevel >= level) DEBUG_WM(level,text,"");
}
template <typename Generic, typename Genericb>
void WiFiManager::DEBUG_WM(Generic text,Genericb textb) {
DEBUG_WM(WM_DEBUG_NOTIFY,text,textb);
}
template <typename Generic, typename Genericb>
void WiFiManager::DEBUG_WM(wm_debuglevel_t level,Generic text,Genericb textb) {
if(!_debug || _debugLevel < level) return;
if(_debugLevel >= WM_DEBUG_MAX){
#ifdef ESP8266
// uint32_t free;
// uint16_t max;
// uint8_t frag;
// ESP.getHeapStats(&free, &max, &frag);// @todo Does not exist in 2.3.0
// _debugPort.printf("[MEM] free: %5d | max: %5d | frag: %3d%% \n", free, max, frag);
#elif defined ESP32
// total_free_bytes; ///< Total free bytes in the heap. Equivalent to multi_free_heap_size().
// total_allocated_bytes; ///< Total bytes allocated to data in the heap.
// largest_free_block; ///< Size of largest free block in the heap. This is the largest malloc-able size.
// minimum_free_bytes; ///< Lifetime minimum free heap size. Equivalent to multi_minimum_free_heap_size().
// allocated_blocks; ///< Number of (variable size) blocks allocated in the heap.
// free_blocks; ///< Number of (variable size) free blocks in the heap.
// total_blocks; ///< Total number of (variable size) blocks in the heap.
multi_heap_info_t info;
heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
uint32_t free = info.total_free_bytes;
uint16_t max = info.largest_free_block;
uint8_t frag = 100 - (max * 100) / free;
_debugPort.printf("[MEM] free: %5u | max: %5u | frag: %3u%% \n", free, max, frag);
#endif
}
_debugPort.print(_debugPrefix);
if(_debugLevel >= debugLvlShow) _debugPort.print("["+(String)level+"] ");
_debugPort.print(text);
if(textb){
_debugPort.print(" ");
_debugPort.print(textb);
}
_debugPort.println();
}
#endif // defined(ESP8266) || defined(ESP32)
#endif // WiFiManagerDebug_h
@@ -1,7 +1,7 @@
/**
* WiFiManagerDfteLogger.h
*
* Optional bridge from DFTE's logging hooks to WiFiManager::DEBUG_WM.
* Optional bridge from DFTE's logging hooks to WiFiManager::log.
* Enabled only when the sketch / library build defines WM_DFTE_LOGGING.
*/
@@ -14,38 +14,26 @@
#include "WiFiManager.h"
/**
* Forwards DeviceFrameworkTemplateEngineLogger calls to WiFiManager's
* existing DEBUG_WM pipeline (respects _debug, _debugLevel, _debugPrefix, _debugPort).
* Forwards DeviceFrameworkTemplateEngineLogger calls to WiFiManager logging
* with subsystem WM/DFTE.
*/
class WiFiManagerDfteLogger : public DeviceFrameworkTemplateEngineLogger {
public:
explicit WiFiManagerDfteLogger(WiFiManager* wm) : _wm(wm) {}
void error(const String& msg) override {
logLine(WM_DEBUG_ERROR, msg);
}
void warn(const String& msg) override {
logLine(WM_DEBUG_NOTIFY, msg);
}
void info(const String& msg) override {
logLine(WM_DEBUG_VERBOSE, msg);
}
void debug(const String& msg) override {
logLine(WM_DEBUG_DEV, msg);
}
void error(const String& msg) override { logLine(WiFiManagerLogLevel::Error, msg); }
void warn(const String& msg) override { logLine(WiFiManagerLogLevel::Warn, msg); }
void info(const String& msg) override { logLine(WiFiManagerLogLevel::Debug, msg); }
void debug(const String& msg) override { logLine(WiFiManagerLogLevel::Trace, msg); }
private:
WiFiManager* _wm;
void logLine(wm_debuglevel_t level, const String& msg) {
void logLine(WiFiManagerLogLevel level, const String& msg) {
if (!_wm) {
return;
}
String line;
line.reserve(static_cast<unsigned>(msg.length()) + 12u);
line += F("[DFTE] ");
line += msg;
_wm->DEBUG_WM(level, line);
_wm->log(level, "WM/DFTE", msg);
}
};
@@ -69,6 +69,21 @@ class WiFiManagerHandlers {
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);
void handleApiWifiMeta(AsyncWebServerRequest *request);
void handleApiWifiSave(AsyncWebServerRequest *request);
void handleApiParamsGet(AsyncWebServerRequest *request);
void handleApiParamsSave(AsyncWebServerRequest *request);
void handleApiInfo(AsyncWebServerRequest *request);
void handleApiStatus(AsyncWebServerRequest *request);
void handleApiDeviceRestart(AsyncWebServerRequest *request);
void handleApiDeviceErase(AsyncWebServerRequest *request);
void handleApiPortalClose(AsyncWebServerRequest *request);
void handleApiPortalExit(AsyncWebServerRequest *request);
// Captive Portal
boolean captivePortal(AsyncWebServerRequest *request);
@@ -91,6 +106,11 @@ class WiFiManagerHandlers {
WiFiManager* _wm;
void collectVisibleScanResults(std::vector<const WiFiManager::WiFiScanNetwork*>& networks);
void appendVisibleScanResultsJson(String& json, const std::vector<const WiFiManager::WiFiScanNetwork*>& networks);
String buildPortalBootstrapJson();
void applyWifiAndParamsFromRequest(AsyncWebServerRequest *request);
void buildPlainStatusSummary(String& out);
static void sendApiJson(AsyncWebServerRequest *request, int code, const String& json);
};
#endif // defined(ESP8266) || defined(ESP32)
@@ -0,0 +1,32 @@
/**
* WiFiManagerLogLevel.h
*
* 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.
*/
#ifndef WiFiManagerLogLevel_h
#define WiFiManagerLogLevel_h
#include <Arduino.h>
/** Default subsystem tag for core WiFiManager messages (macro for C++11 embedded toolchains). */
#define kWiFiMgrLogSubsystem "WM"
enum class WiFiManagerLogLevel : uint8_t {
Silent = 0,
Error = 1,
Warn = 2,
Info = 3,
Debug = 4,
Trace = 5,
};
/** Full line delivered to Print or WiFiManagerLogSink (prefix and tags included). */
struct WiFiManagerLogMessage {
WiFiManagerLogLevel level;
const char* subsystem;
String line;
};
#endif
@@ -0,0 +1,19 @@
/**
* WiFiManagerLogSink.h
*
* Optional host-provided sink for WiFiManager log output. When set, logs are
* delivered here instead of the configured Print stream (usually Serial).
*/
#ifndef WiFiManagerLogSink_h
#define WiFiManagerLogSink_h
#include "WiFiManagerLogLevel.h"
class WiFiManagerLogSink {
public:
virtual void log(const WiFiManagerLogMessage& msg) = 0;
virtual ~WiFiManagerLogSink() = default;
};
#endif
@@ -0,0 +1,45 @@
/**
* WiFiManagerLogTemplates.h
*
* Template implementations for WiFiManager::log(...). Included at the end of WiFiManager.h.
*/
#ifndef WiFiManagerLogTemplates_h
#define WiFiManagerLogTemplates_h
#include "WiFiManagerLogLevel.h"
#include <IPAddress.h>
#include <WString.h>
#if defined(ESP8266) || defined(ESP32)
inline String wmLogStringifyArg(const __FlashStringHelper* v) {
return String(v);
}
inline String wmLogStringifyArg(const IPAddress& v) {
return v.toString();
}
template<typename T>
inline String wmLogStringifyArg(const T& v) {
return String(v);
}
template<typename T>
inline void WiFiManager::log(WiFiManagerLogLevel level, const char* subsystem, T&& text) {
#ifndef WM_NO_LOG
emitLogImpl(level, subsystem, wmLogStringifyArg(text), String());
#endif
}
template<typename T, typename U>
inline void WiFiManager::log(WiFiManagerLogLevel level, const char* subsystem, T&& a, U&& b) {
#ifndef WM_NO_LOG
emitLogImpl(level, subsystem, wmLogStringifyArg(a), wmLogStringifyArg(b));
#endif
}
#endif // ESP8266 || ESP32
#endif // WiFiManagerLogTemplates_h
+15 -1
View File
@@ -55,6 +55,20 @@ 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";
const char R_api_wifi_scan[] PROGMEM = "/api/wifi/scan";
const char R_api_wifi_meta[] PROGMEM = "/api/wifi/meta";
const char R_api_wifi_save[] PROGMEM = "/api/wifi/save";
const char R_api_params[] PROGMEM = "/api/params";
const char R_api_params_save[] PROGMEM = "/api/params/save";
const char R_api_info[] PROGMEM = "/api/info";
const char R_api_status[] PROGMEM = "/api/status";
const char R_api_device_restart[] PROGMEM = "/api/device/restart";
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";
class WiFiManagerServer {
public:
// Singleton access
@@ -122,7 +136,7 @@ class WiFiManagerServer {
std::function<void(PlaceholderRegistry&)> _tplSetupCallback;
#ifdef WM_DFTE_LOGGING
// DFTE -> DEBUG_WM bridge (only installed when WM_DFTE_LOGGING and no other logger registered)
// DFTE -> WiFiManager::log bridge (only installed when WM_DFTE_LOGGING and no other logger registered)
std::unique_ptr<WiFiManagerDfteLogger> _dfteLogger;
bool _wmOwnsDfteLogSink = false;
#endif
@@ -0,0 +1,214 @@
/**
* PortalAppJS.h
* Embedded SPA for WiFiManager config portal (hash routing + JSON APIs).
*/
#ifndef _WM_PORTAL_APP_JS_H_
#define _WM_PORTAL_APP_JS_H_
#include <Arduino.h>
const char PORTAL_APP_JS[] PROGMEM = R"rawliteral(
(function(){
'use strict';
var boot={};
try{var el=document.getElementById('wm-bootstrap');if(el&&el.textContent)boot=JSON.parse(el.textContent);}catch(e){boot={};}
function $(id){return document.getElementById(id);}
function esc(t){if(!t)return'';return String(t).replace(/[&<>"']/g,function(c){return({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]);});}
function setView(html){
var a=$('app');
if(!a)return;
a.innerHTML="<div class='wrap'>"+html+"</div>";
}
function navBar(){
var f=boot.features||{};
var h="<p style='text-align:center'>";
h+="<a href='#/'>Home</a> &middot; ";
h+="<a href='#/wifi'>WiFi</a>";
if(!f.paramsInWifi){h+=" &middot; <a href='#/setup'>Setup</a>";}
h+=" &middot; <a href='#/info'>Info</a>";
h+=" &middot; <a href='#/update'>Update</a>";
h+="</p><hr/>";
return h;
}
function viewHome(){
var sub=esc(boot.subtitle||'');
var t=esc(boot.title||'WiFiManager');
var html=navBar();
html+="<h1>"+t+"</h1><h3>"+sub+"</h3>";
html+="<p>Use the links above to configure WiFi, view device info, or update firmware.</p>";
setView(html);
}
function api(path, opt){
opt=opt||{};
return fetch(path,{method:opt.method||'GET',headers:opt.headers,body:opt.body,credentials:'same-origin'})
.then(function(r){return r.text().then(function(t){return {ok:r.ok,status:r.status,body:t};});});
}
function scanPollTimer(){}
function renderScanList(data){
if(!data||data.scanning){return '<p>Scanning for networks...</p>';}
if(data.error==='timeout')return '<p>Scan timed out. Try Refresh.</p>';
if(data.error==='failed')return '<p>Scan failed.</p>';
if(!data.networks||data.count===0)return '<p>No networks found.</p>';
var html='',i,n,q,pct,qi,enc;
for(i=0;i<data.networks.length;i++){
n=data.networks[i];
pct=(n.quality||0)+'%';
qi=Math.round((n.quality/100)*3)+1;if(qi<1)qi=1;if(qi>4)qi=4;
enc=n.encrypted?'l':'';
html+="<div><a href='#p' onclick='return portalPickSsid(this)' data-ssid='"+esc(n.ssid)+"'>"+esc(n.ssid)+"</a>";
html+="<div role='img' aria-label='"+pct+"' title='"+pct+"' class='q q-"+qi+" "+enc+"'></div>";
html+="<div class='q'>"+pct+"</div></div>";
}
return html;
}
window.portalPickSsid=function(el){
var s=el.getAttribute('data-ssid');
var inp=document.getElementById('wm-s');
if(inp&&s){inp.value=s;}
return false;
};
function wifiRefresh(){
var box=document.getElementById('wm-scan-results');
if(!box)return;
box.innerHTML='<p>Starting scan...</p>';
api('/api/wifi/scan',{method:'POST'}).then(function(){
var iv=setInterval(function(){
api('/api/wifi/scan-status').then(function(res){
try{var d=JSON.parse(res.body);}catch(e){return;}
if(box)box.innerHTML=renderScanList(d);
if(!d.scanning)clearInterval(iv);
});
},800);
});
}
function viewWifi(){
setView(navBar()+"<h2>WiFi</h2><p>Loading...</p>");
api('/api/wifi/meta').then(function(res){
try{var m=JSON.parse(res.body);}catch(e){m={};}
var html=navBar()+"<h2>WiFi</h2>";
html+="<div id='wm-scan-results'></div>";
html+="<form id='wm-wifi-form' onsubmit='return portalWifiSave(event)'>";
html+="<label>SSID</label><input id='wm-s' name='s' maxlength='32' value='"+esc(m.ssidPlaceholder||'')+"'/>";
html+="<label>Password</label><input id='wm-p' name='p' type='password' maxlength='64' placeholder='"+esc(m.passwordPlaceholder||'')+"'/>";
html+="<input type='checkbox' id='wm-showpass' onclick='var p=document.getElementById(\"wm-p\");p.type=this.checked?\"text\":\"password\"'/> <label for='wm-showpass'>Show password</label>";
if(m.staticFieldsHtml){html+=m.staticFieldsHtml;}
if(m.paramsHtml){html+=m.paramsHtml;}
html+="<button type='submit'>Save</button></form>";
html+="<br/><button type='button' id='wm-refresh-scan' onclick='wifiRefresh()'>Refresh scan</button>";
html+="<div id='wm-wifi-msg'></div>";
setView(html);
api('/api/wifi/scan-status').then(function(r2){
try{var d=JSON.parse(r2.body);}catch(e){d={};}
var box=document.getElementById('wm-scan-results');
if(box)box.innerHTML=renderScanList(d);
if(d&&d.scanning){wifiRefresh();}
});
});
}
window.portalWifiSave=function(ev){
ev.preventDefault();
var fd=new FormData(document.getElementById('wm-wifi-form'));
var msg=$('wm-wifi-msg');
if(msg)msg.innerHTML='Saving...';
api('/api/wifi/save',{method:'POST',body:fd}).then(function(res){
try{var j=JSON.parse(res.body);}catch(e){j={ok:false};}
if(msg)msg.innerHTML=(j&&j.message)?esc(j.message):(res.ok?'Saved.':'Error');
});
return false;
};
function viewInfo(){
setView(navBar()+"<p>Loading info...</p>");
api('/api/info').then(function(res){
try{var d=JSON.parse(res.body);}catch(e){d={};}
var html=navBar()+"<h2>Info</h2>";
function section(title,rows){
if(!rows||!rows.length)return'';
var h="<h3>"+esc(title)+"</h3><hr/><dl>";
for(var i=0;i<rows.length;i++){
h+="<dt>"+esc(rows[i].label)+"</dt><dd>"+rows[i].value+"</dd>";
}
h+="</dl>";
return h;
}
html+=section('Status',d.status||[]);
html+=section('Device',d.device||[]);
html+=section('WiFi',d.wifi||[]);
html+=section('About',d.about||[]);
var act=d.actions||{};
if(act.showUpdate)html+="<p><a href='#/update'>Firmware update</a></p>";
if(act.showErase)html+="<p><button type='button' class='D' onclick='portalErase()'>Erase WiFi config</button></p>";
setView(html);
});
}
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');
});
};
function viewSetup(){
setView(navBar()+"<p>Loading...</p>");
api('/api/params').then(function(res){
try{var d=JSON.parse(res.body);}catch(e){d={};}
var html=navBar()+"<h2>Parameters</h2>";
html+="<form id='wm-param-form' onsubmit='return portalParamSave(event)'>"+(d.paramsHtml||'')+"<button type='submit'>Save</button></form>";
html+="<div id='wm-param-msg'></div>";
setView(html);
});
}
window.portalParamSave=function(ev){
ev.preventDefault();
var fd=new FormData(document.getElementById('wm-param-form'));
var msg=$('wm-param-msg');
if(msg)msg.innerHTML='Saving...';
api('/api/params/save',{method:'POST',body:fd}).then(function(res){
try{var j=JSON.parse(res.body);}catch(e){j={};}
if(msg)msg.innerHTML=(j&&j.message)?esc(j.message):'Saved.';
});
return false;
};
function viewUpdate(){
var html=navBar()+"<h2>Firmware update</h2>";
html+="<form method='POST' action='/u' enctype='multipart/form-data'>";
html+="<input type='file' name='update'/>";
html+="<button type='submit'>Update</button></form>";
html+="<p><small>Upload a .bin firmware. Device may restart after update.</small></p>";
setView(html);
}
function route(){
var h=location.hash||'#/';
if(h.indexOf('#/')!==0)h='#/';
if(h==='#/'||h==='#'){viewHome();return;}
if(h==='#/wifi'){viewWifi();return;}
if(h==='#/info'){viewInfo();return;}
if(h==='#/setup'){viewSetup();return;}
if(h==='#/update'){viewUpdate();return;}
viewHome();
}
window.addEventListener('hashchange',route);
window.addEventListener('load',route);
})();
)rawliteral";
#endif // _WM_PORTAL_APP_JS_H_
@@ -9,7 +9,7 @@
#ifdef WM_CUSTOM_ROOT_TEMPLATE_HEADER
#include WM_CUSTOM_ROOT_TEMPLATE_HEADER
#else
#include "Root.h"
#include "RootShell.h"
#endif
#endif // _WM_ROOT_SELECTOR_H_
@@ -0,0 +1,32 @@
/**
* RootShell.h
* Single-page portal shell: only HTML document served for GET /.
* Dynamic UI is driven by embedded JS + JSON APIs under /api/*.
*/
#ifndef _WM_ROOT_SHELL_TEMPLATE_H_
#define _WM_ROOT_SHELL_TEMPLATE_H_
#include <Arduino.h>
const char WM_ROOT_SHELL_TEMPLATE[] PROGMEM =
"<!DOCTYPE html>"
"<html lang='en'>"
"<head>"
"<meta name='format-detection' content='telephone=no'>"
"<meta charset='UTF-8'/>"
"<meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'/>"
"<title>%PAGE_TITLE%</title>"
"%SCRIPTS%"
"%STYLES%"
"</head>"
"<body class='portal'>"
"<div id='app'></div>"
"<script id='wm-bootstrap' type='application/json'>%BOOTSTRAP_JSON%</script>"
"<script>"
"%PORTAL_APP_JS%"
"</script>"
"</body>"
"</html>";
#endif // _WM_ROOT_SHELL_TEMPLATE_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+56 -19
View File
@@ -17,7 +17,6 @@
#endif
#include "templates/CSS.h"
#include "templates/JS.h"
#include "templates/Root.h"
#if defined(ESP8266) || defined(ESP32)
@@ -141,7 +140,7 @@ void WiFiManagerServer::setupTemplateEngine() {
if (!_dfteLogger) {
_dfteLogger = std::make_unique<WiFiManagerDfteLogger>(_wm);
}
deviceFrameworkTemplateEngineEnableLogging(_dfteLogger.get());
deviceFrameworkTemplateEngineEnableLogging(_dfteLogger.get(), static_cast<const void*>(this));
_wmOwnsDfteLogSink = true;
}
#endif
@@ -150,19 +149,19 @@ void WiFiManagerServer::setupTemplateEngine() {
void WiFiManagerServer::createServer(uint16_t port) {
// If server already exists, shutdown first to prevent leaks and connection drops
if (server) {
#ifdef WM_DEBUG_LEVEL
_wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("Server already exists, shutting down first"));
#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("Server already exists, shutting down first"));
#endif
shutdownServer();
}
#ifdef WM_DEBUG_LEVEL
_wm->DEBUG_WM(F("Starting Web Portal"));
#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Info, kWiFiMgrLogSubsystem, F("Starting Web Portal"));
#endif
if(port != 80) {
#ifdef WM_DEBUG_LEVEL
_wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("http server started with custom port: "), port);
#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("http server started with custom port: "), port);
#endif
}
@@ -174,8 +173,8 @@ void WiFiManagerServer::createServer(uint16_t port) {
void WiFiManagerServer::registerRoutes() {
if (!server) {
#ifdef WM_DEBUG_LEVEL
_wm->DEBUG_WM(WM_DEBUG_ERROR, F("[ERROR] Server not created, call createServer() first"));
#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Error, kWiFiMgrLogSubsystem, F("[ERROR] Server not created, call createServer() first"));
#endif
return;
}
@@ -186,8 +185,8 @@ void WiFiManagerServer::registerRoutes() {
}
if (_wm->_webservercallback != NULL) {
#ifdef WM_DEBUG_LEVEL
_wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("[CB] _webservercallback calling"));
#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("[CB] _webservercallback calling"));
#endif
_wm->_webservercallback(); // @CALLBACK
}
@@ -198,6 +197,46 @@ void WiFiManagerServer::registerRoutes() {
server->on(WM_G(R_root), HTTP_GET, [this](AsyncWebServerRequest *request) {
this->_handlers->handleRoot(request);
});
server->on(WM_G(R_api_bootstrap), HTTP_GET, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiBootstrap(request);
});
server->on(WM_G(R_api_wifi_scan_status), HTTP_GET, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiWifiScanStatus(request);
});
server->on(WM_G(R_api_wifi_scan), HTTP_POST, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiWifiScan(request);
});
server->on(WM_G(R_api_wifi_meta), HTTP_GET, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiWifiMeta(request);
});
server->on(WM_G(R_api_wifi_save), HTTP_POST, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiWifiSave(request);
});
server->on(WM_G(R_api_params), HTTP_GET, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiParamsGet(request);
});
server->on(WM_G(R_api_params_save), HTTP_POST, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiParamsSave(request);
});
server->on(WM_G(R_api_info), HTTP_GET, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiInfo(request);
});
server->on(WM_G(R_api_status), HTTP_GET, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiStatus(request);
});
server->on(WM_G(R_api_device_restart), HTTP_POST, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiDeviceRestart(request);
});
server->on(WM_G(R_api_device_erase), HTTP_POST, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiDeviceErase(request);
});
server->on(WM_G(R_api_portal_close), HTTP_POST, [this](AsyncWebServerRequest *request) {
this->_handlers->handleApiPortalClose(request);
});
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);
@@ -267,8 +306,8 @@ void WiFiManagerServer::registerRoutes() {
});
server->begin(); // Web server start
#ifdef WM_DEBUG_LEVEL
_wm->DEBUG_WM(WM_DEBUG_VERBOSE, F("HTTP server started"));
#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Debug, kWiFiMgrLogSubsystem, F("HTTP server started"));
#endif
}
@@ -277,8 +316,8 @@ void WiFiManagerServer::setupDNSD() {
/* Setup the DNS server redirecting all the domains to the apIP */
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
#ifdef WM_DEBUG_LEVEL
_wm->DEBUG_WM(WM_DEBUG_DEV, F("dns server started with ip: "), WiFi.softAPIP());
#ifndef WM_NO_LOG
_wm->log(WiFiManagerLogLevel::Trace, kWiFiMgrLogSubsystem, F("dns server started with ip: "), WiFi.softAPIP());
#endif
const uint8_t DNS_PORT = 53;
dnsServer->start(DNS_PORT, F("*"), WiFi.softAPIP());
@@ -293,9 +332,7 @@ void WiFiManagerServer::processDNS() {
void WiFiManagerServer::shutdownServer() {
#ifdef WM_DFTE_LOGGING
if (_wmOwnsDfteLogSink) {
if (deviceFrameworkTemplateEngineGetLogger() == _dfteLogger.get()) {
deviceFrameworkTemplateEngineDisableLogging();
}
deviceFrameworkTemplateEngineDisableLoggingForOwner(static_cast<const void*>(this));
_dfteLogger.reset();
_wmOwnsDfteLogSink = false;
}
+5 -1
View File
@@ -11,6 +11,8 @@ platform_packages =
monitor_speed = 115200
test_framework = unity
test_build_src = no
build_flags =
-DWM_LOG_LEVEL=5
lib_deps =
ESP32Async/ESPAsyncWebServer@^3.9.1
symlink://../DFTE
@@ -26,13 +28,15 @@ build_unflags =
-std=gnu++11
build_flags =
-std=gnu++14
-DWM_LOG_LEVEL=5
lib_deps =
ESP32Async/ESPAsyncWebServer@^3.9.1
symlink://../DFTE
; Optional: compile tests with DFTE logs bridged into DEBUG_WM (see README)
; Optional: compile tests with DFTE logs bridged into WiFiManager::log (see README)
[env:esp8266_dfte_log]
extends = env:esp8266
build_flags =
${env:esp8266.build_flags}
-DWM_DFTE_LOGGING
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""One-shot migration: DEBUG_WM -> WiFiManager::log(...). Run from repo root."""
import re
import sys
SUB = "kWiFiMgrLogSubsystem"
REPLACEMENTS = [
# Longest / most specific first (with optional whitespace)
(r"_wm->\s*DEBUG_WM\s*\(\s*WM_DEBUG_ERROR\s*,", f"_wm->log(WiFiManagerLogLevel::Error, {SUB},"),
(r"_wm->\s*DEBUG_WM\s*\(\s*WM_DEBUG_VERBOSE\s*,", f"_wm->log(WiFiManagerLogLevel::Debug, {SUB},"),
(r"_wm->\s*DEBUG_WM\s*\(\s*WM_DEBUG_DEV\s*,", f"_wm->log(WiFiManagerLogLevel::Trace, {SUB},"),
(r"_wm->\s*DEBUG_WM\s*\(\s*WM_DEBUG_NOTIFY\s*,", f"_wm->log(WiFiManagerLogLevel::Info, {SUB},"),
(r"_wm->\s*DEBUG_WM\s*\(\s*WM_DEBUG_MAX\s*,", f"_wm->log(WiFiManagerLogLevel::Trace, {SUB},"),
(r"DEBUG_WM\s*\(\s*WM_DEBUG_ERROR\s*,", f"log(WiFiManagerLogLevel::Error, {SUB},"),
(r"DEBUG_WM\s*\(\s*WM_DEBUG_VERBOSE\s*,", f"log(WiFiManagerLogLevel::Debug, {SUB},"),
(r"DEBUG_WM\s*\(\s*WM_DEBUG_DEV\s*,", f"log(WiFiManagerLogLevel::Trace, {SUB},"),
(r"DEBUG_WM\s*\(\s*WM_DEBUG_NOTIFY\s*,", f"log(WiFiManagerLogLevel::Info, {SUB},"),
(r"DEBUG_WM\s*\(\s*WM_DEBUG_MAX\s*,", f"log(WiFiManagerLogLevel::Trace, {SUB},"),
# Default / notify-level (no explicit WM_DEBUG_* level)
(r"_wm->\s*DEBUG_WM\s*\(\s*F\(", f"_wm->log(WiFiManagerLogLevel::Info, {SUB}, F("),
(r"DEBUG_WM\s*\(\s*F\(", f"log(WiFiManagerLogLevel::Info, {SUB}, F("),
(r"_wm->\s*DEBUG_WM\s*\(", f"_wm->log(WiFiManagerLogLevel::Info, {SUB}, "),
(r"DEBUG_WM\s*\(", f"log(WiFiManagerLogLevel::Info, {SUB}, "),
]
def migrate(content: str) -> str:
content = content.replace("#ifdef WM_DEBUG_LEVEL", "#ifndef WM_NO_LOG")
content = content.replace("#ifdef WM_DEBUG_LEVEL", "#ifndef WM_NO_LOG")
for pat, repl in REPLACEMENTS:
content = re.sub(pat, repl, content)
return content
def main():
for path in sys.argv[1:]:
with open(path, "r", encoding="utf-8") as f:
old = f.read()
new = migrate(old)
if new != old:
with open(path, "w", encoding="utf-8") as f:
f.write(new)
print(f"updated: {path}")
else:
print(f"unchanged: {path}")
if __name__ == "__main__":
main()