diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7fcd7f0..d40519d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,3 +36,4 @@ jobs: python-version: '3.11' - run: python -m pip install --upgrade platformio==6.1.19 - run: ./scripts/test.sh compile --platform ${{ matrix.environment }} + - run: ./scripts/test.sh examples --platform ${{ matrix.environment }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e4d75b..3b3186f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,9 @@ jobs: python-version: '3.11' - run: python -m pip install --upgrade platformio==6.1.19 - run: ./scripts/test.sh compile --platform esp8266 + - run: ./scripts/test.sh examples --platform esp8266 - run: ./scripts/test.sh compile --platform esp32 + - run: ./scripts/test.sh examples --platform esp32 - run: ./scripts/check-docs.sh - run: ./scripts/prepare-release.sh "$GITHUB_REF_NAME" - run: ./scripts/release-notes.sh "$GITHUB_REF_NAME" > "$RUNNER_TEMP/release-notes.md" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cdb395..d9ec41c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 3.2.0 + +- Refine the device-hosted portal with a startup Wi-Fi scan, stable loading overlays, clearer connection progress, and a resettable configuration timeout. +- Keep a successful portal-to-station hand-off reachable until the browser acknowledges its redirect, with a bounded fallback for captive or headless clients. +- Rename the presentation field `homeIntro` to `tagline` so portal identity and wording are clearer; update portal bootstrap contract to v3. +- Add focused Basic Portal, Branded Portal, Custom Portal Content, and Station Profiles examples for ESP8266 and ESP32, with real-hardware README captures. +- Strengthen release CI with documentation and clean-consumer/example compilation checks for both supported targets. + ## 3.1.0 - Add an opt-in primary/fallback station-profile controller with bounded failover, reconnection, a durable consumer-supplied store, and profile-aware portal APIs. DeviceFramework uses this to persist verified WiFi profiles transactionally. diff --git a/README.md b/README.md index 2f9fc2c..3b52fbf 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ # WiFiManager -WiFiManager is the maintained ESP8266/ESP32 configuration-portal fork used by DeviceFramework. It is a deliberate breaking fork of upstream [`tzapu/WiFiManager`](https://github.com/tzapu/WiFiManager), with a streamed single-page portal and typed JSON APIs rather than upstream’s legacy page/template model. +WiFiManager gives ESP8266 and ESP32 firmware a polished, self-hosted Wi-Fi setup experience. It reconnects to saved networks and opens a temporary captive portal when it cannot, so users can configure the device from any phone or browser without a cloud service or companion app. -## Why use it +## See it on real hardware -- **Single-shell portal:** one responsive SPA for WiFi, parameters, information, actions, and firmware update flow. -- **Data-first APIs:** portal state and actions are exposed under `/api/...`, not scraped from HTML. -- **Controlled portal UI:** semantic identity/theme values and structured portal APIs without exposing portal internals. -- **ESP8266 and ESP32:** clean PlatformIO consumers resolve the right asynchronous TCP transport automatically. +| Branded ESP32 portal | ESP8266 portal with an application field | +| --- | --- | +| ![Branded WiFiManager portal overview on an ESP32.](docs/assets/portal-esp32-branded-overview.png) | ![WiFiManager network picker and custom MQTT broker field on an ESP8266.](docs/assets/portal-esp8266-custom-wifi.png) | -## Try it +These are unmodified browser captures of [Branded Portal](examples/BrandedPortal/) and [Custom Portal Content](examples/CustomPortalContent/) running on the supported boards. The portal is served by the device itself; no cloud UI or companion app is involved. + +## Start with a working portal ```cpp #include @@ -20,17 +21,17 @@ WiFiManager wifi; void setup() { Serial.begin(115200); wifi.setConfigPortalTimeout(180); - if (!wifi.autoConnect("Device Setup", "change-me")) { - ESP.restart(); - } + wifi.autoConnect("Device Setup", "change-me"); } -void loop() {} +void loop() { + wifi.process(); +} ``` -For a DeviceFramework device, configure the framework’s shared device password instead. DeviceFramework applies it consistently to the provisioning AP, OTA, HTTP Basic authentication, and WebSerial. +When saved Wi-Fi is unavailable, `autoConnect()` starts the portal asynchronously. Call `process()` from every `loop()` iteration while it may be open. Flash [Basic Portal](examples/BasicPortal/) to try this exact flow; its README gives the network name, password, portal address, and expected result after saving Wi-Fi. -### Brand it +## Make it yours ```cpp const char kTitle[] PROGMEM = "Set up Temperature Monitor"; @@ -53,40 +54,41 @@ void setup() { wifi.setPortalConfig(kPortalUI); // before the portal starts wifi.autoConnect("Device Setup"); } + +void loop() { + wifi.process(); +} ``` -The full standalone example, including a static SVG, is [BrandedPortal](examples/BrandedPortal/BrandedPortal.ino). +The [Branded Portal](examples/BrandedPortal/) example includes a static SVG, accessible identity text, and a small semantic theme. [Custom Portal Content](examples/CustomPortalContent/) shows the supported parameters, information sections, and home cards without replacing the portal shell. -## Explore the portal +## What it provides + +- **Captive Wi-Fi setup:** starts an access point only when saved network credentials cannot connect. +- **Responsive portal:** one small portal for Wi-Fi, application fields, information, actions, and firmware update flow. +- **Structured APIs:** JSON endpoints under `/api/...` for applications that need portal state or connection results. +- **Product presentation:** title, logo, tagline, and semantic colour tokens without copying the portal HTML. +- **Primary and fallback networks:** an opt-in two-network controller backed by an application-provided store. +- **ESP8266 and ESP32 support:** the package resolves its asynchronous web dependencies for the selected target. + +## Choose a guide | Goal | Guide | | --- | --- | | Brand the portal or add structured built-in content | [Portal UI](docs/PORTAL_UI.md) | | Configure primary/fallback station profiles | [Station profiles](docs/STATION_PROFILES.md) | | Understand the JSON APIs and station-connect handoff | [Portal API](docs/PORTAL_API.md) | -| Build a clean consumer or work on this fork | [Testing](docs/TESTING.md) · [Development](docs/DEVELOPMENT.md) | +| Build or flash a complete example | [Examples](examples/README.md) | +| Contribute to this fork or prepare a release | [Development](docs/DEVELOPMENT.md) | ## Install ```ini [common] lib_deps = - WiFiManager=https://github.com/alexhopeoconnor/WiFiManager.git#v3.1.0 + WiFiManager=https://github.com/alexhopeoconnor/WiFiManager.git#v3.2.0 ``` -The suffix after `#` is a Git ref. PlatformIO clones the repository and checks out that release tag; GitHub Release assets are unrelated. This repository is supported through PlatformIO. Use a local `symlink://` or `file://` dependency only while actively changing this fork. +The suffix after `#` is a Git ref. PlatformIO clones the repository and checks out that release tag; GitHub Release assets are unrelated. Arduino IDE users can install this repository as a library checkout. -## Development and releases - -```bash -./scripts/bump-version.sh vMAJOR.MINOR.PATCH -# Replace the generated CHANGELOG TODO with the release summary. -./scripts/test.sh compile --platform esp8266 -./scripts/test.sh compile --platform esp32 -./scripts/check-docs.sh -./scripts/prepare-release.sh vMAJOR.MINOR.PATCH --tag -``` - -Tagging repeats the board-free compile checks, validates the package, and creates a GitHub Release from the corresponding changelog section. It does not publish to the PlatformIO Registry or deploy firmware. - -See the [documentation index](docs/README.md), [release history](CHANGELOG.md), and [licence](LICENSE). +See the [documentation index](docs/README.md), [examples](examples/README.md), [release history](CHANGELOG.md), and [licence](LICENSE). diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 6b92ec1..3631095 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -27,15 +27,11 @@ void loop() { ```ini lib_deps = - WiFiManager=https://github.com/alexhopeoconnor/WiFiManager.git#v3.1.0 + WiFiManager=https://github.com/alexhopeoconnor/WiFiManager.git#v3.2.0 ``` -The package manifest resolves DFTE, ESPAsyncWebServer, and the correct TCP transport for ESP8266 or ESP32. A consuming project should not duplicate those dependencies unless it is deliberately testing an unreleased stack change. +The package includes the asynchronous web and TCP dependencies required by the selected ESP8266 or ESP32 target. Add WiFiManager as the application’s direct dependency; do not copy its internal dependency list into your project. -## DeviceFramework applications - -DeviceFramework creates and configures WiFiManager for its normal lifecycle. Use DeviceFramework’s persistent shared device-password API instead of separately configuring an AP, OTA, HTTP, and WebSerial password. - -Next: [portal UI](PORTAL_UI.md) or [portal API](PORTAL_API.md). +Next: build [Basic Portal](../examples/BasicPortal/), then explore [portal UI](PORTAL_UI.md) or [portal API](PORTAL_API.md). Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/PORTAL_API.md b/docs/PORTAL_API.md index 1827eef..a2d992b 100644 --- a/docs/PORTAL_API.md +++ b/docs/PORTAL_API.md @@ -2,6 +2,8 @@ The portal uses JSON endpoints under `/api/...` for WiFi scans and saves, parameters, information, status, restart/erase/exit actions, captive-portal closure, and station-connect status. Consumers should treat the documented response shapes as the contract; portal HTML is not an API. +The current bootstrap contract is version 3. It includes `brand.tagline`, the short product tagline rendered in the portal header. + ## Profile-mode WiFi metadata When an application supplies a `WiFiManagerStationProfileStore`, `GET /api/wifi/meta` reports a primary profile and optional fallback without returning passwords. `POST /api/wifi/save` verifies a submitted candidate before committing it; see [station profiles](STATION_PROFILES.md) for fields and lifecycle. @@ -20,9 +22,13 @@ When `portalSetBehaviorConnectOnSave(true)` is enabled, saving credentials queue } ``` -`stationIp` and `redirectUrl` are present only after a successful join. If the portal server is not on port 80, `redirectUrl` includes that port. +`stationIp` and `redirectUrl` are present only after a successful join. If the portal server is not on port 80, `redirectUrl` includes that port. When connect-on-save is disabled, a saved configuration has no station address and the built-in portal remains open. -On success, WiFiManager keeps the portal alive briefly so the client can read the final status and navigate before the AP is shut down. Captive-portal helpers, DHCP timing, browser behaviour, and network isolation can still prevent an automatic redirect, so clients must handle a visible address as well. +On success, the SPA first reads the station address, then POSTs `/api/wifi/connect-complete`. WiFiManager keeps the portal available for a fallback period while it waits for that acknowledgement, then closes it after a short grace delay so the normal device web server can start. This removes the old client-side race where the portal could disappear before the final status arrived. Captive-portal helpers, DHCP timing, browser behaviour, and network isolation can still prevent an automatic redirect, so clients must retain the visible address as a fallback. + +## Portal timeout + +When a configuration-portal timeout is enabled, `POST /api/portal/timeout-reset` restarts the countdown and returns `{ "ok": true, "timeoutSecondsRemaining": N }`. The built-in overview exposes this as an explicit reset control; custom clients may use the documented endpoint too. ## API design rules diff --git a/docs/PORTAL_UI.md b/docs/PORTAL_UI.md index 0993d7d..e5d8c38 100644 --- a/docs/PORTAL_UI.md +++ b/docs/PORTAL_UI.md @@ -15,7 +15,7 @@ WiFiManager wifi; namespace { const char kTitle[] PROGMEM = "Set up Temperature Monitor"; const char kIdentity[] PROGMEM = "Example Devices"; -const char kIntro[] PROGMEM = "Connect this device to Wi-Fi."; +const char kTagline[] PROGMEM = "Reliable setup for connected devices."; const char kLogoAlt[] PROGMEM = "Example Devices"; const char kLogo[] PROGMEM = R"svg()svg"; const char kPage[] PROGMEM = "#f4f7f3"; @@ -26,7 +26,7 @@ const char kAccentText[] PROGMEM = "#ffffff"; const WiFiManagerPortalConfig kPortalUI = { WiFiManagerPortalText::progmem(kTitle), WiFiManagerPortalText::progmem(kIdentity), - WiFiManagerPortalText::progmem(kIntro), + WiFiManagerPortalText::progmem(kTagline), WiFiManagerPortalAsset::svgFromProgmem(kLogo), WiFiManagerPortalText::progmem(kLogoAlt), { @@ -58,9 +58,9 @@ Leave a text or colour value empty, or a radius at `0`, to retain the built-in s | Field | Used by | | --- | --- | -| `title` | Document title and portal heading | -| `identityText` | Product/device line below the heading | -| `homeIntro` | Introductory text on the home view | +| `title` | Document title and concise setup-page heading | +| `identityText` | Company or product name in the header above navigation | +| `tagline` | Short tagline in the header above navigation | | `logo.svg`, `logoAltText` | Optional trusted inline SVG and its accessible label | | `pageBackground`, `surface`, `text`, `mutedText`, `border` | Portal surfaces and text | | `accent`, `accentHover`, `accentText` | Primary links and actions | @@ -75,6 +75,5 @@ Presentation uses one configuration route: `setPortalConfig()`. Existing structu There is no arbitrary HTML shell, route replacement, navigation injection, raw stylesheet, or script hook. If a product needs a new portal capability, add a narrow WiFiManager contract and test it on both supported targets. -For a DeviceFramework device, use `DeviceFrameworkUIConfig`; DeviceFramework maps its product-level configuration into this portal API. See [DeviceFramework web UI](https://github.com/alexhopeoconnor/DeviceFramework/blob/main/docs/WEB_UI.md). Back to the [documentation index](README.md) or [project overview](../README.md). diff --git a/docs/README.md b/docs/README.md index 2646221..7aca98c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,12 @@ # WiFiManager documentation -This maintained fork intentionally has a narrower, explicit customisation boundary than upstream WiFiManager. - | I want to… | Read | | --- | --- | | Start a basic portal or install a released dependency | [Getting started](GETTING_STARTED.md) | | Brand the portal or add structured built-in content | [Portal UI](PORTAL_UI.md) | | Configure primary/fallback station profiles or their portal workflow | [Station profiles](STATION_PROFILES.md) | | Consume JSON endpoints or station-connect status | [Portal API](PORTAL_API.md) | -| Build a clean consumer | [Testing](TESTING.md) | +| Build or flash a complete example | [Examples](../examples/README.md) | | Work on this fork or make a release | [Development and releases](DEVELOPMENT.md) | -## Documentation rules - -- Portal UI owns branding, semantic theming, and the supported `portal*` surface. -- Portal API owns HTTP response contracts. -- The root README is a short onboarding page, not an API reference. - Back to the [project overview](../README.md). diff --git a/docs/STATION_PROFILES.md b/docs/STATION_PROFILES.md index 3cb4ea6..ce1201c 100644 --- a/docs/STATION_PROFILES.md +++ b/docs/STATION_PROFILES.md @@ -2,7 +2,7 @@ WiFiManager 3.1.0 adds an opt-in station-profile controller for applications that need a primary Wi-Fi network and one fallback. It is independent of the legacy `autoConnect()` flow: existing WiFiManager consumers do not need to change. -DeviceFramework enables this controller and supplies its CRC-protected storage. A direct WiFiManager consumer supplies its own durable store, or can use the controller only for the current process. +A WiFiManager application supplies its own durable store. The controller can also be used with an in-memory store for a temporary session, but credentials will not survive a restart. ## Lifecycle @@ -57,6 +57,6 @@ In profile mode, the existing Wi-Fi page becomes a two-profile form. It remains The portal requires a non-empty primary SSID. Its API never returns a password; it only reports whether one is set. -See [Portal API](PORTAL_API.md) for the shared connection-status response and [DeviceFramework configuration](https://github.com/alexhopeoconnor/DeviceFramework/blob/main/docs/CONFIGURATION.md) for the local-profile JSON that supplies these slots. +See [Portal API](PORTAL_API.md) for the shared connection-status response. The buildable [Station Profiles](../examples/StationProfiles/) example contains a compact EEPROM-backed store for both supported ESP targets. Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/TESTING.md b/docs/TESTING.md index de56b48..8026e10 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -9,6 +9,6 @@ check, so dependency resolution uses the current manifest rather than a stale ./scripts/test.sh compile --platform esp32 ``` -CI runs both checks for pushes to the maintained branch and pull requests. They compile only; hardware portals remain a local integration concern for an application or DeviceFramework’s connected-device suite. +CI runs both checks for pushes to the maintained branch and pull requests. They compile only; hardware portals remain a local integration concern for the consuming application. Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/assets/portal-esp32-branded-overview.png b/docs/assets/portal-esp32-branded-overview.png new file mode 100644 index 0000000..1ad9971 Binary files /dev/null and b/docs/assets/portal-esp32-branded-overview.png differ diff --git a/docs/assets/portal-esp8266-custom-wifi.png b/docs/assets/portal-esp8266-custom-wifi.png new file mode 100644 index 0000000..60e4be2 Binary files /dev/null and b/docs/assets/portal-esp8266-custom-wifi.png differ diff --git a/examples/BasicPortal/BasicPortal.ino b/examples/BasicPortal/BasicPortal.ino new file mode 100644 index 0000000..e5608d2 --- /dev/null +++ b/examples/BasicPortal/BasicPortal.ino @@ -0,0 +1,15 @@ +#include +#include + +WiFiManager portal; + +void setup() { + Serial.begin(115200); + portal.setConfigPortalTimeout(180); + + portal.autoConnect("WiFiManager Basic", "example-pass"); + + Serial.println("Connected. Run your normal application here."); +} + +void loop() { portal.process(); } diff --git a/examples/BasicPortal/README.md b/examples/BasicPortal/README.md new file mode 100644 index 0000000..2beb52c --- /dev/null +++ b/examples/BasicPortal/README.md @@ -0,0 +1,12 @@ +# Basic Portal + +This is the smallest useful WiFiManager application. It first tries the credentials the ESP platform already knows. If it cannot connect, it opens an access point named **WiFiManager Basic** with password **example-pass**. + +1. Build and flash the selected `esp8266` or `esp32` environment. +2. Connect a phone or computer to **WiFiManager Basic**. +3. Open `http://192.168.4.1/` if your captive-portal helper does not open it automatically. +4. Select a network and save it. The board joins that network and the next reboot reconnects without opening the portal. + +The access-point password is only an example. Choose a unique, Wi-Fi-valid password for a real product. + +See the shared [example guide](../README.md) and [getting started](../../docs/GETTING_STARTED.md). diff --git a/examples/BasicPortal/platformio.ini b/examples/BasicPortal/platformio.ini new file mode 100644 index 0000000..7e1654e --- /dev/null +++ b/examples/BasicPortal/platformio.ini @@ -0,0 +1,26 @@ +[platformio] +default_envs = esp8266 +src_dir = . + +[common] +framework = arduino +lib_ldf_mode = deep+ +lib_deps = + WiFiManager=symlink://../.. + +[env:esp8266] +extends = common +platform = espressif8266 +board = d1_mini +platform_packages = + platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#521ae60a89e64bb0d1eb7a0b7addf620ced5cad3 + +[env:esp32] +extends = common +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +board = esp32dev +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -DSOC_WIFI_SUPPORTED=1 + -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src diff --git a/examples/BrandedPortal/BrandedPortal.ino b/examples/BrandedPortal/BrandedPortal.ino index 98ff1fb..b9f2df3 100644 --- a/examples/BrandedPortal/BrandedPortal.ino +++ b/examples/BrandedPortal/BrandedPortal.ino @@ -6,7 +6,7 @@ WiFiManager wifi; namespace { const char kPortalTitle[] PROGMEM = "Set up Temperature Monitor"; const char kPortalIdentity[] PROGMEM = "Example Devices"; -const char kPortalIntro[] PROGMEM = "Connect this device to Wi-Fi."; +const char kPortalTagline[] PROGMEM = "Reliable setup for connected devices."; const char kPortalLogoAlt[] PROGMEM = "Example Devices"; const char kExampleLogo[] PROGMEM = ""; const char kPage[] PROGMEM = "#f4f7f3"; @@ -18,7 +18,7 @@ const char kAccentText[] PROGMEM = "#ffffff"; const WiFiManagerPortalConfig kPortalUI = { WiFiManagerPortalText::progmem(kPortalTitle), WiFiManagerPortalText::progmem(kPortalIdentity), - WiFiManagerPortalText::progmem(kPortalIntro), + WiFiManagerPortalText::progmem(kPortalTagline), WiFiManagerPortalAsset::svgFromProgmem(kExampleLogo), WiFiManagerPortalText::progmem(kPortalLogoAlt), { @@ -42,9 +42,7 @@ void setup() { Serial.println("Portal UI configuration was rejected"); } wifi.setConfigPortalTimeout(180); - if (!wifi.autoConnect("Temperature Monitor")) { - ESP.restart(); - } + wifi.autoConnect("Temperature Monitor"); } -void loop() {} +void loop() { wifi.process(); } diff --git a/examples/BrandedPortal/README.md b/examples/BrandedPortal/README.md new file mode 100644 index 0000000..b1947c0 --- /dev/null +++ b/examples/BrandedPortal/README.md @@ -0,0 +1,9 @@ +# Branded Portal + +This example uses the supported `WiFiManagerPortalConfig` presentation API to give the built-in portal a product name, company identity, tagline, inline SVG mark, and semantic colour tokens. + +Flash the `esp8266` or `esp32` environment, join **Temperature Monitor**, and open `http://192.168.4.1/`. The visual changes come from static firmware data; WiFiManager still owns the portal routes, forms, validation, and captive-network behaviour. + +Use only trusted compiled SVG data. Keep the backing strings static for the lifetime of the firmware, then call `setPortalConfig()` before opening a portal. + +See [Portal UI](../../docs/PORTAL_UI.md) and the shared [example guide](../README.md). diff --git a/examples/BrandedPortal/platformio.ini b/examples/BrandedPortal/platformio.ini new file mode 100644 index 0000000..7e1654e --- /dev/null +++ b/examples/BrandedPortal/platformio.ini @@ -0,0 +1,26 @@ +[platformio] +default_envs = esp8266 +src_dir = . + +[common] +framework = arduino +lib_ldf_mode = deep+ +lib_deps = + WiFiManager=symlink://../.. + +[env:esp8266] +extends = common +platform = espressif8266 +board = d1_mini +platform_packages = + platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#521ae60a89e64bb0d1eb7a0b7addf620ced5cad3 + +[env:esp32] +extends = common +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +board = esp32dev +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -DSOC_WIFI_SUPPORTED=1 + -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src diff --git a/examples/CustomPortalContent/CustomPortalContent.ino b/examples/CustomPortalContent/CustomPortalContent.ino new file mode 100644 index 0000000..30676eb --- /dev/null +++ b/examples/CustomPortalContent/CustomPortalContent.ino @@ -0,0 +1,29 @@ +#include +#include + +WiFiManager portal; +WiFiManagerParameter brokerHost("broker_host", "MQTT broker", "mqtt.local", 40); + +void setup() { + Serial.begin(115200); + + portal.portalAddParameter(&brokerHost); + portal.portalAddInfoSection({ + "device", "Example device", + {{"firmware", "Firmware", "1.0.0"}, {"sensor", "Sensor", "Ready"}}, + }); + portal.portalAddHomeCard({ + "hint", "What this example adds", PortalHomeCardKind::Callout, + "A normal text setting, a status section, and a home-page callout.", {}, + }); + + portal.setSaveParamsCallback([](WiFiManager::WiFiManagerRequestArgs) { + Serial.print("MQTT broker selected: "); + Serial.println(brokerHost.getValue()); + }); + portal.setConfigPortalTimeout(180); + + portal.autoConnect("WiFiManager Content", "example-pass"); +} + +void loop() { portal.process(); } diff --git a/examples/CustomPortalContent/README.md b/examples/CustomPortalContent/README.md new file mode 100644 index 0000000..5874078 --- /dev/null +++ b/examples/CustomPortalContent/README.md @@ -0,0 +1,13 @@ +# Custom Portal Content + +This example keeps WiFiManager’s portal navigation, validation, and captive behaviour, while adding three application-owned pieces of content: + +- an editable MQTT broker host field; +- a compact device-information section; +- a callout on the portal overview. + +It opens **WiFiManager Content** with password **example-pass** until it has working station credentials. Save the form, then inspect serial output to see the selected broker value. + +The parameter object is global because WiFiManager reads it for the lifetime of the portal. In a real application, copy the value into that application’s own validated persistent configuration inside the save callback. + +See [Portal UI](../../docs/PORTAL_UI.md) and the shared [example guide](../README.md). diff --git a/examples/CustomPortalContent/platformio.ini b/examples/CustomPortalContent/platformio.ini new file mode 100644 index 0000000..7e1654e --- /dev/null +++ b/examples/CustomPortalContent/platformio.ini @@ -0,0 +1,26 @@ +[platformio] +default_envs = esp8266 +src_dir = . + +[common] +framework = arduino +lib_ldf_mode = deep+ +lib_deps = + WiFiManager=symlink://../.. + +[env:esp8266] +extends = common +platform = espressif8266 +board = d1_mini +platform_packages = + platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#521ae60a89e64bb0d1eb7a0b7addf620ced5cad3 + +[env:esp32] +extends = common +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +board = esp32dev +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -DSOC_WIFI_SUPPORTED=1 + -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f9c9888 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,22 @@ +# WiFiManager examples + +Every directory below is a standalone PlatformIO project. Build it from the repository root or the example directory: + +```bash +pio run -d examples/BasicPortal -e esp8266 +pio run -d examples/BasicPortal -e esp8266 -t upload +pio device monitor -d examples/BasicPortal -e esp8266 +``` + +Choose `esp32` for an ESP32 development board. The examples use the checked-out WiFiManager source, so they are also useful while developing this fork. + +| Example | Start here when you want to… | +| --- | --- | +| [Basic Portal](BasicPortal/) | provision one board through the normal saved-network-or-portal flow | +| [Custom Portal Content](CustomPortalContent/) | add application settings and useful status cards to the built-in portal | +| [Branded Portal](BrandedPortal/) | change the portal’s identity, logo, and semantic visual theme | +| [Station Profiles](StationProfiles/) | remember a primary Wi-Fi network and one fallback in application-owned EEPROM storage | + +The portal examples intentionally begin with no station credentials. On first boot, connect to the access point printed on serial and open `http://192.168.4.1/`. + +Back to the [project overview](../README.md). diff --git a/examples/StationProfiles/README.md b/examples/StationProfiles/README.md new file mode 100644 index 0000000..8048364 --- /dev/null +++ b/examples/StationProfiles/README.md @@ -0,0 +1,9 @@ +# Station Profiles + +This example gives WiFiManager a small application-owned EEPROM store. The portal accepts a required primary Wi-Fi network and an optional fallback, verifies a submitted network before saving it, then remembers the last successful choice across restarts. + +On a blank board, connect to **WiFiManager Profiles** with password **example-pass** and open `http://192.168.4.1/`. Enter a primary network and, if useful, one fallback. After a successful connection, restart the board to confirm that it tries the saved profiles before reopening the portal. + +`StoredProfiles` is intentionally simple so the ownership boundary is visible. A production application should add its own record versioning and integrity protection around the application’s complete configuration; WiFiManager only owns network-selection policy. + +See [Station profiles](../../docs/STATION_PROFILES.md) and the shared [example guide](../README.md). diff --git a/examples/StationProfiles/StationProfiles.ino b/examples/StationProfiles/StationProfiles.ino new file mode 100644 index 0000000..00b7d2f --- /dev/null +++ b/examples/StationProfiles/StationProfiles.ino @@ -0,0 +1,63 @@ +#include +#include +#include + +namespace { +constexpr uint32_t kStoreMagic = 0x574D5031; // "WMP1" + +struct StoredProfiles { + uint32_t magic; + WiFiManagerStationProfiles profiles; +}; + +class EepromProfileStore final : public WiFiManagerStationProfileStore { +public: + bool begin() { +#if defined(ESP32) + return EEPROM.begin(sizeof(StoredProfiles)); +#else + EEPROM.begin(sizeof(StoredProfiles)); + return true; +#endif + } + + bool load(WiFiManagerStationProfiles& profiles) override { + StoredProfiles stored{}; + EEPROM.get(0, stored); + if (stored.magic != kStoreMagic) { + return false; + } + profiles = stored.profiles; + return true; + } + + bool save(const WiFiManagerStationProfiles& profiles) override { + EEPROM.put(0, StoredProfiles{kStoreMagic, profiles}); + return EEPROM.commit(); + } + + bool clear() override { + EEPROM.put(0, StoredProfiles{}); + return EEPROM.commit(); + } +}; + +EepromProfileStore profileStore; +WiFiManager portal; +} // namespace + +void setup() { + Serial.begin(115200); + if (!profileStore.begin()) { + Serial.println("Could not initialise EEPROM profile storage"); + return; + } + + portal.setStationProfileStore(&profileStore); + portal.setStationRecoveryInterval(30000); + portal.startStationConnection("WiFiManager Profiles", "example-pass"); +} + +void loop() { + portal.process(); +} diff --git a/examples/StationProfiles/platformio.ini b/examples/StationProfiles/platformio.ini new file mode 100644 index 0000000..7e1654e --- /dev/null +++ b/examples/StationProfiles/platformio.ini @@ -0,0 +1,26 @@ +[platformio] +default_envs = esp8266 +src_dir = . + +[common] +framework = arduino +lib_ldf_mode = deep+ +lib_deps = + WiFiManager=symlink://../.. + +[env:esp8266] +extends = common +platform = espressif8266 +board = d1_mini +platform_packages = + platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#521ae60a89e64bb0d1eb7a0b7addf620ced5cad3 + +[env:esp32] +extends = common +platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip +board = esp32dev +build_unflags = -std=gnu++11 +build_flags = + -std=gnu++14 + -DSOC_WIFI_SUPPORTED=1 + -I${platformio.packages_dir}/framework-arduinoespressif32/libraries/Network/src diff --git a/lib/WiFiManager/include/WiFiManager.h b/lib/WiFiManager/include/WiFiManager.h index 576c2c1..2c00050 100644 --- a/lib/WiFiManager/include/WiFiManager.h +++ b/lib/WiFiManager/include/WiFiManager.h @@ -268,7 +268,7 @@ struct PortalHomeCard { struct PortalBrandState { WiFiManagerPortalText title; WiFiManagerPortalText identityTextOverride; - WiFiManagerPortalText homeIntro; + WiFiManagerPortalText tagline; WiFiManagerPortalAsset logo; WiFiManagerPortalText logoAltText; }; @@ -915,7 +915,7 @@ class WiFiManager // preload scanning causes AP to delay showing for users, but also caches and lets the cp load faster once its open // my scan takes 7-10 seconds public: - boolean _preloadwifiscan = false; // preload wifiscan if true + boolean _preloadwifiscan = true; // begin one asynchronous scan as the portal starts unsigned int _scancachetime = 30000; // ms cache time for preload scans protected: @@ -963,6 +963,7 @@ protected: void handleStationAttemptFailure(uint8_t status, const String& message); void handleStationConnectionSuccess(); void completePortalStationAttempt(bool success, uint8_t status, const String& message); + void acknowledgePortalConnectHandoff(); void enterStationPortal(); bool hasUsableStationConnection() const; bool isStationProfileEnabled(const WiFiManagerStationProfiles& profiles, uint8_t slot) const; @@ -1083,6 +1084,7 @@ protected: _cpConnectStationIp = stationIp; _cpConnectStatus = status; } + void wmTestCompleteProfilePortalConnectionSuccess(); void wmTestSetPortalConnectFailure(const String& message, uint8_t status = WL_CONNECT_FAILED) { _cpConnectState = wm_cp_connect_state_t::failed; _cpConnectMessage = message; diff --git a/lib/WiFiManager/include/WiFiManagerHandlers.h b/lib/WiFiManager/include/WiFiManagerHandlers.h index 594385e..5ef4c22 100644 --- a/lib/WiFiManager/include/WiFiManagerHandlers.h +++ b/lib/WiFiManager/include/WiFiManagerHandlers.h @@ -55,6 +55,8 @@ class WiFiManagerHandlers { void handleApiWifiMeta(AsyncWebServerRequest *request); void handleApiWifiSave(AsyncWebServerRequest *request); void handleApiWifiConnectStatus(AsyncWebServerRequest *request); + void handleApiWifiConnectComplete(AsyncWebServerRequest *request); + void handleApiPortalTimeoutReset(AsyncWebServerRequest *request); void handleApiParamsGet(AsyncWebServerRequest *request); void handleApiParamsSave(AsyncWebServerRequest *request); void handleApiInfo(AsyncWebServerRequest *request); diff --git a/lib/WiFiManager/include/WiFiManagerPortalUI.h b/lib/WiFiManager/include/WiFiManagerPortalUI.h index 5b6e187..a1a3bee 100644 --- a/lib/WiFiManager/include/WiFiManagerPortalUI.h +++ b/lib/WiFiManager/include/WiFiManagerPortalUI.h @@ -84,7 +84,7 @@ struct WiFiManagerPortalTheme { struct WiFiManagerPortalConfig { WiFiManagerPortalText title; WiFiManagerPortalText identityText; - WiFiManagerPortalText homeIntro; + WiFiManagerPortalText tagline; WiFiManagerPortalAsset logo; WiFiManagerPortalText logoAltText; WiFiManagerPortalTheme theme; diff --git a/lib/WiFiManager/include/WiFiManagerServer.h b/lib/WiFiManager/include/WiFiManagerServer.h index 79def1f..927cedf 100644 --- a/lib/WiFiManager/include/WiFiManagerServer.h +++ b/lib/WiFiManager/include/WiFiManagerServer.h @@ -15,6 +15,8 @@ * GET /api/wifi/meta * POST /api/wifi/save * GET /api/wifi/connect-status + * POST /api/wifi/connect-complete + * POST /api/portal/timeout-reset * GET /api/params * POST /api/params/save * GET /api/info @@ -55,6 +57,8 @@ 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_wifi_connect_status[] PROGMEM = "/api/wifi/connect-status"; +const char R_api_wifi_connect_complete[] PROGMEM = "/api/wifi/connect-complete"; +const char R_api_portal_timeout_reset[] PROGMEM = "/api/portal/timeout-reset"; 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"; diff --git a/lib/WiFiManager/include/templates/CSS.h b/lib/WiFiManager/include/templates/CSS.h index 9b0676f..c2e8f40 100644 --- a/lib/WiFiManager/include/templates/CSS.h +++ b/lib/WiFiManager/include/templates/CSS.h @@ -74,19 +74,21 @@ const char CSS_STYLE[] PROGMEM = "