DFTE docs, single root library.json, optional DFTE logging bridge

- Document fork/DFTE in README; WiFiManager template setup API forwards to server
- Remove nested lib/WiFiManager/library.json; rely on root library.json only
- Delete legacy extras/ tooling; point HTML help link at this fork
- Add WiFiManagerDfteLogger and WM_DFTE_LOGGING integration in WiFiManagerServer
- Pin ESP8266 Arduino core (postmortem jump fix) in platformio.ini for tests
This commit is contained in:
2026-04-19 21:16:02 +10:00
parent 55f1fd7ce6
commit a7d1755bcf
15 changed files with 205 additions and 727 deletions
+70 -16
View File
@@ -1,36 +1,90 @@
# WiFiManager
Espressif ESPx WiFi Connection manager with fallback web configuration portal
Espressif ESPx WiFi connection manager with a fallback web configuration portal.
## ⚠️ Fork Notice
## Fork notice
**This is a fork with breaking changes.** This fork has been modified and optimized specifically for PlatformIO usage.
**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.
**Repository:** [alexhopeoconnor/WiFiManager](https://github.com/alexhopeoconnor/WiFiManager)
**This repository:** [alexhopeoconnor/WiFiManager](https://github.com/alexhopeoconnor/WiFiManager)
**For the original, unmodified version of WiFiManager, please visit the upstream repository:**
- [tzapu/WiFiManager](https://github.com/tzapu/WiFiManager)
**Upstream (original):** [tzapu/WiFiManager](https://github.com/tzapu/WiFiManager)
This fork may contain breaking changes and is not compatible with the upstream version. Use at your own risk.
## Dependencies
## Authors
**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:
- **Alex Hope-O'Connor** ([@alexhopeoconnor](https://github.com/alexhopeoconnor)) - Fork maintainer
- Original work by **tzapu** and contributors
```ini
lib_deps =
ESP32Async/ESPAsyncWebServer@3.9.1
symlink://../DFTE
```
## Installation
Clone `DFTE` next to `WiFiManager` (same parent directory), or point `lib_deps` at the Git URL used in `library.json`.
This fork is optimized for PlatformIO. Add to your `platformio.ini`:
Other pinned dependency: **ESP32Async/ESPAsyncWebServer** (see `library.json`).
## Installation (PlatformIO)
```ini
[env:your_environment]
platform = espressif8266 ; or espressif32
board = d1_mini ; your board
platform = espressif8266 ; or espressif32
board = d1_mini ; your board
framework = arduino
lib_deps =
path = ../../../WiFiManager ; Relative path to this library
https://github.com/alexhopeoconnor/WiFiManager.git
```
Or a local path:
```ini
lib_deps =
file:///path/to/WiFiManager
```
## HTML customization (DFTE)
- **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 (`DEBUG_WM`) and DFTE logging
### 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
- **`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.
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?
**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.
## License
See [LICENSE](LICENSE) file for details.
See [LICENSE](LICENSE).
-400
View File
@@ -1,400 +0,0 @@
<!-- HTTP_HEAD -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>
<title>{v}</title>
<!-- /HTTP_HEAD -->
<!-- HTTP_STYLE -->
<style>
:root{
/* CSS VARIABLE THEME COLOR */
/*--primarycolor:#9933CC;*/
--primarycolor:#1fa3ec;
}
body.invert,
body.invert a,
body.invert h1 {
background-color:#060606;
color:white;
}
body.invert .msg{
background-color: #282828;
border-top: 1px solid #555;
border-right: 1px solid #555;
border-bottom: 1px solid #555;
color:#fff;
}
body.invert .q[role=img] {
-webkit-filter: invert(1);
filter: invert(1);
}
.c,
body {
text-align: center;
font-family: verdana
}
.wrap {
text-align:left;
display:inline-block;
min-width:260px;
max-width:500px;
}
div,
input,select {
padding: 5px;
font-size: 1em;
/*width: 100%;*/
/*/*margin: 3px 0 10px 0; // BEFORE spacing shows label closer to input, but need to do same for AFTER*/
margin: 5px 0;
box-sizing: border-box;
}
div{
margin: 5px 0;
}
input,button,select,.msg{
border-radius:.3rem;
width: 100%;
}
input[type=radio],input[type=checkbox]{
width: auto;
}
button,input[type="button"],input[type="submit"] {
border: 0;
background-color: var(--primarycolor);
color: #fff;
line-height: 2.4rem;
font-size: 1.2rem;
}
input[type="file"]{
border: 1px solid var(--primarycolor);
}
a {
color: #000;
font-weight: 700;
text-decoration: none;
}
a:hover {
color: var(--primarycolor);
text-decoration: underline;
}
.h {
display: none;
}
.q {
height: 16px;
margin: 0;
padding: 0 5px;
text-align: right;
min-width: 38px;
float:right;
}
.q.q-0:after {
background-position-x: 0;
}
.q.q-1:after {
background-position-x: -16px;
}
.q.q-2:after {
background-position-x: -32px;
}
.q.q-3:after {
background-position-x: -48px;
}
.q.q-4:after {
background-position-x: -64px;
}
.q.l:before {
background-position-x: -80px;
padding-right: 5px
}
.ql .q {
float: left;
}
.q:after,
.q:before {
content: '';width:16px;height:16px;display:inline-block;background-repeat:no-repeat;background-position: 16px 0;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');
}
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
.q:before,
.q:after {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');
background-size: 95px 16px;
}
}
.msg {
padding: 20px;
margin: 20px 0;
border: 1px solid #eee;
border-left-width: 5px;
border-left-color: #777;
}
.msg h4 {
margin-top: 0;
margin-bottom: 5px;
}
.msg.P {
border-left-color: var(--primarycolor);
}
.msg.P h4 {
color: var(--primarycolor);
}
.msg.S {
border-left-color: #5cb85c;
}
.msg.S h4 {
color: #5cb85c;
}
.msg.D {
border-left-color: #dc3630;
}
.msg.D h4 {
color: #dc3630;
}
dt {
font-weight: bold;
}
dd {
margin: 0;
padding: 0 0 0.5em 0;
}
td {
vertical-align: top;
}
button.D{
background-color:#dc3630;
}
button{
/*transition: 0s filter;*/
transition: 0s opacity;
transition-delay: 3s;
transition-duration: 0s;
cursor: pointer;
}
button:active{
opacity: 50% !important;
/*filter: brightness(50%);*/
cursor: wait;
transition-delay: 0s;
}
button:hover{
/*opacity: 80%;*/
}
:disabled {
opacity: 0.5;
}
</style>
<!-- /HTTP_STYLE -->
<!-- HTTP_SCRIPT -->
<script>
function c(l){document.getElementById('s').value=l.innerText||l.textContent;
p = l.nextElementSibling.classList.contains('l');
document.getElementById('p').disabled = !p;
if(p)document.getElementById('p').focus()};
function f() {var x = document.getElementById('p');x.type==='password'?x.type='text':x.type='password';}
</script>
<!-- /HTTP_SCRIPT -->
<!-- HTTP_HEAD_END -->
</head>
<body class="invert">
<div class='wrap'>
<!-- <div class='wrap ql qinv'> -->
<!-- /HTTP_HEAD_END -->
<!-- SAMPLE -->
<h2>/</h2><hR>
<!-- /SAMPLE -->
<!-- HTTP_PORTAL_OPTIONS -->
<!-- /HTTP_PORTAL_OPTIONS -->
<!-- HTTP_PORTAL_MENU[] -->
<form action='/wifi' method='get'><button>Configure WiFi</button></form><br/>
<form action='/0wifi' method='get'><button>Configure WiFi (No Scan)</button></form><br/>
<form action='/info' method='get'><button>Info</button></form><br/>
<form action='/param' method='get'><button>Setup</button></form><br/>
<Hr><br/>
<form action='/close' method='post'><button>Close</button></form><br/>
<form action='/erase' method='post'><button class='D'>Erase</button></form><br/>
<form action='/restart' method='post'><button>Restart</button></form><br/>
<form action='/exit' method='post'><button>Exit</button></form><br/>
<form action='/exit' method='post'><button>Update</button></form><br/>
<form action='/' method='post'><button>Back</button></form><br/>
<!-- /HTTP_PORTAL_MENU -->
<!-- SAMPLE -->
<h2>/wifi</h2><hr>
<div><a href='#p' onclick='c(this)'>Access Point 01</a><div role='img' aria-label='88%' title='88%' class='q q-4 l'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 02</a><div role='img' aria-label='88%' title='88%' class='q q-4'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 03</a><div role='img' aria-label='60%' title='60%' class='q q-3 l'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 04</a><div role='img' aria-label='60%' title='60%' class='q q-3'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 05</a><div role='img' aria-label='60%' title='60%' class='q q-3'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 06</a><div role='img' aria-label='40%' title='40%' class='q q-2'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 07</a><div role='img' aria-label='40%' title='40%' class='q q-2 l'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 08</a><div role='img' aria-label='20%' title='20%' class='q q-1'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 09</a><div role='img' aria-label='20%' title='20%' class='q q-1'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 10</a><div role='img' aria-label='20%' title='20%' class='q q-4 l'></div><div class='q '>100%</div></div>
<div><a href='#p' onclick='c(this)'>Access Point 11</a><div role='img' aria-label='10%' title='10%' class='q q-3'></div><div class='q '>70%</div></div>
<div><a href='#p' onclick='c(this)'>Access Point 12</a><div role='img' aria-label='10%' title='10%' class='q q-1 l'></div><div class='q '>10%</div></div>
<div><a href='#p' onclick='c(this)'>Access Point 13</a><div role='img' aria-label='10%' title='10%' class='q q-0 h'></div><div class='q '>100%</div></div>
<div><a href='#p' onclick='c(this)'>Access Point 14</a><div class='q'>66%</div></div>
<div><a href='#p' onclick='c(this)'>Access Point 15</a><div class='q'>56%</div></div>
<!-- /SAMPLE -->
<!-- HTTP_ITEM -->
<div><a href='#p' onclick='c(this)'>{v}</a><div role='img' aria-label='{r}%' title='{r}%' class='q q-{q} {i}'></div></div>
<!-- /HTTP_ITEM -->
<!-- HTTP_FORM_START -->
<form method='get' action='wifisave'><label for='s'>SSID</label><br/><input id='s' name='s' length=32 placeholder='SSID'><br/><label for='p'>Password</label><input id='p' name='p' length=64 type='password' placeholder='password'><input type='checkbox' id='show-password' onclick='f()'> <label for='show-password'>Show Password</label><br/>
<!-- /HTTP_FORM_START -->
<!-- SAMPLE -->
<h3>custom parameter</h3><hr>
<br/><label for='custom'>Label for Custom Param</label>
<br/><input id='custom' name='custom' length='255' placeholder='placeholder' value='' {c}>
<!-- /SAMPLE -->
<!-- HTTP_FORM_LABEL -->
<br/><label for='{i}'>{p}</label>
<!-- /HTTP_FORM_LABEL -->
<!-- HTTP_FORM_PARAM -->
<br/><input id='{i}' name='{n}' length='{l}' placeholder='{p}' value='{v}' {c}>
<!-- /HTTP_FORM_PARAM -->
<!-- HTTP_FORM_END -->
<br/><button type='submit'>Save</button></form>
<!-- /HTTP_FORM_END -->
<!-- HTTP_SCAN_LINK -->
<br/><form action='/wifi' method='get'><button>Refresh</button></form>
<!-- /HTTP_SCAN_LINK -->
<!-- HTTP_SAVED -->
<div class='msg'><h4>Saving Credentials</h4>Trying to connect ESP to network.<br />If it fails reconnect to AP to try again</div>
<!-- /HTTP_SAVED -->
<!-- HTTP_STATUS_ON -->
<div class='msg P'><strong>Connected</strong> to {v}<br/><em><small>with IP {i}</small></em></div>
<!-- /HTTP_STATUS_ON -->
<!-- HTTP_STATUS_OFF -->
<div class='msg {c}'><strong>Not Connected</strong> to {v}{r}</div>
<!-- /HTTP_STATUS_OFF -->
<!-- sample -->
<div class='msg D'><strong>Not Connected</strong> to apname
<!-- /sample -->
<!-- HTTP_STATUS_OFFPW -->
<br/>Authentication Failure
<!-- /HTTP_STATUS_OFFPW -->
<!-- HTTP_STATUS_OFFNOAP -->
<br/>AP not found
<!-- /HTTP_STATUS_OFFNOAP -->
<!-- HTTP_STATUS_OFFFAIL -->
<br/>Could not Connect
<!-- /HTTP_STATUS_OFFFAIL -->
</div>
<!-- HTTP_STATUS_NONE -->
<div class='msg'>No AP set</div>
<!-- /HTTP_STATUS_NONE -->
<!-- SAMPLE -->
<div class='msg P'><h4>H4 Color Header P</h4>content</div>
<!-- /SAMPLE -->
<!-- SAMPLE -->
<div class='msg S'><h4>H4 Color Header S</h4>content</div>
<!-- /SAMPLE -->
<!-- SAMPLE -->
<h1>Heading 1</h1>
<h2>Heading 2</h2>
<h3>Heading 3</h3>
<h4>Heading 4</h4>
<h3>WIFI HEAD <small><em>(WIFI_OFF)</em></small></h3><hr>
<dl>
<dt>Chip ID</dt><dd>123456</dd>
<dt>Flash Chip ID</dt><dd>1234556</dd>
<dt>IDE Flash Size</dt><dd>4194304 bytes</dd>
<dt>Real Flash Size</dt><dd>4194304 bytes</dd>
<dt>Empty</dt><dd></dd>
<dt>Soft AP IP</dt><dd>192.168.4.1</dd>
<dt>Soft AP MAC</dt><dd>00:00:00:00:00:00</dd>
<dt>Station MAC</dt><dd>00:00:00:00:00:00</dd>
</dl>
<!-- /SAMPLE -->
<!-- HTTP_HELP -->
<br/><h3>Available Pages</h3><hr>
<table class='table'>
<thead><tr><th>Page</th><th>Function</th></tr></thead><tbody>
<tr><td><a href='/'>/</a></td>
<td>Menu page.</td></tr>
<tr><td><a href='/wifi'>/wifi</a></td>
<td>Show WiFi scan results and enter WiFi configuration.(/0wifi noscan)</td></tr>
<tr><td><a href='/wifisave'>/wifisave</a></td>
<td>Save WiFi configuration information and configure device. Needs variables supplied.</td></tr>
<tr><td><a href='/close'>/close</a></td>
<td>Close the configuration server and configuration WiFi network.</td></tr>
<tr><td><a href='/info'>/info</a></td>
<td>Information page</td></tr>
<tr><td><a href='/close'>/close</a></td>
<td>Close the captiveportal popup,configportal will remain active</td></tr>
<tr><td><a href='/exit'>/exit</a></td>
<td>Exit Config Portal, configportal will close</td></tr>
<tr><td><a href='/restart'>/restart</a></td>
<td>Reboot the device</td></tr>
<tr><td><a href='/erase'>/erase</a></td>
<td>Erase WiFi configuration and reboot Device. Device will not reconnect to a network until new WiFi configuration data is entered.</td></tr>
</table>
<h3>About</h3><hr>
Version v1.x.x-xxxxx<Br/>
Build_date<br/>
Build_file<br/>
Arduino_version<br/>
<p/>Github <a href='https://github.com/tzapu/WiFiManager'>https://github.com/tzapu/WiFiManager</a>
<!-- /HTTP_HELP -->
<!-- FORM_UPLOAD -->
<Br/><br/>Form UPLOAD<br/>
<form method='POST' action='u' enctype='multipart/form-data'>
<input type='file' name='update' accept='.bin,application/octet-stream' onchange="(function(el){document.getElementById('uploadbin').style.display = el.value=='' ? 'none' : 'initial';})(this)">
<button id='uploadbin' class="h">Submit</button>
<a href='http://192.168.4.1' target='_blank'><small>* Upload may not function inside captive portal, Open in browser - http://192.168.4.1</a></small>
<!-- /FORM_UPLOAD -->
<!-- FORM_EXTRAS -->
<br/><br/><br/><hr>
<br/>
<label for='custom'>Label for Custom Param</label>
<br/>
<input id='custom' name='custom' length='255' placeholder='placeholder' value='' {c}>
<br/>
<!-- INOUT CHECKBOX -->
<!-- <label for='input_checkbox'>Label for Input Checkbox</label> -->
<input id='input_checkbox' name='input_checkbox' placeholder='placeholder' value='' type="checkbox" {c}>
<label for='input_checkbox'>Label for Input Checkbox</label>
<br/>
<!-- INPUT CHOICE -->
<p>Select Choice</p>
<input style='display: inline-block;' type='radio' id='choice1' name='program_selection' value='1'>
<label for='choice1'>Choice1</label><br>
<input style='display: inline-block;' type='radio' id='choice2' name='program_selection' value='2'>
<label for='choice2'>Choice2</label><br>
<!-- INPUT SELECT -->
<br/>
<label for='input_select'>Label for Input Select</label>
<select name="input_select" id="input_select" class="button">
<option value="0">Option 1</option>
<option value="1" selected>Option 2</option>
<option value="2">Option 3</option>
<option value="3">Option 4</option>
</select>
</form>
<!-- /FORM_EXTRAS -->
<!-- HTTP_END -->
</div>
</body>
</html>
<!-- /HTTP_END -->
-60
View File
@@ -1,60 +0,0 @@
'use strict';
const fs = require('fs');
console.log('starting');
const inFile = 'WiFiManager.template.html';
const outFile = 'template.h';
const defineRegEx = /<!-- ([A-Z_]+) -->/gm;
console.log('parsing', inFile);
fs.readFile(inFile, 'utf8', function (err,data) {
if (err) {
return console.log(err);
}
//console.log(data);
let defines = data.match(defineRegEx);
//console.log(defines);
var stream = fs.createWriteStream(outFile);
stream.once('open', function(fd) {
for (const i in defines) {
const start = defines[i];
const end = start.replace('<!-- ', '<!-- /')
defineRegEx.lastIndex = 0;
const constantName = defineRegEx.exec(start)[1];
console.log(constantName);
var extractRE = new RegExp(start + '([\\s\\S]+)' + end, 'gm');
let extractArray = extractRE.exec(data);
if(extractArray.length > 1) {
let def = extractArray[1];
//console.log(def);
//minimise a bit
def = def.replace(/\s+/g, ' ');
def = def.replace(/>\s+</g, '><');
def = def.trim();
//more extraneous spaces - possible bad results, needs to be checked
//def = def.replace(/(\w)\s(\W)|(\W)\s(\w)|(\W)\s(\W)/g, '$1$2$3$4$5$6');
def = def.replace(/(\w)\s(\W)|(\W)\s(\w)/g, '$1$2$3$4');
//escape double quotes
def = def.replace(/\\([\s\S])|(")/g, "\\$1$2");
console.log(def);
//const char HTTP_HEAD[] PROGMEM =
let string = 'const char ' + constantName + '[] PROGMEM';
for (let i = string.length; i < 42; i++) {
string += ' ';
}
string += '= "' + def + '";\n';
stream.write(string);
}
}
stream.end();
});
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

-12
View File
@@ -1,12 +0,0 @@
const char HTTP_HEAD[] PROGMEM = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\"content=\"width=device-width,initial-scale=1,user-scalable=no\"/><title>{v}</title>";
const char HTTP_STYLE[] PROGMEM = "<style> .c{text-align:center;}div,input{padding:5px;font-size:1em;}input{width:95%;}body{text-align:center;font-family:verdana;}button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;} .q{float:right;width:64px;text-align:right;} .l{background:url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAALVBMVEX///8EBwfBwsLw8PAzNjaCg4NTVVUjJiZDRUUUFxdiZGSho6OSk5Pg4eFydHTCjaf3AAAAZElEQVQ4je2NSw7AIAhEBamKn97/uMXEGBvozkWb9C2Zx4xzWykBhFAeYp9gkLyZE0zIMno9n4g19hmdY39scwqVkOXaxph0ZCXQcqxSpgQpONa59wkRDOL93eAXvimwlbPbwwVAegLS1HGfZAAAAABJRU5ErkJggg==\")no-repeat left center;background-size:1em;} </style>";
const char HTTP_SCRIPT[] PROGMEM = "<script>function c(l){document.getElementById('s').value=l.innerText||l.textContent;document.getElementById('p').focus();} </script>";
const char HTTP_HEAD_END[] PROGMEM = "</head><body><div style=\"text-align:left;display:inline-block;min-width:260px;\">";
const char HTTP_PORTAL_OPTIONS[] PROGMEM = "<form action=\"/wifi\"method=\"get\"><button>Configure WiFi</button></form><br/><form action=\"/0wifi\"method=\"get\"><button>Configure WiFi(No Scan)</button></form><br/><form action=\"/i\"method=\"get\"><button>Info</button></form><br/><form action=\"/r\"method=\"post\"><button>Reset</button></form>";
const char HTTP_ITEM[] PROGMEM = "<div><a href='#p'onclick='c(this)'>{v}</a>&nbsp;<span class='q{i}'>{r}%</span></div>";
const char HTTP_FORM_START[] PROGMEM = "<form method='get'action='wifisave'><input id='s'name='s'length=32 placeholder='SSID'><br/><input id='p'name='p'length=64 type='password'placeholder='password'><br/>";
const char HTTP_FORM_PARAM[] PROGMEM = "<br/><input id='{i}'name='{n}'length={l}placeholder='{p}'value='{v}' {c}>";
const char HTTP_FORM_END[] PROGMEM = "<br/><button type='submit'>save</button></form>";
const char HTTP_SCAN_LINK[] PROGMEM = "<br/><div class=\"c\"><a href=\"/wifi\">Scan</a></div>";
const char HTTP_SAVED[] PROGMEM = "<div>Credentials Saved<br/>Trying to connect ESP to network.<br/>If it fails reconnect to AP to try again</div>";
const char HTTP_END[] PROGMEM = "</div></body></html>";
-182
View File
@@ -1,182 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
<title>Config ESP</title>
<script>
function c(l,e) {
console.log(l);
// preventDefault();
document.getElementById('s').value = l.innerText || l.textContent;
// document.location.href = document.location +"#wmform";
p = l.nextElementSibling.classList.contains("l");
document.getElementById('p').disabled = !p;
if(p)document.getElementById('p').focus();
return false;
}
</script>
<style>
.c,
body {
text-align: center
}
div,
input {
padding: 5px;
font-size: 1em
}
input {
width: 95%
}
body {
font-family: verdana
}
button {
border: 0;
border-radius: .3rem;
background-color: #1fa3ec;
color: #fff;
line-height: 2.4rem;
font-size: 1.2rem;
width: 100%
}
a {
color: #000;
font-weight: 700;
text-decoration: none
}
a:hover {
color: #1fa3ec;
text-decoration: underline
}
.q {
height: 16px;
margin: 0;
padding: 0 5px;
text-align: right;
min-width: 38px
}
.q.q-0:after {
background-position-x: 0
}
.q.q-1:after {
background-position-x: -16px
}
.q.q-2:after {
background-position-x: -32px
}
.q.q-3:after {
background-position-x: -48px
}
.q.q-4:after {
background-position-x: -64px
}
.q.l:before {
background-position-x: -80px;
padding-right: 5px
}
.ql .q {
float: left
}
.qr .q {
float: right
}
.qinv .q {
-webkit-filter: invert(1);
filter: invert(1)
}
.q:after,
.q:before {
content: '';
width: 16px;
height: 16px;
display: inline-block;
background-repeat: no-repeat;
background-position: 16px 0;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAAAQCAMAAADeZIrLAAAAJFBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHJj5lAAAAC3RSTlMAIjN3iJmqu8zd7vF8pzcAAABsSURBVHja7Y1BCsAwCASNSVo3/v+/BUEiXnIoXkoX5jAQMxTHzK9cVSnvDxwD8bFx8PhZ9q8FmghXBhqA1faxk92PsxvRc2CCCFdhQCbRkLoAQ3q/wWUBqG35ZxtVzW4Ed6LngPyBU2CobdIDQ5oPWI5nCUwAAAAASUVORK5CYII=');
}
@media (-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dpi) {
.q:before,
.q:after {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALwAAAAgCAMAAACfM+KhAAAALVBMVEX///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAOrOgAAAADnRSTlMAESIzRGZ3iJmqu8zd7gKjCLQAAACmSURBVHgB7dDBCoMwEEXRmKlVY3L//3NLhyzqIqSUggy8uxnhCR5Mo8xLt+14aZ7wwgsvvPA/ofv9+44334UXXngvb6XsFhO/VoC2RsSv9J7x8BnYLW+AjT56ud/uePMdb7IP8Bsc/e7h8Cfk912ghsNXWPpDC4hvN+D1560A1QPORyh84VKLjjdvfPFm++i9EWq0348XXnjhhT+4dIbCW+WjZim9AKk4UZMnnCEuAAAAAElFTkSuQmCC');
background-size: 95px 16px;
}
}
input:disabled {
opacity: 0.5;
}
</style>
</head>
<body>
<!-- classes, left/right invert -->
<div class="qr" style='text-align:left;display:inline-block;min-width:260px;'>
<div><a href='#p' onclick='c(this)'>Access Point 01</a><div role='img' aria-label='88%' title='88%' class='q q-4 l'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 02</a><div role='img' aria-label='88%' title='88%' class='q q-4'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 03</a><div role='img' aria-label='88%' title='88%' class='q q-3'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 04</a><div role='img' aria-label='88%' title='88%' class='q q-3'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 05</a><div role='img' aria-label='88%' title='88%' class='q q-3'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 06</a><div role='img' aria-label='88%' title='88%' class='q q-2'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 07</a><div role='img' aria-label='88%' title='88%' class='q q-2'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 08</a><div role='img' aria-label='88%' title='88%' class='q q-1'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 09</a><div role='img' aria-label='88%' title='88%' class='q q-1'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 10</a><div role='img' aria-label='88%' title='88%' class='q q-1'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 11</a><div role='img' aria-label='88%' title='88%' class='q q-0'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 12</a><div role='img' aria-label='88%' title='88%' class='q q-0'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 13</a><div role='img' aria-label='88%' title='88%' class='q q-0'></div></div>
<div><a href='#p' onclick='c(this)'>Access Point 14</a><div role='img' aria-label='88%' title='88%' class='q q-0 l'></div></div>
<br/>
<form id="wmform" method='get' action='wifisave'>
<input id='s' name='s' length=32 placeholder='SSID'>
<br/>
<input id='p' name='p' length=64 type='password' placeholder='password'>
<br/>
<br/>
<input id='server' name='server' length=4 placeholder='mqtt server' value=''>
<br/>
<input id='port' name='port' length=5 placeholder='mqtt port' value='8080'>
<br/>
<input id='blynk' name='blynk' length=3 placeholder='blynk token' value='YOUR_BLYNK_TOKEN'>
<br/>
<br/>
<input id='ip' name='ip' length=15 placeholder='Static IP' value='10.0.1.56'>
<br/>
<input id='gw' name='gw' length=15 placeholder='Static Gateway' value='10.0.1.1'>
<br/>
<input id='sn' name='sn' length=15 placeholder='Subnet' value='255.255.255.0'>
<br/>
<br/>
<button type='submit'>save</button>
</form>
<br/>
<div class="c"><a href="/wifi">Scan</a></div>
</div>
</body>
</html>
+14 -1
View File
@@ -21,9 +21,11 @@
#endif
#include <vector>
#include <functional>
#include "WiFiManagerParameter.h"
// #define WM_DFTE_LOGGING // opt-in: register a DFTE log sink that forwards to DEBUG_WM (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
@@ -126,7 +128,7 @@
#include <memory>
#include <unordered_map>
#include <string>
#include <TemplateEngine.h>
// Include utility functions
#include "WiFiManagerUtils.h"
@@ -196,10 +198,12 @@ const char WM_VERSION_STR[] PROGMEM = "v2.0.18";
// Forward declarations
class WiFiManagerServer;
class WiFiManagerHandlers;
class WiFiManagerDfteLogger;
class WiFiManager
{
public:
// Forward declare nested class - defined later after WM_WebServer is available
class WiFiManagerRequestArgs;
@@ -432,6 +436,13 @@ 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<void(PlaceholderRegistry&)> cb);
// Rebuild the server’s shared PlaceholderRegistry (defaults + registerTemplateSetupCallback, or customizer if passed).
void rebuildPlaceholderRegistry(std::function<void(PlaceholderRegistry&)> customizer = nullptr);
// add params to its own menu page and remove from wifi, NOT TO BE COMBINED WITH setMenu!
void setParamsPage(bool enable);
@@ -565,6 +576,7 @@ class WiFiManager
protected:
// vars
std::unique_ptr<WiFiManagerServer> _serverManager;
std::function<void(PlaceholderRegistry&)> _templateSetupCallback;
// ip configs @todo struct ?
IPAddress _ap_static_ip;
@@ -827,6 +839,7 @@ protected:
#endif
protected:
friend class WiFiManagerDfteLogger; // WM_DFTE_LOGGING: bridge calls protected DEBUG_WM
//helpers (rendering methods moved to WiFiManagerHandlers)
boolean isIp(String str);
@@ -0,0 +1,54 @@
/**
* WiFiManagerDfteLogger.h
*
* Optional bridge from DFTE's logging hooks to WiFiManager::DEBUG_WM.
* Enabled only when the sketch / library build defines WM_DFTE_LOGGING.
*/
#ifndef WiFiManagerDfteLogger_h
#define WiFiManagerDfteLogger_h
#if defined(ESP8266) || defined(ESP32)
#include <DeviceFrameworkTemplateEngineDebug.h>
#include "WiFiManager.h"
/**
* Forwards DeviceFrameworkTemplateEngineLogger calls to WiFiManager's
* existing DEBUG_WM pipeline (respects _debug, _debugLevel, _debugPrefix, _debugPort).
*/
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);
}
private:
WiFiManager* _wm;
void logLine(wm_debuglevel_t 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);
}
};
#endif // ESP8266 || ESP32
#endif // WiFiManagerDfteLogger_h
@@ -32,6 +32,10 @@ class WiFiManager;
// Forward declaration - full definition needed in .cpp file
class WiFiManagerHandlers;
#ifdef WM_DFTE_LOGGING
class WiFiManagerDfteLogger;
#endif
// -----------------------------------------------------------------------------------------------
// HTTP ROUTES
@@ -57,6 +61,7 @@ class WiFiManagerServer {
static WiFiManagerServer* instance();
WiFiManagerServer(WiFiManager* wm);
~WiFiManagerServer();
// Template engine setup and access
void setupTemplateEngine();
@@ -116,6 +121,12 @@ class WiFiManagerServer {
std::unique_ptr<PlaceholderRegistry> _tplRegistry;
std::function<void(PlaceholderRegistry&)> _tplSetupCallback;
#ifdef WM_DFTE_LOGGING
// DFTE -> DEBUG_WM bridge (only installed when WM_DFTE_LOGGING and no other logger registered)
std::unique_ptr<WiFiManagerDfteLogger> _dfteLogger;
bool _wmOwnsDfteLogSink = false;
#endif
// Singleton instance
static WiFiManagerServer* s_instance;
};
+1 -1
View File
@@ -58,7 +58,7 @@ const char HTML_HELP[] PROGMEM =
"<tr><td>/erase</td>"
"<td>Erase WiFi configuration and reboot device. Device will not reconnect to a network until new WiFi configuration data is entered.</td></tr>"
"</table>"
"<p/>Github <a href='https://github.com/tzapu/WiFiManager'>https://github.com/tzapu/WiFiManager</a>.";
"<p/>Fork <a href='https://github.com/alexhopeoconnor/WiFiManager'>https://github.com/alexhopeoconnor/WiFiManager</a>.";
#else
const char HTML_HELP[] PROGMEM = "";
#endif
-51
View File
@@ -1,51 +0,0 @@
{
"name": "WiFiManager",
"version": "2.0.18",
"keywords": [
"wifi",
"wi-fi",
"esp",
"esp8266",
"esp32",
"espressif8266",
"espressif32",
"nodemcu",
"wemos",
"arduino"
],
"description": "WiFi Configuration manager with web configuration portal for ESP boards",
"authors": [
{
"name": "tzapu",
"url": "https://github.com/tzapu"
},
{
"name": "tablatronix",
"url": "https://github.com/tablatronix",
"maintainer": true
},
{
"name": "Alex Hope-O'Connor",
"url": "https://github.com/alexhopeoconnor"
}
],
"repository": {
"type": "git",
"url": "https://github.com/alexhopeoconnor/WiFiManager.git"
},
"frameworks": "arduino",
"platforms": [
"espressif8266",
"espressif32"
],
"dependencies": [
{
"name": "ESP32Async/ESPAsyncWebServer",
"version": "3.9.1"
},
"https://github.com/alexhopeoconnor/DFTE.git"
],
"export": {
"include": "include"
}
}
+15
View File
@@ -499,6 +499,7 @@ void WiFiManager::setupConfigPortal() {
// Lazy initialization: only create server manager when config portal is actually needed
if (!_serverManager) {
_serverManager = std::make_unique<WiFiManagerServer>(this);
_serverManager->registerTemplateSetupCallback(_templateSetupCallback);
}
_serverManager->createServer(_httpPort);
_serverManager->registerRoutes();
@@ -1969,6 +1970,20 @@ void WiFiManager::setTitle(String title){
_title = title;
}
void WiFiManager::registerTemplateSetupCallback(std::function<void(PlaceholderRegistry&)> cb) {
_templateSetupCallback = std::move(cb);
if (_serverManager) {
_serverManager->registerTemplateSetupCallback(_templateSetupCallback);
_serverManager->rebuildPlaceholderRegistry();
}
}
void WiFiManager::rebuildPlaceholderRegistry(std::function<void(PlaceholderRegistry&)> customizer) {
if (_serverManager) {
_serverManager->rebuildPlaceholderRegistry(std::move(customizer));
}
}
/**
* Set params as separate page not in wifi
* NOT COMPATIBLE WITH setMenu!
+27
View File
@@ -11,6 +11,10 @@
#include "WiFiManagerServer.h"
#include "WiFiManager.h"
#include "WiFiManagerHandlers.h" // Full definition needed for unique_ptr
#ifdef WM_DFTE_LOGGING
#include "WiFiManagerDfteLogger.h"
#include <DeviceFrameworkTemplateEngineDebug.h>
#endif
#include "templates/CSS.h"
#include "templates/JS.h"
#include "templates/Root.h"
@@ -28,6 +32,8 @@ WiFiManagerServer::WiFiManagerServer(WiFiManager* 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();
@@ -128,6 +134,17 @@ void WiFiManagerServer::setupTemplateEngine() {
// 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<WiFiManagerDfteLogger>(_wm);
}
deviceFrameworkTemplateEngineEnableLogging(_dfteLogger.get());
_wmOwnsDfteLogSink = true;
}
#endif
}
void WiFiManagerServer::createServer(uint16_t port) {
@@ -274,6 +291,16 @@ void WiFiManagerServer::processDNS() {
}
void WiFiManagerServer::shutdownServer() {
#ifdef WM_DFTE_LOGGING
if (_wmOwnsDfteLogSink) {
if (deviceFrameworkTemplateEngineGetLogger() == _dfteLogger.get()) {
deviceFrameworkTemplateEngineDisableLogging();
}
_dfteLogger.reset();
_wmOwnsDfteLogSink = false;
}
#endif
// AsyncWebServer doesn't have stop(), just reset the unique_ptr to free resources
if (server) {
server.reset();
+1 -1
View File
@@ -41,7 +41,7 @@
"dependencies": [
{
"name": "ESP32Async/ESPAsyncWebServer",
"version": "3.9.1"
"version": "^3.9.1"
},
"https://github.com/alexhopeoconnor/DFTE.git"
],
+11 -2
View File
@@ -5,11 +5,14 @@
platform = espressif8266
board = d1_mini
framework = arduino
; Postmortem jump relocation fix for large binaries (see ESP8266-LINKER-WORKAROUND.md in arduino-home-assistant)
platform_packages =
platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#521ae60a89e64bb0d1eb7a0b7addf620ced5cad3
monitor_speed = 115200
test_framework = unity
test_build_src = no
lib_deps =
ESP32Async/ESPAsyncWebServer@3.9.1
ESP32Async/ESPAsyncWebServer@^3.9.1
symlink://../DFTE
[env:esp32]
@@ -24,6 +27,12 @@ build_unflags =
build_flags =
-std=gnu++14
lib_deps =
ESP32Async/ESPAsyncWebServer@3.9.1
ESP32Async/ESPAsyncWebServer@^3.9.1
symlink://../DFTE
; Optional: compile tests with DFTE logs bridged into DEBUG_WM (see README)
[env:esp8266_dfte_log]
extends = env:esp8266
build_flags =
-DWM_DFTE_LOGGING