diff --git a/README.md b/README.md index 8052c4f..e6adea8 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ The [Branded Portal](examples/BrandedPortal/) example includes a static SVG, acc - **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. +- **Structured APIs:** C++ configuration and a local JSON protocol for the built-in portal. - **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. @@ -72,10 +72,16 @@ The [Branded Portal](examples/BrandedPortal/) example includes a static SVG, acc | Goal | Guide | | --- | --- | -| Brand the built-in portal | [Portal UI](docs/PORTAL_UI.md) | -| Add application settings, status, or home cards | [Structured portal content](docs/PORTAL_UI.md#structured-portal-content) | +| Understand library/application ownership and supported boundaries | [Architecture](docs/ARCHITECTURE.md) | +| Get a device online or recover from missing Wi-Fi | [Provisioning lifecycle](docs/PROVISIONING_LIFECYCLE.md) | +| Brand or constrain the built-in portal | [Portal UI and configuration](docs/PORTAL_UI.md) | +| Add and persist application settings, status, or home cards | [Portal content](docs/PORTAL_CONTENT.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) | +| Surface setup, offline, and connection state in firmware | [Observability](docs/OBSERVABILITY.md) | +| Configure AP, station, scan, or reconnect behaviour | [Network configuration](docs/NETWORK_CONFIGURATION.md) | +| Look up a supported C++ method and its timing | [API reference](docs/API_REFERENCE.md) | +| Understand the built-in portal's local JSON protocol | [Portal API](docs/PORTAL_API.md) | +| Follow deployment-oriented integration patterns | [Recipes](docs/recipes/README.md) | | Build or flash a complete example | [Examples](examples/README.md) | | Run documentation and board-free compile checks | [Testing](docs/TESTING.md) | | Contribute to this fork or prepare a release | [Development](docs/DEVELOPMENT.md) | diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..9dc1b71 --- /dev/null +++ b/docs/API_REFERENCE.md @@ -0,0 +1,132 @@ +# API reference + +This is a task-oriented reference for the supported WiFiManager firmware API. Configure an instance during boot, keep it alive for the application's lifetime, and call process() regularly while WiFiManager may have active work. + +For complete end-to-end patterns, use [Provisioning lifecycle](PROVISIONING_LIFECYCLE.md), [Portal content](PORTAL_CONTENT.md), and [Station profiles](STATION_PROFILES.md). + +## Lifecycle + +| API | Timing and meaning | +| --- | --- | +| autoConnect([apName, apPassword]) | Tries legacy saved station credentials. Returns true if it connected during the call; otherwise starts the enabled portal fallback and returns false. | +| startConfigPortal([apName, apPassword]) | Immediately starts a configuration AP and captive portal. | +| stopConfigPortal() | Immediately stops the configuration portal. | +| startWebPortal() / stopWebPortal() | Starts/stops the portal web server without the configuration-AP flow. | +| process() | Services active portal, scan, station-profile, and connection state. Call from loop(). | +| getConfigPortalSSID() | Returns the current configuration AP name. | +| getConfigPortalActive() / getWebPortalActive() | Reports active configuration or web portal state. | +| setHttpPort(port) | Chooses the portal server port before starting it. | + +The application owns any existing HTTP service and must release a shared port before WiFiManager starts its portal. + +## Station profiles + +| API | Timing and meaning | +| --- | --- | +| setStationProfileStore(store) | Supplies an application-owned durable store; WiFiManager does not own it. | +| startStationConnection([apName, apPassword]) | Loads and begins the stored primary/fallback profile flow. | +| startStationCandidate(candidate[, apName, apPassword]) | Attempts an in-memory profile set; when a store is attached, saves it only after a successful usable connection. | +| saveStationProfiles(profiles) | Deliberately stores a complete profile set without a connection verification attempt. | +| clearStationProfiles() | Clears the store and disconnects the station. | +| setStationRecoveryInterval(milliseconds) | Sets delay before profile recovery attempts after a connection loss. | +| isStationProfileMode(), getStationProfiles(), getStationStatus() | Inspects profile-controller state. | + +Profile mode has exactly primary slot 0 and optional fallback slot 1. See [Station profiles](STATION_PROFILES.md) for storage rules and portal fields. + +## Application settings and callbacks + +| API | Timing and meaning | +| --- | --- | +| portalAddParameter(parameter) | Registers an application-owned WiFiManagerParameter; it must outlive the portal. Returns false if registration fails. | +| portalClearParameters() | Removes registered parameter pointers; it does not delete them. | +| getParameters() / getParametersCount() | Inspects registered parameters. | +| setPreSaveParamsCallback(callback) | Invoked before a parameter-only save. | +| setSaveParamsCallback(callback) | Invoked when parameters save; receives WiFiManagerRequestArgs. | +| setPreSaveConfigCallback(callback) | Invoked before a combined Wi-Fi/config save. | +| setSaveConfigCallback(callback) | Invoked after changed Wi-Fi settings connect successfully, or when break-after-config is enabled. | +| setConfigResetCallback(callback) | Invoked when Wi-Fi settings reset. | +| setAPCallback(callback) / setWebServerCallback(callback) | Invoked after the AP/config portal or web server begins. | +| setConfigPortalTimeoutCallback(callback) | Invoked when a configuration portal times out. | +| setPreOtaUpdateCallback(callback) | Invoked immediately before OTA update handling. | + +WiFiManagerRequestArgs provides hasArg(), getArg(), getArgAsInt(), getArgAsFloat(), getArgAsBool(), and count(). It is a callback argument, not durable application configuration. + +WiFiManagerParameter constructors accept an ID, label, default value, maximum length, optional custom HTML, and optional label placement. IDs are request field names; keep them stable and simple. + +## Portal presentation and policy + +| API | Timing and meaning | +| --- | --- | +| setPortalConfig(config) | Applies title, identity text, tagline, SVG logo, and semantic theme. Returns false when a portal is active or the theme is invalid. | +| portalSetPageInfoVisible(), portalSetPageUpdateVisible(), portalSetPageSetupVisible() | Controls built-in page visibility. | +| portalSetActionEraseVisible(), portalSetActionRestartVisible(), portalSetActionExitVisible(), portalSetActionCloseCaptiveVisible(), portalSetActionBackVisible() | Controls built-in action visibility. | +| portalSetLayoutParamsLocation(location) | Selects WiFiPage or SetupPage for custom parameters. | +| portalSetBehaviorCaptivePortalEnabled(), portalSetBehaviorConnectOnSave(), portalSetBehaviorExitAllowed() | Sets portal availability, post-save connection, and exit policy. | +| portalSetBehaviorConnectTimeoutSeconds(), portalSetBehaviorPortalTimeoutSeconds() | Sets connection and portal timeout behavior. | +| portalSetBehaviorAutoReconnect(), portalSetBehaviorApClientCheck(), portalSetBehaviorWebClientCheck() | Sets reconnection and activity/timeout behavior. | +| portalSetFieldPasswordPlaceholderMode(mode) | Selects Hidden, Masked, or Actual password placeholder behavior. | +| portalSetFieldStaticIpVisibility(visibility), portalSetFieldStaticDnsVisibility(visibility) | Selects Hidden, Auto, or Always static-network fields. | +| portalAddInfoSection(), portalClearInfoSections() | Adds/clears copied read-only information sections. | +| portalAddHomeCard(), portalClearHomeCards() | Adds/clears copied overview cards. | + +Use [Portal UI and configuration](PORTAL_UI.md) for configuration examples and [Portal content](PORTAL_CONTENT.md) for ownership/persistence rules. + +## Connection and network policy + +| API | Timing and meaning | +| --- | --- | +| setConfigPortalTimeout(seconds) | Limits configuration portal lifetime; setTimeout() is deprecated alias. | +| setConnectTimeout(seconds), setConnectRetries(count) | Bounds legacy automatic connection attempts. | +| setSaveConnectTimeout(seconds), setSaveConnect(enabled) | Controls and bounds portal save-and-connect behavior. | +| setBreakAfterConfig(enabled) | Exits after a configuration submission even when it did not connect. | +| setEnableConfigPortal(enabled), setDisableConfigPortal(enabled) | Controls autoConnect() fallback/start-stop behavior. | +| setAPClientCheck(enabled), setWebPortalClientCheck(enabled) | Controls portal timeout interaction with AP/web clients. | +| setWiFiAutoReconnect(enabled), setCleanConnect(enabled) | Controls station reconnect and pre-connect disconnect behavior. | +| setHostname(), getWiFiHostname() | Sets/reads the supported target hostname. | +| setWiFiSSIDPrefix(), setWiFiAPChannel(), setWiFiAPHidden() | Configures the temporary setup AP. | +| setAPStaticIPConfig(), setSTAStaticIPConfig() | Configures AP or station static addressing. | +| setCountry(), setMinimumSignalQuality(), setRemoveDuplicateAPs(), setScanDispPerc() | Configures country and scan presentation/filter behavior. | +| setRestorePersistent(enabled) | Controls restoration of the platform Wi-Fi persistence setting. | + +See [Network configuration](NETWORK_CONFIGURATION.md) for deployment constraints. + +## Scan, state, and diagnostics + +| API | Timing and meaning | +| --- | --- | +| requestAsyncScan(forceRefresh) | Requests a non-blocking scan. | +| getScanSnapshot(), getScanRuntimeState(), getScanState() | Returns scan lifecycle state. | +| isScanRunning(), hasValidScanResults(), getScanResults() | Reads scan progress and cached visible results. | +| getRSSIasQuality(rssi) | Converts RSSI to WiFiManager quality. | +| getLastConxResult(), getWLStatusString(), getModeString() | Formats connection and mode diagnostics. | +| hasEnteredConfigPortal(), getConfigPortalConnectState() | Reads portal-session history and last submission state. | +| isConfigPortalConnectPending(), didConfigPortalConnectSucceed(), didConfigPortalConnectFail() | Reads concise portal connection status. | +| getConfigPortalConnectStatus(), getConfigPortalConnectMessage() | Returns platform status and message for the last portal attempt. | +| setEventCallback(callback) | Receives lifecycle notifications; use getters for detailed state. | +| setLogEnabled(), setLogPrefix(), setLogOutput(), setLogSink(), getLogSink() | Configures log output and optional application-owned sink. | + +See [Observability](OBSERVABILITY.md) for event meanings and product feedback. + +## Reset and low-level helpers + +| API | Meaning | +| --- | --- | +| disconnect() | Disconnects without erasing saved configuration. | +| resetSettings() | Clears legacy Wi-Fi settings. | +| erase([optional]) | Erases Wi-Fi configuration. | +| reboot() | Reboots the target. | +| getWiFiIsSaved(), getWiFiSSID(), getWiFiPass() | Reads legacy saved/current station values; handle credentials carefully. | +| getDefaultAPName() | Returns the default generated AP name. | +| debugSoftAPConfig(), debugPlatformInfo() | Writes diagnostic information. | +| htmlEntities(text[, whitespace]) | Escapes text for WiFiManager HTML rendering. | +| preloadWiFi(ssid, password) | Intended for fixtures or controlled integrations that deliberately skip normal station configuration. | + +getServer() and getDNSServer() are exposed for testing/host integration. They are not a supported way for product firmware to add private portal routes or mutate the built-in server. + +## Continue + +- [Architecture and boundaries](ARCHITECTURE.md) +- [Portal API](PORTAL_API.md) +- [Examples](../examples/README.md) + +Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..a607f92 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,75 @@ +# Architecture and boundaries + +WiFiManager is a device-local provisioning component. It tries station Wi-Fi, temporarily hosts an access point and portal when the device cannot connect, and then returns control to the firmware. It is not a cloud service, a remote-management system, or a companion-app framework. + +## Ownership + +| WiFiManager owns | The application owns | +| --- | --- | +| Temporary AP, captive DNS, portal routes, Wi-Fi connection attempts, portal session state | Durable application settings, schema migration, product services, LEDs/display, reboot policy, telemetry, and access-control decisions | +| Wi-Fi credentials in the legacy flow, or profile-selection policy in station-profile mode | The durable station-profile store when profile mode is enabled | +| Structured portal presentation and built-in Wi-Fi/settings forms | Product-specific validation and the persistence of product settings | +| The local portal JSON protocol | Any separate product HTTP API or local web server | + +This separation is intentional: an application can decide how a device should be configured without having to duplicate the portal. + +## Typical boot sequence + +~~~text +Firmware boot + │ + ├─ Load and migrate application settings + ├─ Register portal branding, policy, and application parameters + ├─ Start a WiFiManager connection flow + │ ├─ Connected: start normal application services + │ └─ Not connected: temporary AP + local portal + ├─ Service wifi.process() while a portal or profile controller is active + └─ Persist application data and decide when to restart or resume services +~~~ + +Register portal configuration before calling autoConnect(), startConfigPortal(), startWebPortal(), or a station-profile start method. The active portal uses an immutable response model so that asynchronous requests cannot see a partially changed UI. + +## Structured configuration, not a replacement web app + +WiFiManager supports product identity, semantic theme values, page/action visibility, field policy, parameters, information sections, and home cards. It deliberately does not provide: + +- arbitrary portal HTML-shell replacement; +- raw CSS or JavaScript injection; +- route replacement or navigation injection; +- a cloud API or a supported mobile-companion integration surface. + +Use the portal APIs where their existing semantics fit. If a product needs a new portal capability, add a narrow, documented WiFiManager contract and test it on both supported targets instead of reaching into portal internals. + +## Application-service handoff + +WiFiManager's portal server uses its configured HTTP port, 80 by default. A product already using that port must explicitly release it before WiFiManager starts a portal. Conversely, the application decides when its normal web service is safe to start after a successful connection. + +This is a real integration boundary, not a WiFiManager callback side effect: + +~~~cpp +void beginRecovery() { + stopApplicationWebServer(); // Releases port 80 owned by the product. + wifi.startConfigPortal("Device Setup", "setup-password"); +} +~~~ + +The application may instead use a different WiFiManager HTTP port through setHttpPort(); document that address for installers because the captive-portal redirect and station handoff will include the selected port. + +## Data lifetime and persistence + +WiFiManagerPortalConfig text and SVG values are non-owning. Keep RAM or PROGMEM source data alive for the entire firmware lifetime. WiFiManagerParameter instances are application-owned and must outlive the portal. Portal information sections and home cards are copied when registered. + +A station-profile store is also application-owned. Its load(), save(), and clear() methods are responsible for durable storage and error handling. The profile controller chooses and verifies networks; it does not own the store or an application's migration format. + +## Security and operator expectations + +The configuration portal is intended for local setup. Use a Wi-Fi-valid AP password in deployed products, do not put secrets in information cards or logs, and do not expose password placeholders unless there is an explicit local-installation requirement. The portal's JSON endpoints are the built-in UI's device-local protocol; they are not an authenticated remote-management API. + +## Continue + +- [Provisioning lifecycle](PROVISIONING_LIFECYCLE.md) +- [Portal UI and configuration](PORTAL_UI.md) +- [Portal content](PORTAL_CONTENT.md) +- [Integration recipes](recipes/README.md) + +Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 1e360d1..0e4440c 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -31,6 +31,8 @@ lib_deps = 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. -Next: build [Basic Portal](../examples/BasicPortal/), then explore [portal UI](PORTAL_UI.md) or [portal API](PORTAL_API.md). +Next: build [Basic Portal](../examples/BasicPortal/), then read [Provisioning lifecycle](PROVISIONING_LIFECYCLE.md) for return-state, timeout, and recovery rules. + +For product presentation and settings, continue with [Portal UI and configuration](PORTAL_UI.md) and [Portal content](PORTAL_CONTENT.md). Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/NETWORK_CONFIGURATION.md b/docs/NETWORK_CONFIGURATION.md new file mode 100644 index 0000000..5631c04 --- /dev/null +++ b/docs/NETWORK_CONFIGURATION.md @@ -0,0 +1,90 @@ +# Network configuration + +These APIs configure WiFiManager's AP, station, scan, and reconnection behavior. Set them during boot, before starting a connection or portal flow, so an installer sees one consistent configuration. + +This page is an advanced deployment reference. Use these settings only when the local network and the product's operating policy require them; WiFiManager's defaults are appropriate for many devices. + +## Access-point setup network + +| API | Purpose | +| --- | --- | +| setWiFiSSIDPrefix(prefix) | Changes the prefix used by an automatically generated AP name. | +| setWiFiAPChannel(channel) | Selects the setup AP Wi-Fi channel. | +| setWiFiAPHidden(hidden) | Hides or advertises the setup AP SSID. | +| setAPStaticIPConfig(ip, gateway, subnet) | Sets the AP-side portal address configuration. | +| setHttpPort(port) | Uses a non-default portal HTTP port. | +| setHostname(name) | Sets the station/AP hostname as supported by the target. | +| getConfigPortalSSID() / getDefaultAPName() | Reports the actual/current setup AP name. | + +A hidden AP can make field setup harder because an installer must enter the SSID manually. Treat it as a deployment decision, not a general security control. If setHttpPort() changes the default, publish the full portal address in the product's installation procedure. + +## Station address and credential behavior + +Use a station static address only when the network owner has reserved and documented it: + +~~~cpp +wifi.setSTAStaticIPConfig( + IPAddress(192, 168, 20, 50), + IPAddress(192, 168, 20, 1), + IPAddress(255, 255, 255, 0), + IPAddress(192, 168, 20, 1)); +~~~ + +| API | Purpose | +| --- | --- | +| setSTAStaticIPConfig(ip, gateway, subnet[, dns]) | Supplies a station static address and optional DNS server. | +| setCleanConnect(enabled) | Disconnects before connecting; use when the product requires a fresh association. | +| setRestorePersistent(enabled) | Controls restoration of the platform Wi-Fi persistence preference. | +| setWiFiAutoReconnect(enabled) | Enables Wi-Fi auto-reconnect behavior. | +| setCountry(countryCode) | Applies the supported Wi-Fi country setting. | +| disconnect() | Disconnects without erasing persistent settings. | +| resetSettings() | Clears legacy saved Wi-Fi settings. | +| erase([optional]) | Erases Wi-Fi configuration and schedules the configured reset behavior. | + +Static-IP entry fields can be configured independently of the network setting: + +~~~cpp +wifi.portalSetFieldStaticIpVisibility(PortalFieldVisibility::Auto); +wifi.portalSetFieldStaticDnsVisibility(PortalFieldVisibility::Hidden); +~~~ + +Auto shows these fields when a static station configuration is already in use; Always makes them visible; Hidden suppresses them. Do not expose static network controls in a general user portal unless the installer is expected to manage those values. + +## Scan behavior + +WiFiManager's portal schedules asynchronous scans. Firmware that needs its own nearby-network status can request and inspect the same cached scan state: + +~~~cpp +wifi.requestAsyncScan(); + +if (wifi.hasValidScanResults()) { + for (const auto& network : wifi.getScanResults()) { + Serial.printf("%s: %ld dBm\n", network.ssid.c_str(), network.rssi); + } +} +~~~ + +| API | Purpose | +| --- | --- | +| requestAsyncScan(forceRefresh) | Queues a scan without blocking the application loop. | +| getScanSnapshot() / getScanRuntimeState() | Returns the complete scan lifecycle snapshot. | +| getScanState() / isScanRunning() / hasValidScanResults() | Provides concise status checks. | +| getScanResults() | Returns the cached visible network list. | +| setMinimumSignalQuality(quality) | Filters low-quality scan results. | +| setRemoveDuplicateAPs(enabled) | Controls duplicate SSID removal. | +| setScanDispPerc(enabled) | Uses percentage rather than quality icons in the portal. | +| getRSSIasQuality(rssi) | Converts RSSI for display. | + +Read scan results as a snapshot. Do not retain references across a new scan or portal shutdown. + +## Connection and timeout policy + +See [Provisioning lifecycle](PROVISIONING_LIFECYCLE.md) for setConfigPortalTimeout(), setConnectTimeout(), setConnectRetries(), setSaveConnectTimeout(), setSaveConnect(), and the portal-prefixed behavior equivalents. These settings define recovery behavior; they should reflect how long an on-site installer can reasonably work and how long the product can remain offline. + +## Continue + +- [Portal UI and configuration](PORTAL_UI.md) +- [Provisioning lifecycle](PROVISIONING_LIFECYCLE.md) +- [API reference](API_REFERENCE.md) + +Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/OBSERVABILITY.md b/docs/OBSERVABILITY.md new file mode 100644 index 0000000..a47420b --- /dev/null +++ b/docs/OBSERVABILITY.md @@ -0,0 +1,89 @@ +# Observability + +WiFiManager exposes connection and portal state so product firmware can provide useful local feedback. Use getters as the source of truth and treat callbacks as notifications. + +A device framework consuming WiFiManager uses this pattern to distinguish normal operation, offline recovery, active setup, and a successful portal connection before it changes LED state or starts product services. + +## Portal state + +| API | Meaning | +| --- | --- | +| getConfigPortalActive() | A configuration AP/portal is currently active. | +| getWebPortalActive() | A manually started portal web service is active. | +| hasEnteredConfigPortal() | Setup has been entered at least once since boot. | +| getConfigPortalConnectState() | Idle, queued, waiting, success, or failed for the most recent portal connection attempt. | +| isConfigPortalConnectPending() | A concise check for queued or waiting connection work. | +| didConfigPortalConnectSucceed() / didConfigPortalConnectFail() | Result checks for the most recent portal submission. | +| getConfigPortalConnectStatus() | Platform Wi-Fi status for that attempt. | +| getConfigPortalConnectMessage() | Human-readable status; suitable for logs or local display. | +| getLastConxResult() / getWLStatusString() | Legacy and general Wi-Fi connection result helpers. | + +Use these values to drive product feedback. Do not derive state by scraping portal HTML or assuming that an autoConnect() false return means the portal session failed. + +## Events + +setEventCallback() installs an optional notification hook: + +~~~cpp +wifi.setEventCallback([](WiFiManager::wm_event_t event) { + switch (event) { + case WiFiManager::WM_EVENT_PORTAL_STARTED: + setIndicator(IndicatorState::Setup); + break; + case WiFiManager::WM_EVENT_PORTAL_CONNECT_SUCCESS: + setIndicator(IndicatorState::ConnectingComplete); + break; + case WiFiManager::WM_EVENT_STATION_LINK_LOST: + setIndicator(IndicatorState::Offline); + break; + default: + break; + } +}); +~~~ + +| Event | Product use | +| --- | --- | +| WM_EVENT_PORTAL_STARTED / WM_EVENT_PORTAL_STOPPED | Begin or end setup feedback. | +| WM_EVENT_PORTAL_CONNECT_QUEUED / START / SUCCESS / FAILED | Show progress and result for a portal-submitted network. | +| WM_EVENT_STATION_PROFILE_ATTEMPT / CONNECTED / FAILED | Track profile-controller progress. | +| WM_EVENT_STATION_LINK_LOST / BACKOFF | Show recovery behavior after an established connection drops. | +| WM_EVENT_STATION_PROFILES_CLEARED | Reconcile product state after profiles are cleared. | + +Events are intentionally small and do not carry credentials or mutable request state. Read the relevant getter inside the callback when more detail is needed. + +## Station-profile status + +When using profile mode, getStationStatus() reports: + +- state: idle, loading, attempting, switching, connected, backoff, or portal; +- activeSlot and attemptedSlot; +- configuredProfiles; +- wifiStatus and a human-readable message; +- whether the last connection was a candidate; +- whether the profile store could not save a successful candidate. + +A store-save failure is operationally important: the device may be connected now but will not necessarily reconnect after a restart. Preserve that distinction in an LED, display, or operator log. + +## Logging + +WiFiManager logs to its configured Print output unless a WiFiManagerLogSink is supplied. + +~~~cpp +wifi.setLogPrefix("[network] "); +wifi.setLogOutput(true, WiFiManagerLogLevel::Info); +~~~ + +WiFiManagerLogLevel ranges from Silent through Error, Warn, Info, Debug, and Trace. A custom WiFiManagerLogSink receives a WiFiManagerLogMessage instead of the Print output. Redact SSIDs and never log passwords, portal form values, or product secrets into a remotely collected log. + +## Scan status + +For nearby-network progress, use getScanState(), isScanRunning(), hasValidScanResults(), and getScanResults(); see [Network configuration](NETWORK_CONFIGURATION.md#scan-behavior). The portal API exposes a matching local scan-status representation for the built-in UI. + +## Continue + +- [Provisioning lifecycle](PROVISIONING_LIFECYCLE.md) +- [Station profiles](STATION_PROFILES.md) +- [API reference](API_REFERENCE.md) + +Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/PORTAL_API.md b/docs/PORTAL_API.md index a2d992b..4325c03 100644 --- a/docs/PORTAL_API.md +++ b/docs/PORTAL_API.md @@ -1,40 +1,107 @@ # Portal API -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. +This is the device-local protocol used by WiFiManager's built-in portal shell. It is useful for maintaining the built-in UI and for portal-focused tests, but it is not a cloud API, remote-management interface, or supported companion-app integration surface. -The current bootstrap contract is version 3. It includes `brand.tagline`, the short product tagline rendered in the portal header. +The portal is unauthenticated local setup infrastructure. Do not expose it as a product's general application API, and do not infer a security boundary from hiding an action in the UI. -## Profile-mode WiFi metadata +## Contract version and format -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. +GET /api/bootstrap returns contractVersion 3. The built-in portal uses that versioned response; tests that assert its shape should feature-detect fields rather than assume undocumented portal HTML or JSON properties. -## WiFi connect status +All documented API responses are JSON with no-cache headers. The server also sends permissive CORS headers for the built-in portal implementation; that does not change the local-only security boundary or make these routes a remote integration contract. -When `portalSetBehaviorConnectOnSave(true)` is enabled, saving credentials queues a station join. The SPA polls `GET /api/wifi/connect-status`: +## Route inventory -```json +| Method | Route | Built-in portal purpose | +| --- | --- | --- | +| GET | / | Serves the one portal HTML shell. | +| GET | /api/bootstrap | Reads product brand, portal context, page/action visibility, layout, scan summary, and overview cards. | +| GET | /api/wifi/scan-status | Reads async scan state and visible nearby networks. | +| POST | /api/wifi/scan | Queues a forced asynchronous scan; returns 202. | +| GET | /api/wifi/meta | Reads Wi-Fi form fields, static-IP fields, parameters on the Wi-Fi page, and profile metadata when enabled. | +| POST | /api/wifi/save | Submits Wi-Fi fields, applicable parameters, and optionally a station-profile set. | +| GET | /api/wifi/connect-status | Reads the result of the most recent portal save-and-connect attempt. | +| POST | /api/wifi/connect-complete | Acknowledges that the built-in portal has received the successful station handoff address. | +| POST | /api/portal/timeout-reset | Restarts an active configured portal timeout. | +| GET | /api/params | Reads parameters for the separate Setup page. | +| POST | /api/params/save | Saves Setup-page parameters. | +| GET | /api/info | Reads device/Wi-Fi info, copied information sections, and visible actions. | +| GET | /api/status | Reads a short plain-text status summary in JSON. | +| POST | /api/device/restart | Schedules a target restart. | +| POST | /api/device/erase | Erases Wi-Fi configuration and schedules restart when successful. | +| POST | /api/portal/close | Disables captive-portal detection for the active session. | +| POST | /api/portal/exit | Requests portal exit when portal exit is allowed. | +| POST | /u | Receives multipart firmware upload and returns JSON completion status. | + +Unknown routes are redirected only while captive-portal handling is active; otherwise they return the normal not-found result. + +## Bootstrap and read models + +Bootstrap includes a brand object (title, tagline, logo SVG, and logo alt text), a context object (portal activity, timeout remaining, identity text, status summary, scan state), visible pages/actions, parameter layout, and copied home cards. + +Wi-Fi form metadata is intentionally separate: + +- Legacy mode returns SSID/password fields, configured static fields, and parameters when the layout places them on the Wi-Fi page. +- Profile mode returns primary/fallback profile metadata, the active slot, controller state, static fields, and applicable parameters. +- Password values are never returned. Legacy password placeholder behavior is controlled by portalSetFieldPasswordPlaceholderMode(). + +GET /api/params exposes all registered parameters for the separate Setup page, plus whether the back action is visible. GET /api/info exposes device/Wi-Fi facts, copied information sections, and action visibility. GET /api/status returns a short human-readable text field. + +## Wi-Fi submit and connection handoff + +POST /api/wifi/save accepts form fields. In legacy mode: + +| Field | Meaning | +| --- | --- | +| s | Station SSID. | +| p | Station password. A password without an SSID is treated as a password change for the stored SSID. | +| ip, gw, sn, dns | Optional station static IP, gateway, subnet, and DNS values when those fields are visible. | +| Registered parameter ID or param_N | Application parameter values when parameters are placed on the Wi-Fi page. | + +A normal accepted save returns 202 and directs the portal to poll /api/wifi/connect-status. The status response is: + +~~~json { - "state": "waiting | success | failed", + "state": "idle | waiting | success | failed", "message": "human readable status", "wifiStatus": "WL_CONNECTED", "stationIp": "192.168.1.42", "redirectUrl": "http://192.168.1.42/" } -``` +~~~ -`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. +stationIp and redirectUrl are present only after success. If WiFiManager uses a non-default HTTP port, redirectUrl includes it. -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. +After observing success, the built-in portal POSTs /api/wifi/connect-complete. A 409 response means successful handoff is not ready; otherwise WiFiManager keeps the portal alive briefly, receives the acknowledgement, and then closes after a grace delay. Browser captive redirects can still fail, so the portal keeps the station address visible. -## Portal timeout +When portalSetBehaviorConnectOnSave(false) or setSaveConnect(false) is selected, a Wi-Fi save does not start this station connection/handoff flow. -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. +## Profile-mode submit -## API design rules +In profile mode, POST /api/wifi/save accepts s0/p0 for primary and s1/p1 for fallback. Primary must be non-empty. A blank p0 or p1 preserves an existing password; clear0 or clear1 explicitly clears a password for an open network. -- Use JSON endpoint results for state and actions. -- Keep UI-visible capabilities in the portal bootstrap/API payloads. -- Add a documented endpoint contract before adding a new portal workflow. -- Do not derive state by parsing the rendered shell. +By default, WiFiManager attempts the submitted candidate and returns 202 for status polling. With stationAction=save, it writes the submitted profile set for a later connection attempt and returns 200. See [Station profiles](STATION_PROFILES.md) for candidate verification and storage behavior. + +## Parameter submit + +POST /api/params/save sends registered application parameter fields and returns a successful acknowledgement after the registered save callbacks run. A parameter can be addressed by its stable ID or by param_N index. WiFiManager does not define application validation or durable-storage semantics; the consuming application owns both. + +## Actions and error states + +| Route | Success | Important non-success response | +| --- | --- | --- | +| POST /api/wifi/scan | 202 with accepted/queued state | Scan result arrives through scan-status. | +| POST /api/wifi/save | 202 for queued connection, or 200 for profile save-for-later | 400 for invalid Wi-Fi/profile input; 500 when an explicit profile store save fails. | +| POST /api/wifi/connect-complete | 200 after successful station handoff | 409 when success/address is not ready. | +| POST /api/portal/timeout-reset | 200 with timeout seconds remaining | 409 when no active finite portal timeout exists. | +| POST /api/device/restart | 200, restart scheduled | The target restarts shortly after the response. | +| POST /api/device/erase | 200, erase/restart scheduled | 500 when erase fails. | +| POST /api/portal/close | 200, captive detection disabled | The portal server itself remains subject to its normal lifecycle. | +| POST /api/portal/exit | 200, exit scheduled | 403 when exit is not allowed. | +| POST /u | 200, firmware update/restart scheduled | 500 with update failure detail. | + +## Compatibility boundary + +Use this document to understand and test WiFiManager's own portal behavior. Applications that need a product web API should host and secure that API themselves after WiFiManager has completed its provisioning role. Do not scrape the portal shell, depend on undocumented JSON fields, or add routes through WiFiManager's testing server accessor. Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/PORTAL_CONTENT.md b/docs/PORTAL_CONTENT.md new file mode 100644 index 0000000..b5323c1 --- /dev/null +++ b/docs/PORTAL_CONTENT.md @@ -0,0 +1,103 @@ +# Portal content and application settings + +Use the built-in portal to collect small product settings alongside Wi-Fi credentials. WiFiManager owns the form and its temporary values; the application validates and persists its own settings. + +This matches the product-firmware pattern used by real consumers: load configuration first, expose its current values through WiFiManagerParameter objects, then persist valid submitted values through the application's configuration layer. + +## End-to-end pattern + +~~~cpp +#include + +WiFiManager wifi; +WiFiManagerParameter brokerHost( + "broker_host", "MQTT broker", settings.mqttHost.c_str(), 64); + +void setupPortal() { + wifi.portalAddParameter(&brokerHost); + + wifi.setSaveParamsCallback([](WiFiManager::WiFiManagerRequestArgs args) { + const String candidate = brokerHost.getValue(); + + if (!isValidHostname(candidate)) { + logInvalidBroker(candidate); + return; // Keep the application's known-good stored value. + } + + settings.mqttHost = candidate; + saveApplicationSettings(settings); + }); +} +~~~ + +Call setupPortal() after loading application settings and before a WiFiManager connection or portal start method. The callback receives a copy of the submitted request arguments; use getArg(), getArgAsInt(), getArgAsFloat(), or getArgAsBool() when the application needs request-level values. + +The save-parameters callback is a notification hook, not a validation-response API. Its return type cannot turn the built-in UI's success response into a form error. Validate before using the candidate, preserve known-good data when persistence fails, and provide product-specific feedback through the application's own UI/logging policy. + +## Parameter rules + +| Rule | Why it matters | +| --- | --- | +| Keep each WiFiManagerParameter alive while the portal can use it. | WiFiManager stores the parameter pointer; it does not take ownership. | +| Use a stable ID without spaces or special characters. | The ID is used in portal requests. | +| Choose a bounded value length. | It defines the editable buffer length. | +| Register parameters before opening the portal. | Active portal responses use their established form model. | +| Treat submitted text as untrusted application input. | Portal input is not application validation or durable storage. | + +Use portalClearParameters() only before opening a portal when rebuilding a complete form. It removes registered parameter pointers; it does not destroy application-owned parameter objects. + +## Where settings appear + +By default, custom parameters appear on the Wi-Fi page. To put them on the separate Setup page: + +~~~cpp +wifi.portalSetLayoutParamsLocation(PortalParamsLocation::SetupPage); +~~~ + +Use the Wi-Fi page for a small setting that is naturally provisioned with network credentials. Use the Setup page when product configuration needs its own step. Page visibility and layout are part of the structured portal policy; see [Portal UI and configuration](PORTAL_UI.md). + +## Read-only product context + +Use information sections for labelled facts and home cards for a short overview or callout: + +~~~cpp +PortalInfoSection deviceInfo; +deviceInfo.id = "device"; +deviceInfo.title = "Device"; +deviceInfo.items = { + {"firmware", "Firmware", firmwareVersion}, + {"sensor", "Sensor", sensorReady ? "Ready" : "Checking"}, +}; +wifi.portalAddInfoSection(deviceInfo); + +PortalHomeCard installerHint; +installerHint.id = "installer-hint"; +installerHint.title = "Before you begin"; +installerHint.kind = PortalHomeCardKind::Callout; +installerHint.text = "Connect the device to its final local network."; +wifi.portalAddHomeCard(installerHint); +~~~ + +Information sections and cards are copied at registration. Do not place secrets, passwords, API tokens, or personally identifying values in them. + +## Save callbacks + +| Callback | Use | +| --- | --- | +| setPreSaveParamsCallback() | Observe a parameter-only save before the normal parameter callback. | +| setSaveParamsCallback(args) | Read, validate, and persist application settings after a parameter save. | +| setPreSaveConfigCallback() | Observe a combined Wi-Fi/config save before Wi-Fi connection processing. | +| setSaveConfigCallback() | React after Wi-Fi settings changed and the connection succeeded, or when break-after-config is enabled. | +| setConfigResetCallback() | Clear or reconcile application configuration when the portal resets Wi-Fi settings. | + +Do not use a Wi-Fi-success callback to persist unrelated product settings: parameters can be saved separately, and an application needs its own durable-data policy. + +The buildable [Custom Portal Content](../examples/CustomPortalContent/) example demonstrates parameters, information sections, and home cards. This guide supplies the missing persistence and validation boundary. + +## Continue + +- [Portal UI and configuration](PORTAL_UI.md) +- [Product settings and Wi-Fi recipe](recipes/PRODUCT_SETTINGS_AND_WIFI.md) +- [API reference](API_REFERENCE.md) + +Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/PORTAL_UI.md b/docs/PORTAL_UI.md index 5c750f2..1c1d46e 100644 --- a/docs/PORTAL_UI.md +++ b/docs/PORTAL_UI.md @@ -1,12 +1,12 @@ -# Portal UI +# Portal UI and configuration -`WiFiManagerPortalConfig` is the supported presentation API for the built-in provisioning portal. It changes identity and semantic visual tokens only. WiFiManager continues to own portal routes, forms, navigation, captive behavior, reset, and OTA views. +WiFiManagerPortalConfig is the supported presentation API for the built-in provisioning portal. It changes product identity and semantic visual tokens; the portal-policy methods below control the visibility and behavior of existing built-in features. WiFiManager continues to own portal routes, forms, navigation, captive behavior, reset, and OTA views. -Apply the configuration before `autoConnect()`, `startConfigPortal()`, or `startWebPortal()`. The configuration is non-owning: text and SVG data must have static firmware lifetime and declare whether it is in RAM or PROGMEM. WiFiManager locks presentation after a portal starts, so active asynchronous responses cannot observe a partial configuration. +Apply presentation before autoConnect(), startConfigPortal(), or startWebPortal(). Configure portal policy during boot as well so each portal session begins consistently. Portal text and SVG assets are non-owning, so their RAM or PROGMEM data must have static firmware lifetime. WiFiManager locks presentation while a portal is active so asynchronous responses cannot observe partial configuration; setPortalConfig() returns false if it cannot accept the configuration. ## Standalone branded portal -```cpp +~~~cpp #include #include @@ -17,7 +17,8 @@ const char kTitle[] PROGMEM = "Set up Temperature Monitor"; const char kIdentity[] PROGMEM = "Example Devices"; const char kTagline[] PROGMEM = "Reliable setup for connected devices."; const char kLogoAlt[] PROGMEM = "Example Devices"; -const char kLogo[] PROGMEM = R"svg()svg"; +const char kLogo[] PROGMEM = + R"svg()svg"; const char kPage[] PROGMEM = "#f4f7f3"; const char kSurface[] PROGMEM = "#ffffff"; const char kAccent[] PROGMEM = "#347a45"; @@ -40,47 +41,72 @@ void setup() { kPortalUI.theme.accent = WiFiManagerPortalText::progmem(kAccent); kPortalUI.theme.accentText = WiFiManagerPortalText::progmem(kAccentText); kPortalUI.theme.cornerRadiusPx = 10; - kPortalUI.theme.smallCornerRadiusPx = 6; - wifi.setPortalConfig(kPortalUI); // Configure before the portal starts. + if (!wifi.setPortalConfig(kPortalUI)) { + Serial.println("Portal UI configuration was rejected"); + } wifi.autoConnect("Temperature Monitor"); } void loop() { wifi.process(); } -``` +~~~ -The complete buildable example is [BrandedPortal](../examples/BrandedPortal/BrandedPortal.ino). The compile fixture exercises this API on ESP8266 and ESP32. +The complete buildable example is [Branded Portal](../examples/BrandedPortal/BrandedPortal.ino). The compile fixture exercises this API on ESP8266 and ESP32. -## Configuration reference +## Presentation reference -Leave a text or colour value empty, or a radius at `0`, to retain the built-in stylesheet value. The default title is `WiFiManager`. +Leave a text or colour value empty, or a radius at 0, to retain the built-in stylesheet value. | Field | Used by | | --- | --- | -| `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 | -| `success`, `danger`, `dangerHover` | Status and destructive actions | -| `cornerRadiusPx`, `smallCornerRadiusPx` | Card and compact-control corners, limited to 64 px | +| title | Document title and concise setup-page heading | +| identityText | Company or product name in the header above navigation | +| tagline | Short product context 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 | +| success, danger, dangerHover | Status and destructive actions | +| cornerRadiusPx, smallCornerRadiusPx | Card and compact-control corners, limited to 64 px | -Theme values accept only simple semantic CSS value syntax and are emitted once into a small `#wm-portal-theme` block. This is deliberately not a raw CSS or JavaScript injection API. An SVG is a trusted compiled firmware asset, never form, MQTT, or network input. +Theme values accept only simple semantic CSS value syntax and are emitted once into a small portal theme block. This is deliberately not a raw CSS or JavaScript injection API. An SVG is a trusted compiled firmware asset, never form, MQTT, or network input. -## Structured portal content +## Portal policy -`portalAddParameter()` adds an editable application value. `portalAddInfoSection()` renders labelled read-only values, and `portalAddHomeCard()` adds a text, callout, or key/value card to the overview. +Use the portal-prefixed methods to constrain a product's use of built-in pages and actions. Configure them during boot, before the portal starts, so a session begins with the intended policy. -Register content before opening the portal. A `WiFiManagerParameter` remains application-owned and must outlive the portal; info sections and home cards are copied when registered. Handle a saved value in `setSaveParamsCallback()`, then validate and persist it in your application. +~~~cpp +// An installer portal that does not expose destructive reset or OTA actions. +wifi.portalSetPageUpdateVisible(false); +wifi.portalSetActionEraseVisible(false); +wifi.portalSetActionRestartVisible(false); -The buildable [Custom Portal Content](../examples/CustomPortalContent/) example uses all three without replacing the built-in portal shell. +// Keep application settings on their own Setup page. +wifi.portalSetLayoutParamsLocation(PortalParamsLocation::SetupPage); -## Built-in portal capabilities +// Do not show saved passwords or static-IP fields unless the product needs them. +wifi.portalSetFieldPasswordPlaceholderMode(PortalPasswordPlaceholderMode::Hidden); +wifi.portalSetFieldStaticIpVisibility(PortalFieldVisibility::Hidden); +wifi.portalSetFieldStaticDnsVisibility(PortalFieldVisibility::Hidden); +~~~ -Presentation uses one configuration route: `setPortalConfig()`. Existing structured portal capabilities remain separate: `portalAddParameter()`, `portalAddInfoSection()`, `portalAddHomeCard()`, page visibility, and portal behavior configure documented built-in functionality rather than private markup. See [Portal API](PORTAL_API.md) and [Station profiles](STATION_PROFILES.md). +| Group | Methods | Use | +| --- | --- | --- | +| Pages | portalSetPageInfoVisible(), portalSetPageUpdateVisible(), portalSetPageSetupVisible() | Show only product-appropriate built-in pages. | +| Actions | portalSetActionEraseVisible(), portalSetActionRestartVisible(), portalSetActionExitVisible(), portalSetActionCloseCaptiveVisible(), portalSetActionBackVisible() | Control existing action affordances; hiding an action is not a security boundary. | +| Layout | portalSetLayoutParamsLocation() | Put registered parameters on the Wi-Fi page or separate Setup page. | +| Connection/portal behavior | portalSetBehaviorCaptivePortalEnabled(), portalSetBehaviorConnectOnSave(), portalSetBehaviorExitAllowed(), portalSetBehaviorConnectTimeoutSeconds(), portalSetBehaviorPortalTimeoutSeconds(), portalSetBehaviorAutoReconnect(), portalSetBehaviorApClientCheck(), portalSetBehaviorWebClientCheck() | Set portal behavior through the structured configuration vocabulary. | +| Field policy | portalSetFieldPasswordPlaceholderMode(), portalSetFieldStaticIpVisibility(), portalSetFieldStaticDnsVisibility() | Limit password disclosure and network-field visibility. | -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. +The older setConfigPortalTimeout(), setSaveConnect(), setShowStaticFields(), and related methods remain available. Prefer a single vocabulary within a product; the portal-prefixed methods make the policy visible in the portal's structured model. +## Structured content + +Use portalAddParameter() for editable product settings, portalAddInfoSection() for labelled read-only values, and portalAddHomeCard() for overview text or key/value cards. Parameters remain application-owned; information sections and cards are copied when registered. + +See [Portal content](PORTAL_CONTENT.md) for the full persistence, validation, callback, and lifetime rules. The buildable [Custom Portal Content](../examples/CustomPortalContent/) example shows all three content types. + +## Supported boundary + +There is no arbitrary HTML shell, route replacement, navigation injection, raw stylesheet, or script hook. Product branding and policy configure documented built-in functionality rather than private markup. If a product needs a new portal capability, add a narrow WiFiManager contract and test it on both supported targets. Back to the [documentation index](README.md) or [project overview](../README.md). diff --git a/docs/PROVISIONING_LIFECYCLE.md b/docs/PROVISIONING_LIFECYCLE.md new file mode 100644 index 0000000..5541d32 --- /dev/null +++ b/docs/PROVISIONING_LIFECYCLE.md @@ -0,0 +1,92 @@ +# Provisioning lifecycle + +Choose a connection flow that matches the device's operating policy, then call process() regularly for as long as WiFiManager is active. The consuming firmware owns its product-service lifecycle and restart decision. + +## Choose a flow + +| Need | Preferred API | What it does | +| --- | --- | --- | +| One saved station network, with portal fallback | autoConnect() | Tries saved credentials and starts the configuration portal when the attempt cannot connect. | +| Primary plus fallback network with application-owned storage | setStationProfileStore() + startStationConnection() | Loads, selects, retries, and recovers a fixed two-profile station set. | +| Verify a received profile before retaining it | startStationCandidate() | Attempts a primary/fallback candidate in memory and saves it only after success. | +| Start setup under application control | startConfigPortal() | Starts an AP and captive configuration portal immediately. | +| Show the portal while station Wi-Fi is already available | startWebPortal() | Starts the portal web service without starting a configuration AP. | + +The first two are the normal deployed-device flows. StartConfigPortal() and startWebPortal() are supported control APIs, but should be used only when the application has a clear policy for entering and leaving them. + +## Basic recovery flow + +~~~cpp +#include +#include + +WiFiManager wifi; + +void setup() { + Serial.begin(115200); + wifi.setConfigPortalTimeout(180); + + if (wifi.autoConnect("Device Setup", "change-me")) { + startNormalApplication(); + } else { + Serial.println("Wi-Fi setup portal is active"); + } +} + +void loop() { + wifi.process(); + + if (wifi.didConfigPortalConnectSucceed()) { + // The application decides whether to start services or reboot. + startNormalApplication(); + } +} +~~~ + +autoConnect() returns true when WiFiManager connected during that call. It returns false when it could not connect and has entered the portal path; false is not, by itself, an instruction to restart. Call process() every loop iteration while setup may be needed. + +A portal timeout is in seconds. setConfigPortalTimeout(0), the default, leaves the portal open. A field-installed product may deliberately use a longer bounded window, such as 15 minutes, so an installer has time to complete setup without leaving an unattended portal indefinitely. + +## Portal connection outcome + +For a portal save-and-connect attempt, use these getters instead of inferring state from the rendered page: + +| Getter | Meaning | +| --- | --- | +| getConfigPortalActive() | The configuration portal is currently running. | +| hasEnteredConfigPortal() | The portal has been entered at least once this runtime session. | +| isConfigPortalConnectPending() | A portal-submitted station connection is queued or waiting. | +| didConfigPortalConnectSucceed() / didConfigPortalConnectFail() | Result of the last portal-submitted connection attempt. | +| getConfigPortalConnectStatus() / getConfigPortalConnectMessage() | Platform Wi-Fi status and a human-readable result. | + +The local portal UI obtains the same state through its documented local API. Do not parse portal HTML to determine connection state. + +## Timing and policy + +Set these before the flow starts: + +| API | Use | +| --- | --- | +| setConfigPortalTimeout(seconds) | Limits a captive configuration session; setTimeout() is its deprecated alias. | +| setConnectTimeout(seconds) and setConnectRetries(count) | Bounds legacy automatic connection attempts. | +| setSaveConnectTimeout(seconds) | Bounds a portal save-and-connect attempt. | +| setSaveConnect(enabled) | Controls whether a normal portal save attempts a station connection. | +| setBreakAfterConfig(enabled) | Exits after a configuration submission even if the connection was unsuccessful. | +| setEnableConfigPortal(enabled) / setDisableConfigPortal(enabled) | Controls autoConnect() portal fallback and its post-save shutdown behavior. | +| setAPClientCheck(enabled) / setWebPortalClientCheck(enabled) | Controls whether AP/web-client activity affects the portal timeout. | + +The portal-prefixed behavior methods provide the same configuration through the structured portal contract; prefer one vocabulary consistently in a product. + +## Port ownership and clean handoff + +Before beginning a portal on port 80, stop any application server that already owns that port. After a portal connection succeeds, wait for the application's own readiness requirements before starting its server again. A successful station connection does not automatically make an application-level service ready. + +See [Local web-service handoff](recipes/LOCAL_WEB_SERVICE_HANDOFF.md) for the integration sequence. + +## Continue + +- [Station profiles](STATION_PROFILES.md) +- [Observability](OBSERVABILITY.md) +- [API reference](API_REFERENCE.md) + +Back to [documentation](README.md) · [project overview](../README.md). diff --git a/docs/README.md b/docs/README.md index 4a3c899..1290aa8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,11 +2,17 @@ | I want to… | Read | | --- | --- | +| Understand what WiFiManager owns and what the application owns | [Architecture](ARCHITECTURE.md) | | Start a basic portal or install a released dependency | [Getting started](GETTING_STARTED.md) | -| Brand the built-in portal | [Portal UI](PORTAL_UI.md) | -| Add application settings, status, or home cards | [Structured portal content](PORTAL_UI.md#structured-portal-content) | +| Choose and operate a provisioning flow | [Provisioning lifecycle](PROVISIONING_LIFECYCLE.md) | +| Brand or constrain the built-in portal | [Portal UI and configuration](PORTAL_UI.md) | +| Add and persist application settings, status, or home cards | [Portal content](PORTAL_CONTENT.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) | +| Configure AP, station, scan, and reconnect settings | [Network configuration](NETWORK_CONFIGURATION.md) | +| Surface portal and station state in product firmware | [Observability](OBSERVABILITY.md) | +| Look up a supported C++ method and lifecycle rule | [API reference](API_REFERENCE.md) | +| Understand the built-in portal's local JSON protocol | [Portal API](PORTAL_API.md) | +| Apply an existing device-integration pattern | [Recipes](recipes/README.md) | | Run documentation and board-free compile checks | [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) | diff --git a/docs/STATION_PROFILES.md b/docs/STATION_PROFILES.md index ce1201c..3ce1fc9 100644 --- a/docs/STATION_PROFILES.md +++ b/docs/STATION_PROFILES.md @@ -1,8 +1,8 @@ # Station profiles -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. +WiFiManager 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. -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. +Profile mode is enabled only when the application supplies a WiFiManagerStationProfileStore. The store may be an in-memory implementation for a temporary session, but durable deployments should provide persistent storage. ## Lifecycle @@ -14,13 +14,13 @@ A profile set has exactly two fixed slots: The controller never treats the ESP SDK's saved single network as an additional source of truth. It begins one bounded connection attempt at a time, moves to the fallback after failure, and retries both profiles after a temporary loss of a previously working connection. If a new device has no valid profiles, it opens the normal configuration portal. -A candidate submitted by the portal is only committed after it connects and receives a usable IP address. A failed candidate leaves the last saved profile set intact. +A candidate submitted by the portal or another application subsystem is only committed after it connects and receives a usable IP address. A failed candidate leaves the last saved profile set intact. ## Direct WiFiManager use -Implement a small store appropriate to the application. The manager neither allocates nor owns it: +Implement a small store appropriate to the application. WiFiManager neither allocates nor owns it: -```cpp +~~~cpp class MyProfileStore final : public WiFiManagerStationProfileStore { public: bool load(WiFiManagerStationProfiles& profiles) override; @@ -40,23 +40,51 @@ void setup() { void loop() { wifi.process(); } -``` +~~~ -The store must return a complete `WiFiManagerStationProfiles` value. Each enabled profile has a NUL-terminated SSID of at most 32 characters and an optional NUL-terminated password of at most 64 characters. Keep slot 0 enabled; set `hasPassword = false` for an open network. +The store must return a complete WiFiManagerStationProfiles value. Each enabled profile has a NUL-terminated SSID of at most 32 characters and an optional NUL-terminated password of at most 64 characters. Keep slot 0 enabled; set hasPassword to false for an open network. -To stage profiles supplied by another subsystem, use `startStationCandidate(candidate)`. It connects the primary/fallback set first and calls the store only after success. Use `saveStationProfiles(profiles)` only when deliberately saving without a connection check. `clearStationProfiles()` clears the supplied store and disconnects the station. +When load() returns false, WiFiManager treats the profile set as unavailable and opens the normal configuration portal. When save() or clear() returns false, getStationStatus().storageSaveFailed is set and the status message explains the failure. + +## Verified candidate flow + +Use startStationCandidate(candidate) when another application subsystem supplies a complete primary/fallback proposal: + +~~~cpp +wifi.setStationProfileStore(&profiles); + +if (!wifi.startStationCandidate(candidate, "Example Setup", "setup-password")) { + reportInvalidProfileCandidate(); +} +~~~ + +WiFiManager tries the candidate in memory, then calls the attached store only after the station connects and has a usable IP address. Inspect getStationStatus() after WM_EVENT_STATION_PROFILE_CONNECTED: lastConnectionWasCandidate tells the application that this was a candidate, and storageSaveFailed distinguishes a usable but non-durable connection. + +Use saveStationProfiles(profiles) only when deliberately saving without a connection check. clearStationProfiles() asks the supplied store to clear profiles and disconnects the station only after that clear succeeds. + +The [Primary and fallback Wi-Fi recipe](recipes/PRIMARY_AND_FALLBACK_WIFI.md) shows why candidate verification protects known-good data. ## Portal contract -In profile mode, the existing Wi-Fi page becomes a two-profile form. It remains driven by the same JSON endpoints: +In profile mode, the existing Wi-Fi page becomes a two-profile form. It remains driven by the same local portal endpoints: -- `GET /api/wifi/meta` returns `profiles`, `activeSlot`, and controller `state`. -- `POST /api/wifi/save` accepts `s0`/ `p0` for primary and `s1`/ `p1` for fallback. A blank submitted password preserves an existing password; send `clear0` or `clear1` for an intentional open network. -- A normal save verifies the candidate by connecting. Poll `GET /api/wifi/connect-status` for its result. -- Set `stationAction=save` only to store the submitted profiles for a later connection attempt. +- GET /api/wifi/meta returns profiles, activeSlot, and controller state without passwords. +- POST /api/wifi/save accepts s0/p0 for primary and s1/p1 for fallback. A blank submitted password preserves an existing password; send clear0 or clear1 for an intentional open network. +- A normal save verifies the candidate by connecting. The built-in portal polls GET /api/wifi/connect-status for its result. +- stationAction=save stores the submitted profiles for a later connection attempt. -The portal requires a non-empty primary SSID. Its API never returns a password; it only reports whether one is set. +The portal requires a non-empty primary SSID. Its local protocol never returns a password. See [Portal API](PORTAL_API.md) for the shared connection-status response. -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. +## Recovery and troubleshooting + +| Symptom | Inspect | Meaning | +| --- | --- | --- | +| Portal starts immediately | getStationStatus().configuredProfiles and message | Store had no valid primary profile, so WiFiManager did not fall back to SDK-owned credentials. | +| Repeated network recovery | state, attemptedSlot, activeSlot, and WM_EVENT_STATION_BACKOFF | The controller is trying configured profiles with the recovery interval. | +| Candidate is connected but lost after reboot | lastConnectionWasCandidate and storageSaveFailed | The candidate connected, but the application store did not retain it. | +| Portal form rejects a save | Local API response/message | Primary profile was missing or SSID/password bounds were invalid. | +| Profiles do not clear | storageSaveFailed and message | The application store rejected clear(); fix its storage error before assuming Wi-Fi was removed. | + +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/recipes/FIELD_INSTALLER_PROVISIONING.md b/docs/recipes/FIELD_INSTALLER_PROVISIONING.md new file mode 100644 index 0000000..d2574e5 --- /dev/null +++ b/docs/recipes/FIELD_INSTALLER_PROVISIONING.md @@ -0,0 +1,58 @@ +# Field installer provisioning + +A field-installed controller or sensor often needs more time for setup than a desk-bound demo, but should not host an unattended configuration portal forever. Existing consuming device sketches solve this by choosing a deliberate 15-minute configuration window and exposing clear local feedback while setup is active. + +## Flow + +1. Boot with the product's normal configuration. +2. Try the configured station flow. +3. If Wi-Fi is unavailable, start the local setup AP and portal. +4. Keep the portal available for the installation window. +5. On a successful connection, let the application start services or reboot according to its own policy. +6. On timeout, return to the product's recovery policy rather than assuming the installer finished. + +~~~cpp +constexpr unsigned long kInstallerWindowSeconds = 15 * 60; + +void setup() { + configureProductPortal(); + wifi.setConfigPortalTimeout(kInstallerWindowSeconds); + + wifi.setAPCallback([](WiFiManager*) { + setIndicator(IndicatorState::Setup); + showInstallerInstructions(); + }); + + wifi.setConfigPortalTimeoutCallback([] { + setIndicator(IndicatorState::Offline); + recordSetupTimeout(); + }); + + wifi.autoConnect(deviceSetupName(), deviceSetupPassword()); +} + +void loop() { + wifi.process(); + runApplicationWork(); +} +~~~ + +The timeout is a product decision. A short interval is appropriate for a user-facing appliance; a longer maintenance window may be justified for a device installed in a cabinet or plant room. Set 0 only when an always-open setup portal is an explicit operational choice. + +## What to show an installer + +Keep feedback independent of a browser redirect: + +- the setup SSID and any required password; +- the device's portal address and non-default port, if configured; +- a distinct setup indicator while the portal is active; +- a distinct failure/offline indication after timeout; +- confirmation only after Wi-Fi has actually connected. + +Use getConfigPortalActive(), didConfigPortalConnectSucceed(), and getConfigPortalConnectMessage() for this state. See [Observability](../OBSERVABILITY.md). + +## Product boundary + +WiFiManager supplies the local portal and timer. The product decides what happens after timeout: keep retrying profiles, sleep, wait for a physical action, or operate in an offline mode. Do not treat autoConnect() returning false as a reason to reboot immediately; the portal may be the intended next state. + +Continue with [Provisioning lifecycle](../PROVISIONING_LIFECYCLE.md) or [Provisioning state feedback](PROVISIONING_STATE_FEEDBACK.md). diff --git a/docs/recipes/LOCAL_WEB_SERVICE_HANDOFF.md b/docs/recipes/LOCAL_WEB_SERVICE_HANDOFF.md new file mode 100644 index 0000000..f9ebd56 --- /dev/null +++ b/docs/recipes/LOCAL_WEB_SERVICE_HANDOFF.md @@ -0,0 +1,35 @@ +# Local web-service handoff + +A product may already host its own local HTTP service when a later connection failure opens WiFiManager's provisioning portal. Because WiFiManager owns its portal server port while active, the product must release that port before WiFiManager registers its portal routes. + +A real consuming framework does this from the AP callback: it shuts down its normal web service after the setup AP has started but before WiFiManager registers portal routes. + +## Automatic recovery handoff + +~~~cpp +void setup() { + wifi.setAPCallback([](WiFiManager*) { + stopApplicationWebServer(); // Releases port 80 for WiFiManager. + setIndicator(IndicatorState::Setup); + }); + + wifi.autoConnect("Device Setup", "setup-password"); +} +~~~ + +The AP callback runs after AP mode begins and before the portal routes are registered. It is the appropriate hook for automatic portal fallback. If the application explicitly starts setup itself, release its server before calling startConfigPortal(). + +## After a successful connection + +The portal's successful Wi-Fi connection means the station has an address. It does not guarantee that the product's own web application, MQTT connection, or sensors are ready. The product should: + +1. observe didConfigPortalConnectSucceed() or the matching event; +2. persist any application settings required for normal operation; +3. choose whether to restart or recreate its own server; +4. only advertise the product service when it is ready. + +If the product uses a non-default WiFiManager HTTP port, it can avoid a port conflict but must treat the portal URL and its station-connect handoff URL as that non-default port. + +Do not add routes to WiFiManager's internal server through getServer(). That accessor is for testing/host integration, not a supported product-extension API. + +Continue with [Provisioning lifecycle](../PROVISIONING_LIFECYCLE.md) and [Observability](../OBSERVABILITY.md). diff --git a/docs/recipes/PRIMARY_AND_FALLBACK_WIFI.md b/docs/recipes/PRIMARY_AND_FALLBACK_WIFI.md new file mode 100644 index 0000000..3a7de06 --- /dev/null +++ b/docs/recipes/PRIMARY_AND_FALLBACK_WIFI.md @@ -0,0 +1,49 @@ +# Primary and fallback Wi-Fi + +Some deployed devices receive a primary Wi-Fi network and an optional fallback from a local provisioning source. The consuming framework uses WiFiManager's fixed two-profile controller so it can verify a candidate connection before replacing durable known-good profiles. + +## Flow + +1. The application obtains a complete candidate profile set. +2. It gives the candidate to startStationCandidate(). +3. WiFiManager attempts the primary profile and then fallback when needed. +4. On a usable station connection, WiFiManager saves the candidate through the application-owned store. +5. If connection or storage fails, the application can report the result without silently replacing known-good data. + +~~~cpp +WiFiManagerStationProfiles candidate = makeDeploymentProfiles(); + +wifi.setStationProfileStore(&profileStore); +wifi.setStationRecoveryInterval(30000); + +if (!wifi.startStationCandidate(candidate, "Device Setup", "setup-password")) { + reportRejectedProfileSet(); +} +~~~ + +A candidate needs a non-empty enabled primary slot. The fallback slot is optional. WiFiManager never exposes profile passwords through the portal metadata API. + +## Treat persistence result as part of success + +A candidate can connect successfully while the profile store fails to write. Read getStationStatus() when WM_EVENT_STATION_PROFILE_CONNECTED arrives: + +~~~cpp +wifi.setEventCallback([](WiFiManager::wm_event_t event) { + if (event != WiFiManager::WM_EVENT_STATION_PROFILE_CONNECTED) { + return; + } + + const auto& status = wifi.getStationStatus(); + if (status.lastConnectionWasCandidate && status.storageSaveFailed) { + reportConnectedButNotRetained(); + } +}); +~~~ + +This distinction matters in unattended devices: the device works now, but may fail to reconnect after a restart. + +## Bootstrap and reconcile are application decisions + +WiFiManager verifies and selects station profiles. It does not define whether a product should accept a supplied profile only on first boot, replace values after an explicit revision, or merge settings from another system. Keep that policy in the application, alongside its durable configuration and schema migration. + +See [Station profiles](../STATION_PROFILES.md) for store requirements, portal fields, and the complete controller lifecycle. diff --git a/docs/recipes/PRODUCT_SETTINGS_AND_WIFI.md b/docs/recipes/PRODUCT_SETTINGS_AND_WIFI.md new file mode 100644 index 0000000..d7d12bb --- /dev/null +++ b/docs/recipes/PRODUCT_SETTINGS_AND_WIFI.md @@ -0,0 +1,52 @@ +# Product settings and Wi-Fi + +A connected device often needs more than an SSID and password: a device name, broker host, endpoint, operating mode, or installer-selected option. The actual consuming framework pattern is to load those settings before WiFiManager begins, expose them as portal parameters, and persist them through the application's own storage layer. + +## Flow + +1. Load the application's durable settings and run its schema migration. +2. Build WiFiManagerParameter objects from the loaded values. +3. Register those parameters before a portal can start. +4. Register the save-parameters callback. +5. Validate and persist submitted application values through the application's storage layer. +6. Separately observe a successful Wi-Fi configuration/save if product services need a connected station first. + +~~~cpp +void configurePortalFromSettings() { + brokerHost.setValue(settings.mqttHost.c_str(), 64); + wifi.portalAddParameter(&brokerHost); + + wifi.setSaveParamsCallback([](WiFiManager::WiFiManagerRequestArgs) { + const String requestedHost = brokerHost.getValue(); + + if (!isValidHostname(requestedHost)) { + logRejectedSetting("broker_host"); + return; + } + + settings.mqttHost = requestedHost; + if (!saveApplicationSettings(settings)) { + reportConfigurationStorageFailure(); + } + }); +} +~~~ + +WiFiManager does not own settings, schema migration, or the storage transaction. In particular, an application should keep known-good settings if validation or storage fails. + +## Keep the two persistence paths separate + +| Data | Owner | Save trigger | +| --- | --- | --- | +| Wi-Fi credentials in legacy mode | WiFiManager/platform Wi-Fi | Portal Wi-Fi save and connection flow | +| Primary/fallback station profiles | Application-provided profile store | Profile controller after verified candidate connection, or explicit save | +| Product settings | Application | setSaveParamsCallback() and application validation | +| Product schema/revision | Application | Application boot/migration policy | + +A profile provisioning revision is not the same thing as an application schema version. The application owns both migration and the decision to apply a configuration update. + +## Form placement + +Use portalSetLayoutParamsLocation(PortalParamsLocation::WiFiPage) for one or two values that naturally belong in initial connectivity setup. Use SetupPage for product configuration that deserves a separate step. Put read-only status in PortalInfoSection or PortalHomeCard rather than turning it into a parameter. + +For lifetime and callback details, see [Portal content](../PORTAL_CONTENT.md). diff --git a/docs/recipes/PROVISIONING_STATE_FEEDBACK.md b/docs/recipes/PROVISIONING_STATE_FEEDBACK.md new file mode 100644 index 0000000..60d99f5 --- /dev/null +++ b/docs/recipes/PROVISIONING_STATE_FEEDBACK.md @@ -0,0 +1,43 @@ +# Provisioning state feedback + +A physical device should give useful feedback without relying on a captive-browser redirect. Existing consuming firmware uses WiFiManager state to make setup, recovery, and connected states visible through an LED, display, or local log. + +## State model + +| Product state | WiFiManager signal | Typical feedback | +| --- | --- | --- | +| Normal connected operation | Station/profile controller connected, no configuration portal | Normal indicator and application services. | +| Trying known networks | Profile status is attempting or switching | Short recovery indication; no claim that setup is active yet. | +| Setup portal active | getConfigPortalActive() is true or WM_EVENT_PORTAL_STARTED | Installer-oriented setup indication and instructions. | +| Portal submitted credentials | isConfigPortalConnectPending() is true | Progress indication. | +| Portal connection succeeded | didConfigPortalConnectSucceed() or WM_EVENT_PORTAL_CONNECT_SUCCESS | Connected confirmation; application decides when services start. | +| Portal connection failed | didConfigPortalConnectFail() or WM_EVENT_PORTAL_CONNECT_FAILED | Failure indication with retry/recovery policy. | +| Candidate connected but not stored | Station event plus storageSaveFailed | Warning: active connection may not survive restart. | + +## Polling pattern + +~~~cpp +void updateIndicator() { + if (wifi.getConfigPortalActive()) { + setIndicator(IndicatorState::Setup); + } else if (wifi.isConfigPortalConnectPending()) { + setIndicator(IndicatorState::Connecting); + } else if (wifi.didConfigPortalConnectFail()) { + setIndicator(IndicatorState::Offline); + } else { + setIndicator(IndicatorState::Normal); + } +} +~~~ + +Call this from the application loop after wifi.process(). For profile mode, add getStationStatus() so the product can distinguish an active station recovery attempt from a portal session. + +## Event pattern + +Events reduce polling for transitions, but getters remain the authoritative state. Use setEventCallback() to record a transition or wake a product state machine, then read the relevant portal/profile status. Avoid performing long work in the callback; keep it suitable for the normal firmware loop. + +## Operator-facing messages + +Use getConfigPortalConnectMessage() and getWLStatusString() for concise local diagnostics. Never place Wi-Fi passwords, portal parameter values, or secrets in an operator display or remotely collected logs. + +See [Observability](../OBSERVABILITY.md) for the complete event list and [Field installer provisioning](FIELD_INSTALLER_PROVISIONING.md) for timeout policy. diff --git a/docs/recipes/README.md b/docs/recipes/README.md new file mode 100644 index 0000000..2367405 --- /dev/null +++ b/docs/recipes/README.md @@ -0,0 +1,15 @@ +# Integration recipes + +These patterns come from real firmware that consumes this WiFiManager fork. They describe the boundary between a product application and WiFiManager; they are not additional framework APIs and do not require a cloud service or companion app. + +| Scenario | Start here when… | +| --- | --- | +| [Field installer provisioning](FIELD_INSTALLER_PROVISIONING.md) | A physical device needs a deliberately bounded setup window on site. | +| [Product settings and Wi-Fi](PRODUCT_SETTINGS_AND_WIFI.md) | The portal collects Wi-Fi and application-owned settings together. | +| [Primary and fallback Wi-Fi](PRIMARY_AND_FALLBACK_WIFI.md) | A deployment supplies a candidate primary/fallback network that must be verified before storage. | +| [Local web-service handoff](LOCAL_WEB_SERVICE_HANDOFF.md) | The application already owns port 80 when recovery provisioning may begin. | +| [Provisioning state feedback](PROVISIONING_STATE_FEEDBACK.md) | LEDs, a display, or logs need to distinguish setup, recovery, and connected states. | + +The normal [Basic Portal](../../examples/BasicPortal/) example remains the shortest way to try the legacy saved-network-or-portal flow. These recipes explain the product concerns that a standalone example should not pretend to solve. + +Back to [documentation](../README.md) · [project overview](../../README.md). diff --git a/examples/BasicPortal/README.md b/examples/BasicPortal/README.md index 2beb52c..ca4d743 100644 --- a/examples/BasicPortal/README.md +++ b/examples/BasicPortal/README.md @@ -9,4 +9,4 @@ This is the smallest useful WiFiManager application. It first tries the credenti 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). +See the shared [example guide](../README.md), [getting started](../../docs/GETTING_STARTED.md), and [provisioning lifecycle](../../docs/PROVISIONING_LIFECYCLE.md). diff --git a/examples/BrandedPortal/README.md b/examples/BrandedPortal/README.md index b1947c0..6a1cbfe 100644 --- a/examples/BrandedPortal/README.md +++ b/examples/BrandedPortal/README.md @@ -6,4 +6,4 @@ Flash the `esp8266` or `esp32` environment, join **Temperature Monitor**, and op 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). +See [Portal UI and configuration](../../docs/PORTAL_UI.md), [architecture boundaries](../../docs/ARCHITECTURE.md), and the shared [example guide](../README.md). diff --git a/examples/CustomPortalContent/CustomPortalContent.ino b/examples/CustomPortalContent/CustomPortalContent.ino index 9246fa6..d0b49ac 100644 --- a/examples/CustomPortalContent/CustomPortalContent.ino +++ b/examples/CustomPortalContent/CustomPortalContent.ino @@ -28,6 +28,7 @@ void setup() { portal.portalAddHomeCard(hint); portal.setSaveParamsCallback([](WiFiManager::WiFiManagerRequestArgs) { + // A product validates and persists this value here; this demo only prints it. Serial.print("MQTT broker selected: "); Serial.println(brokerHost.getValue()); }); diff --git a/examples/CustomPortalContent/README.md b/examples/CustomPortalContent/README.md index a811f08..0ccad74 100644 --- a/examples/CustomPortalContent/README.md +++ b/examples/CustomPortalContent/README.md @@ -10,4 +10,4 @@ It opens **WiFiManager Content** with password **example-pass** until it has wor 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 [structured portal content](../../docs/PORTAL_UI.md#structured-portal-content) and the shared [example guide](../README.md). +See [Portal content](../../docs/PORTAL_CONTENT.md), [product settings and Wi-Fi](../../docs/recipes/PRODUCT_SETTINGS_AND_WIFI.md), and the shared [example guide](../README.md). diff --git a/examples/StationProfiles/README.md b/examples/StationProfiles/README.md index 8048364..2a76322 100644 --- a/examples/StationProfiles/README.md +++ b/examples/StationProfiles/README.md @@ -6,4 +6,4 @@ On a blank board, connect to **WiFiManager Profiles** with password **example-pa `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). +See [Station profiles](../../docs/STATION_PROFILES.md), [primary and fallback Wi-Fi](../../docs/recipes/PRIMARY_AND_FALLBACK_WIFI.md), and the shared [example guide](../README.md). diff --git a/scripts/check-docs.sh b/scripts/check-docs.sh index 46bac1a..279ce33 100755 --- a/scripts/check-docs.sh +++ b/scripts/check-docs.sh @@ -8,7 +8,7 @@ link_pattern='\]\(([^ )]+)' while IFS= read -r file; do in_fence=false while IFS= read -r line || [[ -n "$line" ]]; do - if [[ "$line" =~ ^[[:space:]]*\`\`\` ]]; then + if [[ "$line" =~ ^[[:space:]]*(\`\`\`|~~~) ]]; then [[ "$in_fence" == true ]] && in_fence=false || in_fence=true continue fi