# AI Tools (/docs/elegantota/ai-tools) > Use the ElegantOTA documentation from AI coding assistants through the MCP server, llms.txt and Markdown page exports. Library: ElegantOTA The ElegantOTA documentation is available in machine-readable formats so that AI coding assistants can answer questions and generate code from the current API instead of outdated training data. ## Ask AI Select **Ask AI** at the bottom of any documentation page to open the documentation assistant. It searches these pages before answering and links to the sources it used. Answers are grounded in the documentation only; verify generated code against the referenced pages. ## MCP server The documentation is served as a [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP. No authentication is required. One server covers every SOFTT library (ESP-DASH, ElegantOTA, NetWizard and WebSerial); pass `library: "elegantota"` to limit results to ElegantOTA. ```text https://docs.softt.io/api/mcp ``` The server exposes three read-only tools: | Tool | Description | | --- | --- | | `search_docs` | Full-text search across the documentation. Accepts `query`, an optional `library` (`espdash`, `elegantota`, `netwizard`, `webserial` or `all`), an optional `version` for ESP-DASH (`v5`, `v4` or `all`) and `limit`. | | `get_page` | Returns the complete Markdown of a page, such as `/docs/elegantota/ota-modes/auto`. | | `list_pages` | Lists pages with their title, description and URL, filtered by `library` and `version`. | ### Connect a client ```bash claude mcp add --transport http softt https://docs.softt.io/api/mcp ``` Add the server to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for all projects: ```json title=".cursor/mcp.json" { "mcpServers": { "softt": { "url": "https://docs.softt.io/api/mcp" } } } ``` Add the server to `.vscode/mcp.json` in your workspace: ```json title=".vscode/mcp.json" { "servers": { "softt": { "type": "http", "url": "https://docs.softt.io/api/mcp" } } } ``` Clients that support Streamable HTTP connect directly to the server URL. For clients that only support stdio, use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge: ```json { "softt": { "command": "npx", "args": ["-y", "mcp-remote", "https://docs.softt.io/api/mcp"] } } ``` Once connected, ask your assistant questions such as "Add ElegantOTA to my ESP32 sketch with authentication and an onEnd callback" and it will retrieve the relevant pages before writing code. ## llms.txt The site publishes [llms.txt](https://llmstxt.org) files for tools that ingest documentation directly: | URL | Contents | | --- | --- | | [`/llms.txt`](/llms.txt) | Index of every page with its title, description and link. | | [`/llms-full.txt`](/llms-full.txt) | The complete documentation of every library and version as a single Markdown file. | ## Markdown pages Every page is also available as Markdown: - Append `.md` to a page URL, for example [`/docs/elegantota/ota-modes/auto.md`](/docs/elegantota/ota-modes/auto.md). - Requests with an `Accept: text/markdown` header receive Markdown instead of HTML. - Use **Copy Markdown** at the top of a page to copy it to the clipboard, or **Open** to send it to an assistant such as ChatGPT or Claude. # Branding (/docs/elegantota/branding) > Replace the logo in the ElegantOTA Pro portal with your own light and dark logos by editing the logo.h and logo.cpp library files. Library: ElegantOTA Availability: ElegantOTA Pro only The ElegantOTA Pro portal serves its logo from the device. The logo is stored as byte arrays in the `logo.h` and `logo.cpp` files of the library source, so you can replace it with your product's logo without modifying any HTML or CSS, and without rebuilding the portal. ![Custom logo in the portal](https://docs.softt.io/img/elegantota/branding.png) The portal requests one of two logo variants, depending on the active color scheme: | Route | Array | Shown in | | --- | --- | --- | | `/ota/logo/light` | `ELEGANTOTA_LIGHT_LOGO` | Light theme | | `/ota/logo/dark` | `ELEGANTOTA_DARK_LOGO` | Dark theme | ## Replace the logo ### Prepare the logos Create the logo you want to display. SVG is recommended, because it stays sharp at any size and compresses well; PNG and JPEG also work. Keep the file small: it is stored in flash and sent to the browser. Prepare one variant that is legible on a light background and one that is legible on a dark background. ### Convert the logos Convert each logo into a byte array with the [File2Raw](https://file2raw.labrat.one/) utility: 1. Open File2Raw and select the logo file. 2. Select **Gzip Compress** and **Use PROGMEM (Arduino)**. 3. Click **Convert** and copy the generated data. Repeat the procedure for the light and the dark variant. The conversion utility is provided as is, without warranty or liability of any kind. ### Update `logo.h` Open `src/logo.h` in the ElegantOTA Pro library and update the definitions: ```cpp title="logo.h" #define ELEGANTOTA_LOGO_GZIPPED 1 // 1 if the data is gzip-compressed #define ELEGANTOTA_LOGO_WIDTH 220 // Display width in px #define ELEGANTOTA_LOGO_HEIGHT 34 // Display height in px #define ELEGANTOTA_LIGHT_LOGO_MIME "image/svg+xml" extern const uint8_t ELEGANTOTA_LIGHT_LOGO[2392]; // Length of your array #define ELEGANTOTA_DARK_LOGO_MIME "image/svg+xml" extern const uint8_t ELEGANTOTA_DARK_LOGO[2390]; // Length of your array ``` - Set each MIME type to match the image format, for example `"image/png"` or `"image/jpeg"`. - Set `ELEGANTOTA_LOGO_GZIPPED` to `1` if you selected **Gzip Compress**, and to `0` otherwise. The setting applies to both variants. - Set the width and height to the proportions of your logo. The portal displays the logo at exactly this size. - Set the array lengths in the `extern` declarations to the lengths of the new arrays. ### Update `logo.cpp` Open `src/logo.cpp` and replace the contents of `ELEGANTOTA_LIGHT_LOGO` and `ELEGANTOTA_DARK_LOGO` with the generated data. The array lengths in `logo.cpp` must match the declarations in `logo.h` exactly. ### Upload the firmware Compile the sketch with the modified library and upload the firmware to your device. The device allows browsers to cache the logo for 15 minutes. Clear the browser cache or perform a hard refresh to see the new logo immediately. The portal now displays your logo and switches between the light and dark variants with the color scheme. Logos and other branding elements may be subject to copyright and trademark law. Ensure that you have the necessary rights or permissions to use any branding elements in your project. # Commercial License (/docs/elegantota/commercial-license) > Compare the licensing, support and features of ElegantOTA Lite (AGPL-3.0) and ElegantOTA Pro (commercial license). Library: ElegantOTA ElegantOTA is available in two editions of the same OTA update library: ElegantOTA Lite, which is open source and licensed under AGPL-3.0, and ElegantOTA Pro, which is distributed under the SOFTT Commercial License. The editions differ in features, support and licensing terms. ## Comparison | | ElegantOTA Lite | ElegantOTA Pro | | --- | --- | --- | | License | Open source, AGPL-3.0 | SOFTT Commercial License | | Source-disclosure obligations | Free to use, modify and distribute. If you use ElegantOTA Lite in a commercial product, AGPL-3.0 requires you to make the source code of your product, including any modifications to ElegantOTA Lite, publicly available. | You can use ElegantOTA Pro in a commercial product without open-sourcing your proprietary code. | | Support | Community-driven, through online forums, documentation and user-contributed resources. Professional support may be limited. | Dedicated support and assistance. | | OTA modes | [File](/docs/elegantota/ota-modes/file) | File, [Direct Download](/docs/elegantota/ota-modes/direct-download) and [Auto Update](/docs/elegantota/ota-modes/auto) | | Features | Core OTA functionality. | Extended functionality, such as runtime control of OTA modes and update targets, hardware ID, firmware version, custom title and branding. Features available only in ElegantOTA Pro are marked in this documentation. | | Cost model | Free | One-time license fee | ## Choosing an edition - **ElegantOTA Lite** fits open-source projects, and projects in which the AGPL-3.0 requirements are acceptable and basic OTA update functionality is sufficient. - **ElegantOTA Pro** fits commercial products. It provides a more comprehensive, professionally supported solution and lets you keep your code proprietary, in exchange for a one-time commercial license fee. ## Get ElegantOTA Pro ElegantOTA Pro is available on the official product page at [store.softt.io](https://store.softt.io/products/elegantota-pro). # Auto Reboot (/docs/elegantota/features/auto-reboot) > Control whether ElegantOTA reboots the device automatically after a successful OTA update. Library: ElegantOTA By default, ElegantOTA reboots the device after a successful update so that the new firmware or filesystem image takes effect. If your application must decide when to restart, for example to finish a task or save state first, disable auto reboot. ![Successful update in the portal](https://docs.softt.io/img/elegantota/success.png) ## Set auto reboot Call `setAutoReboot()` with `false` to disable automatic reboots, or with `true` to enable them again. You can call it at any time. ```cpp ElegantOTA.setAutoReboot(false); ``` ## Example ```cpp title="main.cpp" #include // ... web server declaration void setup() { // ... connect to Wi-Fi ElegantOTA.begin(&server); ElegantOTA.setAutoReboot(false); // Restart the device from your own code server.begin(); } void loop() { server.handleClient(); ElegantOTA.loop(); } ``` ## Behavior - With auto reboot enabled, `ElegantOTA.loop()` restarts the device about two seconds after a successful update. Your sketch must call `ElegantOTA.loop()` for the reboot to happen. - The portal adapts its message: with auto reboot enabled, it states that the device is rebooting to finish the update; with auto reboot disabled, it asks you to restart the device. - With auto reboot disabled, the new image takes effect at the next restart. Restart the device from your code, for example in the [`onEnd`](/docs/elegantota/features/callbacks#onend) callback or later, when your application is ready. # Callbacks (/docs/elegantota/features/callbacks) > Run your own code when an ElegantOTA update starts, progresses and ends, and, in ElegantOTA Pro, when an update channel offers a new version. Library: ElegantOTA ElegantOTA provides callbacks that run your code at different stages of an update. Use them to prepare the device, report progress or react to the result. | Callback | Signature | Edition | | --- | --- | --- | | [`onStart`](#onstart) | `void()` | Lite and Pro | | [`onProgress`](#onprogress) | `void(size_t current, size_t final)` | Lite and Pro | | [`onEnd`](#onend) | `void(bool success)` | Lite and Pro | | [`onUpdateAvailable`](#onupdateavailable) | `void(const char* version, const char* notes)` | Pro | Each registration function accepts a `std::function`, so you can pass a function, a lambda or a bound member function. Registering a callback again replaces the previous one. ## `onStart` `onStart` fires when an update begins, before the flash region is opened. Use it to prepare the device, for example to save important data, put outputs in a safe state or stop tasks that access the filesystem. ```cpp ElegantOTA.onStart([]() { Serial.println("OTA update started"); // Prepare the device here }); ``` For an upload from the portal, `onStart` fires when the device accepts the `/ota/start` request. In ElegantOTA Pro, for a direct download or an update channel installation, it fires after the server has responded and the size check has passed. ## `onProgress` ![Update progress in the portal](https://docs.softt.io/img/elegantota/progress.png) `onProgress` fires repeatedly while data is written. It receives `current`, the number of bytes received so far, and `final`, the total size reported for the transfer. For a download whose server does not report the size, `final` is `0`. ```cpp unsigned long ota_progress_millis = 0; ElegantOTA.onProgress([](size_t current, size_t final) { // Log at most once per second if (millis() - ota_progress_millis > 1000) { ota_progress_millis = millis(); if (final > 0) { Serial.printf("Progress: %u%%\n", (unsigned)((current * 100) / final)); } else { Serial.printf("Received: %u bytes\n", (unsigned)current); } } }); ``` `onProgress` can fire very frequently. Keep it short, and throttle logging or other slow work, as in the example above, so that the update is not slowed down. ## `onEnd` `onEnd` fires when the update completes, whether it succeeded or failed. The `success` parameter indicates the result. Use it to clean up or to act on the outcome. ```cpp ElegantOTA.onEnd([](bool success) { if (success) { Serial.println("OTA update completed successfully"); } else { Serial.println("OTA update failed"); } }); ``` If [auto reboot](/docs/elegantota/features/auto-reboot) is enabled, the device restarts shortly after a successful update. In ElegantOTA Pro, `onEnd` fires for a direct download or an update channel installation only if `onStart` fired for it. ## `onUpdateAvailable` Pro `onUpdateAvailable` fires when a check of the [update channel](/docs/elegantota/ota-modes/auto) finds a version newer than the running firmware. It receives the offered version and the release notes from the manifest (an empty string if the manifest has none), and lets you decide when to install. ```cpp ElegantOTA.onUpdateAvailable([](const char* version, const char* notes) { Serial.printf("Version %s is available\n", version); // Install now, at a later time, or after a user confirms ElegantOTA.installUpdate(); }); ``` To install every newer version without a decision in your code, call `ElegantOTA.setAutoInstall(true)`. ## Source of an update Pro `onStart`, `onProgress` and `onEnd` fire for every update, regardless of how it reached the device. In ElegantOTA Pro, `ElegantOTA.source()` returns the source of the current update: ```cpp ElegantOTA.onStart([]() { switch (ElegantOTA.source()) { case OTA_SOURCE_UPLOAD: Serial.println("Uploaded from the portal"); break; case OTA_SOURCE_DIRECT: Serial.println("Downloaded by the device"); break; case OTA_SOURCE_CHANNEL: Serial.println("Installed from the update channel"); break; } }); ``` | Value | Source | | --- | --- | | `OTA_SOURCE_UPLOAD` | A `.bin` file uploaded from the portal ([File](/docs/elegantota/ota-modes/file)). | | `OTA_SOURCE_DIRECT` | A URL from the portal or from `pullFrom()` ([Direct Download](/docs/elegantota/ota-modes/direct-download)). | | `OTA_SOURCE_CHANNEL` | The update channel ([Auto Update](/docs/elegantota/ota-modes/auto)). | `source()` is available only when `ELEGANTOTA_ENABLE_REMOTE` is `1` (the default). ## Example ```cpp title="main.cpp" #include // ... web server declaration unsigned long ota_progress_millis = 0; void onOTAStart() { Serial.println("OTA update started"); } void onOTAProgress(size_t current, size_t final) { if (millis() - ota_progress_millis > 1000) { ota_progress_millis = millis(); Serial.printf("OTA progress: %u of %u bytes\n", (unsigned)current, (unsigned)final); } } void onOTAEnd(bool success) { Serial.println(success ? "OTA update finished successfully" : "OTA update failed"); } void setup() { // ... connect to Wi-Fi ElegantOTA.begin(&server); ElegantOTA.onStart(onOTAStart); ElegantOTA.onProgress(onOTAProgress); ElegantOTA.onEnd(onOTAEnd); server.begin(); } void loop() { server.handleClient(); ElegantOTA.loop(); } ``` # Custom Title (/docs/elegantota/features/custom-title) > Set the page title of the ElegantOTA Pro portal that the browser displays in its tab. Library: ElegantOTA Availability: ElegantOTA Pro only `setTitle()` sets the title of the portal page. The browser displays it in the tab and window title and uses it for bookmarks, which helps you distinguish the portals of several devices. ![Custom title in the browser tab](https://docs.softt.io/img/elegantota/custom-title.png) ## Set the title ```cpp ElegantOTA.setTitle("OTA Update - XYZ Inc"); ``` If no title is set, the page title is `OTA`. Do not use double quotes (`"`) or backslashes (`\`) in the title. The value is embedded in the JSON that the portal loads without escaping, and these characters prevent the portal from loading its device information. ## Example ```cpp title="main.cpp" #include // ... web server declaration void setup() { // ... connect to Wi-Fi ElegantOTA.setTitle("OTA Update - XYZ Inc"); ElegantOTA.begin(&server); server.begin(); } void loop() { server.handleClient(); ElegantOTA.loop(); } ``` # Firmware Version (/docs/elegantota/features/firmware-version) > Display the running firmware version in the ElegantOTA Pro portal and provide the version that update channels compare against. Library: ElegantOTA Availability: ElegantOTA Pro only The firmware version identifies the software running on a device. ElegantOTA Pro displays it as **Firmware** in the device section of the portal, and an [update channel](/docs/elegantota/ota-modes/auto) compares it with the version advertised in the manifest to decide whether an update is available. ![Firmware version in the portal](https://docs.softt.io/img/elegantota/firmware-version.png) ## Set the firmware version ```cpp ElegantOTA.setFWVersion("1.0.0"); ``` ## Example ```cpp title="main.cpp" #include // ... web server declaration void setup() { // ... connect to Wi-Fi ElegantOTA.setFWVersion("1.0.0"); ElegantOTA.begin(&server); server.begin(); } void loop() { server.handleClient(); ElegantOTA.loop(); } ``` ## Use cases - **Firmware updates**: determine whether a device needs an update, and verify that it received the correct one. Update channels use the version for this comparison. - **Compatibility**: confirm that devices that communicate with each other run compatible software. - **Tracking**: keep a history of the firmware versions installed on each device for troubleshooting. - **Remote diagnostics**: relate reported problems to specific releases. ## Best practices - Use a consistent versioning scheme such as [Semantic Versioning](https://semver.org). Update channels compare the numeric fields of the version one by one; see [Version comparison](/docs/elegantota/ota-modes/auto#version-comparison). - Do not use double quotes (`"`) or backslashes (`\`) in the version. The value is embedded in the JSON that the portal loads without escaping. - Set the version in `setup()` so that it always reflects the installed firmware. - Update the value passed to `setFWVersion()` in every release. If a release does not change it, the device keeps reporting the previous version, and an update channel keeps offering the release that is already installed. # Hardware ID (/docs/elegantota/features/hardware-id) > Display a unique hardware ID in the ElegantOTA Pro portal to identify a device among others on your network. Library: ElegantOTA Availability: ElegantOTA Pro only The hardware ID is a string that identifies a device. ElegantOTA Pro displays it as **Identifier** in the device section of the portal, so you can confirm which device you are about to update. ![Hardware ID in the portal](https://docs.softt.io/img/elegantota/hardware-id.png) ## Set the hardware ID ```cpp ElegantOTA.setID("my_device_001"); ``` If no hardware ID is set, the portal shows a placeholder. ## Example ```cpp title="main.cpp" #include // ... web server declaration void setup() { // ... connect to Wi-Fi ElegantOTA.setID("my_device_001"); ElegantOTA.begin(&server); server.begin(); } void loop() { server.handleClient(); ElegantOTA.loop(); } ``` ## Best practices - Use an ID that is unique for each device to avoid confusion. - Do not use double quotes (`"`) or backslashes (`\`). The value is embedded in the JSON that the portal loads without escaping, and these characters prevent the portal from loading its device information. - Avoid spaces and other special characters, which can cause problems in network communication or file names. - Keep a record of the hardware IDs of all your devices. - Set the hardware ID during initialization, in `setup()`, before the portal is used. # Async Mode (/docs/elegantota/getting-started/async-mode) > Serve the ElegantOTA portal through ESPAsyncWebServer, with the dependencies and configuration required on each platform. Library: ElegantOTA By default, ElegantOTA registers its routes on the synchronous web server of your platform's Arduino core (`WebServer` or `ESP8266WebServer`). In async mode, it uses `ESPAsyncWebServer` (`AsyncWebServer`) instead. Enable async mode only if your project uses `ESPAsyncWebServer`. You cannot use the synchronous web server and `ESPAsyncWebServer` in the same firmware. ## Dependencies Install the following libraries for your platform: - (ESP32Async) [AsyncTCP](https://github.com/ESP32Async/AsyncTCP#v3.5.0) >= **v3.5.0** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.12.0) >= **v3.12.0** - (ESP32Async) [ESPAsyncTCP](https://github.com/ESP32Async/ESPAsyncTCP#v2.0.0) >= **v2.0.0** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.12.0) >= **v3.12.0** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP#v1.3.2) >= **v1.3.2** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.12.0) >= **v3.12.0** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP#v1.3.2) >= **v1.3.2** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.12.0) >= **v3.12.0** In PlatformIO, the ElegantOTA library manifest declares `esp32async/ESPAsyncWebServer@^3.12.0` as a dependency. Add the TCP library for your platform to `lib_deps` yourself, for example: ```ini title="platformio.ini" lib_deps = ayushsharma82/ElegantOTA@^4.0.0 esp32async/AsyncTCP@^3.5.0 ; ESP32 ; esp32async/ESPAsyncTCP@^2.0.0 ; ESP8266 ; ayushsharma82/RPAsyncTCP@^1.3.2 ; RP2040 / RP2350 ``` ### Use a different fork of a dependency If your project depends on a fork of `ESPAsyncWebServer` that is not listed above, prevent PlatformIO from installing the declared dependency. This configuration is not tested. 1. Install ElegantOTA manually, as described in [Installation](/docs/elegantota/getting-started/installation). 2. Open the `library.json` file in the ElegantOTA library folder and delete the `dependencies` entry. ## Enable async mode Async mode is controlled by the `ELEGANTOTA_USE_ASYNC_WEBSERVER` macro, which defaults to `0`. The Arduino IDE cannot set build flags, so you enable async mode in the library source: 1. Open your Arduino libraries folder. 2. Open the `src` folder of the ElegantOTA library. 3. In `ElegantOTA.h` (ElegantOTA Pro: `ElegantOTAPro.h`), set the `ELEGANTOTA_USE_ASYNC_WEBSERVER` macro to `1`: ```cpp #define ELEGANTOTA_USE_ASYNC_WEBSERVER 1 ``` 4. Save the file. Repeat this change after you update the library, because an update replaces the file. Add the macro to `build_flags` in `platformio.ini`: ```ini title="platformio.ini" build_flags = -D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 ``` PlatformIO then compiles ElegantOTA in async mode. ## Usage In async mode, pass a pointer to your `AsyncWebServer` to `ElegantOTA.begin()`. Keep calling `ElegantOTA.loop()` from `loop()`; it is still responsible for rebooting the device after a successful update and, in ElegantOTA Pro, for direct downloads and update channel checks. ```cpp title="main.cpp" #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) #include #include #endif #include #include AsyncWebServer server(80); void setup() { // ... connect to Wi-Fi server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { request->send(200, "text/plain", "Hi! This is ElegantOTA AsyncDemo."); }); ElegantOTA.begin(&server); server.begin(); } void loop() { ElegantOTA.loop(); } ``` ## Benefits - **Non-blocking request handling**: `ESPAsyncWebServer` handles HTTP requests outside your `loop()` function, so the main loop does not have to call a request handler to serve the portal. - **Responsiveness**: the device can continue other work while it serves the portal and receives an update. # Examples (/docs/elegantota/getting-started/examples) > Example sketches in the ElegantOTA and ElegantOTA Pro repositories for the synchronous and asynchronous web servers and for update channels. Library: ElegantOTA The ElegantOTA repositories include example sketches that show how to integrate OTA updates with different web servers. Each example builds on the setup described in the [Integration Guide](/docs/elegantota/getting-started/integration) and registers the `onStart`, `onProgress` and `onEnd` [callbacks](/docs/elegantota/features/callbacks), logging progress once per second. ## ElegantOTA Lite | Example | Description | | --- | --- | | [Demo](https://github.com/ayushsharma82/ElegantOTA/tree/master/examples/Demo) | Uses the built-in synchronous web server of the platform. Requires no additional libraries. | | [AsyncDemo](https://github.com/ayushsharma82/ElegantOTA/tree/master/examples/AsyncDemo) | Uses `ESPAsyncWebServer`. Use it only if your project already uses `ESPAsyncWebServer`, and enable [async mode](/docs/elegantota/getting-started/async-mode) before compiling it. | All examples are available in the [examples folder](https://github.com/ayushsharma82/ElegantOTA/tree/master/examples) of the repository. In the Arduino IDE, you can also open them from **File > Examples > ElegantOTA** after installing the library. ## ElegantOTA Pro ElegantOTA Pro ships with the same `Demo` and `AsyncDemo` examples, plus: | Example | Description | | --- | --- | | UpdateChannel | Configures an [update channel](/docs/elegantota/ota-modes/auto) with scheduled checks, reports the source of each update and decides when to install a new version in the `onUpdateAvailable` callback. | The examples work on the Raspberry Pi Pico W and Pico 2 W only with a LittleFS partition. In the Arduino IDE, select a flash layout with a filesystem, for example **Tools > Flash Size > 2MB (Sketch 1MB, FS 1MB)**. # Installation (/docs/elegantota/getting-started/installation) > Install ElegantOTA Lite or ElegantOTA Pro in the Arduino IDE or PlatformIO. Library: ElegantOTA This page describes how to install ElegantOTA in the Arduino IDE and in PlatformIO. In its default (synchronous) configuration, ElegantOTA uses the web server built into your platform's Arduino core and needs no additional libraries. To use `ESPAsyncWebServer` instead, also install the dependencies listed in [Async Mode](/docs/elegantota/getting-started/async-mode). ## Install ElegantOTA Lite ### Library Manager [#library-manager] Open **Sketch > Include Library > Manage Libraries**, search for "ElegantOTA" and click **Install**. ### Manual installation [#manual-installation] 1. Download the [ElegantOTA repository archive](https://github.com/ayushsharma82/ElegantOTA/archive/master.zip). 2. Extract the archive into your Arduino libraries folder: - Windows: `Documents/Arduino/libraries/` - Linux: `/libraries/` ### Import a ZIP library [#import-a-zip-library] 1. Download the [ElegantOTA repository archive](https://github.com/ayushsharma82/ElegantOTA/archive/master.zip). 2. Open **Sketch > Include Library > Add .ZIP Library** and select the downloaded file. ElegantOTA supports multiple architectures. Without strict compatibility checks, PlatformIO tries to resolve the dependencies of every platform, which often causes compilation errors. Before installing ElegantOTA: 1. Delete the `.pio/libdeps` folder of your project, if it exists. 2. Add the following line to the environment in your `platformio.ini` file: ```ini lib_compat_mode = strict ``` 3. Save `platformio.ini`. ### Library Manager [#library-manager-1] Open the PlatformIO menu, go to **Libraries**, search for "ElegantOTA" and add it to your project. Alternatively, add the library to `lib_deps`: ```ini title="platformio.ini" lib_deps = ayushsharma82/ElegantOTA@^4.0.0 lib_compat_mode = strict ``` ### Manual installation [#manual-installation-1] 1. Download the [ElegantOTA repository archive](https://github.com/ayushsharma82/ElegantOTA/archive/master.zip). 2. Extract the archive into the `lib` folder of your project. ## Install ElegantOTA Pro ElegantOTA Pro is not published in the Arduino or PlatformIO library registries. Install it from the source you received with your license. 1. Download the ElegantOTA Pro library as a ZIP archive. 2. Open **Sketch > Include Library > Add .ZIP Library** and select the archive. Alternatively, extract the library folder into the `libraries` folder of your sketchbook. Copy the ElegantOTA Pro library folder into the `lib` folder of your project, or reference its Git repository in `lib_deps`. Pulling the library over Git requires your machine to be authenticated with the GitHub account that has access to the repository. As with ElegantOTA Lite, add `lib_compat_mode = strict` to `platformio.ini` and delete an existing `.pio/libdeps` folder before rebuilding. In your sketch, include the Pro header instead of the Lite header: ```cpp #include ``` The global `ElegantOTA` object and the core API are the same in both editions. ## Next steps - [Integration Guide](/docs/elegantota/getting-started/integration): add the OTA portal to an existing web server sketch. - [Async Mode](/docs/elegantota/getting-started/async-mode): use ElegantOTA with `ESPAsyncWebServer`. - [Examples](/docs/elegantota/getting-started/examples): browse the example sketches. # Integration Guide (/docs/elegantota/getting-started/integration) > Add the ElegantOTA update portal to an existing web server sketch in three steps. Library: ElegantOTA This guide adds ElegantOTA to a sketch that already runs a web server. It uses the synchronous web server of your platform's Arduino core; for `ESPAsyncWebServer`, see [Async Mode](/docs/elegantota/getting-started/async-mode). ### Include the library Include ElegantOTA at the top of your sketch: ```cpp #include ``` With ElegantOTA Pro, include `` instead. ### Call `begin()` in `setup()` Pass a pointer to your web server to `ElegantOTA.begin()`. This registers the portal and the update endpoints on the server. ```cpp ElegantOTA.begin(&server); ``` `begin()` optionally accepts a username and a password to protect the portal with HTTP Basic Authentication. See [Authentication](/docs/elegantota/security/authentication). ### Call `loop()` in `loop()` Call `ElegantOTA.loop()` from the `loop()` function of your sketch. ElegantOTA uses it to reboot the device after a successful update. In ElegantOTA Pro, it also drives direct downloads and update channel checks. ```cpp ElegantOTA.loop(); ``` ### Open the portal Upload the sketch and open `http:///update` in a browser. ![ElegantOTA portal](https://docs.softt.io/img/elegantota/portal.png) ## Complete example The following sketch connects to a Wi-Fi network, serves a page at `/` and adds the OTA portal: ```cpp title="main.cpp" #if defined(ESP8266) #include #include #include #elif defined(ESP32) #include #include #include #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) #include #include #include #include #endif #include const char* ssid = "your-ssid"; const char* password = "your-password"; #if defined(ESP8266) ESP8266WebServer server(80); #else WebServer server(80); #endif void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); // Wait for the connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Serial.print("IP address: "); Serial.println(WiFi.localIP()); server.on("/", []() { server.send(200, "text/plain", "Hi! This is ElegantOTA Demo."); }); ElegantOTA.begin(&server); // Start ElegantOTA server.begin(); Serial.println("HTTP server started"); } void loop() { server.handleClient(); ElegantOTA.loop(); } ``` ## Next steps - [Callbacks](/docs/elegantota/features/callbacks): run your own code when an update starts, progresses and ends. - [Authentication](/docs/elegantota/security/authentication): protect the portal with a username and password. - [File](/docs/elegantota/ota-modes/file): how the portal writes firmware and filesystem images. # Introduction (/docs/elegantota) > ElegantOTA is a C++ library that serves a web portal for over-the-air firmware and filesystem updates on ESP32, ESP8266, RP2040 and RP2350. Library: ElegantOTA ElegantOTA adds an over-the-air (OTA) update portal to your microcontroller. The portal is served by the device itself at `/update`: you select a `.bin` file, the portal checks that it fits the target flash region, and the device writes it while the portal reports progress and the result. You do not need to write HTML, CSS or JavaScript; the integration takes three lines of code. GitHub repository: https://github.com/ayushsharma82/ElegantOTA ![ElegantOTA](https://docs.softt.io/img/elegantota/banner-light.jpg) ## Key capabilities - **Three-line integration**: include the library, call `ElegantOTA.begin()` and call `ElegantOTA.loop()`. - **Firmware and filesystem updates**: write a firmware image or a LittleFS/SPIFFS filesystem image. - **Capacity check**: the portal compares the image size with the target region before any data is written, and refuses an image that does not fit. - **Integrity verification**: the portal computes the MD5 digest of the file in the browser, and the device rejects the update if the written data does not match. - **Progress and status**: live progress, transfer rate and byte counts during the update. - **HTTP Basic Authentication**: optional username and password protection for the portal and its endpoints. - **Synchronous or asynchronous web server**: works with the platform's built-in `WebServer` or with `ESPAsyncWebServer`. - **Light and dark portal themes**. ## Supported boards | Platform | Notes | | --- | --- | | **ESP32** | Including the ESP32-S2, ESP32-S3, ESP32-C3 and ESP32-C6 variants. | | **RP2040** (Raspberry Pi Pico W) | Requires a LittleFS partition. | | **RP2350** (Raspberry Pi Pico 2 W) | Requires a LittleFS partition. | | **ESP8266** | Maintained for existing projects. Prefer ESP32 or RP2350 for new designs. | These boards stage the incoming image inside the filesystem region, so a LittleFS partition is mandatory. In the Arduino IDE, select a flash layout with a filesystem, for example **Tools > Flash Size > 2MB (Sketch 1MB, FS 1MB)**. ElegantOTA requires a board with a Wi-Fi chip, such as the Pico W or Pico 2 W; on an RP2040 or RP2350 board without Wi-Fi, `ElegantOTA.begin()` does not register any routes. ## ElegantOTA Lite and ElegantOTA Pro This documentation covers both editions of ElegantOTA. ElegantOTA Lite is open source and licensed under AGPL-3.0. ElegantOTA Pro is a commercial edition that adds: - Two additional OTA modes: [Direct Download](/docs/elegantota/ota-modes/direct-download), in which the device downloads firmware from a URL, and [Auto Update](/docs/elegantota/ota-modes/auto), in which the device checks an update channel on a schedule. - Runtime control over the available OTA modes and update targets. - [Hardware ID](/docs/elegantota/features/hardware-id), [firmware version](/docs/elegantota/features/firmware-version) and a [custom title](/docs/elegantota/features/custom-title) in the portal. - Custom [branding](/docs/elegantota/branding). - A commercial license. Pages for features that are available only in ElegantOTA Pro are marked accordingly. See [Commercial License](/docs/elegantota/commercial-license) for a comparison, or visit [store.softt.io](https://store.softt.io/products/elegantota-pro). ## Explore the documentation # Auto Update (/docs/elegantota/ota-modes/auto) > Configure an update channel so the device checks a JSON manifest on a schedule and installs newer firmware. Covers the manifest, version comparison and onUpdateAvailable. Library: ElegantOTA Availability: ElegantOTA Pro only In Auto mode, the device updates itself. You compile an **update channel** into the firmware: a URL that points to a small JSON manifest describing the latest release. The device fetches the manifest on a schedule, compares the advertised version with its own, and can install the new firmware without anyone opening the portal. Auto mode is designed for fleets of devices that you cannot reach individually. ![Auto Update in the ElegantOTA Pro portal](https://docs.softt.io/img/elegantota/update-channel.png) ## The manifest Host a manifest like the following anywhere the device can reach over HTTP or HTTPS, for example in object storage, as a GitHub release asset or on your own server: ```json title="latest.json" { "version": "1.2.0", "url": "https://example.com/firmware/my-device-1.2.0.bin", "md5": "6f5902ac237024bdd0c176cb93063dc4", "notes": "Reconnects to Wi-Fi after a router reboot" } ``` - All values must be JSON strings, and only top-level fields are read. Unknown fields are ignored, so you can keep your own metadata in the same file. - A manifest whose reported size (`Content-Length`) exceeds 4096 bytes is rejected. Keep the manifest small. - An update channel always carries firmware; the manifest has no field to select a target. A filesystem image has no version for the device to compare against, so write one with [File](/docs/elegantota/ota-modes/file) or [Direct Download](/docs/elegantota/ota-modes/direct-download), where you choose the target explicitly. Include `md5` whenever possible. It is the only way for the device to verify that the data it wrote matches the image you published. ## Set up an update channel ### Set the running version The device compares the manifest version with its own version, so set it with [`setFWVersion()`](/docs/elegantota/features/firmware-version): ```cpp ElegantOTA.setFWVersion("1.0.0"); ``` If no firmware version is set, every manifest version is considered newer, and the device offers or installs the advertised firmware on every check. ### Set the manifest URL ```cpp ElegantOTA.setUpdateURL("https://example.com/firmware/latest.json"); ``` ### Set the check interval ```cpp ElegantOTA.setCheckInterval(6 * 60 * 60 * 1000UL); // Every 6 hours ``` The interval is in milliseconds. If you pass `0` or do not call `setCheckInterval()`, the device checks only when you call `checkForUpdate()` or when someone clicks **Check for updates** in the portal. ### Choose whether to install automatically ```cpp ElegantOTA.setAutoInstall(true); // Install as soon as a newer version is found ``` With `setAutoInstall(false)` (the default), a check that finds a newer version only records it. The portal then shows an **Install** button, and your code decides when to install, for example in the [`onUpdateAvailable`](#react-to-a-new-version) callback. ## Version comparison The numeric parts of the versions are compared field by field, so `1.10.0` is newer than `1.9.4`. A leading `v` is ignored. A pre-release suffix after a hyphen sorts below the same version without a suffix, and two pre-release suffixes are compared as text. | Running | Manifest | Update offered | | --- | --- | --- | | `1.0.0` | `1.0.1` | Yes | | `1.9.4` | `1.10.0` | Yes | | `1.2.0` | `v1.2.0` | No (same version) | | `1.2.3` | `1.2` | No | | `1.0.0-rc1` | `1.0.0` | Yes (a release is newer than its pre-release) | | `1.0.0-rc1` | `1.0.0-rc2` | Yes | | `1.0.0` | `1.0.0-rc1` | No | Versions that contain no digits, such as build hashes or code names, are considered newer whenever they differ from the running version. ## React to a new version `onUpdateAvailable` fires whenever a check finds a newer version. Use it to decide when to install, for example when the device is idle, when the battery is charged or after a user confirms the update. ```cpp void onUpdateAvailable(const char* version, const char* notes) { Serial.printf("Version %s is available\n", version); if (strlen(notes)) Serial.println(notes); if (machineIsIdle()) { // Your own condition ElegantOTA.installUpdate(); } } void setup() { // ... ElegantOTA.begin(&server); ElegantOTA.onUpdateAvailable(onUpdateAvailable); } ``` The callback fires on every check that finds a newer version, not only on the first. If auto install is enabled, the installation starts after the callback returns. During the installation, the regular [callbacks](/docs/elegantota/features/callbacks) `onStart`, `onProgress` and `onEnd` fire, and `ElegantOTA.source()` returns `OTA_SOURCE_CHANNEL`. ## Check on demand `setCheckInterval()` starts its timer when you call it, so the first scheduled check takes place one interval later. To check at boot as well, call `checkForUpdate()`: ```cpp ElegantOTA.begin(&server); ElegantOTA.checkForUpdate(); // Check once now ``` `checkForUpdate()` and `installUpdate()` queue the work and return immediately. The manifest and the firmware are fetched in `ElegantOTA.loop()`, so make sure your sketch calls it. Scheduled checks are skipped while a download is in progress. ## Reference ```cpp void setUpdateURL(const char * url); // Manifest URL const char * getUpdateURL(); void setCheckInterval(uint32_t interval_ms); // 0 disables scheduled checks void setAutoInstall(bool enable); // Install without confirmation (default: false) bool checkAutoInstall(); bool checkForUpdate(); // Queue a check now bool installUpdate(); // Install the version found by the last check bool updateAvailable(); // A newer version was found const char * availableVersion(); // Its version, or "" const char * availableNotes(); // Its notes, or "" void onUpdateAvailable(std::function callable); ``` `checkForUpdate()` returns `false` if Auto mode is disabled, no update channel is set, or the device is busy. `installUpdate()` returns `false` if Auto mode is disabled, no newer version is waiting, the device is busy, or firmware updates are disabled with [`setFirmwareMode(false)`](/docs/elegantota/security/disable-firmware-ota). ## Disable Auto mode All three OTA modes are enabled by default. Disabling Auto mode stops scheduled checks, removes the **Auto** tab from the portal, and makes `checkForUpdate()` and `installUpdate()` return `false`: ```cpp ElegantOTA.setAutoMode(false); ``` You can use it to pause automatic updates while a critical task is running and enable them again afterwards: ```cpp ElegantOTA.setAutoMode(!machineIsBusy()); ``` Disabling Auto mode differs from not configuring a channel. Without `setUpdateURL()`, there is nothing to check, and the **Auto** tab states that no update channel is set. With `setAutoMode(false)`, a channel is configured but deliberately not checked. ```cpp if (ElegantOTA.checkAutoMode() == false) { Serial.println("Automatic updates are paused"); } ``` ## HTTPS The manifest and the firmware are fetched with the same client, so `setCACert()` applies to both. See [Direct Download](/docs/elegantota/ota-modes/direct-download#https) for how to pin a root certificate. An update channel instructs the device to run whatever firmware the manifest points to. Treat the server that hosts your manifest and firmware as part of your firmware's trust boundary: pin a root certificate, and restrict write access to the storage that serves the files. ## Other OTA modes - [File](/docs/elegantota/ota-modes/file): upload a `.bin` file from your computer. Available in ElegantOTA Lite and Pro. - [Direct Download](/docs/elegantota/ota-modes/direct-download): give the device a URL to download a single image. # Direct Download (/docs/elegantota/ota-modes/direct-download) > Let the device download a firmware or filesystem image itself from an HTTP or HTTPS URL, from the portal or from your own code. Library: ElegantOTA Availability: ElegantOTA Pro only In Direct Download mode, the device downloads the image itself over HTTP or HTTPS. In a regular upload, the image travels twice: from the build server to the browser, and from the browser to the device. With Direct Download, you enter a URL in the portal and the device fetches the image directly. This is useful when the image is large, when the browser is on a slow connection, or when you do not want to keep the portal open during the transfer. The portal shows the progress, but the device performs the download, so the transfer continues if you close the page. ![Direct Download in the ElegantOTA Pro portal](https://docs.softt.io/img/elegantota/direct-download.png) The URL must start with `http://` or `https://`, point to a raw `.bin` image and be at most 256 characters long. It must be reachable from the device's network, not only from your computer. Redirects are followed, so a GitHub release asset link works. ## Use the portal 1. Open the portal and select the **Direct** tab. 2. Choose the target (firmware or filesystem) in the **Write to** selector. 3. Paste the URL and click **Fetch and write**. The device connects to the server and, if the server reports the image size, checks that the image fits the target region before it writes anything. It then streams the image into flash. ## Start a download from your code You can start the same download from your sketch with `pullFrom()`, without anyone using the portal: ```cpp title="main.cpp" #include // ... web server declaration void setup() { // ... connect to Wi-Fi and configure the web server ElegantOTA.begin(&server); server.begin(); } void loop() { server.handleClient(); ElegantOTA.loop(); // Drives the download } void installNightlyBuild() { ElegantOTA.pullFrom("https://example.com/firmware/nightly.bin"); } ``` `pullFrom()` queues the download and returns immediately. The transfer is performed by `ElegantOTA.loop()`, a slice at a time, so your sketch keeps running while the download is in progress. ### Signature ```cpp bool pullFrom(const char * url, OTA_Mode mode = OTA_MODE_FIRMWARE, const char * md5 = NULL); ``` `pullFrom()` returns `true` if the download was queued, and `false` if: - the device is already busy with a download or an update channel check, - the URL does not start with `http://` or `https://`, or is too long, or - the selected update target is disabled with [`setFirmwareMode()`](/docs/elegantota/security/disable-firmware-ota) or [`setFilesystemMode()`](/docs/elegantota/security/disable-filesystem-ota). Pass an MD5 digest whenever you can. The device compares it with the data it wrote, so a truncated or corrupted download is rejected instead of installed: ```cpp ElegantOTA.pullFrom("https://example.com/firmware/2.1.0.bin", OTA_MODE_FIRMWARE, "6f5902ac237024bdd0c176cb93063dc4"); ``` ## Monitor the download The same [callbacks](/docs/elegantota/features/callbacks) fire as for an upload from the portal: `onStart`, `onProgress` and `onEnd`. To distinguish the two, check the source of the update: ```cpp ElegantOTA.onStart([]() { if (ElegantOTA.source() == OTA_SOURCE_DIRECT) { Serial.println("The device is downloading an image"); } }); ``` `onStart` fires after the server has responded and the size check has passed. `onEnd` fires only for a download whose `onStart` fired, so a URL that cannot be reached does not produce an `onEnd(false)`. If the server does not report the image size, the `final` argument of `onProgress` is `0`. You can also read the state of the device-side task with `state()`: ```cpp switch (ElegantOTA.state()) { case OTA_STATE_IDLE: break; // Nothing in progress case OTA_STATE_QUEUED: break; // Accepted, not started case OTA_STATE_CHECKING: break; // Fetching the update channel manifest case OTA_STATE_CONNECTING: break; // Opening the connection case OTA_STATE_TRANSFERRING: break; // Writing to flash case OTA_STATE_SUCCESS: break; // Written; the device reboots if auto reboot is enabled case OTA_STATE_FAILED: break; // The portal shows the reason default: break; } ``` `state()` describes only downloads and update channel checks. Uploads from the portal do not change it. ## HTTPS `https://` URLs work without configuration, but the server certificate is **not verified** unless you pin a root certificate. An unverified connection protects the transfer against passive eavesdropping, but not against a server that impersonates the real one. For production devices, pin the root certificate of the host you download from: ```cpp static const char UPDATE_ROOT_CA[] PROGMEM = R"CERT( -----BEGIN CERTIFICATE----- ... your root CA ... -----END CERTIFICATE----- )CERT"; void setup() { ElegantOTA.setCACert(UPDATE_ROOT_CA); ElegantOTA.begin(&server); } ``` Pass `NULL` to return to unverified connections. On ESP32, the library keeps a pointer to the certificate, so the certificate must remain valid for the lifetime of the program; declare it `static` or global, as above. On these platforms, HTTPS uses BearSSL with a receive buffer of `ELEGANTOTA_TLS_RX_SIZE` bytes (default `16384`), which costs RAM. If HTTPS downloads fail while HTTP downloads work and free heap is low, reduce `ELEGANTOTA_TLS_RX_SIZE`, or serve the image over plain HTTP on a network you trust. ## Disable Direct Download All three OTA modes are enabled by default. Anyone who can open the portal, which means anyone who passes [authentication](/docs/elegantota/security/authentication), can make the device download an image from a URL of their choice. To prevent this, disable the mode: ```cpp ElegantOTA.setDirectDownloadMode(false); ``` The **Direct** tab disappears from the portal and the `/ota/download` endpoint refuses requests. `pullFrom()` called from your own code is not affected. See [Disable Direct Download](/docs/elegantota/security/disable-direct-download). ## Compile-time options The following macros control the download. Define them as build flags or before including the library. | Macro | Default | Description | | --- | --- | --- | | `ELEGANTOTA_PULL_BUDGET` | `8192` | Maximum number of bytes transferred per `loop()` call. Increase it for faster downloads; decrease it if your sketch needs more time per loop iteration. | | `ELEGANTOTA_PULL_TIMEOUT` | `10000` | Time in milliseconds without received data after which a stalled download is aborted. | | `ELEGANTOTA_TLS_RX_SIZE` | `16384` | BearSSL receive buffer size in bytes (ESP8266, RP2040, RP2350). | | `ELEGANTOTA_TLS_TX_SIZE` | `512` | BearSSL transmit buffer size in bytes (ESP8266, RP2040, RP2350). | | `ELEGANTOTA_ENABLE_REMOTE` | `1` | Set to `0` to remove the HTTP client, Direct Download and Auto Update from the build and save flash. | ## Other OTA modes - [File](/docs/elegantota/ota-modes/file): upload a `.bin` file from your computer. Available in ElegantOTA Lite and Pro. - [Auto Update](/docs/elegantota/ota-modes/auto): the device checks an update channel and installs new firmware without anyone at the portal. # File (/docs/elegantota/ota-modes/file) > The default OTA mode, in which you upload a .bin file from the portal. Covers staging, the capacity check, MD5 verification, update targets and the HTTP requests involved. Library: ElegantOTA In File mode, you upload a `.bin` file from your computer through the portal, and the device writes it to flash. File mode is available in ElegantOTA Lite and ElegantOTA Pro and requires no configuration beyond the [integration](/docs/elegantota/getting-started/integration). ![File mode in the ElegantOTA portal](https://docs.softt.io/img/elegantota/file.png) ## Upload a file Upload the raw `.bin` image produced by your build: `firmware.bin` in PlatformIO, or the `.bin` file in the build output of the Arduino IDE. Do not upload a `.uf2` file or an archive. 1. Open `http:///update` and select the **File** tab. 2. Drag a `.bin` file onto the drop zone, or click it to choose a file. 3. Review the staged file, then click **Write firmware** (or **Write filesystem**). Selecting a file does not write anything. The portal first stages the file: it reads it, computes its MD5 digest and compares its size with the capacity of the target region. The write button is enabled only when the file fits. ### Capacity check The bar below the header represents the target flash region to scale. If the staged file is larger than the region, the portal reports it, for example: ```text 1.42 MB will not fit the 1.19 MB region ``` The write button remains disabled and nothing is written. When no file is staged, the bar shows how much of the region the running firmware occupies. ### Update targets The **Write to** selector chooses whether the file is written to the firmware region or to the filesystem region. The capacity check follows the selection, because the two regions have different sizes. A filesystem update replaces the entire LittleFS or SPIFFS image. All files currently stored on the device are lost. In ElegantOTA Pro, you can disable either target. See [Disable Firmware OTA](/docs/elegantota/security/disable-firmware-ota) and [Disable Filesystem OTA](/docs/elegantota/security/disable-filesystem-ota). ## Verification The portal computes the MD5 digest of the file in the browser and sends it to the device before the transfer starts. The device compares the digest with the data written to flash and rejects the update if they differ. A truncated upload, an interrupted connection or a corrupted file is therefore rejected, and the device continues to run its current firmware. ## HTTP requests The portal performs the update with two requests. Both require the credentials configured with [authentication](/docs/elegantota/security/authentication), if any. | Request | Purpose | | --- | --- | | `GET /ota/start?mode=fr&hash=` | Opens the target flash region and records the expected MD5 digest. | | `POST /ota/upload` | Transfers the file as `multipart/form-data`. | The `mode` parameter is `fr` for firmware or `fs` for the filesystem image; any other value, or no value, selects firmware. The `hash` parameter is optional. If it is present but is not a 32-character hexadecimal string, the device rejects the request before it opens the flash region. After a successful write, the device reboots into the new firmware unless you disabled [auto reboot](/docs/elegantota/features/auto-reboot). ## Callbacks The [callbacks](/docs/elegantota/features/callbacks) fire for File mode as for the other modes: ```cpp ElegantOTA.onStart([]() { Serial.println("Update started"); }); ElegantOTA.onProgress([](size_t current, size_t final) { Serial.printf("%u / %u bytes\n", current, final); }); ElegantOTA.onEnd([](bool success) { Serial.println(success ? "Update installed" : "Update failed"); }); ``` In ElegantOTA Pro, `ElegantOTA.source()` returns `OTA_SOURCE_UPLOAD` for an update uploaded from the portal, which distinguishes it from an update the device downloaded itself. ## Disable File mode Pro All three OTA modes are enabled by default. In ElegantOTA Pro, you can disable File mode so that the portal offers only the modes you want: ```cpp // Do not accept file uploads from the portal ElegantOTA.setFileMode(false); ``` The **File** tab disappears from the portal, and `/ota/start` refuses requests, regardless of the client that sends them. If all modes are disabled, the portal states that no update method is enabled. To read the current setting, call `checkFileMode()`: ```cpp if (ElegantOTA.checkFileMode() == false) { Serial.println("File uploads from the portal are disabled"); } ``` `setFileMode()` controls whether an image may be uploaded from the browser. [`setFilesystemMode()`](/docs/elegantota/security/disable-filesystem-ota) controls whether the filesystem region may be overwritten, by any mode. The names are similar, but the settings are independent. ## Other OTA modes Uploading a file is suitable during development and for devices you can reach. ElegantOTA Pro adds two modes that do not require anyone at the portal: - [Direct Download](/docs/elegantota/ota-modes/direct-download): the device downloads an image from a URL. - [Auto Update](/docs/elegantota/ota-modes/auto): the device checks an update channel on a schedule and installs new firmware. In ElegantOTA Lite, the portal shows both modes as locked tabs. # Authentication (/docs/elegantota/security/authentication) > Protect the ElegantOTA portal and update endpoints with HTTP Basic Authentication using a username and password. Library: ElegantOTA ElegantOTA supports HTTP Basic Authentication. When it is enabled, the browser asks for a username and password before it opens the portal, and the device rejects update requests without valid credentials. This prevents unauthorized users from installing firmware on your device. ## Enable authentication Call `setAuth()` with a username and a password: ```cpp ElegantOTA.setAuth("username", "password"); ``` Alternatively, pass the credentials to `begin()`: ```cpp ElegantOTA.begin(&server, "username", "password"); ``` Authentication is enabled only when both the username and the password are non-empty. ## Example ```cpp title="main.cpp" #include // ... web server declaration void setup() { // ... connect to Wi-Fi ElegantOTA.begin(&server); ElegantOTA.setAuth("myUsername", "myPassword"); server.begin(); } void loop() { server.handleClient(); ElegantOTA.loop(); } ``` ## Clear authentication To disable authentication after it was set, call `clearAuth()`: ```cpp ElegantOTA.clearAuth(); ``` ## Protected endpoints When authentication is enabled, the following routes require credentials: | Route | Edition | | --- | --- | | `/update` (portal) | Lite and Pro | | `/ota/start`, `/ota/upload` | Lite and Pro | | `/ota/download`, `/ota/check`, `/ota/install`, `/ota/status` | Pro | The `/ota/metadata` route, which the portal uses to display device information, is served without authentication. It reports the chip family, the partition sizes and the size of the running firmware. In ElegantOTA Pro, it also reports the portal title, the hardware ID, the firmware version, the enabled modes and the update channel URL. The Pro logo routes (`/ota/logo/light` and `/ota/logo/dark`) are also public. ## Use cases - **Security**: prevent unauthorized firmware or filesystem updates. - **Access control**: assign unique credentials to each device so that only authorized personnel or systems can update it. - **Compliance**: meet security standards that require authentication for device management interfaces. ## Best practices - Use strong, unique credentials for each device. Do not use default or easily guessed credentials. - Store credentials securely and avoid hard-coding them in source code that is shared or published. - Rotate credentials regularly. - Test the configuration to confirm that the portal rejects requests without valid credentials. - HTTP Basic Authentication sends credentials encoded, not encrypted. Use it on networks you trust. # Disable Direct Download (/docs/elegantota/security/disable-direct-download) > Prevent the ElegantOTA Pro portal from accepting firmware URLs, or remove the HTTP client from the build entirely. Library: ElegantOTA Availability: ElegantOTA Pro only [Direct Download](/docs/elegantota/ota-modes/direct-download) lets a portal user give the device a URL to download an image from. It is **enabled by default**, so anyone who can open the portal can make the device download from any address. Behind [authentication](/docs/elegantota/security/authentication), this is a convenience. Without authentication, on a network you do not fully control, it allows others to install firmware you did not write. If your product does not need Direct Download, disable it. ## Disable Direct Download ```cpp ElegantOTA.setDirectDownloadMode(false); ``` ## Enable Direct Download ```cpp ElegantOTA.setDirectDownloadMode(true); ``` Direct Download is enabled by default after a cold boot. ## Check the setting ```cpp if (ElegantOTA.checkDirectDownloadMode() == true) { Serial.println("Direct Download is enabled"); } else { Serial.println("Direct Download is disabled"); } ``` ## Behavior While Direct Download is disabled: - The **Direct** tab disappears from the portal. - The `/ota/download` endpoint refuses requests, regardless of the client that sends them. The setting only controls URLs submitted through the portal endpoint. The following continue to work: - Calls to `ElegantOTA.pullFrom()` from your own code. - Uploading a `.bin` file in [File](/docs/elegantota/ota-modes/file) mode. - [Auto Update](/docs/elegantota/ota-modes/auto), which only downloads from the update channel compiled into the firmware. Disabling Direct Download does not replace [authentication](/docs/elegantota/security/authentication). Anyone who can reach an unprotected portal can still upload firmware in File mode. ## Remove remote updates from the build If you need neither Direct Download nor Auto Update, compile the HTTP client out of the library to save flash: ```ini title="platformio.ini" build_flags = -D ELEGANTOTA_ENABLE_REMOTE=0 ``` The portal then offers only File mode, and the related functions, including `pullFrom()`, `setDirectDownloadMode()`, `setAutoMode()`, `setUpdateURL()`, `setCACert()`, `state()`, `source()` and `onUpdateAvailable()`, are not compiled. Remove any calls to them from your sketch. # Disable Filesystem OTA (/docs/elegantota/security/disable-filesystem-ota) > Enable or disable filesystem (LittleFS or SPIFFS) updates at runtime in ElegantOTA Pro, and check whether the filesystem update target is enabled. Library: ElegantOTA Availability: ElegantOTA Pro only ElegantOTA Pro lets you enable or disable each update target at runtime, for security reasons or while a task is running that an update must not interrupt. The update target defines what an update may overwrite: the firmware or the filesystem image. The setting applies to every [OTA mode](/docs/elegantota/ota-modes/file), and the portal hides a disabled target. Use the functions on this page to control updates of the filesystem image (LittleFS or SPIFFS). ![Update target selector in the portal](https://docs.softt.io/img/elegantota/ota-modes.png) ## Disable filesystem updates ```cpp ElegantOTA.setFilesystemMode(false); ``` ## Enable filesystem updates ```cpp ElegantOTA.setFilesystemMode(true); ``` Both update targets are enabled by default after a cold boot. ## Check the setting ```cpp if (ElegantOTA.checkFilesystemMode() == true) { Serial.println("Filesystem updates are enabled"); } else { Serial.println("Filesystem updates are disabled"); } ``` ## Behavior While filesystem updates are disabled: - The **Filesystem** option disappears from the **Write to** selector in the portal. - `/ota/start` rejects filesystem uploads. - `pullFrom()` with `OTA_MODE_FILESYSTEM` and filesystem direct downloads from the portal are rejected. `setFilesystemMode()` controls whether the filesystem region may be overwritten. It is unrelated to [`setFileMode()`](/docs/elegantota/ota-modes/file#disable-file-mode), which controls whether images may be uploaded from the browser. # Disable Firmware OTA (/docs/elegantota/security/disable-firmware-ota) > Enable or disable firmware updates at runtime in ElegantOTA Pro, and check whether the firmware update target is enabled. Library: ElegantOTA Availability: ElegantOTA Pro only ElegantOTA Pro lets you enable or disable each update target at runtime, for security reasons or while a task is running that an update must not interrupt. The update target defines what an update may overwrite: the firmware or the filesystem image. The setting applies to every [OTA mode](/docs/elegantota/ota-modes/file), and the portal hides a disabled target. ![Update target selector in the portal](https://docs.softt.io/img/elegantota/ota-modes.png) Use this setting carefully. While firmware updates are disabled, the device cannot be updated over the air, and if the setting is left disabled by mistake, the device can no longer be recovered by an OTA update. ## Disable firmware updates ```cpp ElegantOTA.setFirmwareMode(false); ``` ## Enable firmware updates ```cpp ElegantOTA.setFirmwareMode(true); ``` Both update targets are enabled by default after a cold boot. ## Check the setting ```cpp if (ElegantOTA.checkFirmwareMode() == true) { Serial.println("Firmware updates are enabled"); } else { Serial.println("Firmware updates are disabled"); } ``` ## Behavior While firmware updates are disabled: - The **Firmware** option disappears from the **Write to** selector in the portal. - `/ota/start` rejects firmware uploads. - `pullFrom()` with `OTA_MODE_FIRMWARE` and direct downloads from the portal are rejected. - `installUpdate()` and automatic installation from an [update channel](/docs/elegantota/ota-modes/auto) are rejected, because an update channel always carries firmware. If both firmware and filesystem updates are disabled, the portal states that the device does not accept updates. # Using with Other SOFTT Libraries (/docs/elegantota/using-with-other-libraries) > Combine ElegantOTA with ESP-DASH, NetWizard and WebSerial in the same project, sharing one AsyncWebServer. Library: ElegantOTA ESP-DASH, ElegantOTA, NetWizard and WebSerial are designed to work together in the same project. A single sketch can provide WiFi provisioning (NetWizard), a real-time dashboard (ESP-DASH), over-the-air updates (ElegantOTA) and a remote serial terminal (WebSerial), all served by one `AsyncWebServer` instance. The Lite and Pro editions of each library can be mixed freely. ## Requirements - **One shared `AsyncWebServer`.** Create a single `AsyncWebServer server(80);` and pass it to every library. Call `server.begin()` once, after all libraries are initialized. - **Async mode for ElegantOTA and NetWizard.** ESP-DASH and WebSerial only run on `ESPAsyncWebServer`. ElegantOTA and NetWizard default to the core `WebServer`, so enable their async mode by setting `ELEGANTOTA_USE_ASYNC_WEBSERVER=1` and `NETWIZARD_USE_ASYNC_WEBSERVER=1`. See [ElegantOTA async mode](/docs/elegantota/getting-started/async-mode) and [NetWizard async mode](/docs/netwizard/getting-started/async-mode). - **One copy of each dependency.** Install a single copy of `ESPAsyncWebServer` and `AsyncTCP` (ESP32) or `RPAsyncTCP` (RP2040/RP2350) whose version satisfies the highest minimum listed on the installation page of each library you use. Do not install two forks of `ESPAsyncWebServer` side by side. - **A board that every library supports.** ESP32 is supported by all four libraries. Check each library's supported boards before targeting RP2040, RP2350 or ESP8266: for example, ESP-DASH v5 and NetWizard do not support ESP8266. ## Default paths Each library registers its own routes on the shared server. The defaults do not collide, except for ESP-DASH and the NetWizard portal, which both use `/`: | Library | Default path | How to change it | | --- | --- | --- | | ESP-DASH | `/` | Pass a URI to the constructor: `ESPDash dashboard(server, "/dashboard", true);` ([custom URI](/docs/espdash/v5/features/custom-uri)) | | ElegantOTA | `/update` | Fixed | | WebSerial | `/webserial` | Pass a path to `begin()`: `WebSerial.begin(&server, "/terminal");` | | NetWizard | `/` (captive portal, access point clients only) | Fixed | When NetWizard is part of the project, serve ESP-DASH at `/dashboard` and add `server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER);` so that clients on the local network still see the dashboard at the device's root URL. See [NetWizard with ESP-DASH](/docs/netwizard/getting-started/using-with-espdash). ## Example The sketch below combines all four libraries on an ESP32. Build it with `-D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 -D NETWIZARD_USE_ASYNC_WEBSERVER=1`, or set both macros in the library headers as described on the async mode pages. ```cpp title="main.cpp" #include #include #include #include #include // NetWizard Pro: #include #include // ElegantOTA Pro: #include AsyncWebServer server(80); // WiFi provisioning: captive portal at / for access point clients NetWizard NW(&server); // Dashboard at /dashboard, so it does not conflict with the portal ESPDash dashboard(server, "/dashboard", true); dash::TemperatureCard temperature(dashboard, "Temperature"); unsigned long lastUpdate = 0; void setup() { Serial.begin(115200); // Connect to the saved network, or start the captive portal NW.setStrategy(NetWizardStrategy::BLOCKING); NW.autoConnect("My Device", ""); // Over-the-air updates at /update ElegantOTA.begin(&server); // Remote serial terminal at /webserial WebSerial.begin(&server); WebSerial.onMessage([](const String& msg) { Serial.println(msg); }); // Show the dashboard at / for clients on the local network server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER); server.begin(); } void loop() { NW.loop(); ElegantOTA.loop(); WebSerial.loop(); if (millis() - lastUpdate > 3000) { lastUpdate = millis(); temperature.setValue((int)random(0, 50)); dashboard.sendUpdates(); WebSerial.println("Dashboard updated"); } } ``` Each library's `loop()` method must be called on every iteration, and `loop()` must not block, so that the portal, OTA updates and buffered terminal output keep running alongside the dashboard. # AI Tools (/docs/espdash/v4/ai-tools) > Use the ESP-DASH documentation from AI coding assistants through the MCP server, llms.txt and Markdown page exports. Library: ESP-DASH v4.x.x The ESP-DASH documentation is available in machine-readable formats so that AI coding assistants can answer questions and generate code from the current API instead of outdated training data. ## Ask AI Select **Ask AI** at the bottom of any documentation page to open the documentation assistant. It searches these pages before answering and links to the sources it used. Answers are grounded in the documentation only; verify generated code against the referenced pages. ## MCP server The documentation is served as a [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP. No authentication is required. One server covers every SOFTT library (ESP-DASH, ElegantOTA, NetWizard and WebSerial); pass `library: "espdash"` to limit results to ESP-DASH. ```text https://docs.softt.io/api/mcp ``` The server exposes three read-only tools: | Tool | Description | | --- | --- | | `search_docs` | Full-text search across the documentation. Accepts `query`, an optional `library` (`espdash`, `elegantota`, `netwizard`, `webserial` or `all`), an optional `version` for ESP-DASH (`v5`, `v4` or `all`) and `limit`. | | `get_page` | Returns the complete Markdown of a page, such as `/docs/espdash/v4/cards/slider`. | | `list_pages` | Lists pages with their title, description and URL, filtered by `library` and `version`. | ### Connect a client ```bash claude mcp add --transport http softt https://docs.softt.io/api/mcp ``` Add the server to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for all projects: ```json title=".cursor/mcp.json" { "mcpServers": { "softt": { "url": "https://docs.softt.io/api/mcp" } } } ``` Add the server to `.vscode/mcp.json` in your workspace: ```json title=".vscode/mcp.json" { "servers": { "softt": { "type": "http", "url": "https://docs.softt.io/api/mcp" } } } ``` Clients that support Streamable HTTP connect directly to the server URL. For clients that only support stdio, use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge: ```json { "softt": { "command": "npx", "args": ["-y", "mcp-remote", "https://docs.softt.io/api/mcp"] } } ``` Once connected, ask your assistant questions such as "Add an ESP-DASH v4 slider card that controls LED brightness" and it will retrieve the relevant pages before writing code. ## llms.txt The site publishes [llms.txt](https://llmstxt.org) files for tools that ingest documentation directly: | URL | Contents | | --- | --- | | [`/llms.txt`](/llms.txt) | Index of every page with its title, description and link. | | [`/llms-full.txt`](/llms-full.txt) | The complete documentation of every library and version as a single Markdown file. | ## Markdown pages Every page is also available as Markdown: - Append `.md` to a page URL, for example [`/docs/espdash/v4/cards/slider.md`](/docs/espdash/v4/cards/slider.md). - Requests with an `Accept: text/markdown` header receive Markdown instead of HTML. - Use **Copy Markdown** at the top of a page to copy it to the clipboard, or **Open** to send it to an assistant such as ChatGPT or Claude. # Branding (/docs/espdash/v4/branding) > Replace the default ESP-DASH Pro v4 dashboard logo with your own image by editing the logo byte array in the library source. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only Branding refers to the logo displayed on the dashboard. You can replace the default logo by defining your image as a byte array in the `logo.h` and `logo.cpp` files of the ESP-DASH Pro library source, without modifying any HTML or CSS. ![Dashboard with a custom logo](https://docs.softt.io/img/v4/custom-logo.png) ## Add a custom logo ### Prepare the logo Create or obtain the logo image. SVG is recommended; PNG and JPEG are also supported. Optimize the image to keep the file size small, as it is stored in flash. ### Convert the logo to a byte array 1. Open the [File2Raw utility](https://file2raw.labrat.one/), which converts files into C++ byte arrays. 2. Select your logo file. 3. Enable **Gzip Compress** and **Use PROGMEM (Arduino)**. 4. Click **Convert**. ### Modify logo.h 1. Open `logo.h` in the ESP-DASH Pro library source. 2. Locate the `DASH_LOGO` declaration: ```cpp extern const uint8_t DASH_LOGO[]; ``` 3. Set the length of the `DASH_LOGO` array to the length of your logo's byte array. 4. Set `DASH_LOGO_MIME` to the MIME type of your image. The default is `image/png`; change it if your logo uses another format, for example `image/jpeg` or `image/svg+xml`. 5. Set `DASH_LOGO_WIDTH` and `DASH_LOGO_HEIGHT` to the size at which the logo is displayed on the dashboard. ### Modify logo.cpp 1. Open `logo.cpp` in the ESP-DASH Pro library source. 2. Replace the contents of the `DASH_LOGO` array with the byte array generated by File2Raw. ### Upload the firmware Compile your sketch with the modified ESP-DASH Pro library and upload it to your device. The dashboard now displays your logo. Logos and other branding elements may be protected by copyright and trademark law. Make sure you have the rights or permissions required to use them in your project. # Air Card (/docs/espdash/v4/cards/air) > Display air pressure, wind speed and similar readings with an air icon on the ESP-DASH Pro v4 dashboard. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The air card displays a value next to an air icon, with an optional unit symbol. Use it for wind speed, air pressure or similar measurements. ![Air card](https://docs.softt.io/img/v4/air-card.png) ## Constructor ```cpp Card air(&dashboard, AIR_CARD, "Wind Speed", "km/h"); ``` ## Methods ### update() Sets the displayed value. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp air.update(int value); air.update(float value); air.update(const String &value); ``` Pass a second `const char* symbol` argument to change the symbol at the same time: ```cpp air.update(value, "m/s"); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card air(&dashboard, AIR_CARD, "Wind Speed", "km/h"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate > 3000) { lastUpdate = millis(); air.update((int)random(0, 100)); dashboard.sendUpdates(); } } ``` # Color Picker Card (/docs/espdash/v4/cards/color-picker) > Let users select a color and receive it as a hex string on the ESP-DASH Pro v4 dashboard. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The color picker card lets the user select a color and returns it as a hex color string. Use it to set LED colors, display themes and similar parameters. For background on hex color notation, see [HTML hex colors](https://www.w3schools.com/html/html_colors_hex.asp). ![Color picker card](https://docs.softt.io/img/v4/color-picker.png) ## Constructor ```cpp Card colorPicker(&dashboard, COLOR_PICKER_CARD, "LED Color"); ``` ## Callback The callback fires when the user selects a color on the dashboard. `hex` is the hex string of the selected color. Call `update()` with the received value and then `dashboard.sendUpdates()` inside the callback. Otherwise the dashboard does not register the input and the card remains in its waiting state. ```cpp colorPicker.attachCallback([&](const char* hex) { Serial.println("Color: " + String(hex)); colorPicker.update(hex); dashboard.sendUpdates(); }); ``` ## Methods ### update() Sets the selected color using a hex color string. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp colorPicker.update(const char* value); colorPicker.update(const String &value); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card colorPicker(&dashboard, COLOR_PICKER_CARD, "LED Color"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); colorPicker.attachCallback([&](const char* hex) { Serial.println("Color: " + String(hex)); colorPicker.update(hex); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` # Dropdown Card (/docs/espdash/v4/cards/dropdown) > Let users select one option from a comma-separated list of choices on the ESP-DASH Pro v4 dashboard. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The dropdown card lets the user select one option from a list. Choices are defined as a single comma-separated (`,`) string, with no limit on their number. ![Dropdown card, closed](https://docs.softt.io/img/v4/dropdown-card.png) ![Dropdown card, open](https://docs.softt.io/img/v4/dropdown-card-2.png) ## Constructor ```cpp Card dropdown(&dashboard, DROPDOWN_CARD, "Mode", "Auto,Manual,Off"); ``` ## Callback The callback fires when the user selects an option on the dashboard. `value` is the selected choice. Call `update()` with the received value and then `dashboard.sendUpdates()` inside the callback. Otherwise the dashboard does not register the selection. ```cpp dropdown.attachCallback([&](const char* value) { Serial.println("Selected: " + String(value)); dropdown.update(value); dashboard.sendUpdates(); }); ``` ## Methods ### update() Sets the selected value: ```cpp dropdown.update(const char* value); ``` Sets the selected value and replaces the list of choices: ```cpp dropdown.update(const char* value, const char* choices); ``` Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card dropdown(&dashboard, DROPDOWN_CARD, "Mode", "Auto,Manual,Off"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); dropdown.update("Auto"); dropdown.attachCallback([&](const char* value) { Serial.println("Selected: " + String(value)); dropdown.update(value); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` # Energy Card (/docs/espdash/v4/cards/energy) > Display power consumption and energy usage with an energy icon on the ESP-DASH Pro v4 dashboard. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The energy card displays a value next to an energy icon, with an optional unit symbol. Use it for power consumption, energy usage or similar measurements. ![Energy card](https://docs.softt.io/img/v4/energy-card.png) ## Constructor ```cpp Card energy(&dashboard, ENERGY_CARD, "Power Consumption", "kWh"); ``` ## Methods ### update() Sets the displayed value. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp energy.update(int value); energy.update(float value); energy.update(const String &value); ``` Pass a second `const char* symbol` argument to change the symbol at the same time: ```cpp energy.update(value, "Wh"); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card energy(&dashboard, ENERGY_CARD, "Power Consumption", "kWh"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate > 3000) { lastUpdate = millis(); energy.update((int)random(0, 100)); dashboard.sendUpdates(); } } ``` # Generic Card (/docs/espdash/v4/cards/generic) > Display any integer, float, boolean or string value on the ESP-DASH v4 dashboard with the generic card. Library: ESP-DASH v4.x.x The generic card displays an arbitrary value: integers, floats, booleans or strings. Use it for readings and states that do not fit one of the specialized cards. ![Generic card](https://docs.softt.io/img/v4/generic-card.png) ## Constructor ```cpp Card generic(&dashboard, GENERIC_CARD, "Generic"); ``` ## Methods ### update() Sets the displayed value. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp generic.update(int value); generic.update(float value); generic.update(bool value); generic.update(const char* value); generic.update(const String &value); ``` Each overload also accepts a second `const char* symbol` argument that replaces the card's symbol: ```cpp generic.update(value, "units"); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card generic(&dashboard, GENERIC_CARD, "Generic"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate > 3000) { lastUpdate = millis(); generic.update((int)random(0, 100)); dashboard.sendUpdates(); } } ``` # Humidity Card (/docs/espdash/v4/cards/humidity) > Display humidity readings with a humidity icon and an optional unit symbol on the ESP-DASH v4 dashboard. Library: ESP-DASH v4.x.x The humidity card displays a value next to a humidity icon, with an optional unit symbol such as `%`. ![Humidity card](https://docs.softt.io/img/v4/humidity-card.png) ## Constructor ```cpp Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%"); ``` ## Methods ### update() Sets the displayed value. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp humidity.update(int value); humidity.update(float value); humidity.update(const char* value); humidity.update(const String &value); ``` Each overload also accepts a second `const char* symbol` argument that replaces the card's symbol: ```cpp humidity.update(value, "RH"); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate > 3000) { lastUpdate = millis(); humidity.update((int)random(30, 70)); dashboard.sendUpdates(); } } ``` # Image Card (/docs/espdash/v4/cards/image) > Display an image from a URL on the ESP-DASH Pro v4 dashboard with the image card. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The image card displays an image loaded from a URL. Use it to show camera snapshots, diagrams or other remotely hosted images on the dashboard. ![Image card](https://docs.softt.io/img/v4/image-card.png) ## Constructor ```cpp Card image(&dashboard, IMAGE_CARD, "Camera", "lg"); ``` ## Methods ### update() Sets the URL of the image to display. The card shows no image until a URL is set. ```cpp image.update(const char* url); ``` Pass a second argument to change the image size at the same time. Supported sizes are `lg` and `xl`: ```cpp image.update(const char* url, const char* size); ``` Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card image(&dashboard, IMAGE_CARD, "Camera", "lg"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); image.update("https://picsum.photos/800/600"); server.begin(); } void loop() { // ... } ``` # Joystick Card (/docs/espdash/v4/cards/joystick) > Control devices with an on-screen joystick that reports direction or X/Y coordinates on the ESP-DASH Pro v4 dashboard. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The joystick card provides an on-screen joystick and reports its movement through callbacks, either as a direction or as X/Y coordinates. Use it to drive motors, pan-tilt mounts or robots. ![Joystick card](https://docs.softt.io/img/v4/joystick-card.png) ## Constructor ```cpp Card joystick(&dashboard, JOYSTICK_CARD, "Joystick"); ``` ### Direction lock By default, the joystick moves on both axes. To restrict it to a single axis, pass `lockX` or `lockY` as the fourth argument: ```cpp Card joystickX(&dashboard, JOYSTICK_CARD, "Horizontal", "lockX"); Card joystickY(&dashboard, JOYSTICK_CARD, "Vertical", "lockY"); ``` ## Callback The joystick card supports two callbacks, which you can register independently. ### Directional callback Fires when the joystick moves and receives the active direction: `up`, `down`, `left`, `right` or `idle`. ```cpp joystick.attachCallback([&](const char* direction) { Serial.println("Direction: " + String(direction)); }); ``` ### Coordinates callback Fires when the joystick moves and receives the position of the thumb. Both axes range from `-60` to `60`: - X axis: `-60` is left, `60` is right. - Y axis: `-60` is up, `60` is down. ```cpp joystick.attachCallback([&](int8_t x, int8_t y) { Serial.printf("X: %d, Y: %d\n", x, y); }); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card joystick(&dashboard, JOYSTICK_CARD, "Joystick"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); joystick.attachCallback([&](const char* direction) { Serial.println("Direction: " + String(direction)); }); joystick.attachCallback([&](int8_t x, int8_t y) { Serial.printf("X: %d, Y: %d\n", x, y); }); server.begin(); } void loop() { // ... } ``` ## Notes - The joystick card does not need `update()`, since no value is passed back to the dashboard. # Link Card (/docs/espdash/v4/cards/link) > Open a URL from the ESP-DASH Pro v4 dashboard when the user clicks the link card. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The link card opens a URL when the user clicks it. Use it to link to external resources, documentation or other pages served by the device. ![Link card](https://docs.softt.io/img/v4/link-card.png) ## Constructor ```cpp Card link(&dashboard, LINK_CARD, "Documentation"); ``` ## Methods ### update() Sets the URL that opens when the user clicks the card. The card has no target until a URL is set. ```cpp link.update(const char* url); ``` Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card link(&dashboard, LINK_CARD, "Documentation"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); link.update("https://espdash.pro"); server.begin(); } void loop() { // ... } ``` # Password Card (/docs/espdash/v4/cards/password) > Collect passwords and other secrets through a masked input field on the ESP-DASH Pro v4 dashboard. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The password card collects sensitive text, such as passwords or secrets. The dashboard masks the input, and the value is not sent back to the dashboard. ![Password card](https://docs.softt.io/img/v4/password-card.png) ## Constructor ```cpp Card pass(&dashboard, PASSWORD_CARD, "WiFi Password"); ``` ## Callback The callback fires when the user submits a value on the dashboard. `value` is the submitted string. Because the value is never displayed on the dashboard, no `update()` call is required. ```cpp pass.attachCallback([&](const char* value) { // Store or apply the value }); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); String storedPassword; Card pass(&dashboard, PASSWORD_CARD, "WiFi Password"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); pass.attachCallback([&](const char* value) { storedPassword = value; Serial.println("Password received"); }); server.begin(); } void loop() { // ... } ``` ## Notes - The password card does not need `update()`, since its value is not passed back to the dashboard. # Progress Bar Card (/docs/espdash/v4/cards/progress-bar) > Show a value within a fixed integer or float range as a horizontal progress bar on the ESP-DASH v4 dashboard. Library: ESP-DASH v4.x.x The progress bar card renders a horizontal bar that represents a value within a fixed range. Use it to show completion, levels or utilization. ![Progress bar card](https://docs.softt.io/img/v4/progress-card.png) ## Constructor Pass the `min` and `max` of the range to display. ```cpp Card progress(&dashboard, PROGRESS_CARD, "Progress", "", 0, 255); ``` For a fractional range, use the float overload. This overload has no default arguments: pass `symbol` explicitly, and pass `min`, `max` and `step` as `float` values. ```cpp Card progress(&dashboard, PROGRESS_CARD, "Progress", "", 0.0f, 1.0f, 0.01f); ``` ## Methods ### update() Sets the current value. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp progress.update(int value); progress.update(float value); ``` Pass a second `const char* symbol` argument to change the symbol at the same time: ```cpp progress.update(244, "%"); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card progress(&dashboard, PROGRESS_CARD, "Progress", "", 0, 255); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate > 3000) { lastUpdate = millis(); progress.update((int)random(0, 255)); dashboard.sendUpdates(); } } ``` # Push Button Card (/docs/espdash/v4/cards/push-button) > Trigger a stateless action from the ESP-DASH Pro v4 dashboard with the push button card and its callback. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The push button card triggers a callback each time the user clicks it. It is stateless, which makes it suitable for one-shot actions such as restarting a device or starting a task. ![Push button card](https://docs.softt.io/img/v4/push-button.png) ## Constructor ```cpp Card pushButton(&dashboard, PUSH_BUTTON_CARD, "Restart"); ``` ## Callback The callback takes no arguments and fires on every click. Because the button has no state, no `update()` or `dashboard.sendUpdates()` call is required. ```cpp pushButton.attachCallback([&]() { Serial.println("Push button clicked"); }); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); bool restartRequested = false; Card pushButton(&dashboard, PUSH_BUTTON_CARD, "Restart"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); pushButton.attachCallback([&]() { restartRequested = true; }); server.begin(); } void loop() { if (restartRequested) { delay(100); ESP.restart(); } } ``` ## Notes - The push button card does not need `update()`, since no value is passed back to the dashboard. # Slider Card (/docs/espdash/v4/cards/slider) > Select integer or float values within a defined range from the ESP-DASH v4 dashboard with the slider card. Library: ESP-DASH v4.x.x The slider card lets the user select a numeric value within a defined range. Use it for adjustable parameters such as brightness, speed or setpoints. ![Slider card](https://docs.softt.io/img/v4/slider-card.png) ## Constructor Pass the `min` and `max` of the range, and optionally the `step` size: ```cpp Card slider(&dashboard, SLIDER_CARD, "Brightness", "", 0, 255, 1); ``` For a fractional range, use the float overload. This overload has no default arguments: pass `symbol` explicitly, and pass `min`, `max` and `step` as `float` values. ```cpp Card slider(&dashboard, SLIDER_CARD, "Setpoint", "°C", 15.0f, 30.0f, 0.5f); ``` ## Callback ```cpp void attachCallback(std::function cb); void attachCallbackF(std::function cb); ``` The callback fires when the user moves the slider on the dashboard. `value` is the newly selected value. - For integer sliders, use `attachCallback()`. The value is passed as `int`. - For sliders created with the float constructor, use `attachCallbackF()`. The value is passed as `float`. Call `update()` with the received value and then `dashboard.sendUpdates()` inside the callback. Otherwise the dashboard does not register the new value. ```cpp slider.attachCallback([&](int value) { Serial.println("Slider: " + String(value)); slider.update(value); dashboard.sendUpdates(); }); ``` For a float slider: ```cpp slider.attachCallbackF([&](float value) { Serial.println("Slider: " + String(value)); slider.update(value); dashboard.sendUpdates(); }); ``` ## Methods ### update() Sets the slider position. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp slider.update(int value); slider.update(float value); ``` Pass a second `const char* symbol` argument to change the symbol at the same time: ```cpp slider.update(244, "%"); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); const int LED_PIN = 2; Card slider(&dashboard, SLIDER_CARD, "Brightness", "", 0, 255, 1); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); pinMode(LED_PIN, OUTPUT); slider.attachCallback([&](int value) { analogWrite(LED_PIN, value); slider.update(value); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` ## Notes - An integer slider passes its value to `attachCallback()`. If you construct the slider with the float overload but register only `attachCallback()`, the value is truncated to `int`. # Status Card (/docs/espdash/v4/cards/status) > Display a message with an idle, success, warning or danger indicator on the ESP-DASH v4 dashboard. Library: ESP-DASH v4.x.x The status card displays a message together with one of four status indicators: idle, success, warning or danger. Use it to report system health or the state of a process. ![Status card, idle](https://docs.softt.io/img/v4/status-card-idle.png) ![Status card, warning](https://docs.softt.io/img/v4/status-card-warning.png) ![Status card, success](https://docs.softt.io/img/v4/status-card-success.png) ![Status card, danger](https://docs.softt.io/img/v4/status-card-danger.png) ## Status constants | Constant | Indicator | | --- | --- | | `DASH_STATUS_IDLE` | Idle (grey) | | `DASH_STATUS_SUCCESS` | Success (green) | | `DASH_STATUS_WARNING` | Warning (yellow) | | `DASH_STATUS_DANGER` | Danger (red) | ## Constructor ```cpp Card status(&dashboard, STATUS_CARD, "System Status", DASH_STATUS_SUCCESS); ``` ## Methods ### update() Sets the message: ```cpp status.update("All systems operational"); ``` Sets the message and the status at the same time: ```cpp status.update("Sensor offline", DASH_STATUS_DANGER); ``` Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card status(&dashboard, STATUS_CARD, "System Status", DASH_STATUS_IDLE); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); status.update("Running", DASH_STATUS_SUCCESS); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate > 3000) { lastUpdate = millis(); if (WiFi.RSSI() < -80) { status.update("Weak WiFi signal", DASH_STATUS_WARNING); } else { status.update("Running", DASH_STATUS_SUCCESS); } dashboard.sendUpdates(); } } ``` # Temperature Card (/docs/espdash/v4/cards/temperature) > Display temperature readings with a temperature icon and an optional unit symbol on the ESP-DASH v4 dashboard. Library: ESP-DASH v4.x.x The temperature card displays a value next to a temperature icon, with an optional unit symbol such as `°C` or `°F`. ![Temperature card](https://docs.softt.io/img/v4/temperature-card.png) ## Constructor ```cpp Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); ``` ## Methods ### update() Sets the displayed value. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp temperature.update(int value); temperature.update(float value); temperature.update(const char* value); temperature.update(const String &value); ``` Each overload also accepts a second `const char* symbol` argument that replaces the card's symbol: ```cpp temperature.update(value, "°F"); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate > 3000) { lastUpdate = millis(); temperature.update(22.5f + random(0, 50) / 10.0f); dashboard.sendUpdates(); } } ``` # Text Input Card (/docs/espdash/v4/cards/text-input) > Collect text input such as credentials or identifiers from the ESP-DASH Pro v4 dashboard with the text input card. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The text input card collects a string entered by the user on the dashboard. Use it for configurable values such as WiFi credentials, device names or user IDs. ![Text input card](https://docs.softt.io/img/v4/text-input-card.png) ## Constructor ```cpp Card text(&dashboard, TEXT_INPUT_CARD, "User Email"); ``` ## Callback The callback fires when the user submits text on the dashboard. `value` is the submitted string. Call `update()` with the received value and then `dashboard.sendUpdates()` inside the callback. Otherwise the dashboard does not register the input and the card remains in its waiting state. ```cpp text.attachCallback([&](const char* value) { Serial.println("Text input: " + String(value)); text.update(value); dashboard.sendUpdates(); }); ``` ## Methods ### update() Sets the text shown in the card. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp text.update(const char* value); text.update(const String &value); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card text(&dashboard, TEXT_INPUT_CARD, "User Email"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); text.attachCallback([&](const char* value) { Serial.println("Text input: " + String(value)); text.update(value); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` # Time Sync Card (/docs/espdash/v4/cards/time-sync) > Synchronize the device clock with the browser time from the ESP-DASH Pro v4 dashboard. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The time sync card sends the current time from the browser to the device. Use it to set the device clock from the dashboard. ![Time sync card](https://docs.softt.io/img/v4/time-sync-card.png) The time is taken from the browser. Use this card only when the browser and the device are in the same timezone. ## Constructor ```cpp Card timeSync(&dashboard, TIME_SYNC_CARD, "Time Sync"); ``` ## Callback The callback fires when the user triggers a sync on the dashboard. `value` contains the time reported by the browser. ```cpp timeSync.attachCallback([&](const char* value) { Serial.println("Time sync: " + String(value)); }); ``` ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card timeSync(&dashboard, TIME_SYNC_CARD, "Time Sync"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); timeSync.attachCallback([&](const char* value) { Serial.println("Time sync: " + String(value)); }); server.begin(); } void loop() { // ... } ``` ## Notes - The time sync card does not need `update()`. # Toggle Button Card (/docs/espdash/v4/cards/toggle-button) > Switch between on and off states from the ESP-DASH v4 dashboard with the toggle button card and its callback. Library: ESP-DASH v4.x.x The toggle button card switches between two states, `0` (off) and `1` (on). Use it to turn outputs such as relays or LEDs on and off from the dashboard. ![Toggle button card, off](https://docs.softt.io/img/v4/button-card-false.png) ![Toggle button card, on](https://docs.softt.io/img/v4/button-card-true.png) ## Constructor ```cpp Card button(&dashboard, BUTTON_CARD, "Light"); ``` ## Callback ```cpp void attachCallback(std::function cb); ``` The callback fires when the user clicks the button on the dashboard. `value` is the requested state, which is the inverse of the current state: if the button is `0`, a click invokes the callback with `1`. Call `update()` with the received value and then `dashboard.sendUpdates()` inside the callback. Otherwise the dashboard does not register the new state. ```cpp button.attachCallback([&](int value) { Serial.println("Button: " + String(value == 1 ? "on" : "off")); button.update(value); dashboard.sendUpdates(); }); ``` ## Methods ### update() Sets the button state. Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ```cpp button.update(1); // on button.update(0); // off ``` `update(bool value)` is also available. ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); const int LED_PIN = 2; Card button(&dashboard, BUTTON_CARD, "Light"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); pinMode(LED_PIN, OUTPUT); button.attachCallback([&](int value) { digitalWrite(LED_PIN, value == 1 ? HIGH : LOW); button.update(value); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` # Week Selector Card (/docs/espdash/v4/cards/week-selector) > Let users select days of the week as a comma-separated list on the ESP-DASH Pro v4 dashboard. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only The week selector card lets the user select days of the week. Its value is a comma-separated (`,`) string of the selected days, such as `mon,wed,fri`. Use it for schedules and recurring timers. ![Week selector card](https://docs.softt.io/img/v4/week-selector.png) ## Constructor ```cpp Card week(&dashboard, WEEK_SELECTOR_CARD, "Select Days"); ``` ## Callback The callback fires whenever the user selects or deselects a day. `days` is a comma-separated list of the selected days, using the values `mon`, `tue`, `wed`, `thu`, `fri`, `sat` and `sun`. Call `update()` with the received value and then `dashboard.sendUpdates()` inside the callback. Otherwise the dashboard does not register the input and the card remains in its waiting state. ```cpp week.attachCallback([&](const char* days) { Serial.println("Selected days: " + String(days)); week.update(days); dashboard.sendUpdates(); }); ``` ## Methods ### update() Sets the selected days from a comma-separated list. The order of days does not matter: `tue,mon,thu` selects Monday, Tuesday and Thursday. ```cpp week.update(const char* value); week.update(const String &value); ``` Call `dashboard.sendUpdates()` afterwards to push the change to connected clients. ## Example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #endif #include #include const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; AsyncWebServer server(80); ESPDash dashboard(&server); Card week(&dashboard, WEEK_SELECTOR_CARD, "Select Days"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.print("IP address: "); Serial.println(WiFi.localIP()); week.update("mon,tue,wed,thu,fri"); week.attachCallback([&](const char* days) { Serial.println("Selected days: " + String(days)); week.update(days); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` # Area Chart (/docs/espdash/v4/charts/area) > Create a area chart in ESP-DASH v4 and update its X-axis and Y-axis data with updateX() and updateY(). Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only An area chart plots a series of values as a line with the region below it filled, which emphasizes the magnitude of a trend over time. ![Area Chart](https://docs.softt.io/img/v4/area-chart.png) ## Constructor ```cpp Chart chart1(&dashboard, AREA_CHART, "Chart Name"); ``` ## Methods Declare the axis arrays in global scope, or otherwise keep them in memory for the lifetime of the chart. The chart references the arrays you pass to `updateX()` and `updateY()`; they must not be freed or go out of scope. ### updateX() Sets the X-axis data (labels) of the chart. ```cpp String XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; chart1.updateX(XAxis, 7); ``` The following overloads are available: ```cpp void updateX(int arr_x[], size_t x_size); void updateX(float arr_x[], size_t x_size); void updateX(String arr_x[], size_t x_size); void updateX(const char* arr_x[], size_t x_size); ``` ### updateY() Sets the Y-axis data (values) of the chart. ```cpp int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; chart1.updateY(YAxis, 7); ``` The following overloads are available: ```cpp void updateY(int arr_y[], size_t y_size); void updateY(float arr_y[], size_t y_size); ``` ## Example The X-axis labels usually change rarely, so set them once in `setup()`. Update the Y-axis values whenever new data is available, then call `dashboard.sendUpdates()`. ```cpp title="main.cpp" // ... AsyncWebServer server(80); ESPDash dashboard(&server); /* Axis data in global scope */ String XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; Chart trafficChart(&dashboard, AREA_CHART, "Network Traffic"); void setup() { // ... /* Set the X-axis once */ trafficChart.updateX(XAxis, 7); server.begin(); } void loop() { /* Example data */ for (int i = 0; i < 7; i++) { YAxis[i] = (int)random(0, 100); } trafficChart.updateY(YAxis, 7); dashboard.sendUpdates(); delay(3000); } ``` # Bar Chart (/docs/espdash/v4/charts/bar) > Create a bar chart in ESP-DASH v4 and update its X-axis and Y-axis data with updateX() and updateY(). Library: ESP-DASH v4.x.x A bar chart plots a series of values as vertical bars against labeled categories, for example energy usage per day of the week. `BAR_CHART` is the only chart type available in ESP-DASH Lite. Line, area and pie charts are available in ESP-DASH Pro. ![Bar Chart](https://docs.softt.io/img/v4/bar-chart.png) ## Constructor ```cpp Chart chart1(&dashboard, BAR_CHART, "Chart Name"); ``` ## Methods Declare the axis arrays in global scope, or otherwise keep them in memory for the lifetime of the chart. The chart references the arrays you pass to `updateX()` and `updateY()`; they must not be freed or go out of scope. ### updateX() Sets the X-axis data (labels) of the chart. ```cpp String XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; chart1.updateX(XAxis, 7); ``` The following overloads are available: ```cpp void updateX(int arr_x[], size_t x_size); void updateX(float arr_x[], size_t x_size); void updateX(String arr_x[], size_t x_size); void updateX(const char* arr_x[], size_t x_size); ``` ### updateY() Sets the Y-axis data (values) of the chart. ```cpp int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; chart1.updateY(YAxis, 7); ``` The following overloads are available: ```cpp void updateY(int arr_y[], size_t y_size); void updateY(float arr_y[], size_t y_size); ``` ## Example The X-axis labels usually change rarely, so set them once in `setup()`. Update the Y-axis values whenever new data is available, then call `dashboard.sendUpdates()`. ```cpp title="main.cpp" // ... AsyncWebServer server(80); ESPDash dashboard(&server); /* Axis data in global scope */ String XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; Chart power(&dashboard, BAR_CHART, "Power Usage (kWh)"); void setup() { // ... /* Set the X-axis once */ power.updateX(XAxis, 7); server.begin(); } void loop() { /* Example data */ for (int i = 0; i < 7; i++) { YAxis[i] = (int)random(0, 200); } power.updateY(YAxis, 7); dashboard.sendUpdates(); delay(3000); } ``` # Line Chart (/docs/espdash/v4/charts/line) > Create a line chart in ESP-DASH v4 and update its X-axis and Y-axis data with updateX() and updateY(). Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only A line chart connects a series of values with a line, which makes it suited to showing trends over time, such as sensor readings. ![Line Chart](https://docs.softt.io/img/v4/line-chart.png) ## Constructor ```cpp Chart chart1(&dashboard, LINE_CHART, "Chart Name"); ``` ## Methods Declare the axis arrays in global scope, or otherwise keep them in memory for the lifetime of the chart. The chart references the arrays you pass to `updateX()` and `updateY()`; they must not be freed or go out of scope. ### updateX() Sets the X-axis data (labels) of the chart. ```cpp String XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; chart1.updateX(XAxis, 7); ``` The following overloads are available: ```cpp void updateX(int arr_x[], size_t x_size); void updateX(float arr_x[], size_t x_size); void updateX(String arr_x[], size_t x_size); void updateX(const char* arr_x[], size_t x_size); ``` ### updateY() Sets the Y-axis data (values) of the chart. ```cpp int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; chart1.updateY(YAxis, 7); ``` The following overloads are available: ```cpp void updateY(int arr_y[], size_t y_size); void updateY(float arr_y[], size_t y_size); ``` ## Example The X-axis labels usually change rarely, so set them once in `setup()`. Update the Y-axis values whenever new data is available, then call `dashboard.sendUpdates()`. ```cpp title="main.cpp" // ... AsyncWebServer server(80); ESPDash dashboard(&server); /* Axis data in global scope */ String XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; Chart temperatureChart(&dashboard, LINE_CHART, "Temperature"); void setup() { // ... /* Set the X-axis once */ temperatureChart.updateX(XAxis, 7); server.begin(); } void loop() { /* Example data */ for (int i = 0; i < 7; i++) { YAxis[i] = (int)random(0, 50); } temperatureChart.updateY(YAxis, 7); dashboard.sendUpdates(); delay(3000); } ``` # Pie Chart (/docs/espdash/v4/charts/pie) > Create a pie chart in ESP-DASH v4 and update its X-axis and Y-axis data with updateX() and updateY(). Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only A pie chart shows the proportion of each category in a whole. The X-axis data provides the category labels and the Y-axis data provides the value of each slice. ![Pie Chart](https://docs.softt.io/img/v4/pie-chart.png) ## Constructor ```cpp Chart chart1(&dashboard, PIE_CHART, "Chart Name"); ``` ## Methods Declare the axis arrays in global scope, or otherwise keep them in memory for the lifetime of the chart. The chart references the arrays you pass to `updateX()` and `updateY()`; they must not be freed or go out of scope. ### updateX() Sets the X-axis data (labels) of the chart. ```cpp String XAxis[] = {"Biscuits", "Cookies", "Milk", "Thing4", "Thing5"}; chart1.updateX(XAxis, 5); ``` The following overloads are available: ```cpp void updateX(int arr_x[], size_t x_size); void updateX(float arr_x[], size_t x_size); void updateX(String arr_x[], size_t x_size); void updateX(const char* arr_x[], size_t x_size); ``` ### updateY() Sets the Y-axis data (values) of the chart. ```cpp int YAxis[] = {0, 0, 0, 0, 0}; chart1.updateY(YAxis, 5); ``` The following overloads are available: ```cpp void updateY(int arr_y[], size_t y_size); void updateY(float arr_y[], size_t y_size); ``` ## Example The X-axis labels usually change rarely, so set them once in `setup()`. Update the Y-axis values whenever new data is available, then call `dashboard.sendUpdates()`. ```cpp title="main.cpp" // ... AsyncWebServer server(80); ESPDash dashboard(&server); /* Axis data in global scope */ String XAxis[] = {"Biscuits", "Cookies", "Milk", "Thing4", "Thing5"}; int YAxis[] = {0, 0, 0, 0, 0}; Chart inventoryChart(&dashboard, PIE_CHART, "Inventory"); void setup() { // ... /* Set the X-axis once */ inventoryChart.updateX(XAxis, 5); server.begin(); } void loop() { /* Example data */ for (int i = 0; i < 5; i++) { YAxis[i] = (int)random(0, 100); } inventoryChart.updateY(YAxis, 5); dashboard.sendUpdates(); delay(3000); } ``` # Commercial License (/docs/espdash/v4/commercial-license) > Compare the licensing and support terms of ESP-DASH Lite (GPL-3.0) and ESP-DASH Pro (commercial). Library: ESP-DASH v4.x.x ESP-DASH is available in two editions: ESP-DASH Lite, which is open source and licensed under GPL-3.0, and ESP-DASH Pro, which is distributed under a commercial license. Both editions are built on the same real-time dashboard library; they differ in features, licensing terms and support. ## Licensing | | ESP-DASH Lite | ESP-DASH Pro | | --- | --- | --- | | License | GPL-3.0 | Commercial, one-time fee | | Use, modify and distribute | Yes, under the terms of GPL-3.0 | Yes, under the terms of the commercial license | | Commercial products | Permitted, but GPL-3.0 requires you to release the source code of your product, including any modifications to ESP-DASH Lite | Permitted without releasing your proprietary source code | ## Support - **ESP-DASH Lite**: support is community-driven, through online forums, the documentation and user-contributed resources. Professional support is limited. - **ESP-DASH Pro**: includes dedicated support, for teams that require timely and reliable assistance. ## Choose an edition - Choose **ESP-DASH Lite** if your project is open source, or if the requirements of GPL-3.0 are acceptable for your use case and you need the core dashboard functionality. - Choose **ESP-DASH Pro** if you are developing a commercial product and need to keep your source code proprietary, or if you need the additional widgets and features and professional support. ESP-DASH Pro is available from the official product page at [store.softt.io](https://store.softt.io/products/esp-dash-pro). # Brotli Compression (/docs/espdash/v4/features/brotli-compression) > Enable Brotli compression for the ESP-DASH Pro v4 dashboard webpage to reduce flash usage, and review its browser and HTTPS requirements. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only Starting with ESP-DASH Pro v4.3.0, you can compress the dashboard webpage with Brotli instead of gzip. Brotli reduces the size of the webpage by about 40% compared to gzip, the default, which frees flash space for your application. ![Brotli compression](https://docs.softt.io/img/v4/brotli-compression.png) Over unencrypted HTTP, only Safari accepts Brotli-compressed responses. Other browsers, such as Chrome, Edge and Firefox, accept Brotli only over HTTPS. Enable Brotli only if your deployment meets these requirements. Brotli compression is disabled by default. ## Enable Brotli compression ### Open dash_webpage.h Open `dash_webpage.h` in the `src` directory of the ESP-DASH Pro library. ### Set the option Locate `DASH_USE_BROTLI_COMPRESSION` and set it to `1`: ```cpp #define DASH_USE_BROTLI_COMPRESSION 1 ``` ### Rebuild the firmware Save the file, then compile and upload your sketch. # Custom Title (/docs/espdash/v4/features/custom-title) > Change the ESP-DASH Pro v4 dashboard title shown in the browser tab with setTitle(). Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only `setTitle()` on the `ESPDash` instance replaces the default title of the dashboard, which is shown in the browser tab. ![Browser tab with a custom dashboard title](https://docs.softt.io/img/v4/custom-title.png) ## Usage ```cpp dashboard.setTitle("New Dashboard XYZ"); ``` # Custom URI (/docs/espdash/v4/features/custom-uri) > Serve the ESP-DASH v4 dashboard at a custom path instead of the root of the web server. Library: ESP-DASH v4.x.x By default, ESP-DASH serves the dashboard at the root path of the web server (`http:///`). To serve it at a different path, pass the URI as the second argument of the `ESPDash` constructor. ## Usage ```cpp ESPDash dashboard(&server, "/dash"); ``` The dashboard is then available at `http:///dash`. ## Constructor ```cpp ESPDash(AsyncWebServer* server, const char* uri, bool enable_default_stats = true); ``` To disable the built-in system statistics while using a custom URI, see [Disable Statistics](/docs/espdash/v4/security/disable-statistics). # Toggle Chart Animations (/docs/espdash/v4/features/toggle-chart-animations) > Enable or disable chart animations in ESP-DASH Pro v4 with setChartAnimations(), useful for high-frequency chart updates. Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only `setChartAnimations()` on the `ESPDash` instance enables or disables chart animations for the dashboard. Disabling animations is useful when you update charts at a high rate. Chart animations are enabled by default. ## Usage Call `setChartAnimations()` in `setup()`. ### Enable animations ```cpp dashboard.setChartAnimations(true); ``` ### Disable animations ```cpp dashboard.setChartAnimations(false); ``` # Toggle Display (/docs/espdash/v4/features/toggle-display) > Show or hide ESP-DASH Pro v4 cards, charts, tabs and statistics at runtime with setDisplay(). Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only `setDisplay()` shows or hides a widget on the dashboard. It is available on cards, charts, tabs and statistics, and lets you adapt the visible layout to the state of your application. All widgets are displayed by default. ## Usage Call `setDisplay()` on the widget, for example in `setup()`. ### Hide a widget ```cpp card1.setDisplay(false); // Hide a card chart1.setDisplay(false); // Hide a chart tab1.setDisplay(false); // Hide a tab stat1.setDisplay(false); // Hide a statistic ``` ### Show a widget ```cpp card1.setDisplay(true); // Show a card chart1.setDisplay(true); // Show a chart tab1.setDisplay(true); // Show a tab stat1.setDisplay(true); // Show a statistic ``` # Widget Index (/docs/espdash/v4/features/widget-index) > Control the position of ESP-DASH Pro v4 cards and charts on the dashboard with setIndex(). Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only `setIndex()` sets the position of a card or chart on the dashboard, so you can arrange the layout independently of the order in which widgets are declared. ![Widgets ordered by index](https://docs.softt.io/img/v4/widget-indexing.png) ## Usage ```cpp card1.setIndex(0); // Set the card index chart1.setIndex(1); // Set the chart index ``` Widgets are sorted in ascending order of their index: the widget with the lowest index is placed first, and the widget with the highest index is placed last. # Widget Size (/docs/espdash/v4/features/widget-size) > Set the responsive width of ESP-DASH Pro v4 cards and charts per screen breakpoint with setSize(). Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only `setSize()` sets the responsive width of a card or chart. You define a width for each screen breakpoint, and the dashboard layout adapts to the screen size without changes to the webpage. ![Widgets with custom sizes](https://docs.softt.io/img/v4/widget-size.png) ## Breakpoints Each breakpoint accepts a value from `1` to `12`. ## Methods `setSize()` has two overloads, declared in `Card.h` of ESP-DASH Pro. ### setSize(xs, sm, md, lg, xl, xxl) Pass each breakpoint as a separate argument: ```cpp void setSize(const uint8_t xs, const uint8_t sm, const uint8_t md, const uint8_t lg, const uint8_t xl, const uint8_t xxl); ``` ```cpp card1.setSize(12, 12, 6, 4, 3, 2); ``` ### setSize(const CardSize &size) Pass a `CardSize` structure: ```cpp struct CardSize { uint8_t xs; uint8_t sm; uint8_t md; uint8_t lg; uint8_t xl; uint8_t xxl; }; void setSize(const CardSize &size); ``` ```cpp card1.setSize({ .xs = 12, .sm = 12, .md = 6, .lg = 4, .xl = 3, .xxl = 2 }); ``` # Examples (/docs/espdash/v4/getting-started/examples) > Example sketches for ESP-DASH v4 that demonstrate cards, charts, interactive widgets and dynamic dashboards. Library: ESP-DASH v4.x.x The ESP-DASH repository includes documented example sketches that demonstrate common use cases. Each example is self-contained and can be uploaded after setting your Wi-Fi credentials. ## Available examples The following examples ship with ESP-DASH Lite v4.0.9: | Example | Description | | --- | --- | | `Basic` | Creates temperature and humidity cards and updates them at a fixed interval. | | `AccessPoint` | Starts the board in Access Point (SoftAP) mode and serves the dashboard at `192.168.4.1`. | | `Chart` | Creates a bar chart and updates its Y-axis values at a regular interval. | | `Interactive` | Creates a button card and a slider card that respond to user input. | | `Dynamic` | Creates cards on the heap and removes one from the dashboard at runtime. | | `Benchmark` | Exercises the library to verify that ESP-DASH works correctly on your platform. | You can find the examples in the [`examples` folder of the v4.0.9 release](https://github.com/ayushsharma82/ESP-DASH/tree/v4.0.9/examples). In the Arduino IDE, they are also available under **File > Examples > ESP-DASH** after installation. The `master` branch of the repository contains the ESP-DASH v5 examples, which use a different API. Use the v4.0.9 tag for v4 sketches. # Installation (/docs/espdash/v4/getting-started/installation) > Install ESP-DASH v4 and its platform-specific dependencies in the Arduino IDE or PlatformIO. Library: ESP-DASH v4.x.x This page lists the dependencies ESP-DASH v4 requires on each platform and describes how to install the library in the Arduino IDE and PlatformIO. ## Dependencies ESP-DASH requires the following libraries in your libraries folder. Install only the versions listed below; other versions can cause ESP-DASH to fail to compile or to work only partially. As of v4.0.9, ESP-DASH depends on `ESP32Async/ESPAsyncWebServer`, which is the only officially maintained version of the library. Remove older forks and install the latest release from the ESP32Async organization. The ESP32Async organization was formed by several core collaborators to maintain a single active version. All changes and improvements from mathieucarbou's fork of ESPAsyncWebServer have been merged into `ESP32Async/ESPAsyncWebServer`. Install the dependencies for your platform: - [ESP8266 Arduino Core](https://github.com/esp8266/Arduino) >= **v3.1.2** - (ESP32Async) [ESPAsyncTCP](https://github.com/ESP32Async/ESPAsyncTCP#v2.0.0) @ **v2.0.0** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.7.1) >= **v3.7.1** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson#v7.3.0) >= **v7.3.0** - [ESP32 Arduino Core](https://github.com/espressif/arduino-esp32) >= **v3.1.3** - (ESP32Async) [AsyncTCP](https://github.com/ESP32Async/AsyncTCP#v3.3.5) >= **v3.3.5** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.7.1) >= **v3.7.1** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson#v7.3.0) >= **v7.3.0** - [Pico Arduino Core](https://github.com/earlephilhower/arduino-pico) @ **v4.4.3** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP#v1.3.0) >= **v1.3.0** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.7.1) >= **v3.7.1** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson#v7.3.0) >= **v7.3.0** - [Pico Arduino Core](https://github.com/earlephilhower/arduino-pico) @ **v4.4.3** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP#v1.3.0) >= **v1.3.0** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.7.1) >= **v3.7.1** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson#v7.3.0) >= **v7.3.0** RP2040 refers to the Raspberry Pi Pico W and RP2350 to the Raspberry Pi Pico 2 W. RP2350 support was added in ESP-DASH v4.0.9. ## Install ESP-DASH The latest ESP-DASH release is v5. When installing ESP-DASH v4, select a 4.x version (the final v4 release is v4.0.9) rather than the latest version. ### Library Manager [#library-manager] Open **Sketch > Include Library > Manage Libraries**, search for "ESP-DASH", select a 4.x version and click **Install**. ### Manual installation [#manual-installation] 1. Download the [ESP-DASH v4.0.9 archive](https://github.com/ayushsharma82/ESP-DASH/archive/refs/tags/v4.0.9.zip). 2. Extract the archive into your Arduino libraries folder: - Windows: `Documents/Arduino/libraries/` - Linux: `/libraries/` ### Import a ZIP library [#import-a-zip-library] 1. Download the [ESP-DASH v4.0.9 archive](https://github.com/ayushsharma82/ESP-DASH/archive/refs/tags/v4.0.9.zip). 2. Open **Sketch > Include Library > Add .ZIP Library** and select the downloaded file. ESP-DASH supports multiple architectures (ESP8266, ESP32 and RP2040). Without strict compatibility checks, PlatformIO tries to install the dependencies of every platform, which often causes compilation errors. Before installing ESP-DASH: 1. Delete the `.pio/libdeps` folder of your project, if it exists. 2. Add the following line to the environment in your `platformio.ini` file: ```ini lib_compat_mode = strict ``` 3. Save `platformio.ini`. ### Library Manager [#library-manager-1] Open the PlatformIO menu, go to **Libraries**, search for "ESP-DASH" and add a 4.x version to your project. Alternatively, pin the version in `platformio.ini`: ```ini title="platformio.ini" lib_deps = ayushsharma82/ESP-DASH@^4.0.9 ``` ### Manual installation [#manual-installation-1] 1. Download the [ESP-DASH v4.0.9 archive](https://github.com/ayushsharma82/ESP-DASH/archive/refs/tags/v4.0.9.zip). 2. Extract the archive into the `lib` folder of your project. ## Next steps - [Integration](/docs/espdash/v4/getting-started/integration): add a dashboard to an existing web server sketch. - [Examples](/docs/espdash/v4/getting-started/examples): browse the example sketches. # Integration (/docs/espdash/v4/getting-started/integration) > Add an ESP-DASH v4 dashboard to an existing ESPAsyncWebServer sketch on ESP8266, ESP32 or RP2040. Library: ESP-DASH v4.x.x This guide shows how to add ESP-DASH to a sketch that already runs an `AsyncWebServer`. Each step adds a few lines to your existing code; the complete sketch is shown at the end. ### Include dependencies The includes use conditional compilation so the same sketch builds for ESP8266, ESP32, RP2040 and RP2350. ```cpp #include #if defined(ESP8266) /* ESP8266 dependencies */ #include #include #include #elif defined(ESP32) /* ESP32 dependencies */ #include #include #include #elif defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2350) /* RP2040 or RP2350 dependencies */ #include #include #include #endif #include ``` ### Create the AsyncWebServer instance Create an `AsyncWebServer` instance on port 80. ```cpp AsyncWebServer server(80); ``` You are not limited to a single dashboard. You can host multiple dashboards on different ports. ### Attach ESP-DASH Pass a pointer to the `AsyncWebServer` instance to the `ESPDash` constructor. ESP-DASH registers its routes and WebSocket handler on that server. ```cpp ESPDash dashboard(&server); ``` You can continue to use the same `AsyncWebServer` instance for your own routes. ### Create widgets Create widgets and pass a pointer to the dashboard as the first argument so that each widget can add itself to, and remove itself from, the dashboard. ```cpp /* Card format: (dashboard, card type, card name, symbol (optional)) */ Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%"); ``` See the [Cards](/docs/espdash/v4/cards/generic) section for the available card types. ### Configure setup() Connect to Wi-Fi and start the server with `server.begin()`. ```cpp void setup() { Serial.begin(115200); /* Connect to Wi-Fi */ WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.printf("WiFi Failed!\n"); return; } Serial.print("IP Address: "); Serial.println(WiFi.localIP()); /* Start AsyncWebServer */ server.begin(); } ``` ### Update widgets in loop() Update the card values, then call `dashboard.sendUpdates()` to push the changes to all connected clients. You can call these methods anywhere in your sketch; the example uses `loop()` for simplicity. ```cpp void loop() { /* Update card values */ temperature.update((int)random(0, 50)); humidity.update((int)random(0, 100)); /* Send updates to the dashboard */ dashboard.sendUpdates(); /* For demonstration only; use a millis() interval in production code */ delay(3000); } ``` ### Upload the sketch Set `ssid` and `password` to match your access point, upload the sketch and open the IP address printed on the serial monitor in a browser. ```cpp title="main.cpp" #include #if defined(ESP8266) /* ESP8266 dependencies */ #include #include #include #elif defined(ESP32) /* ESP32 dependencies */ #include #include #include #elif defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2350) /* RP2040 or RP2350 dependencies */ #include #include #include #endif #include /* Wi-Fi credentials */ const char* ssid = ""; const char* password = ""; AsyncWebServer server(80); ESPDash dashboard(&server); /* Card format: (dashboard, card type, card name, symbol (optional)) */ Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%"); void setup() { Serial.begin(115200); /* Connect to Wi-Fi */ WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.printf("WiFi Failed!\n"); return; } Serial.print("IP Address: "); Serial.println(WiFi.localIP()); /* Start AsyncWebServer */ server.begin(); } void loop() { /* Update card values */ temperature.update((int)random(0, 50)); humidity.update((int)random(0, 100)); /* Send updates to the dashboard */ dashboard.sendUpdates(); /* For demonstration only; use a millis() interval in production code */ delay(3000); } ``` ## Next steps - [Cards](/docs/espdash/v4/cards/generic): the card types and their parameters. - [Charts](/docs/espdash/v4/charts/bar): plot data series on the dashboard. - [Authentication](/docs/espdash/v4/security/authentication): restrict access to the dashboard. # Introduction (/docs/espdash/v4) > Documentation for ESP-DASH v4 (legacy), a library for building real-time web dashboards on ESP8266, ESP32 and RP2040 microcontrollers. Library: ESP-DASH v4.x.x This is the documentation for ESP-DASH v4, a legacy release. New projects should use ESP-DASH v5. To upgrade an existing project, see [Migrating from v4 to v5](/docs/espdash/v5/getting-started/migrating-from-v4-to-v5). ESP-DASH is a C++ library that serves a real-time dashboard directly from your microcontroller. You declare cards, charts and statistics in your sketch, and ESP-DASH generates the web interface and keeps every connected client synchronized over WebSockets. No HTML, CSS or JavaScript is required. GitHub repository: https://github.com/ayushsharma82/ESP-DASH ![ESP-DASH dashboard](https://docs.softt.io/img/feature.png) ## Capabilities - Automatic generation of the dashboard webpage, served from flash. - Real-time updates pushed to all connected clients. - A C++ interface for every widget; no front-end code is required. - Ready-to-use cards for common data types, such as temperature, humidity, status, sliders and buttons. - Dynamic layouts: widgets can be added or removed at runtime. - Built-in charts and a statistics page with system and custom key-value metrics. ## Supported boards ESP-DASH v4 supports the following microcontrollers: - **ESP8266** - **ESP32** - **RP2040** (Raspberry Pi Pico W) See [Installation](/docs/espdash/v4/getting-started/installation) for the required core and library versions for each platform. ## ESP-DASH Lite and ESP-DASH Pro This documentation covers both editions of ESP-DASH v4. ESP-DASH Lite is open source and licensed under GPL-3.0. ESP-DASH Pro is a commercial edition with additional cards, charts, tabs, branding and layout features. Pages and features that are available only in ESP-DASH Pro are marked accordingly. See [Commercial License](/docs/espdash/v4/commercial-license) for a comparison, or visit [store.softt.io](https://store.softt.io/products/esp-dash-pro). ## Explore the documentation # Authentication (/docs/espdash/v4/security/authentication) > Protect an ESP-DASH v4 dashboard with HTTP Basic Authentication using setAuthentication(). Library: ESP-DASH v4.x.x ESP-DASH supports HTTP Basic Authentication. When enabled, the browser prompts for a username and password before the dashboard loads. ![Browser authentication prompt](https://docs.softt.io/img/v4/authentication.png) ## Enable authentication Call `setAuthentication()` on the dashboard instance, typically in `setup()`: ```cpp dashboard.setAuthentication("username", "password"); ``` `setAuthentication()` accepts the credentials either as `const char*` or as `String`: ```cpp void setAuthentication(const char* user, const char* pass); void setAuthentication(const String &user, const String &pass); ``` HTTP Basic Authentication transmits credentials encoded, not encrypted. Over plain HTTP, anyone on the same network can read them. Use it as an access control on trusted networks. # Disable Statistics (/docs/espdash/v4/security/disable-statistics) > Disable the built-in system statistics in ESP-DASH v4 and hide the Statistics tab from the sidebar. Library: ESP-DASH v4.x.x ESP-DASH displays system statistics, such as the hardware platform, SDK version and free heap, on the [Statistics](/docs/espdash/v4/statistics) page by default. To disable them, pass `false` as the last argument of the `ESPDash` constructor. ## Disable system statistics If you serve the dashboard at the default URI, pass `false` as the second argument: ```cpp ESPDash dashboard(&server, false); ``` If you use a [custom URI](/docs/espdash/v4/features/custom-uri), pass `false` as the third argument: ```cpp ESPDash dashboard(&server, "/", false); ``` ## Hide the Statistics tab When system statistics are disabled and the sketch defines no custom statistics, the dashboard hides the Statistics tab from the sidebar automatically. # Statistics (/docs/espdash/v4/statistics) > Display built-in system metrics and custom key-value statistics on the ESP-DASH v4 Statistics page. Library: ESP-DASH v4.x.x The dashboard includes a dedicated Statistics page that lists key-value pairs. By default, it shows system statistics such as the hardware platform, SDK version, free heap and Wi-Fi signal strength. Since ESP-DASH v4, you can also add your own statistics to this page with the `Statistic` class. ![Statistics page](https://docs.softt.io/img/v4/statistics.png) System statistics can be turned off at any time. See [Disable Statistics](/docs/espdash/v4/security/disable-statistics). ## Constructor Declare statistics in global scope, outside of any function. ```cpp Statistic stat1(&dashboard, "Key", "Value"); ``` ESP-DASH Lite stores the `key` pointer without copying it, so pass a string literal or a buffer that remains valid for the lifetime of the statistic. The value is copied. ## Methods ### set(value) Updates the value of the statistic. ```cpp stat1.set("New Value"); ``` ### set(key, value) Updates both the key and the value of the statistic. ```cpp stat1.set("New Key", "New Value"); ``` ESP-DASH Lite v4.0.9 provides only `set(const char* value)`. The two-argument overload that also changes the key is not available in ESP-DASH Lite. ## Example ```cpp title="main.cpp" // ... AsyncWebServer server(80); ESPDash dashboard(&server); Statistic uptime(&dashboard, "Uptime (s)", "0"); void setup() { // ... server.begin(); } void loop() { uptime.set(String(millis() / 1000).c_str()); dashboard.sendUpdates(); delay(3000); } ``` # Tabs (/docs/espdash/v4/tabs) > Organize ESP-DASH Pro v4 widgets into multiple dashboard pages with the Tab class and setTab(). Library: ESP-DASH v4.x.x Availability: ESP-DASH Pro only Tabs split the dashboard into multiple pages so you can group related cards and charts. They are useful for dashboards with a large number of widgets. ![Dashboard sidebar with tabs](https://docs.softt.io/img/v4/tabs.png) By default, ESP-DASH places every widget in the **Overview** tab. If every card and chart is assigned to a custom tab, the dashboard hides the Overview tab from the sidebar automatically. ## Constructor Declare tabs in global scope, outside of any function. ```cpp Tab tab1(&dashboard, "Custom Tab 1"); ``` ## Assign widgets to a tab Call `setTab()` on a card or chart and pass a pointer to the tab: ```cpp card1.setTab(&tab1); chart1.setTab(&tab1); ``` ## Example ```cpp title="main.cpp" // ... AsyncWebServer server(80); ESPDash dashboard(&server); Tab sensorsTab(&dashboard, "Sensors"); Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); Card humidity(&dashboard, HUMIDITY_CARD, "Humidity", "%"); void setup() { // ... temperature.setTab(&sensorsTab); humidity.setTab(&sensorsTab); server.begin(); } void loop() { temperature.update((int)random(0, 50)); humidity.update((int)random(0, 100)); dashboard.sendUpdates(); delay(3000); } ``` # AI Tools (/docs/espdash/v5/ai-tools) > Use the ESP-DASH documentation from AI coding assistants through the MCP server, llms.txt and Markdown page exports. Library: ESP-DASH v5.x.x The ESP-DASH documentation is available in machine-readable formats so that AI coding assistants can answer questions and generate code from the current API instead of outdated training data. ## Ask AI Select **Ask AI** at the bottom of any documentation page to open the documentation assistant. It searches these pages before answering and links to the sources it used. Answers are grounded in the documentation only; verify generated code against the referenced pages. ## MCP server The documentation is served as a [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP. No authentication is required. One server covers every SOFTT library (ESP-DASH, ElegantOTA, NetWizard and WebSerial); pass `library: "espdash"` to limit results to ESP-DASH. ```text https://docs.softt.io/api/mcp ``` The server exposes three read-only tools: | Tool | Description | | --- | --- | | `search_docs` | Full-text search across the documentation. Accepts `query`, an optional `library` (`espdash`, `elegantota`, `netwizard`, `webserial` or `all`), an optional `version` for ESP-DASH (`v5`, `v4` or `all`) and `limit`. | | `get_page` | Returns the complete Markdown of a page, such as `/docs/espdash/v5/cards/slider`. | | `list_pages` | Lists pages with their title, description and URL, filtered by `library` and `version`. | ### Connect a client ```bash claude mcp add --transport http softt https://docs.softt.io/api/mcp ``` Add the server to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for all projects: ```json title=".cursor/mcp.json" { "mcpServers": { "softt": { "url": "https://docs.softt.io/api/mcp" } } } ``` Add the server to `.vscode/mcp.json` in your workspace: ```json title=".vscode/mcp.json" { "servers": { "softt": { "type": "http", "url": "https://docs.softt.io/api/mcp" } } } ``` Clients that support Streamable HTTP connect directly to the server URL. For clients that only support stdio, use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge: ```json { "softt": { "command": "npx", "args": ["-y", "mcp-remote", "https://docs.softt.io/api/mcp"] } } ``` Once connected, ask your assistant questions such as "Add an ESP-DASH slider card that controls LED brightness" and it will retrieve the relevant pages before writing code. ## llms.txt The site publishes [llms.txt](https://llmstxt.org) files for tools that ingest documentation directly: | URL | Contents | | --- | --- | | [`/llms.txt`](/llms.txt) | Index of every page with its title, description and link. | | [`/llms-full.txt`](/llms-full.txt) | The complete documentation of every library and version as a single Markdown file. | ## Markdown pages Every page is also available as Markdown: - Append `.md` to a page URL, for example [`/docs/espdash/v5/cards/slider.md`](/docs/espdash/v5/cards/slider.md). - Requests with an `Accept: text/markdown` header receive Markdown instead of HTML. - Use **Copy Markdown** at the top of a page to copy it to the clipboard, or **Open** to send it to an assistant such as ChatGPT or Claude. # Branding (/docs/espdash/v5/branding) > Replace the default ESP-DASH Pro dashboard logo with your own light and dark logos by editing the logo.h and logo.cpp library files. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only Branding refers to the logo displayed on the ESP-DASH Pro dashboard. The logo is stored as byte arrays in the `logo.h` and `logo.cpp` files of the library source, so you can replace it without modifying any HTML or CSS. ![Custom logo on the dashboard](https://docs.softt.io/img/v5/branding-light.png) ## Replace the logo ### Prepare the logos Create or obtain the logo you want to display. Use SVG (recommended), PNG or JPG, and optimize the image to keep the file size small. Prepare two versions of the logo, one for each theme. The light-theme logo should use darker colors and the dark-theme logo lighter colors, so that the logo remains legible in both themes. ### Convert the logos Convert both logo images into byte arrays. The converter below generates ready-to-use `logo.h` and `logo.cpp` files with gzip-compressed data: (Interactive logo converter available at https://docs.softt.io/docs/espdash/v5/branding) To convert the images manually instead, use the [File2Raw](https://ayushsharma82.github.io/file2raw/) utility, as described in the `logo.cpp` file of the ESP-DASH Pro library: 1. Open [File2Raw](https://ayushsharma82.github.io/file2raw/) and upload the image file. 2. Select **Gzip Compress** and **Use PROGMEM (Arduino)**. 3. Click **Convert** and copy the generated data. Repeat the procedure for the light-theme and the dark-theme logo. The conversion utility is provided as is, without warranty or liability of any kind. ### Replace the logo files in the ESP-DASH Pro library If you used the converter, replace `logo.h` and `logo.cpp` in the `src` folder of the ESP-DASH Pro library with the downloaded files. If you converted the images manually, update the existing files: 1. In `logo.cpp`, replace the contents of `DASH_LOGO_LIGHT` and `DASH_LOGO_DARK` with the generated data. 2. In `logo.h`, update the array sizes in the `extern` declarations of `DASH_LOGO_LIGHT` and `DASH_LOGO_DARK` to match the new data. 3. In `logo.h`, set `DASH_LOGO_LIGHT_MIME` and `DASH_LOGO_DARK_MIME` to the MIME type of each image, for example `"image/svg+xml"` or `"image/png"`. 4. Keep `DASH_LOGO_GZIPPED` set to `1` when the data is gzip-compressed. ```cpp title="logo.h" #define DASH_LOGO_LIGHT_MIME "image/svg+xml" #define DASH_LOGO_DARK_MIME "image/svg+xml" #define DASH_LOGO_GZIPPED 1 #define DASH_LOGO_WIDTH 158 #define DASH_LOGO_HEIGHT 32 extern const uint8_t DASH_LOGO_LIGHT[1983]; extern const uint8_t DASH_LOGO_DARK[1961]; ``` ### Upload the firmware Compile the sketch with the modified ESP-DASH Pro library and upload the firmware to your device. Browsers cache the logo. Clear the browser cache or perform a hard refresh to see the new logo immediately. ### Review the changes Open the dashboard in a browser. The dashboard switches between the light and dark logos according to the active theme. To adjust the logo size, change `DASH_LOGO_WIDTH` and `DASH_LOGO_HEIGHT` in `logo.h` and upload the firmware again. Clear the browser cache after each change. Logos and other branding elements may be subject to copyright and trademark law. Ensure that you have the necessary rights or permissions to use any branding elements in your project. # Action Button Card (/docs/espdash/v5/cards/action-button) > A button that asks the user to confirm in a dialog before the device runs an action. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The action button card opens a confirmation dialog when pressed and reports whether the user confirmed or cancelled. Use it for actions that should not run by accident, such as restarting the device or erasing stored data. ![Action button card](https://docs.softt.io/img/v5/cards/light/action-button.png) ![Action button confirmation dialog](https://docs.softt.io/img/v5/cards/light/action-button-dialog.png) ## Constructor ```cpp dash::ActionButtonCard(ESPDash& dashboard, const char* name, const char* title, const char* description); ``` ```cpp dash::ActionButtonCard restartButton( dashboard, "Restart Device", "Are you sure you want to restart the device?", "This action will restart the device and any unsaved data will be lost." ); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onAction(std::function callback); ``` The callback fires when the user closes the confirmation dialog. `confirm` is `true` if the user confirmed the action and `false` if the user cancelled it. The card holds no value, so no dashboard update is required inside the callback. ```cpp restartButton.onAction([](bool confirm) { if (confirm) { Serial.println("Action confirmed"); } else { Serial.println("Action cancelled"); } }); ``` ## Methods ### `setTitle()` ```cpp void setTitle(dash::string title); ``` Changes the title of the confirmation dialog. ```cpp restartButton.setTitle("Restart now?"); ``` ### `setDescription()` ```cpp void setDescription(dash::string description); ``` Changes the message shown in the confirmation dialog. ```cpp restartButton.setDescription("The device will be unavailable for a few seconds."); ``` ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::ActionButtonCard restartButton( dashboard, "Restart Device", "Are you sure you want to restart the device?", "This action will restart the device and any unsaved data will be lost." ); uint32_t restartAt = 0; void setup() { Serial.begin(115200); // ... connect to WiFi restartButton.onAction([](bool confirm) { if (confirm) { // Defer the restart so the callback can return first restartAt = millis() + 1000; } }); server.begin(); } void loop() { if (restartAt != 0 && millis() > restartAt) { ESP.restart(); } } ``` # Air Card (/docs/espdash/v5/cards/air) > Display an atmospheric pressure reading with a configurable unit using dash::AirCard. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The air card displays an atmospheric pressure reading with a unit, such as the output of a BMP280 or BME680 sensor. ![Air card](https://docs.softt.io/img/v5/cards/light/air.png) ## Constructor ```cpp dash::AirCard card(dashboard, name, unit); ``` Template parameters: ```cpp dash::AirCard pressure(dashboard, "Pressure"); // float, 2 decimals, hPa dash::AirCard pressureKpa(dashboard, "Pressure", "kPa"); dash::AirCard pressurePa(dashboard, "Pressure", "Pa"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(T value); ``` Sets the pressure value. Returns `true` if the value changed and an update is pending. ### `value()` ```cpp const T& value() const; ``` Returns the current value. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a value has been set. ### `removeValue()` ```cpp bool removeValue(); ``` Clears the value. The dashboard then shows `0`. Returns `true` if a value was removed. ### `setUnit()` ```cpp bool setUnit(const char* unit); ``` Sets the unit, for example `"hPa"` or `"kPa"`. Returns `true` if the unit changed. ### `unit()` ```cpp const char* unit() const; ``` Returns the current unit. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::AirCard pressure(dashboard, "Pressure", "hPa"); void setup() { // ... connect to Wi-Fi server.begin(); } void loop() { float reading = 1013.25f; // replace with a sensor reading pressure.setValue(reading); dashboard.sendUpdates(); delay(3000); } ``` ## Notes - The card stores the `unit` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. - Changing the unit does not convert the value; set a value in the new unit as well. # Color Picker Card (/docs/espdash/v5/cards/color-picker) > Let users pick a color in the browser and receive it on the device as a hex string. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The color picker card opens the browser's native color picker and sends the selected color to the device as a hex string such as `#ff0000`. Use it to control RGB LEDs, light strips or any color setting. ![Color picker card](https://docs.softt.io/img/v5/cards/light/color-picker.png) ## Constructor ```cpp dash::ColorPickerCard(ESPDash& dashboard, const char* name); ``` The value type is always `dash::string`; the card takes no template parameters. ```cpp dash::ColorPickerCard colorPicker(dashboard, "Color Picker"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onChange(std::function& value)> callback); ``` The callback fires when the user picks a color. Changes are debounced in the browser, so dragging across the picker produces one event after the user pauses for about 300 ms. `value` holds the color as a 7-character hex string (`#rrggbb`). Call `setOptionalValue()` (or `setValue()`) and `dashboard.sendUpdates()` inside the callback to store the color and keep the dashboard in sync. ```cpp colorPicker.onChange([](const std::optional& value) { Serial.printf("Color: %s\n", value.value_or("").c_str()); colorPicker.setOptionalValue(value); dashboard.sendUpdates(); }); ``` ## Methods ### `setValue()` ```cpp bool setValue(dash::string value); ``` Sets the current color. Use a hex string in the form `#rrggbb`. Returns `true` if the value changed. ```cpp colorPicker.setValue("#ff0000"); ``` ### `setOptionalValue()` ```cpp bool setOptionalValue(std::optional value); ``` Sets the color from an optional value, as received in the callback. An empty optional clears the value. Returns `true` if the value changed. ### `value()` ```cpp const dash::string& value() const; ``` Returns the current color. Check `hasValue()` first if the color may not have been set yet. ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::ColorPickerCard colorPicker(dashboard, "LED Color"); void setup() { Serial.begin(115200); // ... connect to WiFi colorPicker.setValue("#2563eb"); colorPicker.onChange([](const std::optional& value) { if (value.has_value()) { // Convert "#rrggbb" to a 24-bit integer uint32_t rgb = strtoul(value.value().c_str() + 1, nullptr, 16); uint8_t r = (rgb >> 16) & 0xFF; uint8_t g = (rgb >> 8) & 0xFF; uint8_t b = rgb & 0xFF; Serial.printf("R=%u G=%u B=%u\n", r, g, b); } colorPicker.setOptionalValue(value); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` # Dropdown Card (/docs/espdash/v5/cards/dropdown) > Present a list of predefined options in a dropdown and receive the selected value on the device. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The dropdown card presents a list of options defined as a comma-separated string and reports the selected option back to the device. Use it for mode selection, presets or any setting with a fixed set of values. ![Dropdown card](https://docs.softt.io/img/v5/cards/light/dropdown.png) ![Dropdown card with the option list open](https://docs.softt.io/img/v5/cards/light/dropdown-open.png) ## Constructor ```cpp template dash::DropdownCard(ESPDash& dashboard, const char* name, const char* options = ""); ``` ### Template parameters ### Parameters ```cpp // String values (default) dash::DropdownCard dropdown(dashboard, "Mode", "Auto,Manual,Off"); // Float values with 3 decimal places dash::DropdownCard dropdownFloat(dashboard, "Float Dropdown", "1.234,2.345,3.456"); // Integer values dash::DropdownCard dropdownInt(dashboard, "Int Dropdown", "1,2,3,4"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onChange(std::function callback); ``` The callback fires when the user selects an option. `value` is the selected option converted to `T`. The card does not update its own value; call `setValue()` and `dashboard.sendUpdates()` inside the callback to confirm the selection on the dashboard. ```cpp dropdown.onChange([](const dash::string& value) { Serial.printf("Selected: %s\n", value.c_str()); dropdown.setValue(value); dashboard.sendUpdates(); }); ``` ## Methods ### `setOptions()` ```cpp bool setOptions(const char* options); ``` Replaces the list of options with a comma-separated string. Returns `true` if the options changed. ```cpp dropdown.setOptions("Low,Medium,High"); ``` ### `options()` ```cpp const char* options() const; ``` Returns the current comma-separated list of options. ### `setValue()` ```cpp bool setValue(T value); ``` Sets the selected option. `value` must be of type `T` and should match one of the options. Returns `true` if the value changed. ```cpp dropdown.setValue("Manual"); ``` ### `value()` ```cpp const T& value() const; ``` Returns the currently selected value. ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::DropdownCard dropdown(dashboard, "Mode", "Auto,Manual,Off"); void setup() { Serial.begin(115200); // ... connect to WiFi dropdown.setValue("Auto"); dropdown.onChange([](const dash::string& value) { Serial.printf("Mode: %s\n", value.c_str()); dropdown.setValue(value); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` ## Notes - The number of options is not limited. Options are split on commas without trimming, so do not add spaces around the separators unless they are part of the option text. - The card stores the `options` pointer and does not copy the string. Pass a string literal or a buffer that remains valid for the lifetime of the card. # Energy Card (/docs/espdash/v5/cards/energy) > Display an energy or power consumption value with a configurable unit using dash::EnergyCard. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The energy card displays an energy or power consumption value with a unit, such as readings from an energy meter or a current sensor. ![Energy card](https://docs.softt.io/img/v5/cards/light/energy.png) ## Constructor ```cpp dash::EnergyCard card(dashboard, name, unit); ``` Template parameters: ```cpp dash::EnergyCard energy(dashboard, "Energy consumption", "kWh"); // float, 2 decimals dash::EnergyCard energyJ(dashboard, "Energy consumption", "J"); dash::EnergyCard energyWh(dashboard, "Energy consumption", "Wh"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(T value); ``` Sets the energy value. Returns `true` if the value changed and an update is pending. ### `value()` ```cpp const T& value() const; ``` Returns the current value. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a value has been set. ### `removeValue()` ```cpp bool removeValue(); ``` Clears the value. The dashboard then shows `0`. Returns `true` if a value was removed. ### `setUnit()` ```cpp bool setUnit(const char* unit); ``` Sets the unit displayed next to the value. Returns `true` if the unit changed. ### `unit()` ```cpp const char* unit() const; ``` Returns the current unit. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::EnergyCard energy(dashboard, "Energy consumption", "kWh"); void setup() { // ... connect to Wi-Fi server.begin(); } void loop() { float consumption = 123.4f; // replace with a meter reading energy.setValue(consumption); dashboard.sendUpdates(); delay(3000); } ``` ## Notes - The default unit is an empty string. Pass a unit to the constructor or call `setUnit()`. - The card stores the `unit` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. # Feedback Card (/docs/espdash/v5/cards/feedback) > Display a status message with an info, success, warning or danger indicator using dash::FeedbackCard. Library: ESP-DASH v5.x.x The feedback card displays a text message together with a status indicator. Use it to report system state, the result of an operation, warnings or errors. ![Feedback card with no status](https://docs.softt.io/img/v5/cards/light/feedback-idle.png) ![Feedback card with info status](https://docs.softt.io/img/v5/cards/light/feedback-info.png) ![Feedback card with warning status](https://docs.softt.io/img/v5/cards/light/feedback-warning.png) ![Feedback card with success status](https://docs.softt.io/img/v5/cards/light/feedback-success.png) ![Feedback card with danger status](https://docs.softt.io/img/v5/cards/light/feedback-danger.png) ## Constructor ```cpp dash::FeedbackCard card(dashboard, name, initialStatus, initialMessage); ``` Template parameters: ```cpp dash::FeedbackCard feedback(dashboard, "Status"); dash::FeedbackCard network(dashboard, "Network", dash::Status::INFO, "Connecting"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ### Status values | Value | Meaning | | --- | --- | | `dash::Status::NONE` | No status indicator (default). | | `dash::Status::INFO` | Informational message. | | `dash::Status::SUCCESS` | Operation completed successfully. | | `dash::Status::WARNING` | Warning condition. | | `dash::Status::DANGER` | Error or critical failure. | ## Methods ### `setFeedback()` ```cpp bool setFeedback(const char* message, dash::Status status); bool setFeedback(const dash::string& message, dash::Status status); // T = dash::string only ``` Sets the message and the status in one call. Returns `true` if either changed. ```cpp feedback.setFeedback("Light is on", dash::Status::SUCCESS); ``` ### `setMessage()` ```cpp bool setMessage(const char* message); bool setMessage(const dash::string& message); // T = dash::string only ``` Sets the message and keeps the current status. Returns `true` if the message changed. ### `setStatus()` ```cpp bool setStatus(dash::Status status); ``` Sets the status and keeps the current message. Returns `true` if the status changed. ### `status()` ```cpp dash::Status status() const; ``` Returns the current status. ### `value()` ```cpp const T& value() const; ``` Returns the current message. Call it only when `hasValue()` returns `true`; the constructor always sets a message, so this holds unless you call `removeValue()`. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::FeedbackCard feedback(dashboard, "Status"); void setup() { // ... connect to Wi-Fi server.begin(); feedback.setFeedback("System ready", dash::Status::SUCCESS); dashboard.sendUpdates(); } void loop() { if (WiFi.RSSI() < -80) { feedback.setFeedback("Weak Wi-Fi signal", dash::Status::WARNING); } else { feedback.setFeedback("System ready", dash::Status::SUCCESS); } dashboard.sendUpdates(); delay(3000); } ``` ## Notes - With `T = const char*`, the card stores the pointer and does not copy the string. Pass string literals or buffers that outlive the card. Use `dash::string` for messages built at runtime. # File Upload Card (/docs/espdash/v5/cards/file-upload) > Let users upload a file from the browser to an HTTP endpoint on the device, with progress and result indication. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The file upload card lets the user select a file in the browser and upload it to an HTTP endpoint on the device. The card shows upload progress and whether the upload succeeded. Use it to transfer configuration files, data files or other assets to the device. ![File upload card](https://docs.softt.io/img/v5/cards/light/file-upload.png) ## Constructor ```cpp template dash::FileUploadCard(ESPDash& dashboard, const char* name, const char* extensions = ""); ``` ### Template parameters ### Parameters ```cpp dash::FileUploadCard<> fileUpload(dashboard, "Upload", ".txt"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## How uploads work The card does not receive file data itself and has no callback. When the user selects a file, the browser sends it to the URL set with `setValue()`: - One file is uploaded per selection. - The request is an HTTP `POST` with a `multipart/form-data` body; the file is in the form field named `file`. - The card shows a success indicator if the endpoint responds with status `200`, and an error indicator for any other status. You must register a handler for the upload URL on the `AsyncWebServer` instance and process the incoming data there, for example by writing it to a file system. See the [example](#example) below. ## Methods ### `setValue()` ```cpp bool setValue(T url); ``` Sets the URL the file is uploaded to. Returns `true` if the value changed. ```cpp fileUpload.setValue("/upload"); ``` ### `value()` ```cpp const T& value() const; ``` Returns the current upload URL. ### `setExtensions()` ```cpp bool setExtensions(const char* extensions); ``` Sets the file types offered in the browser's file selection dialog, as a comma-separated list of file extensions or MIME types. Returns `true` if the value changed. ```cpp fileUpload.setExtensions(".txt,.jpg"); ``` ### `extensions()` ```cpp const char* extensions() const; ``` Returns the current list of accepted file extensions or MIME types. ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::FileUploadCard<> fileUpload(dashboard, "Upload", ".txt"); void setup() { Serial.begin(115200); // ... connect to WiFi fileUpload.setValue("/upload"); // Handle the upload request sent by the card server.on( "/upload", HTTP_POST, [](AsyncWebServerRequest* request) { // Called once the upload is complete; status 200 marks it as successful request->send(200); }, [](AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) { if (index == 0) { Serial.printf("Upload start: %s\n", filename.c_str()); // ... open the destination file } // ... write `len` bytes from `data` if (final) { Serial.printf("Upload end: %s, %u bytes\n", filename.c_str(), (unsigned)(index + len)); // ... close the destination file } }); server.begin(); } void loop() { // ... } ``` ## Notes - The file type filter applies only to the browser's selection dialog. Validate the file name, type and size in your upload handler. - The card stores the `extensions` pointer and does not copy the string. Pass a string literal or a buffer that remains valid for the lifetime of the card. # Generic Card (/docs/espdash/v5/cards/generic) > Display a numeric or text value with an optional symbol using dash::GenericCard. Library: ESP-DASH v5.x.x The generic card displays a single value of any supported type together with an optional symbol or unit. Use it for counters, status strings and other values that no dedicated card covers. ![Generic card](https://docs.softt.io/img/v5/cards/light/generic.png) ## Constructor ```cpp dash::GenericCard card(dashboard, name, symbol); ``` Template parameters: ```cpp dash::GenericCard restarts(dashboard, "Restarts", "times"); dash::GenericCard energy(dashboard, "Energy", "kWh"); dash::GenericCard status(dashboard, "Status"); // dash::string ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(const T& value); ``` Sets the displayed value. Returns `true` if the value changed and an update is pending, `false` if the new value equals the current one. ### `value()` ```cpp const T& value() const; ``` Returns the current value. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a value has been set. ### `removeValue()` ```cpp bool removeValue(); ``` Clears the value. The dashboard then shows `0` for numeric types or an empty string for text types. Returns `true` if a value was removed. ### `setSymbol()` ```cpp bool setSymbol(const char* symbol); ``` Sets the symbol or unit displayed next to the value. Returns `true` if the symbol changed. ### `symbol()` ```cpp const char* symbol() const; ``` Returns the current symbol. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::GenericCard counter(dashboard, "Counter", "events"); int events = 0; void setup() { // ... connect to Wi-Fi server.begin(); } void loop() { counter.setValue(++events); dashboard.sendUpdates(); delay(3000); } ``` ## Notes - The card stores the `symbol` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. - `dash::string` is Arduino `String` by default, or `std::string` when `DASH_USE_STL_STRING` is defined. # Humidity Card (/docs/espdash/v5/cards/humidity) > Display a relative humidity reading with a configurable unit using dash::HumidityCard. Library: ESP-DASH v5.x.x The humidity card displays a numeric humidity reading, by default as a percentage, such as the output of a DHT22 or BME280 sensor. ![Humidity card](https://docs.softt.io/img/v5/cards/light/humidity.png) ## Constructor ```cpp dash::HumidityCard card(dashboard, name, unit); ``` Template parameters: ```cpp dash::HumidityCard humidity(dashboard, "Humidity"); // float, 2 decimals, % dash::HumidityCard greenhouse(dashboard, "Greenhouse"); dash::HumidityCard room(dashboard, "Room"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(const T& value); ``` Sets the humidity value. Returns `true` if the value changed and an update is pending. ### `value()` ```cpp const T& value() const; ``` Returns the current value. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a value has been set. ### `removeValue()` ```cpp bool removeValue(); ``` Clears the value. The dashboard then shows `0`. Returns `true` if a value was removed. ### `setUnit()` ```cpp bool setUnit(const char* unit); ``` Sets the unit displayed next to the value. Returns `true` if the unit changed. ### `unit()` ```cpp const char* unit() const; ``` Returns the current unit. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::HumidityCard humidity(dashboard, "Humidity", "%"); void setup() { // ... connect to Wi-Fi server.begin(); } void loop() { float reading = 55.2f; // replace with a sensor reading humidity.setValue(reading); dashboard.sendUpdates(); delay(3000); } ``` ## Notes - The card stores the `unit` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. # Image Card (/docs/espdash/v5/cards/image) > Display an image from a URL in one of two sizes using dash::ImageCard. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The image card displays an image loaded from a URL. Use it for camera snapshots, QR codes, diagrams or status graphics. ![Image card](https://docs.softt.io/img/v5/cards/light/image-large.png) ## Constructor ```cpp dash::ImageCard card(dashboard, name, imageSize); ``` Template parameters: ```cpp dash::ImageCard image(dashboard, "Image"); dash::ImageCard imageXL(dashboard, "Image XL", dash::ImageSize::XL); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(T url); ``` Sets the URL of the image to display. Returns `true` if the URL changed. ```cpp image.setValue("/img/example.png"); ``` ### `value()` ```cpp const T& value() const; ``` Returns the current URL. Call it only when `hasValue()` returns `true`. ### `setImageSize()` ```cpp bool setImageSize(dash::ImageSize size); ``` Sets the display size: `dash::ImageSize::LG` or `dash::ImageSize::XL`. Returns `true` if the size changed. ### `imageSize()` ```cpp dash::ImageSize imageSize() const; ``` Returns the current display size. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::ImageCard image(dashboard, "Camera", dash::ImageSize::XL); void setup() { // ... connect to Wi-Fi server.begin(); image.setValue("https://example.com/snapshot.jpg"); dashboard.sendUpdates(); } void loop() { // ... } ``` ## Notes - The browser loads the image directly from the URL. The URL must be reachable from the device viewing the dashboard, not only from the microcontroller. # Indicator Button Card (/docs/espdash/v5/cards/indicator-button) > A push button with a built-in status indicator that the device can set to show the result or state of an action. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The indicator button card is a push button with a built-in status indicator. The device controls the indicator independently of the button, so you can use it to report the state of the action the button triggers, for example whether a light is on or whether a process completed successfully. ![Indicator button card without indicator](https://docs.softt.io/img/v5/cards/light/indicator-button-none.png) ![Indicator button card with info status](https://docs.softt.io/img/v5/cards/light/indicator-button-info.png) ![Indicator button card with success status](https://docs.softt.io/img/v5/cards/light/indicator-button-success.png) ![Indicator button card with warning status](https://docs.softt.io/img/v5/cards/light/indicator-button-warning.png) ![Indicator button card with danger status](https://docs.softt.io/img/v5/cards/light/indicator-button-danger.png) ## Constructor ```cpp dash::IndicatorButtonCard(ESPDash& dashboard, const char* name); ``` ```cpp dash::IndicatorButtonCard indicatorButton(dashboard, "Indicator Button"); ``` The indicator is hidden until you set it with `setIndicator()` or `setValue()`. The initial status is `dash::Status::INFO`. An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onPush(std::function callback); ``` The callback fires each time the user presses the button. It receives no arguments. Pressing the button does not change the indicator; to reflect a new state, call `setIndicator()` and `dashboard.sendUpdates()` inside the callback. ```cpp indicatorButton.onPush([]() { indicatorButton.setIndicator(true, dash::Status::SUCCESS); dashboard.sendUpdates(); }); ``` ## Methods ### `setIndicator()` ```cpp bool setIndicator(bool show, dash::Status status); ``` Shows or hides the indicator and sets its status color. Returns `true` if the indicator changed. ```cpp indicatorButton.setIndicator(true, dash::Status::WARNING); ``` ### `status()` ```cpp dash::Status status() const; ``` Returns the current indicator status. ### `setValue()` ```cpp bool setValue(bool value); ``` Shows (`true`) or hides (`false`) the indicator while keeping the current status. Returns `true` if the value changed. ### `value()` ```cpp const bool& value() const; ``` Returns `true` if the indicator is shown. Check `hasValue()` first if the indicator may not have been set yet. ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::IndicatorButtonCard lightButton(dashboard, "Light"); const int LIGHT_PIN = 2; bool lightOn = false; void setup() { Serial.begin(115200); pinMode(LIGHT_PIN, OUTPUT); // ... connect to WiFi lightButton.setIndicator(false, dash::Status::SUCCESS); lightButton.onPush([]() { lightOn = !lightOn; digitalWrite(LIGHT_PIN, lightOn ? HIGH : LOW); lightButton.setIndicator(lightOn, dash::Status::SUCCESS); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` # Input Card (/docs/espdash/v5/cards/input) > Collect text or numeric input from the dashboard with typed parsing using dash::InputCard. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The input card provides a text field on the dashboard. The library parses the entered text into the card's type, so you can collect strings, integers or floating-point numbers, for example a device name, a threshold or a calibration offset. ![Input card](https://docs.softt.io/img/v5/cards/light/input.png) ## Constructor ```cpp dash::InputCard card(dashboard, name, placeholder); ``` Template parameters: ```cpp dash::InputCard text(dashboard, "Device name"); // dash::string dash::InputCard threshold(dashboard, "Threshold", "0–100"); dash::InputCard offset(dashboard, "Offset", "°C"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onChange(std::function& value)> callback); ``` The callback fires when the user submits a value on the dashboard. The parameter type is `const std::optional&`, where `T` is the card's value type: - For integral and floating-point types, the text is parsed into `T`. If the field is empty or the text cannot be parsed, the callback receives `std::nullopt`. - For `dash::string` and `const char*`, the callback receives the text as entered. It receives `std::nullopt` only if the dashboard sends no value. The card does not update its own value when the callback fires. Call `setOptionalValue(value)` and then `dashboard.sendUpdates()` inside the callback to show the accepted value on the dashboard. ```cpp dash::InputCard threshold(dashboard, "Threshold"); // in setup() threshold.onChange([](const std::optional& value) { if (value) { Serial.printf("Threshold: %d\n", *value); } else { Serial.println("Invalid or empty input"); } threshold.setOptionalValue(value); dashboard.sendUpdates(); }); ``` ## Methods ### `setValue()` ```cpp bool setValue(T value); ``` Sets the value shown in the field. Returns `true` if the value changed. ### `setOptionalValue()` ```cpp bool setOptionalValue(std::optional value); ``` Sets the value if `value` holds one, or clears it if `value` is `std::nullopt`. Use it to pass the callback argument back to the card. Returns `true` if the value changed. ### `value()` ```cpp const T& value() const; ``` Returns the current value. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a value has been set. ### `optional()` ```cpp const std::optional& optional() const; ``` Returns the current value as a `std::optional`, empty if no value is set. ### `removeValue()` ```cpp bool removeValue(); ``` Clears the value. Returns `true` if a value was removed. ### `setPlaceholder()` ```cpp bool setPlaceholder(const char* placeholder); ``` Sets the placeholder text shown in the empty field. Returns `true` if the placeholder changed. ### `placeholder()` ```cpp const char* placeholder() const; ``` Returns the current placeholder text. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::InputCard deviceName(dashboard, "Device name", "Enter a name"); void setup() { Serial.begin(115200); // ... connect to Wi-Fi deviceName.onChange([](const std::optional& value) { Serial.printf("Device name: %s\n", value.value_or("").c_str()); deviceName.setOptionalValue(value); dashboard.sendUpdates(); }); deviceName.setValue("esp-node-1"); server.begin(); } void loop() { // ... } ``` ## Notes - With `T = const char*`, the pointer passed to the callback refers to the incoming message buffer. Copy the text, or use `dash::string`, if you need it after the callback returns. - The card stores the `placeholder` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. # Joystick Card (/docs/espdash/v5/cards/joystick) > A virtual joystick that streams X/Y coordinates and direction to the device, optionally locked to one axis. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The joystick card provides a virtual joystick and streams its position and direction to the device while the user drags it. The joystick can be locked to the X or Y axis. Use it to drive robots, pan-tilt mounts or any control that needs continuous directional input. ![Joystick card](https://docs.softt.io/img/v5/cards/light/joystick.png) ## Constructor ```cpp dash::JoystickCard(ESPDash& dashboard, const char* name, dash::JoystickLock lock = dash::JoystickLock::NONE); ``` ```cpp dash::JoystickCard joystick(dashboard, "Joystick"); dash::JoystickCard joystickX(dashboard, "Joystick X", dash::JoystickLock::X); dash::JoystickCard joystickY(dashboard, "Joystick Y", dash::JoystickLock::Y); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onChange(std::function callback); ``` While the user holds the joystick, the dashboard sends its position every 50 ms and the callback fires for each update. When the user releases the joystick, the callback fires once more with `x` and `y` set to `0` and the direction set to `"idle"`. No dashboard update is required inside the callback. The `dash::Joystick` object provides: ### Coordinate range `x` and `y` are the offset of the thumb from the joystick center in CSS pixels. The thumb is clamped to a circle with a radius of 45, so each axis ranges from `-45` to `45` and the combined offset never exceeds 45 (`x² + y² ≤ 45²`). Full deflection on a diagonal therefore yields roughly `±32` on each axis. | Movement | `x` | `y` | | --- | --- | --- | | Right | negative, down to `-45` | `0` | | Left | positive, up to `45` | `0` | | Up | `0` | positive, up to `45` | | Down | `0` | negative, down to `-45` | With `dash::JoystickLock::X`, `y` is always `0`. With `dash::JoystickLock::Y`, `x` is always `0`. The X axis is inverted relative to the usual screen convention. Negate `x()` if your application expects positive values to the right. ```cpp joystick.onChange([](const dash::Joystick& joy) { Serial.printf("x=%d y=%d dir=%s\n", joy.x(), joy.y(), joy.direction().c_str()); }); ``` ## Methods ### `setLock()` ```cpp bool setLock(dash::JoystickLock lock); ``` Locks the joystick to the X or Y axis, or unlocks it with `dash::JoystickLock::NONE`. Returns `true` if the lock changed. ```cpp joystick.setLock(dash::JoystickLock::X); // Horizontal movement only joystick.setLock(dash::JoystickLock::Y); // Vertical movement only joystick.setLock(dash::JoystickLock::NONE); // Free movement ``` ### `lock()` ```cpp dash::JoystickLock lock() const; ``` Returns the current axis lock. ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::JoystickCard joystick(dashboard, "Drive"); void setup() { Serial.begin(115200); // ... connect to WiFi joystick.onChange([](const dash::Joystick& joy) { Serial.printf("Joystick: %s\n", joy.to_string().c_str()); // ... map joy.x() and joy.y() to motor speeds }); server.begin(); } void loop() { // ... } ``` # Link Card (/docs/espdash/v5/cards/link) > A card that opens a URL when clicked, in a new tab or in the current tab. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The link card displays a clickable link that opens a URL set by the device. Use it to point users to documentation, related dashboards or other services on the network. ![Link card](https://docs.softt.io/img/v5/cards/light/link.png) ## Constructor ```cpp template dash::LinkCard(ESPDash& dashboard, const char* name); ``` ### Template parameters ### Parameters ```cpp dash::LinkCard<> link(dashboard, "Documentation"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(T url); ``` Sets the URL that opens when the user clicks the card. Returns `true` if the value changed. ```cpp link.setValue("https://espdash.pro"); ``` ### `value()` ```cpp const T& value() const; ``` Returns the current URL. ### `setTarget()` ```cpp bool setTarget(dash::string target); ``` Sets the browsing context in which the URL opens, using the values of the HTML `target` attribute. `_blank` (default) opens the URL in a new tab; `_self` opens it in the current tab. Returns `true` if the target changed. ```cpp link.setTarget("_self"); ``` ### `target()` ```cpp const dash::string& target() const; ``` Returns the current target. ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::LinkCard<> link(dashboard, "Documentation"); void setup() { Serial.begin(115200); // ... connect to WiFi link.setValue("https://espdash.pro"); link.setTarget("_blank"); server.begin(); } void loop() { // ... } ``` # Password Card (/docs/espdash/v5/cards/password) > Collect passwords and other secrets from the dashboard without echoing them back using dash::PasswordCard. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The password card provides a masked input field for sensitive text such as passwords, API keys or PINs. The entered text is delivered to your callback, and the card never sends the actual secret back to the dashboard. ![Password card](https://docs.softt.io/img/v5/cards/light/password.png) ## Constructor ```cpp dash::PasswordCard card(dashboard, name, placeholder); ``` `dash::PasswordCard` is not a template. It derives from `dash::InputCard`, so its value type is `const char*`. ```cpp dash::PasswordCard wifiPassword(dashboard, "Wi-Fi password"); dash::PasswordCard apiKey(dashboard, "API key", "********"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onChange(std::function& value)> callback); ``` The callback fires when the user submits a value on the dashboard. `value` holds the entered text, or `std::nullopt` if the dashboard sends no value. To show on the dashboard that a secret has been stored, call `setOptionalValue(value)` and then `dashboard.sendUpdates()` inside the callback. The card displays the placeholder instead of the entered text. ```cpp dash::PasswordCard apiKey(dashboard, "API key", "********"); // in setup() apiKey.onChange([](const std::optional& value) { if (value) { // store or use *value here } apiKey.setOptionalValue(value); dashboard.sendUpdates(); }); ``` ## Methods ### `setValue()` ```cpp bool setValue(const char* value); ``` Marks the field as set or empty. If `value` is `nullptr` or an empty string, the field is cleared. Otherwise the card stores and displays the placeholder text, not `value`. The secret itself is never stored in the card or sent to the dashboard. ### `setOptionalValue()` ```cpp bool setOptionalValue(std::optional value); ``` Calls `setValue()` with the contained value, or clears the field if `value` is `std::nullopt`. Use it to pass the callback argument back to the card. ### `value()` ```cpp const char* const& value() const; ``` Returns the value currently displayed: the placeholder text if a secret is set, or an empty string if the field is clear. It does not return the secret. Call it only when `hasValue()` returns `true`. ### `setPlaceholder()` ```cpp bool setPlaceholder(const char* placeholder); ``` Sets the text displayed in place of the secret. It takes effect the next time `setValue()` stores a non-empty value. Returns `true` if the placeholder changed. ### `placeholder()` ```cpp const char* placeholder() const; ``` Returns the current placeholder text. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::PasswordCard apiKey(dashboard, "API key", "********"); dash::string storedKey; void setup() { Serial.begin(115200); // ... connect to Wi-Fi apiKey.onChange([](const std::optional& value) { if (value) { storedKey = *value; // copy; the pointer is only valid during the callback Serial.println("API key updated"); } else { Serial.println("API key cleared"); } apiKey.setOptionalValue(value); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` ## Notes - With the default empty placeholder, a stored secret is displayed as an empty field. Pass a placeholder such as `"********"` to indicate that a value is set. - The pointer passed to the callback refers to the incoming message buffer. Copy the text if you need it after the callback returns. - The card stores the `placeholder` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. # Progress Card (/docs/espdash/v5/cards/progress-bar) > Display a value as a progress bar within a minimum and maximum range using dash::ProgressCard. Library: ESP-DASH v5.x.x The progress card displays a value as a horizontal bar between a minimum and a maximum. Use it for fill levels, battery charge, task progress or any bounded quantity. ![Progress card](https://docs.softt.io/img/v5/cards/light/progress-bar.png) ## Constructor ```cpp dash::ProgressCard card(dashboard, name, minValue, maxValue, unit); ``` Template parameters: ```cpp dash::ProgressCard progress(dashboard, "Progress", 0, 100, "%"); // int dash::ProgressCard load(dashboard, "Load", 0, 1, "kWh"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(const T& value); ``` Sets the value. Values below `min()` or above `max()` are clamped to the nearest bound. Returns `true` if the stored value changed. ### `value()` ```cpp const T& value() const; ``` Returns the current value. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a value has been set. ### `removeValue()` ```cpp bool removeValue(); ``` Clears the value. The dashboard then shows `0`. Returns `true` if a value was removed. ### `setMin()` ```cpp bool setMin(T minValue); ``` Sets the lower bound. If the current value is below the new bound, it is raised to the bound. Returns `true` if the bound changed. ### `setMax()` ```cpp bool setMax(T maxValue); ``` Sets the upper bound. If the current value is above the new bound, it is lowered to the bound. Returns `true` if the bound changed. ### `min()` and `max()` ```cpp T min() const; T max() const; ``` Return the current lower and upper bounds. ### `setUnit()` ```cpp bool setUnit(const char* unit); ``` Sets the unit displayed next to the value. Returns `true` if the unit changed. ### `unit()` ```cpp const char* unit() const; ``` Returns the current unit. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::ProgressCard tankLevel(dashboard, "Tank level", 0, 100, "%"); void setup() { // ... connect to Wi-Fi server.begin(); } void loop() { int level = random(0, 101); // replace with a sensor reading tankLevel.setValue(level); dashboard.sendUpdates(); delay(3000); } ``` ## Notes - The card stores the `unit` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. # Push Button Card (/docs/espdash/v5/cards/push-button) > A stateless button that runs a callback on the device each time it is pressed. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The push button card runs a callback on the device each time the user presses it. It is stateless: it holds no value and sends no data other than the press event. Use it to trigger one-shot actions such as starting a measurement or resetting a counter. ![Push button card](https://docs.softt.io/img/v5/cards/light/push-button.png) ## Constructor ```cpp dash::PushButtonCard(ESPDash& dashboard, const char* name); ``` ```cpp dash::PushButtonCard pushButton(dashboard, "Push Button"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onPush(std::function callback); ``` The callback fires each time the user presses the button. It receives no arguments. Because the card has no value, no dashboard update is required inside the callback. ```cpp pushButton.onPush([]() { Serial.println("Push button pressed"); }); ``` ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::PushButtonCard pushButton(dashboard, "Reset Counter"); uint32_t counter = 0; void setup() { Serial.begin(115200); // ... connect to WiFi pushButton.onPush([]() { counter = 0; Serial.println("Counter reset"); }); server.begin(); } void loop() { // ... } ``` # Range Slider Card (/docs/espdash/v5/cards/range-slider) > A dual-thumb slider for selecting a low and high value within configurable bounds. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The range slider card lets the user select a range, defined by a low and a high value, with a dual-thumb slider. Use it for thresholds, alarm windows, filters or any setting that needs a lower and an upper limit. ![Range slider card](https://docs.softt.io/img/v5/cards/light/range-slider.png) ## Constructor ```cpp template dash::RangeSliderCard(ESPDash& dashboard, const char* name, T minValue, T maxValue, T step = 1, const char* unit = ""); ``` ### Template parameters ### Parameters ```cpp dash::RangeSliderCard rangeSlider(dashboard, "Range Slider", 0, 100, 1, "%"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## The `dash::Range` type The card's value is a `dash::Range`, which holds a low and a high value. The constructor orders its arguments, so `low()` is never greater than `high()`. ## Callback ```cpp void onChange(std::function& range)> callback); ``` The callback fires when the user moves either thumb. Changes are debounced in the browser and sent about 300 ms after the user stops moving. `range` is the new selection. Call `setValue()` and `dashboard.sendUpdates()` inside the callback to store the range and keep the dashboard in sync. ```cpp rangeSlider.onChange([](const dash::Range& range) { Serial.printf("Range: %s\n", range.to_string().c_str()); rangeSlider.setValue(range); dashboard.sendUpdates(); }); ``` ## Methods ### `setValue()` ```cpp bool setValue(dash::Range value); ``` Sets the selected range. If the range lies entirely below the minimum or entirely above the maximum, both ends are set to that bound. Returns `true` if the value changed. ```cpp rangeSlider.setValue({25, 75}); ``` ### `value()` ```cpp const dash::Range& value() const; ``` Returns the selected range. ```cpp const dash::Range& range = rangeSlider.value(); Serial.printf("Low: %u, High: %u\n", range.low(), range.high()); ``` ### `setMin()` ```cpp bool setMin(T minValue); ``` Sets the lowest selectable value. If the current range lies entirely below the new minimum, it is moved to the minimum. Returns `true` if the minimum changed. ### `setMax()` ```cpp bool setMax(T maxValue); ``` Sets the highest selectable value. If the current range lies entirely above the new maximum, it is moved to the maximum. Returns `true` if the maximum changed. ### `setStep()` ```cpp bool setStep(T step); ``` Sets the increment between selectable values. Returns `true` if the step changed. ### `setUnit()` ```cpp bool setUnit(const char* unit); ``` Sets the unit label. Returns `true` if the unit changed. ```cpp rangeSlider.setUnit("°C"); ``` ### `min()`, `max()`, `step()`, `unit()` ```cpp T min() const; T max() const; T step() const; const char* unit() const; ``` Return the current minimum, maximum, step and unit. ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::RangeSliderCard comfortRange(dashboard, "Comfort Range", 10, 35, 1, "°C"); void setup() { Serial.begin(115200); // ... connect to WiFi comfortRange.setValue({20, 24}); comfortRange.onChange([](const dash::Range& range) { Serial.printf("Comfort range: %d to %d\n", range.low(), range.high()); comfortRange.setValue(range); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` ## Notes - The card stores the `unit` pointer and does not copy the string. Pass a string literal or a buffer that remains valid for the lifetime of the card. - `setValue()` does not clamp a range that only partly exceeds the bounds. Validate values from other sources before passing them to the card. # Separator Card (/docs/espdash/v5/cards/separator) > Divide the dashboard into labeled sections with a title and subtitle using dash::SeparatorCard. Library: ESP-DASH v5.x.x The separator card is a non-interactive element that divides the dashboard into sections. It displays a prominent title and an optional subtitle above the cards that follow it. ![Separator card](https://docs.softt.io/img/v5/cards/light/separator.png) ## Constructor ```cpp dash::SeparatorCard card(dashboard, title, initialMessage); ``` Template parameters: ```cpp dash::SeparatorCard sensors(dashboard, "Sensors", "Live readings from the environment sensors"); dash::SeparatorCard controls(dashboard, "Controls"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setSubtitle()` ```cpp bool setSubtitle(const char* subtitle); ``` Sets the subtitle shown below the title. Returns `true` if the subtitle changed. ```cpp sensors.setSubtitle("Last calibrated at boot"); ``` ### `setValue()` ```cpp bool setValue(const T& value); ``` Equivalent to `setSubtitle()`; the subtitle is the card's value. With `T = dash::string`, this overload also accepts a `dash::string` built at runtime. ### `value()` ```cpp const T& value() const; ``` Returns the current subtitle as `T` (`dash::string` by default). ### `setName()` ```cpp bool setName(const char* name); ``` Changes the section title set in the constructor. Returns `true` if the title changed. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::SeparatorCard environment(dashboard, "Environment", "Indoor sensors"); dash::TemperatureCard temperature(dashboard, "Temperature"); dash::HumidityCard humidity(dashboard, "Humidity"); void setup() { // ... connect to Wi-Fi server.begin(); } void loop() { temperature.setValue(23.5f); humidity.setValue(55.0f); environment.setSubtitle("Updated every 3 seconds"); dashboard.sendUpdates(); delay(3000); } ``` ## Notes - With `T = const char*`, the card stores the subtitle pointer and does not copy the string. Pass string literals or buffers that outlive the card. # Slider Card (/docs/espdash/v5/cards/slider) > Let users select a numeric value within a range from the dashboard using dash::SliderCard. Library: ESP-DASH v5.x.x The slider card lets the user select a numeric value by dragging a handle along a range with a fixed step. Use it to control brightness, speed, set points or any other bounded setting. ![Slider card](https://docs.softt.io/img/v5/cards/light/slider.png) ## Constructor ```cpp dash::SliderCard card(dashboard, name, minValue, maxValue, step, unit); ``` Template parameters: ```cpp dash::SliderCard slider(dashboard, "Slider", 0, 255); // int, step 1 dash::SliderCard volume(dashboard, "Volume", 0, 100, 1, "%"); dash::SliderCard voltage(dashboard, "Voltage", 0.0f, 1.0f, 0.01f, "V"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ### Percentage slider `dash::PercentageSliderCard` is a preconfigured slider with a range of 0 to 100, a step of 1 and the unit `"%"`. `T` defaults to `uint8_t`. ```cpp dash::PercentageSliderCard brightness(dashboard, "Brightness"); // uint8_t, 0–100 % ``` It accepts only `dashboard` and `name` and provides the same callback and methods as `dash::SliderCard`. ## Callback ```cpp void onChange(std::function callback); ``` The callback fires when the user moves the slider on the dashboard. `value` is the selected value, of the card's type `T`. The card does not update its own value when the callback fires. Call `setValue(value)` and then `dashboard.sendUpdates()` inside the callback to confirm the new value on the dashboard. ```cpp dash::SliderCard volume(dashboard, "Volume", 0, 100, 1, "%"); // in setup() volume.onChange([](int value) { Serial.printf("Volume: %d\n", value); volume.setValue(value); dashboard.sendUpdates(); }); ``` ## Methods ### `setValue()` ```cpp bool setValue(const T& value); ``` Sets the slider position. Values below `min()` or above `max()` are clamped to the nearest bound. Returns `true` if the stored value changed. ### `value()` ```cpp const T& value() const; ``` Returns the current value. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a value has been set. ### `setMin()` ```cpp bool setMin(T minValue); ``` Sets the lower bound. If the current value is below the new bound, it is raised to the bound. Returns `true` if the bound changed. ### `setMax()` ```cpp bool setMax(T maxValue); ``` Sets the upper bound. If the current value is above the new bound, it is lowered to the bound. Returns `true` if the bound changed. ### `setStep()` ```cpp bool setStep(T step); ``` Sets the increment between selectable values. Returns `true` if the step changed. ### `min()`, `max()` and `step()` ```cpp T min() const; T max() const; T step() const; ``` Return the current lower bound, upper bound and step. ### `setUnit()` ```cpp bool setUnit(const char* unit); ``` Sets the unit displayed next to the value. Returns `true` if the unit changed. ### `unit()` ```cpp const char* unit() const; ``` Returns the current unit. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::SliderCard brightness(dashboard, "Brightness", 0, 255, 5); void setup() { Serial.begin(115200); // ... connect to Wi-Fi brightness.onChange([](int value) { Serial.printf("Brightness: %d\n", value); // apply the value, for example with analogWrite() brightness.setValue(value); dashboard.sendUpdates(); }); brightness.setValue(128); server.begin(); } void loop() { // ... } ``` ## Notes - The card stores the `unit` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. - For floating-point sliders, choose a `Precision` that can represent `step`, for example `Precision = 2` for a step of `0.01`. - `dash::PercentageSliderCard` declares a `Precision` template parameter but does not pass it to `dash::SliderCard`; values are always sent with the default precision of 2. # Temperature Card (/docs/espdash/v5/cards/temperature) > Display a temperature reading with a configurable unit using dash::TemperatureCard. Library: ESP-DASH v5.x.x The temperature card displays a numeric temperature reading with a unit, such as the output of a thermistor or a digital temperature sensor. ![Temperature card](https://docs.softt.io/img/v5/cards/light/temperature.png) ## Constructor ```cpp dash::TemperatureCard card(dashboard, name, unit); ``` Template parameters: ```cpp dash::TemperatureCard temperature(dashboard, "Temperature"); // float, 2 decimals, °C dash::TemperatureCard outdoor(dashboard, "Outdoor", "°F"); dash::TemperatureCard core(dashboard, "Core"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(const T& value); ``` Sets the temperature value. Returns `true` if the value changed and an update is pending. ### `value()` ```cpp const T& value() const; ``` Returns the current value. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a value has been set. ### `removeValue()` ```cpp bool removeValue(); ``` Clears the value. The dashboard then shows `0`. Returns `true` if a value was removed. ### `setUnit()` ```cpp bool setUnit(const char* unit); ``` Sets the unit, for example `"°C"` or `"°F"`. Returns `true` if the unit changed. ### `unit()` ```cpp const char* unit() const; ``` Returns the current unit. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::TemperatureCard temperature(dashboard, "Temperature", "°C"); void setup() { // ... connect to Wi-Fi server.begin(); } void loop() { float reading = 23.5f; // replace with a sensor reading temperature.setValue(reading); dashboard.sendUpdates(); delay(3000); } ``` ## Notes - The card stores the `unit` pointer and does not copy the string. Pass a string literal or a buffer that outlives the card. - Changing the unit does not convert the value; set a value in the new unit as well. # Time Difference Card (/docs/espdash/v5/cards/time-difference) > Display the time elapsed since, or remaining until, a timestamp set by the device, relative to the browser's clock. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The time difference card displays the difference between a timestamp set by the device and the browser's current time, in a human-readable form such as "5 minutes ago" or "in 2 hours". Use it for values like "last updated", "started" or "next run". The timestamp can be in the past or in the future, and the display refreshes every second in the browser without further updates from the device. ![Time difference card](https://docs.softt.io/img/v5/cards/light/time-difference.png) ## Constructor ```cpp template dash::TimeDifferenceCard(ESPDash& dashboard, const char* name); ``` ### Template parameters ### Parameters ```cpp // Unix timestamp in seconds dash::TimeDifferenceCard lastUpdate(dashboard, "Last Update"); // Date string dash::TimeDifferenceCard startedAt(dashboard, "Started"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Methods ### `setValue()` ```cpp bool setValue(T value); ``` Sets the reference timestamp. Returns `true` if the value changed. - **Integral `T`**: a Unix timestamp (UTC). Values below `1e12` are interpreted as seconds, larger values as milliseconds. - **String `T`**: a date string that the browser's `Date` parser accepts, preferably ISO 8601 in UTC, for example `"2025-08-29T18:21:14Z"`. ```cpp lastUpdate.setValue(static_cast(time(nullptr))); startedAt.setValue("2025-08-29T18:21:14Z"); ``` ### `value()` ```cpp const T& value() const; ``` Returns the current reference timestamp. ### `setFormat()` ```cpp bool setFormat(dash::string format); ``` Sets the display format. Returns `true` if the format changed. ```cpp lastUpdate.setFormat("full"); ``` ### `format()` ```cpp const dash::string& format() const; ``` Returns the current format (`"auto"` or `"full"`). ## Example ```cpp title="main.cpp" #include #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::TimeSyncCard timeSync(dashboard, "Time Sync"); dash::TimeDifferenceCard lastSync(dashboard, "Last Time Sync"); void setup() { Serial.begin(115200); // ... connect to WiFi // Show when the clock was last synchronized from the browser timeSync.onSync([](const timeval& tv) { lastSync.setValue(static_cast(tv.tv_sec)); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` ## Notes - The difference is computed against the browser's clock. The device clock must be set correctly (for example with NTP or the [Time Sync Card](/docs/espdash/v5/cards/time-sync)) for timestamps derived from `time()` to be meaningful. - A 32-bit `int` can hold Unix timestamps in seconds but not in milliseconds. Prefer timestamps in seconds. # Time Sync Card (/docs/espdash/v5/cards/time-sync) > Set the device clock from the browser's current time with one click, optionally applying a timezone. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The time sync card sets the device's system clock from the browser's current time when the user presses its button. Use it on devices that have no internet access for NTP, or to correct the clock without additional hardware. ![Time sync card](https://docs.softt.io/img/v5/cards/light/time-sync.png) ## Constructor ```cpp dash::TimeSyncCard(ESPDash& dashboard, const char* name, const char* tzSpec = ""); ``` ```cpp dash::TimeSyncCard timeSync(dashboard, "Time Sync"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## How synchronization works When the user presses the button, the browser sends its current time as a Unix timestamp in milliseconds. The card then: 1. Sets the system clock with `settimeofday()`. 2. If a timezone specification is set, sets the `TZ` environment variable and calls `tzset()`, so that `localtime()` and related functions return local time. 3. Calls the registered callback, if any. The system clock is set even if no callback is registered. ## Callback ```cpp void onSync(std::function callback); ``` The callback fires after the system clock and timezone have been set. `tv` contains the time received from the browser; `tv.tv_sec` holds the seconds since the Unix epoch (UTC). Use it to log the sync, update an RTC or refresh cards that depend on the time. No dashboard update is required for the time sync card itself. ```cpp timeSync.onSync([](const timeval& tv) { Serial.printf("Time synced: %lld\n", (long long)tv.tv_sec); }); ``` ## Methods ### `setTimezoneSpec()` ```cpp void setTimezoneSpec(dash::string tzSpec); ``` Sets the POSIX timezone string applied after each sync. The default is an empty string, which leaves the timezone unchanged (UTC unless set elsewhere). For timezone strings, see the [tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) and [micro-timezonedb](https://github.com/rzeldent/micro-timezonedb). ```cpp timeSync.setTimezoneSpec("CET-1CEST,M3.5.0,M10.5.0/3"); // Europe/Paris ``` ### `timezoneSpec()` ```cpp const dash::string& timezoneSpec() const; ``` Returns the current timezone specification. ## Example ```cpp title="main.cpp" #include #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::TimeSyncCard timeSync(dashboard, "Time Sync", "CET-1CEST,M3.5.0,M10.5.0/3"); void setup() { Serial.begin(115200); // ... connect to WiFi timeSync.onSync([](const timeval& tv) { struct tm local; localtime_r(&tv.tv_sec, &local); Serial.printf("Local time: %02d:%02d:%02d\n", local.tm_hour, local.tm_min, local.tm_sec); }); server.begin(); } void loop() { // ... } ``` # Toggle Button Card (/docs/espdash/v5/cards/toggle-button) > Display and control an on/off state from the dashboard using dash::ToggleButtonCard. Library: ESP-DASH v5.x.x The toggle button card displays a boolean state and lets the user switch it from the dashboard. Use it to control relays, LEDs or any feature that is either on or off. ![Toggle button card in the off state](https://docs.softt.io/img/v5/cards/light/toggle-button-false.png) ![Toggle button card in the on state](https://docs.softt.io/img/v5/cards/light/toggle-button-true.png) ## Constructor ```cpp dash::ToggleButtonCard card(dashboard, name); ``` `dash::ToggleButtonCard` is not a template; its value type is `bool`. An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Callback ```cpp void onChange(std::function callback); ``` The callback fires when the user clicks the button on the dashboard. `state` is the state requested by the user. The card does not update its own value when the callback fires. Call `setValue(state)` and then `dashboard.sendUpdates()` inside the callback to confirm the new state on the dashboard. ```cpp dash::ToggleButtonCard light(dashboard, "Light"); // in setup() light.onChange([](bool state) { digitalWrite(LED_BUILTIN, state ? HIGH : LOW); light.setValue(state); dashboard.sendUpdates(); }); ``` ## Methods ### `setValue()` ```cpp bool setValue(const bool& value); ``` Sets the state: `true` for on, `false` for off. Returns `true` if the state changed. ### `value()` ```cpp const bool& value() const; ``` Returns the current state. Call it only when `hasValue()` returns `true`. ### `hasValue()` ```cpp bool hasValue() const; ``` Returns `true` if a state has been set. ### `toggle()` ```cpp bool toggle(); ``` Inverts the current state. If no state has been set, the card switches to on. Returns `true` if the state changed. ### `on()` ```cpp bool on(); ``` Sets the state to on. Returns `true` if the state changed. ### `off()` ```cpp bool off(); ``` Sets the state to off. Returns `true` if the state changed. ## Example ```cpp title="main.cpp" #include #include // ESP32; use the networking headers for your board #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::ToggleButtonCard light(dashboard, "Light"); void setup() { Serial.begin(115200); pinMode(LED_BUILTIN, OUTPUT); // ... connect to Wi-Fi light.onChange([](bool state) { Serial.printf("Light: %s\n", state ? "on" : "off"); digitalWrite(LED_BUILTIN, state ? HIGH : LOW); light.setValue(state); dashboard.sendUpdates(); }); light.off(); server.begin(); } void loop() { // ... } ``` # Week Selector Card (/docs/espdash/v5/cards/week-selector) > Let users select one or more days of the week and receive the selection as a comma-separated string. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The week selector card shows a button for each day of the week and lets the user select any combination of days. The selection is exchanged as a comma-separated string of day identifiers. Use it for schedules, timers and recurring tasks. ![Week selector card](https://docs.softt.io/img/v5/cards/light/week-selector.png) ## Constructor ```cpp template dash::WeekCard(ESPDash& dashboard, const char* name, bool extended = false); ``` ### Template parameters ### Parameters ```cpp dash::WeekCard<> week(dashboard, "Select Days"); ``` An overload without the `dashboard` argument creates the card without attaching it to a dashboard; attach it later with `dashboard.add(card)`. ## Value format The value is a comma-separated list of the selected days, in the order they were selected, using these identifiers: `sun`, `mon`, `tue`, `wed`, `thu`, `fri`, `sat`. An empty string means no day is selected. ```text mon,tue,wed ``` ## Callback ```cpp void onChange(std::function callback); ``` The callback fires each time the user selects or deselects a day. `value` is the complete selection in the format described above. Call `setValue()` and `dashboard.sendUpdates()` inside the callback to store the selection and keep the dashboard in sync. ```cpp week.onChange([](const dash::string& days) { Serial.printf("Selected days: %s\n", days.c_str()); week.setValue(days); dashboard.sendUpdates(); }); ``` ## Methods ### `setValue()` ```cpp bool setValue(T value); ``` Sets the selected days. Returns `true` if the value changed. ```cpp week.setValue("mon,tue,wed"); ``` ### `value()` ```cpp const T& value() const; ``` Returns the current selection. ### `setExtended()` ```cpp bool setExtended(bool extended); ``` Enables or disables extended mode. In extended mode the day buttons show abbreviated names ("Sun", "Mon", "Tue", ...) instead of single letters ("S", "M", "T", ...). Returns `true` if the mode changed. ```cpp week.setExtended(true); ``` ### `isExtended()` ```cpp bool isExtended() const; ``` Returns `true` if extended mode is enabled. ## Example ```cpp title="main.cpp" #include #include #include #include #include AsyncWebServer server(80); ESPDash dashboard(server); dash::WeekCard<> week(dashboard, "Watering Days"); void setup() { Serial.begin(115200); // ... connect to WiFi week.setValue("mon,wed,fri"); week.setExtended(true); week.onChange([](const dash::string& days) { Serial.printf("Selected days: %s\n", days.c_str()); week.setValue(days); dashboard.sendUpdates(); }); server.begin(); } void loop() { // ... } ``` # Area Chart (/docs/espdash/v5/charts/area) > Create an area chart with dash::AreaChart, set its X-axis and Y-axis data and push updates to the dashboard in ESP-DASH Pro v5. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only An area chart is a line chart with the region below the line filled, which emphasizes magnitude and cumulative totals over time or across categories. In ESP-DASH v5, area charts are created with the `dash::AreaChart` class template, where `X` and `Y` define the data types of the two axes. ![Area chart](https://docs.softt.io/img/v5/charts/light/area.png) All v5 chart types share the same constructor signatures and methods. This page documents them for `dash::AreaChart`; see [Bar Chart](/docs/espdash/v5/charts/bar) for the common reference. ## Constructor ```cpp dash::AreaChart chart(ESPDash& dashboard, const char* name); dash::AreaChart chart(const char* name); ``` The first form creates the chart and adds it to `dashboard`. The second form creates the chart without adding it to a dashboard; add it later with `dashboard.add(chart)`. ### Template parameters `dash::string` is an alias for Arduino `String`, or for `std::string` when `DASH_USE_STL_STRING` is defined. Any other type for `X` or `Y` fails to compile. ### Constructor parameters ```cpp // Category labels on the X-axis, integer values on the Y-axis dash::AreaChart rainfall(dashboard, "Cumulative Rainfall"); // Numeric X-axis, floating-point Y-axis dash::AreaChart growth(dashboard, "Growth"); ``` ## Methods ### setX ```cpp bool setX(X arr_x[], size_t x_size); ``` Sets the X-axis data. The element type of `arr_x` must match the template parameter `X`. The chart stores a pointer to the array and marks the X-axis as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp const char* months[] = {"Jan", "Feb", "Mar", "Apr"}; rainfall.setX(months, 4); ``` ### setY ```cpp bool setY(Y arr_y[], size_t y_size); ``` Sets the Y-axis data. The element type of `arr_y` must match the template parameter `Y`. The chart stores a pointer to the array and marks the Y-axis as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp int totals[] = {10, 25, 40, 60}; rainfall.setY(totals, 4); ``` Modifying the array in place does not mark the chart as changed. After you update the values, call `setY()` (or `setX()`) again, then `dashboard.sendUpdates()`. Otherwise the change reaches clients only on the next full layout refresh, such as a page reload. ### x and y ```cpp const X* x() const; const Y* y() const; ``` Return a pointer to the current X-axis or Y-axis array, or `nullptr` if it has not been set. ```cpp const int* current = rainfall.y(); ``` ### xSize and ySize ```cpp size_t xSize() const; size_t ySize() const; ``` Return the number of elements passed to the last `setX()` or `setY()` call, or `0` if it has not been set. ### setName ```cpp bool setName(const char* name); ``` Changes the chart title. Returns `false` if the new title is identical to the current one; otherwise marks the title as changed and returns `true`. As with the constructor, the pointer is stored and must remain valid. ## Example ```cpp title="main.cpp" #include #include #include #include #include const char* ssid = ""; const char* password = ""; AsyncWebServer server(80); ESPDash dashboard(server); // Chart data must outlive the chart, so it is declared at global scope const char* XAxis[] = {"Jan", "Feb", "Mar", "Apr"}; int YAxis[] = {10, 25, 40, 60}; dash::AreaChart rainfall(dashboard, "Cumulative Rainfall"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.println(WiFi.localIP()); rainfall.setX(XAxis, 4); rainfall.setY(YAxis, 4); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate >= 5000) { lastUpdate = millis(); // Add new rainfall to the latest month YAxis[3] += (int)random(0, 5); // Mark the Y-axis as changed, then push the update to all clients rainfall.setY(YAxis, 4); dashboard.sendUpdates(); } } ``` ## Notes - **Data lifetime.** Charts do not copy axis data. `setX()` and `setY()` store a pointer to your array, and the array is read each time the chart is serialized for a client. Declare the arrays at global scope or as `static`, never as local variables of the function that calls `setX()` or `setY()`. - **Array constness.** `setX()` and `setY()` take a non-const pointer, so the array elements themselves cannot be `const` (for example, `const int values[]` does not compile). An array of `const char*` labels is valid because the elements are pointers. - **Matching sizes.** Pass the same number of elements to `setX()` and `setY()` so that each value has a corresponding label. - **Layout options.** Charts also support [`setSize()`](/docs/espdash/v5/features/widget-size), [`setIndex()`](/docs/espdash/v5/features/widget-index), [`setDisplay()`](/docs/espdash/v5/features/toggle-display) and assignment to [tabs](/docs/espdash/v5/tabs). To disable chart animations globally, see [Toggle Chart Animations](/docs/espdash/v5/features/toggle-chart-animations). # Bar Chart (/docs/espdash/v5/charts/bar) > Create a bar chart with dash::BarChart, set its X-axis and Y-axis data and push updates to the dashboard in ESP-DASH v5. Library: ESP-DASH v5.x.x A bar chart compares quantities across categories or discrete intervals, such as daily energy consumption or event counts per hour. In ESP-DASH v5, bar charts are created with the `dash::BarChart` class template, where `X` and `Y` define the data types of the two axes. ![Bar chart](https://docs.softt.io/img/v5/charts/light/bar.png) ## Constructor ```cpp dash::BarChart chart(ESPDash& dashboard, const char* name); dash::BarChart chart(const char* name); ``` The first form creates the chart and adds it to `dashboard`. The second form creates the chart without adding it to a dashboard; add it later with `dashboard.add(chart)`. ### Template parameters `dash::string` is an alias for Arduino `String`, or for `std::string` when `DASH_USE_STL_STRING` is defined. Any other type for `X` or `Y` fails to compile. ### Constructor parameters ```cpp // Category labels on the X-axis, integer values on the Y-axis dash::BarChart sales(dashboard, "Sales by Day"); // Numeric X-axis, floating-point Y-axis dash::BarChart production(dashboard, "Production"); ``` ## Methods ### setX ```cpp bool setX(X arr_x[], size_t x_size); ``` Sets the X-axis data. The element type of `arr_x` must match the template parameter `X`. The chart stores a pointer to the array and marks the X-axis as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp const char* days[] = {"Mon", "Tue", "Wed", "Thu", "Fri"}; sales.setX(days, 5); ``` ### setY ```cpp bool setY(Y arr_y[], size_t y_size); ``` Sets the Y-axis data. The element type of `arr_y` must match the template parameter `Y`. The chart stores a pointer to the array and marks the Y-axis as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp int totals[] = {10, 20, 15, 30, 25}; sales.setY(totals, 5); ``` Modifying the array in place does not mark the chart as changed. After you update the values, call `setY()` (or `setX()`) again, then `dashboard.sendUpdates()`. Otherwise the change reaches clients only on the next full layout refresh, such as a page reload. ### x and y ```cpp const X* x() const; const Y* y() const; ``` Return a pointer to the current X-axis or Y-axis array, or `nullptr` if the axis has not been set. ```cpp const int* current = sales.y(); ``` ### xSize and ySize ```cpp size_t xSize() const; size_t ySize() const; ``` Return the number of elements passed to the last `setX()` or `setY()` call, or `0` if the axis has not been set. ### setName ```cpp bool setName(const char* name); ``` Changes the chart title. Returns `false` if the new title is identical to the current one; otherwise marks the title as changed and returns `true`. As with the constructor, the pointer is stored and must remain valid. ## Example ```cpp title="main.cpp" #include #include #include #include #include const char* ssid = ""; const char* password = ""; AsyncWebServer server(80); ESPDash dashboard(server); // Chart data must outlive the chart, so it is declared at global scope const char* XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; dash::BarChart power(dashboard, "Power Usage (kWh)"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.println(WiFi.localIP()); // The X-axis does not change in this example, so it is set once power.setX(XAxis, 7); power.setY(YAxis, 7); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate >= 3000) { lastUpdate = millis(); for (int i = 0; i < 7; i++) { YAxis[i] = (int)random(0, 200); } // Mark the Y-axis as changed, then push the update to all clients power.setY(YAxis, 7); dashboard.sendUpdates(); } } ``` ## Notes - **Data lifetime.** Charts do not copy axis data. `setX()` and `setY()` store a pointer to your array, and the array is read each time the chart is serialized for a client. Declare the arrays at global scope or as `static`, never as local variables of the function that calls `setX()` or `setY()`. - **Array constness.** `setX()` and `setY()` take a non-const pointer, so the array elements themselves cannot be `const` (for example, `const int values[]` does not compile as a Y-axis). An array of `const char*` labels is valid because the elements are pointers. - **Matching sizes.** Pass the same number of elements to `setX()` and `setY()` so that each value has a corresponding label. - **Shared API.** All v5 chart types ([Line](/docs/espdash/v5/charts/line), [Area](/docs/espdash/v5/charts/area), [Pie](/docs/espdash/v5/charts/pie) and [Doughnut](/docs/espdash/v5/charts/doughnut)) derive from the same base class and expose the same constructor signatures and methods as `dash::BarChart`. - **Layout options.** In ESP-DASH Pro, charts also support [`setSize()`](/docs/espdash/v5/features/widget-size), [`setIndex()`](/docs/espdash/v5/features/widget-index), [`setDisplay()`](/docs/espdash/v5/features/toggle-display) and assignment to [tabs](/docs/espdash/v5/tabs). # Doughnut Chart (/docs/espdash/v5/charts/doughnut) > Create a doughnut chart with dash::DoughnutChart, set its labels and values and push updates to the dashboard in ESP-DASH Pro v5. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only A doughnut chart shows how parts contribute to a whole, like a pie chart, but with a hollow center that makes it visually distinct. In ESP-DASH v5, doughnut charts are created with the `dash::DoughnutChart` class template, where `X` is the label type and `Y` is the value type. ![Doughnut chart](https://docs.softt.io/img/v5/charts/light/doughnut.png) All v5 chart types share the same constructor signatures and methods. This page documents them for `dash::DoughnutChart`; see [Bar Chart](/docs/espdash/v5/charts/bar) for the common reference. In a doughnut chart, the X-axis holds the segment labels and the Y-axis holds the segment values. Labels are set with `setX()` and values with `setY()`. ## Constructor ```cpp dash::DoughnutChart chart(ESPDash& dashboard, const char* name); dash::DoughnutChart chart(const char* name); ``` The first form creates the chart and adds it to `dashboard`. The second form creates the chart without adding it to a dashboard; add it later with `dashboard.add(chart)`. ### Template parameters `dash::string` is an alias for Arduino `String`, or for `std::string` when `DASH_USE_STL_STRING` is defined. Any other type for `X` or `Y` fails to compile. ### Constructor parameters ```cpp // Text labels, integer values dash::DoughnutChart share(dashboard, "Market Share"); // Numeric labels, floating-point values dash::DoughnutChart distribution(dashboard, "Distribution"); ``` ## Methods ### setX ```cpp bool setX(X arr_x[], size_t x_size); ``` Sets the segment labels. The element type of `arr_x` must match the template parameter `X`. The chart stores a pointer to the array and marks the labels as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp const char* products[] = {"A", "B", "C"}; share.setX(products, 3); ``` ### setY ```cpp bool setY(Y arr_y[], size_t y_size); ``` Sets the segment values. Each value is drawn in proportion to the sum of all values. The element type of `arr_y` must match the template parameter `Y`. The chart stores a pointer to the array and marks the values as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp int shares[] = {40, 30, 30}; share.setY(shares, 3); ``` Modifying the array in place does not mark the chart as changed. After you update the values, call `setY()` (or `setX()`) again, then `dashboard.sendUpdates()`. Otherwise the change reaches clients only on the next full layout refresh, such as a page reload. ### x and y ```cpp const X* x() const; const Y* y() const; ``` Return a pointer to the current labels or values array, or `nullptr` if it has not been set. ```cpp const char* const* labels = share.x(); const int* values = share.y(); ``` ### xSize and ySize ```cpp size_t xSize() const; size_t ySize() const; ``` Return the number of elements passed to the last `setX()` or `setY()` call, or `0` if it has not been set. ### setName ```cpp bool setName(const char* name); ``` Changes the chart title. Returns `false` if the new title is identical to the current one; otherwise marks the title as changed and returns `true`. As with the constructor, the pointer is stored and must remain valid. ## Example ```cpp title="main.cpp" #include #include #include #include #include const char* ssid = ""; const char* password = ""; AsyncWebServer server(80); ESPDash dashboard(server); // Chart data must outlive the chart, so it is declared at global scope const char* Labels[] = {"A", "B", "C"}; int Values[] = {40, 30, 30}; dash::DoughnutChart share(dashboard, "Market Share"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.println(WiFi.localIP()); share.setX(Labels, 3); share.setY(Values, 3); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate >= 5000) { lastUpdate = millis(); for (int i = 0; i < 3; i++) { Values[i] = (int)random(10, 50); } // Mark the values as changed, then push the update to all clients share.setY(Values, 3); dashboard.sendUpdates(); } } ``` ## Notes - **Data lifetime.** Charts do not copy labels or values. `setX()` and `setY()` store a pointer to your array, and the array is read each time the chart is serialized for a client. Declare the arrays at global scope or as `static`, never as local variables of the function that calls `setX()` or `setY()`. - **Array constness.** `setX()` and `setY()` take a non-const pointer, so the array elements themselves cannot be `const` (for example, `const int values[]` does not compile). An array of `const char*` labels is valid because the elements are pointers. - **Matching sizes.** Pass the same number of elements to `setX()` and `setY()` so that each value has a corresponding label. - **Layout options.** Charts also support [`setSize()`](/docs/espdash/v5/features/widget-size), [`setIndex()`](/docs/espdash/v5/features/widget-index), [`setDisplay()`](/docs/espdash/v5/features/toggle-display) and assignment to [tabs](/docs/espdash/v5/tabs). To disable chart animations globally, see [Toggle Chart Animations](/docs/espdash/v5/features/toggle-chart-animations). # Line Chart (/docs/espdash/v5/charts/line) > Create a line chart with dash::LineChart, set its X-axis and Y-axis data and push updates to the dashboard in ESP-DASH Pro v5. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only A line chart shows how a value changes over time or across an ordered sequence, such as sensor readings, water usage or voltage over time. In ESP-DASH v5, line charts are created with the `dash::LineChart` class template, where `X` and `Y` define the data types of the two axes. ![Line chart](https://docs.softt.io/img/v5/charts/light/line.png) All v5 chart types share the same constructor signatures and methods. This page documents them for `dash::LineChart`; see [Bar Chart](/docs/espdash/v5/charts/bar) for the common reference. ## Constructor ```cpp dash::LineChart chart(ESPDash& dashboard, const char* name); dash::LineChart chart(const char* name); ``` The first form creates the chart and adds it to `dashboard`. The second form creates the chart without adding it to a dashboard; add it later with `dashboard.add(chart)`. ### Template parameters `dash::string` is an alias for Arduino `String`, or for `std::string` when `DASH_USE_STL_STRING` is defined. Any other type for `X` or `Y` fails to compile. ### Constructor parameters ```cpp // Category labels on the X-axis, integer values on the Y-axis dash::LineChart usage(dashboard, "Water Usage (L)"); // Numeric X-axis, floating-point Y-axis dash::LineChart temperature(dashboard, "Temperature"); ``` ## Methods ### setX ```cpp bool setX(X arr_x[], size_t x_size); ``` Sets the X-axis data. The element type of `arr_x` must match the template parameter `X`. The chart stores a pointer to the array and marks the X-axis as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp const char* days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; usage.setX(days, 7); ``` ### setY ```cpp bool setY(Y arr_y[], size_t y_size); ``` Sets the Y-axis data. The element type of `arr_y` must match the template parameter `Y`. The chart stores a pointer to the array and marks the Y-axis as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp int liters[] = {120, 95, 110, 130, 105, 90, 140}; usage.setY(liters, 7); ``` Modifying the array in place does not mark the chart as changed. After you update the values, call `setY()` (or `setX()`) again, then `dashboard.sendUpdates()`. Otherwise the change reaches clients only on the next full layout refresh, such as a page reload. ### x and y ```cpp const X* x() const; const Y* y() const; ``` Return a pointer to the current X-axis or Y-axis array, or `nullptr` if it has not been set. ```cpp const int* current = usage.y(); ``` ### xSize and ySize ```cpp size_t xSize() const; size_t ySize() const; ``` Return the number of elements passed to the last `setX()` or `setY()` call, or `0` if it has not been set. ### setName ```cpp bool setName(const char* name); ``` Changes the chart title. Returns `false` if the new title is identical to the current one; otherwise marks the title as changed and returns `true`. As with the constructor, the pointer is stored and must remain valid. ## Example ```cpp title="main.cpp" #include #include #include #include #include const char* ssid = ""; const char* password = ""; AsyncWebServer server(80); ESPDash dashboard(server); // Chart data must outlive the chart, so it is declared at global scope const char* XAxis[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; int YAxis[] = {0, 0, 0, 0, 0, 0, 0}; dash::LineChart usage(dashboard, "Water Usage (L)"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.println(WiFi.localIP()); usage.setX(XAxis, 7); usage.setY(YAxis, 7); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate >= 5000) { lastUpdate = millis(); for (int i = 0; i < 7; i++) { YAxis[i] = (int)random(0, 100); } // Mark the Y-axis as changed, then push the update to all clients usage.setY(YAxis, 7); dashboard.sendUpdates(); } } ``` ## Notes - **Data lifetime.** Charts do not copy axis data. `setX()` and `setY()` store a pointer to your array, and the array is read each time the chart is serialized for a client. Declare the arrays at global scope or as `static`, never as local variables of the function that calls `setX()` or `setY()`. - **Array constness.** `setX()` and `setY()` take a non-const pointer, so the array elements themselves cannot be `const` (for example, `const int values[]` does not compile). An array of `const char*` labels is valid because the elements are pointers. - **Matching sizes.** Pass the same number of elements to `setX()` and `setY()` so that each value has a corresponding label. - **Layout options.** Charts also support [`setSize()`](/docs/espdash/v5/features/widget-size), [`setIndex()`](/docs/espdash/v5/features/widget-index), [`setDisplay()`](/docs/espdash/v5/features/toggle-display) and assignment to [tabs](/docs/espdash/v5/tabs). To disable chart animations globally, see [Toggle Chart Animations](/docs/espdash/v5/features/toggle-chart-animations). # Pie Chart (/docs/espdash/v5/charts/pie) > Create a pie chart with dash::PieChart, set its labels and values and push updates to the dashboard in ESP-DASH Pro v5. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only A pie chart shows how parts contribute to a whole, such as market share or the distribution of storage usage. In ESP-DASH v5, pie charts are created with the `dash::PieChart` class template, where `X` is the label type and `Y` is the value type. ![Pie chart](https://docs.softt.io/img/v5/charts/light/pie.png) All v5 chart types share the same constructor signatures and methods. This page documents them for `dash::PieChart`; see [Bar Chart](/docs/espdash/v5/charts/bar) for the common reference. In a pie chart, the X-axis holds the segment labels and the Y-axis holds the segment values. Labels are set with `setX()` and values with `setY()`. ## Constructor ```cpp dash::PieChart chart(ESPDash& dashboard, const char* name); dash::PieChart chart(const char* name); ``` The first form creates the chart and adds it to `dashboard`. The second form creates the chart without adding it to a dashboard; add it later with `dashboard.add(chart)`. ### Template parameters `dash::string` is an alias for Arduino `String`, or for `std::string` when `DASH_USE_STL_STRING` is defined. Any other type for `X` or `Y` fails to compile. ### Constructor parameters ```cpp // Text labels, integer values dash::PieChart share(dashboard, "Market Share"); // Numeric labels, floating-point values dash::PieChart distribution(dashboard, "Distribution"); ``` ## Methods ### setX ```cpp bool setX(X arr_x[], size_t x_size); ``` Sets the segment labels. The element type of `arr_x` must match the template parameter `X`. The chart stores a pointer to the array and marks the labels as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp const char* products[] = {"A", "B", "C"}; share.setX(products, 3); ``` ### setY ```cpp bool setY(Y arr_y[], size_t y_size); ``` Sets the segment values. Each value is drawn in proportion to the sum of all values. The element type of `arr_y` must match the template parameter `Y`. The chart stores a pointer to the array and marks the values as changed; the data is sent on the next call to `dashboard.sendUpdates()`. Always returns `true`. ```cpp int shares[] = {40, 30, 30}; share.setY(shares, 3); ``` Modifying the array in place does not mark the chart as changed. After you update the values, call `setY()` (or `setX()`) again, then `dashboard.sendUpdates()`. Otherwise the change reaches clients only on the next full layout refresh, such as a page reload. ### x and y ```cpp const X* x() const; const Y* y() const; ``` Return a pointer to the current labels or values array, or `nullptr` if it has not been set. ```cpp const char* const* labels = share.x(); const int* values = share.y(); ``` ### xSize and ySize ```cpp size_t xSize() const; size_t ySize() const; ``` Return the number of elements passed to the last `setX()` or `setY()` call, or `0` if it has not been set. ### setName ```cpp bool setName(const char* name); ``` Changes the chart title. Returns `false` if the new title is identical to the current one; otherwise marks the title as changed and returns `true`. As with the constructor, the pointer is stored and must remain valid. ## Example ```cpp title="main.cpp" #include #include #include #include #include const char* ssid = ""; const char* password = ""; AsyncWebServer server(80); ESPDash dashboard(server); // Chart data must outlive the chart, so it is declared at global scope const char* Labels[] = {"A", "B", "C"}; int Values[] = {40, 30, 30}; dash::PieChart share(dashboard, "Market Share"); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi connection failed"); return; } Serial.println(WiFi.localIP()); share.setX(Labels, 3); share.setY(Values, 3); server.begin(); } void loop() { static unsigned long lastUpdate = 0; if (millis() - lastUpdate >= 5000) { lastUpdate = millis(); for (int i = 0; i < 3; i++) { Values[i] = (int)random(10, 50); } // Mark the values as changed, then push the update to all clients share.setY(Values, 3); dashboard.sendUpdates(); } } ``` ## Notes - **Data lifetime.** Charts do not copy labels or values. `setX()` and `setY()` store a pointer to your array, and the array is read each time the chart is serialized for a client. Declare the arrays at global scope or as `static`, never as local variables of the function that calls `setX()` or `setY()`. - **Array constness.** `setX()` and `setY()` take a non-const pointer, so the array elements themselves cannot be `const` (for example, `const int values[]` does not compile). An array of `const char*` labels is valid because the elements are pointers. - **Matching sizes.** Pass the same number of elements to `setX()` and `setY()` so that each value has a corresponding label. - **Layout options.** Charts also support [`setSize()`](/docs/espdash/v5/features/widget-size), [`setIndex()`](/docs/espdash/v5/features/widget-index), [`setDisplay()`](/docs/espdash/v5/features/toggle-display) and assignment to [tabs](/docs/espdash/v5/tabs). To disable chart animations globally, see [Toggle Chart Animations](/docs/espdash/v5/features/toggle-chart-animations). # Commercial License (/docs/espdash/v5/commercial-license) > Compare the licensing, support and features of ESP-DASH Lite (GPL-3.0) and ESP-DASH Pro (commercial license). Library: ESP-DASH v5.x.x ESP-DASH is available in two editions of the same real-time dashboard library: ESP-DASH Lite, which is open source and licensed under GPL-3.0, and ESP-DASH Pro, which is distributed under a commercial license. The editions differ in features, support and licensing terms. ## Comparison | | ESP-DASH Lite | ESP-DASH Pro | | --- | --- | --- | | License | Open source, GPL-3.0 | Commercial license | | Source-disclosure obligations | Free to use, modify and distribute. If you use ESP-DASH Lite in a commercial product, GPL-3.0 requires you to make the source code of your product, including any modifications to ESP-DASH Lite, publicly available. | You can use ESP-DASH Pro in a commercial product without open-sourcing your proprietary code. | | Support | Community-driven, through online forums, documentation and user-contributed resources. Professional support may be limited. | Dedicated support and assistance. | | Features | Core dashboard functionality. | Extended functionality. Features available only in ESP-DASH Pro are marked in this documentation. | | Cost model | Free | One-time license fee | ## Choosing an edition - **ESP-DASH Lite** fits open-source projects, and projects in which the GPL-3.0 requirements are acceptable and core dashboard functionality is sufficient. - **ESP-DASH Pro** fits commercial products. It provides a more comprehensive, professionally supported solution and lets you keep your code proprietary, in exchange for a one-time commercial license fee. ## Get ESP-DASH Pro ESP-DASH Pro is available on the official product page at [store.softt.io](https://store.softt.io/products/esp-dash-pro). # Brotli Compression (/docs/espdash/v5/features/brotli-compression) > Serve the ESP-DASH Pro dashboard with Brotli instead of gzip compression to reduce flash usage, including browser and HTTPS requirements. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only ESP-DASH Pro stores the dashboard webpage in flash in compressed form. By default it uses gzip. Enabling Brotli compression reduces the size of the stored webpage and frees flash space for your application. ![Brotli logo](https://docs.softt.io/img/v4/brotli-compression.png) In the current release, the embedded webpage is 178,415 bytes with gzip and 151,846 bytes with Brotli, a reduction of about 15%. The exact figures vary between releases. Brotli compression is supported only by Safari over unencrypted HTTP. Other browsers, such as Chrome, Edge and Firefox, accept Brotli-encoded responses only over HTTPS. The dashboard is always sent with `Content-Encoding: br` when Brotli is enabled, so a browser that does not accept Brotli cannot load it. Brotli compression is disabled by default. Enable it only if every browser that will access your dashboard meets the requirements above. ## Enable Brotli compression Brotli compression is controlled by the `DASH_USE_BROTLI_COMPRESSION` macro. Set it to `1` to enable Brotli or `0` to use gzip. ### Open edp.h [#open-edph] Open `src/edp.h` in the ESP-DASH Pro library folder. ### Change the macro [#change-the-macro] Locate `DASH_USE_BROTLI_COMPRESSION` and change its value from `0` to `1`: ```cpp title="edp.h" #ifndef DASH_USE_BROTLI_COMPRESSION #define DASH_USE_BROTLI_COMPRESSION 1 #endif ``` ### Rebuild [#rebuild] Save the file, then compile and upload your sketch again. Because the default in `edp.h` is guarded by `#ifndef`, you can set the macro as a build flag instead of editing the library: ```ini title="platformio.ini" build_flags = -D DASH_USE_BROTLI_COMPRESSION=1 ``` Then rebuild and upload your project. Changes to `edp.h` are overwritten when you update the library. After an update, apply the change again or use a build flag. # Custom Title (/docs/espdash/v5/features/custom-title) > Replace the default dashboard title shown in the browser tab with setTitle() in ESP-DASH Pro. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only `setTitle()` replaces the default dashboard title, which the browser displays in the tab and window title bar. ![Custom dashboard title in a browser tab](https://docs.softt.io/img/v5/features/custom-title-light.png) ## Method ```cpp void setTitle(const char* title); ``` If `setTitle()` is not called, the dashboard title is `DASH Pro`. ## Usage Call `setTitle()` on the `ESPDash` instance, typically in `setup()`: ```cpp dashboard.setTitle("Greenhouse Controller"); ``` The title is sent to the browser together with the full dashboard layout. If you change it after clients have connected, it takes effect when the page is reloaded or when you call `dashboard.refreshLayout()`. # Custom URI (/docs/espdash/v5/features/custom-uri) > Serve the ESP-DASH dashboard at a custom path instead of the web server root. Library: ESP-DASH v5.x.x By default, ESP-DASH serves the dashboard at the root path of the web server (`http:///`). To serve it at a different path, pass the URI to the `ESPDash` constructor. ## Constructor ```cpp ESPDash(AsyncWebServer& server, const char* uri, bool enable_default_stats); ``` The defaults shown apply to the shorter overloads `ESPDash(AsyncWebServer& server)` and `ESPDash(AsyncWebServer& server, bool enable_default_stats)`. There is no overload that takes only a server and a URI, so `enable_default_stats` must be passed explicitly when you set a custom URI. ## Usage ```cpp AsyncWebServer server(80); ESPDash dashboard(server, "/dashboard", true); ``` The dashboard is then available at `http:///dashboard`. ## Notes - The custom URI applies only to the dashboard page. The WebSocket endpoint that the page connects to is always `/dashws`, so do not register another handler at that path. - The dashboard registers its route on the server you pass in, so other routes you add to the same `AsyncWebServer` continue to work alongside it. - [Authentication](/docs/espdash/v5/security/authentication), if enabled, applies to the dashboard page at the custom URI. # Toggle Chart Animations (/docs/espdash/v5/features/toggle-chart-animations) > Enable or disable chart animations for the whole dashboard with setChartAnimations() in ESP-DASH Pro. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only Charts animate when their data changes. When charts are updated at a high rate, the animations can overlap and make the data harder to read. `setChartAnimations()` enables or disables animations for all charts on the dashboard. Chart animations are enabled by default. ## Method ```cpp void setChartAnimations(bool enable); ``` `setChartAnimations()` is a member of `ESPDash` and applies to every chart on the dashboard. The setting is sent to the browser together with the full dashboard layout, so call it in `setup()`, before clients connect. ## Usage ### Disable animations ```cpp dashboard.setChartAnimations(false); ``` ### Enable animations ```cpp dashboard.setChartAnimations(true); ``` The setting is not part of incremental updates sent by `dashboard.sendUpdates()`. If you change it after clients have connected, it takes effect when the page is reloaded or when you call `dashboard.refreshLayout()`. # Toggle Display (/docs/espdash/v5/features/toggle-display) > Show or hide cards, charts, statistics and tabs at runtime with setDisplay() in ESP-DASH Pro. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only `setDisplay()` shows or hides a widget on the dashboard without removing it. Use it to adapt the dashboard to the current state of your application, for example to reveal configuration cards only in a maintenance mode. All widgets are displayed by default. ## Methods `setDisplay()` and `displayed()` are defined on `dash::Component`, so they are available on every card, chart, statistic and tab. ### setDisplay ```cpp bool setDisplay(bool display); ``` Pass `true` to show the widget and `false` to hide it. Returns `false` if the widget is already in the requested state; otherwise marks the display state as changed and returns `true`. The change is sent to clients on the next call to `dashboard.sendUpdates()`. ### displayed ```cpp bool displayed() const; ``` Returns `true` if the widget is currently displayed. ## Usage ### Hide a widget ```cpp card.setDisplay(false); // Card chart.setDisplay(false); // Chart stat.setDisplay(false); // Statistic tab.setDisplay(false); // Tab ``` ### Show a widget ```cpp card.setDisplay(true); // Card chart.setDisplay(true); // Chart stat.setDisplay(true); // Statistic tab.setDisplay(true); // Tab ``` ## Example ```cpp title="main.cpp" // ... dash::ToggleButtonCard maintenance(dashboard, "Maintenance Mode"); dash::SliderCard threshold(dashboard, "Alarm Threshold", 0, 100); void setup() { // ... // Hide the threshold slider until maintenance mode is enabled threshold.setDisplay(false); maintenance.onChange([](bool state) { maintenance.setValue(state); threshold.setDisplay(state); dashboard.sendUpdates(); }); } ``` # Widget Index (/docs/espdash/v5/features/widget-index) > Control the position of cards and charts on the dashboard with setIndex() in ESP-DASH Pro. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only By default, widgets appear on the dashboard in the order in which they are created. With `setIndex()` you assign an explicit position to a card or chart, independent of declaration order. ![Widgets reordered by index](https://docs.softt.io/img/v5/features/widget-indexing-dark.png) ## Methods `setIndex()` and `index()` are available on all cards and charts (every class derived from `dash::Widget`). ### setIndex ```cpp bool setIndex(uint8_t index); ``` Sets the position of the widget. Returns `false` if the widget already has this index; otherwise marks the index as changed and returns `true`. The new position is sent to clients on the next call to `dashboard.sendUpdates()`. ### index ```cpp uint8_t index() const; ``` Returns the current index of the widget. ## Usage Widgets are arranged in ascending order of index: the widget with the lowest index is placed first and the widget with the highest index is placed last. ```cpp dash::TemperatureCard temperature(dashboard, "Temperature"); dash::HumidityCard humidity(dashboard, "Humidity"); dash::BarChart power(dashboard, "Power Usage (kWh)"); void setup() { // ... // Show the chart first, followed by humidity and temperature power.setIndex(0); humidity.setIndex(1); temperature.setIndex(2); } ``` When you reorder widgets with `setIndex()`, assign an index to every card and chart in the layout. This keeps the resulting order explicit rather than dependent on the default. The index is a `uint8_t`, so valid values range from `0` to `255`. # Widget Size (/docs/espdash/v5/features/widget-size) > Set the responsive width of cards and charts per screen-size breakpoint with setSize() in ESP-DASH Pro. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only The dashboard uses a responsive 12-column grid. With `setSize()` you set how many columns a card or chart occupies at each screen-size breakpoint, without modifying the dashboard webpage. ![Widgets of different sizes on the dashboard](https://docs.softt.io/img/v5/features/widget-size-light.png) ## Breakpoints Each breakpoint accepts a column count from `1` to `12`. A value of `12` makes the widget span the full width of the dashboard at that breakpoint; `6` makes it span half the width. | Breakpoint | Target screen | Default (cards) | Default (charts) | | --- | --- | --- | --- | | `xs` | Small smartphone | `12` | `12` | | `sm` | Smartphone | `12` | `12` | | `md` | Tablet | `6` | `12` | | `lg` | HD laptop | `4` | `6` | | `xl` | Full HD laptop | `3` | `6` | | `xxl` | 2K display and above | `2` | `4` | The defaults apply to every widget whose size has not been set with `setSize()`. ## Methods `setSize()` is available on all cards and charts (every class derived from `dash::Widget`). Statistics and tabs do not have a size. ### setSize with individual values ```cpp bool setSize(uint8_t xs, uint8_t sm, uint8_t md, uint8_t lg, uint8_t xl, uint8_t xxl); ``` ```cpp card.setSize(12, 12, 6, 4, 3, 2); ``` ### setSize with a Size structure ```cpp typedef struct { uint8_t xs; uint8_t sm; uint8_t md; uint8_t lg; uint8_t xl; uint8_t xxl; } dash::Widget::Size; bool setSize(const Size& size); ``` ```cpp chart.setSize({12, 12, 12, 6, 6, 4}); ``` Both overloads return `false` if the requested size equals the current size. Otherwise they mark the size as changed and return `true`; the new size is sent to clients on the next call to `dashboard.sendUpdates()`. ### Default and full-width sizes `dash::Card` and `dash::Chart` provide two static helpers that return a `Size`: - `defaultSize()` returns the default size for that widget family (see the table above). - `fullSize()` returns a size that uses the `xs` default at every breakpoint, which spans all 12 columns unless you override the defaults. ```cpp card.setSize(dash::Card::fullSize()); // Full width on every screen chart.setSize(chart.defaultSize()); // Restore the default chart size ``` ### Reading the size ```cpp const Size* size() const; ``` Returns the custom size of the widget, or `nullptr` if `setSize()` has not been called. ## Example ```cpp title="main.cpp" // ... dash::TemperatureCard temperature(dashboard, "Temperature"); dash::LineChart history(dashboard, "Temperature History"); void setup() { // ... // Half width on tablets, a quarter of the width on laptops and larger temperature.setSize(12, 12, 6, 3, 3, 3); // Full width at every breakpoint history.setSize(history.fullSize()); } ``` ## Change the default sizes The defaults are defined as preprocessor macros guarded by `#ifndef`, so you can override them at compile time. Define the macros for the whole build, not only in your sketch, because the library sources must see the same values. In PlatformIO, use `build_flags`: ```ini title="platformio.ini" build_flags = -D DASH_DEFAULT_CARD_SIZE_MD=4 -D DASH_DEFAULT_CHART_SIZE_LG=12 ``` The available macros are `DASH_DEFAULT_CARD_SIZE_` and `DASH_DEFAULT_CHART_SIZE_`, where `` is `XS`, `SM`, `MD`, `LG`, `XL` or `XXL`. # Examples (/docs/espdash/v5/getting-started/examples) > Example sketches in the ESP-DASH repository that demonstrate common dashboard implementations. Library: ESP-DASH v5.x.x The ESP-DASH repository includes documented example sketches that demonstrate common use cases. Each example builds on the setup described in the [Integration Guide](/docs/espdash/v5/getting-started/integration). ## Available examples | Example | Description | | --- | --- | | [AccessPoint](https://github.com/ayushsharma82/ESP-DASH/tree/master/examples/AccessPoint) | Starts the board in access point (SoftAP) mode and serves the dashboard at `192.168.4.1`. | | [Basic](https://github.com/ayushsharma82/ESP-DASH/tree/master/examples/Basic) | Creates a dashboard with a few cards and updates them in real time at a 3-second interval. | | [Benchmark](https://github.com/ayushsharma82/ESP-DASH/tree/master/examples/Benchmark) | Verifies that ESP-DASH Lite works correctly on your platform. | | [Chart](https://github.com/ayushsharma82/ESP-DASH/tree/master/examples/Chart) | Creates a bar chart and updates it at a regular interval. | | [Dynamic](https://github.com/ayushsharma82/ESP-DASH/tree/master/examples/Dynamic) | Allocates cards on the heap and removes a card from the dashboard after 30 seconds by deleting it. Requires familiarity with heap allocation, pointers and destructors. | | [Interactive](https://github.com/ayushsharma82/ESP-DASH/tree/master/examples/Interactive) | Creates an interactive dashboard with a toggle button and a slider. | All examples are available in the [examples folder](https://github.com/ayushsharma82/ESP-DASH/tree/master/examples) of the repository. In the Arduino IDE, you can also open them from **File > Examples > ESP-DASH** after installing the library. # Installation (/docs/espdash/v5/getting-started/installation) > Install ESP-DASH v5 and its platform-specific dependencies in the Arduino IDE or PlatformIO. Library: ESP-DASH v5.x.x This page lists the dependencies ESP-DASH v5 requires on each platform and describes how to install the library in the Arduino IDE and PlatformIO. ## Dependencies ESP-DASH requires the following libraries in your libraries folder. Install only the versions listed below; other versions can cause ESP-DASH to fail to compile or to work only partially. Install the dependencies for your platform: - [ESP32 Arduino Core](https://github.com/espressif/arduino-esp32) >= **v3.2.0** - (ESP32Async) [AsyncTCP](https://github.com/ESP32Async/AsyncTCP#v3.4.4) >= **v3.4.4** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.7.9) >= **v3.7.9** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson#v7.4.2) >= **v7.4.2** - [Pico Arduino Core](https://github.com/earlephilhower/arduino-pico) @ **v4.5.4** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP#v1.3.2) >= **v1.3.2** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.7.9) >= **v3.7.9** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson#v7.4.2) >= **v7.4.2** - [Pico Arduino Core](https://github.com/earlephilhower/arduino-pico) @ **v4.5.4** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP#v1.3.2) >= **v1.3.2** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.7.9) >= **v3.7.9** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson#v7.4.2) >= **v7.4.2** RP2040 refers to the Raspberry Pi Pico W and RP2350 to the Raspberry Pi Pico 2 W. ESP8266 is not officially supported in v5; use [ESP-DASH v4](/docs/espdash/v4/getting-started/installation) for ESP8266 projects. ## Install ESP-DASH ESP-DASH can be installed in the Arduino IDE or in PlatformIO. ### Library Manager [#library-manager] Open **Sketch > Include Library > Manage Libraries**, search for "ESP-DASH" and click **Install**. ### Manual installation [#manual-installation] 1. Download the [ESP-DASH repository archive](https://github.com/ayushsharma82/ESP-DASH/archive/master.zip). 2. Extract the archive into your Arduino libraries folder: - Windows: `Documents/Arduino/libraries/` - Linux: `/libraries/` ### Import a ZIP library [#import-a-zip-library] 1. Download the [ESP-DASH repository archive](https://github.com/ayushsharma82/ESP-DASH/archive/master.zip). 2. Open **Sketch > Include Library > Add .ZIP Library** and select the downloaded file. ESP-DASH supports multiple architectures (ESP32 and RP2040/RP2350). Without strict compatibility checks, PlatformIO tries to install the dependencies of every platform, which often causes compilation errors. Before installing ESP-DASH: 1. Delete the `.pio/libdeps` folder of your project, if it exists. 2. Add the following line to the environment in your `platformio.ini` file: ```ini lib_compat_mode = strict ``` 3. Save `platformio.ini`. ### Library Manager [#library-manager-1] Open the PlatformIO menu, go to **Libraries**, search for "ESP-DASH" and add it to your project. ### Manual installation [#manual-installation-1] 1. Download the [ESP-DASH repository archive](https://github.com/ayushsharma82/ESP-DASH/archive/master.zip). 2. Extract the archive into the `lib` folder of your project. ## Next steps - [Integration Guide](/docs/espdash/v5/getting-started/integration): add a dashboard to an existing web server sketch. - [Examples](/docs/espdash/v5/getting-started/examples): browse the example sketches. - [Migrating from v4 to v5](/docs/espdash/v5/getting-started/migrating-from-v4-to-v5): update an existing ESP-DASH v4 project. # Integration Guide (/docs/espdash/v5/getting-started/integration) > Add an ESP-DASH v5 dashboard to an existing ESPAsyncWebServer sketch on ESP32, RP2040 or RP2350. Library: ESP-DASH v5.x.x This guide shows how to add ESP-DASH to a sketch that already runs an `AsyncWebServer`. Each step lists the lines to add; the complete sketch is at the end of the page. ### Include the dependencies Include the Wi-Fi, async TCP and web server headers for your platform, followed by `ESPDash.h`. The preprocessor conditions below select the correct headers for ESP32, RP2040 and RP2350, so the same sketch compiles on every supported board. ```cpp #include #if defined(ESP32) /* ESP32 Dependencies */ #include #include #include #elif defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2350) /* RP2040 or RP2350 Dependencies */ #include #include #include #endif #include ``` ### Create the web server Create an `AsyncWebServer` instance on port 80. ```cpp /* Start Webserver */ AsyncWebServer server(80); ``` You are not limited to a single dashboard. You can host multiple dashboards on different ports. ### Attach ESP-DASH to the server Pass the `AsyncWebServer` instance to the `ESPDash` constructor by reference. ESP-DASH registers its routes and WebSocket handler on this server and handles all dashboard networking through it. ```cpp /* Attach ESP-DASH to AsyncWebServer */ ESPDash dashboard(server); ``` You can continue to use the same `AsyncWebServer` instance for your own routes. ### Create widgets Declare the widgets in global scope, passing the dashboard instance as the first argument. Each card type has its own constructor parameters; see the [Cards](/docs/espdash/v5/cards/generic) section for the available types. ```cpp dash::TemperatureCard temperature(dashboard, "Temperature"); dash::HumidityCard humidity(dashboard, "Humidity"); ``` ### Connect to Wi-Fi and start the server In `setup()`, connect to your access point and start the server with `server.begin()`. The `ssid` and `password` variables are declared in the complete sketch below. ```cpp void setup() { Serial.begin(115200); /* Connect WiFi */ WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.printf("WiFi Failed!\n"); return; } Serial.print("IP Address: "); Serial.println(WiFi.localIP()); /* Start AsyncWebServer */ server.begin(); } ``` ### Update values and send updates Set new widget values, then call `dashboard.sendUpdates()` to push the changes to all connected clients. You can call these methods anywhere in your sketch; this example uses `loop()` for simplicity. ```cpp void loop() { /* Update Card Values */ temperature.setValue((int)random(0, 50)); humidity.setValue((int)random(0, 100)); /* Send Updates to our Dashboard (realtime) */ dashboard.sendUpdates(); /* Delay is just for demonstration purposes in this example, Replace this code with 'millis interval' in your final project. */ delay(3000); } ``` `delay()` blocks the loop. In production code, schedule updates with a non-blocking `millis()` interval instead. ### Upload the sketch Set `ssid` and `password` to the credentials of your access point, upload the sketch and open the IP address printed on the serial monitor in a browser. ```cpp title="main.cpp" /* ----------------------- ESPDASH Lite - Basic Example ----------------------- Skill Level: Intermediate In this example we will be creating a basic dashboard which consists of some cards and then update them in realtime ( at 3s interval ). Github: https://github.com/ayushsharma82/ESP-DASH WiKi: https://docs.espdash.pro Works with ESP32, RP2040+W and RP2350+W based devices / projects. ------------------------------- Upgrade to ESP-DASH Pro: https://store.softt.io/products/esp-dash-pro */ #include #if defined(ESP32) /* ESP32 Dependencies */ #include #include #include #elif defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2350) /* RP2040 or RP2350 Dependencies */ #include #include #include #endif #include /* Your WiFi Credentials */ const char* ssid = ""; // SSID const char* password = ""; // Password /* Start Webserver */ AsyncWebServer server(80); /* Attach ESP-DASH to AsyncWebServer */ ESPDash dashboard(server); /* Dashboard Cards */ dash::TemperatureCard temperature(dashboard, "Temperature"); dash::HumidityCard humidity(dashboard, "Humidity"); void setup() { Serial.begin(115200); /* Connect WiFi */ WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.printf("WiFi Failed!\n"); return; } Serial.print("IP Address: "); Serial.println(WiFi.localIP()); /* Start AsyncWebServer */ server.begin(); } void loop() { /* Update Card Values */ temperature.setValue((int)random(0, 50)); humidity.setValue((int)random(0, 100)); /* Send Updates to our Dashboard (realtime) */ dashboard.sendUpdates(); /* Delay is just for demonstration purposes in this example, Replace this code with 'millis interval' in your final project for non-blocking operation. */ delay(3000); } ``` ## Next steps - [Cards](/docs/espdash/v5/cards/generic): display values and capture user input. - [Charts](/docs/espdash/v5/charts/bar): plot data series on the dashboard. - [Statistics](/docs/espdash/v5/statistics): show device information and other key values. - [Examples](/docs/espdash/v5/getting-started/examples): browse the example sketches. # Migrating from v4 to v5 (/docs/espdash/v5/getting-started/migrating-from-v4-to-v5) > Update an ESP-DASH v4 sketch to the v5 API, covering dashboard setup, cards, charts, callbacks and widget refreshes. Library: ESP-DASH v5.x.x ESP-DASH v5 introduces a refined API with a dedicated class for every widget type. This guide describes the main differences from v4 and shows how to migrate existing code. ## Dashboard initialization The `ESPDash` constructor now takes the `AsyncWebServer` instance by reference instead of by pointer. ```cpp title="v4" ESPDash dashboard(&server); ``` ```cpp title="v5" ESPDash dashboard(server); ``` ## Cards In v4, every card was a `Card` object whose type was selected with a macro. In v5, each card type is its own class in the `dash` namespace, with its own constructor parameters and methods. Review the [Cards](/docs/espdash/v5/cards/generic) section for each card type you use and migrate your code accordingly. The following example migrates a temperature card: ```cpp title="v4" Card temperature(&dashboard, TEMPERATURE_CARD, "Temperature", "°C"); ``` ```cpp title="v5" dash::TemperatureCard temperature(dashboard, "Temperature"); ``` Key changes: - Use a dedicated class such as `dash::TemperatureCard` or `dash::HumidityCard` instead of `Card` with a type macro. - Pass the dashboard by reference, not by pointer. - Each card and chart class has its own set of methods. - The generic `update()` method is replaced by type-specific methods such as `setValue()`. ## Charts Charts are also class templates in the `dash` namespace. The template parameters define the types of the X-axis and Y-axis values. ```cpp title="v4" Chart power(&dashboard, BAR_CHART, "Power Usage (kWh)"); power.updateX(XAxis, 7); power.updateY(YAxis, 7); ``` ```cpp title="v5" dash::BarChart power(dashboard, "Power Usage (kWh)"); power.setX(XAxis, 7); power.setY(YAxis, 7); ``` Key changes: - Use a dedicated class such as `dash::BarChart` or `dash::LineChart` instead of `Chart` with a type macro. - Specify the axis types as template arguments, for example `dash::BarChart`. - `updateX()` is replaced by `setX()` and `updateY()` by `setY()`. Both take an array and its size. See the [Charts](/docs/espdash/v5/charts/bar) section for the methods of each chart type. ## Interactive cards Interactive cards, such as buttons and sliders, use dedicated classes and typed callbacks. The following example migrates a toggle button: ```cpp title="v4" Card button(&dashboard, BUTTON_CARD, "Test Button"); button.attachCallback([&](int value) { /* ... */ }); button.update(value); ``` ```cpp title="v5" dash::ToggleButtonCard button(dashboard, "Test Button"); button.onChange([](bool state) { /* ... */ }); button.setValue(state); ``` Key changes: - Use a dedicated class such as `dash::ToggleButtonCard`. - `attachCallback()` is replaced by `onChange()`. - Callbacks receive a value of the appropriate type, for example `bool` instead of `int` for a toggle button. Each interactive card has its own callback signature and methods; see the page for the card in the [Cards](/docs/espdash/v5/cards/generic) section. ## Summary of API changes | Operation | v4 | v5 | | --- | --- | --- | | Dashboard initialization | `ESPDash(&server)` | `ESPDash(server)` | | Card creation | `Card(..., TEMPERATURE_CARD, ...)` | `dash::TemperatureCard(...)` | | Chart creation | `Chart(..., BAR_CHART, ...)` | `dash::BarChart<...>(...)` | | Value update | `update(value)` | Widget-specific, for example `setValue()` | | Event handler | `attachCallback(...)` | Widget-specific, for example `onChange()` | | Refresh a single widget | `dashboard.refreshCard(&card)` / `dashboard.refreshChart(&chart)` | `dashboard.refresh(widget)` | ## Notes - v5 uses the term "widget" for all user interface elements, including cards, charts, statistics and tabs. - Wi-Fi and web server setup is unchanged. - `dashboard.sendUpdates()` still pushes all pending changes to connected clients. - `dashboard.refresh(widget)` sends an update for a single widget instead of the entire dashboard. Use it, typically inside callbacks, to reduce the amount of data sent when only one widget has changed. - In ESP-DASH Pro, each tab can have an icon. See [Tabs](/docs/espdash/v5/tabs). # Introduction (/docs/espdash/v5) > ESP-DASH is a C++ library for building real-time, on-device web dashboards for ESP32, RP2040 and RP2350 microcontrollers. Library: ESP-DASH v5.x.x ESP-DASH is a UI library that serves a complete, real-time dashboard directly from your microcontroller. You describe the dashboard in C++ with ready-made cards, charts and statistics; ESP-DASH generates the web interface and keeps every connected browser in sync. You do not need to write HTML, CSS or JavaScript. GitHub repository: https://github.com/ayushsharma82/ESP-DASH ![ESP-DASH dashboard](https://docs.softt.io/img/v5/splash-light.png) ## Key capabilities - **Automatic web interface**: the dashboard page is generated and served by the library. - **Real-time updates**: value changes are pushed to all connected clients over a WebSocket. - **C++ API**: widgets are declared and updated from your sketch; no front-end code is required. - **Ready-to-use widgets**: cards for displaying values and capturing user input, plus statistics for device information. - **Dynamic layout**: widgets can be added to or removed from the dashboard at runtime. - **Built-in charts**: plot data series directly on the dashboard. ## Supported boards - **ESP32** - **RP2040** (Raspberry Pi Pico W) - **RP2350** (Raspberry Pi Pico 2 W) See [Installation](/docs/espdash/v5/getting-started/installation) for the required core and library versions for each platform. ESP8266 is not officially supported in ESP-DASH v5. For ESP8266 projects, use [ESP-DASH v4](/docs/espdash/v4). ## ESP-DASH Lite and ESP-DASH Pro This documentation covers both editions of ESP-DASH. ESP-DASH Lite is open source and licensed under GPL-3.0. ESP-DASH Pro is a commercial edition with additional widgets and features such as tabs and custom branding. Pages for features that are available only in ESP-DASH Pro are marked accordingly. See [Commercial License](/docs/espdash/v5/commercial-license) for a comparison, or visit [store.softt.io](https://store.softt.io/products/esp-dash-pro). ## Explore the documentation # Authentication (/docs/espdash/v5/security/authentication) > Restrict access to an ESP-DASH dashboard with HTTP Basic Authentication using a username and password. Library: ESP-DASH v5.x.x ESP-DASH supports HTTP Basic Authentication. Enable it to control who can view your dashboard. ![Browser authentication prompt](https://docs.softt.io/img/v4/authentication.png) ## Enable authentication Call `setAuthentication()` on the dashboard instance with a username and password: ```cpp dashboard.setAuthentication("username", "password"); ``` When authentication is enabled, ESP-DASH requires the credentials both for the dashboard page and for the WebSocket connection that carries dashboard updates. ## Notes - Authentication is enabled only when both the username and the password are non-empty. - `setAuthentication()` also accepts `dash::string` arguments. # Disable Statistics (/docs/espdash/v5/security/disable-statistics) > Disable the built-in system statistics in ESP-DASH v5 and hide the Statistics tab from the sidebar. Library: ESP-DASH v5.x.x ESP-DASH displays system statistics (Hardware, SDK Version, Free Heap (SRAM), WiFi Mode and WiFi Signal) on the [Statistics](/docs/espdash/v5/statistics) page by default. To disable them, pass `false` as the last argument of the `ESPDash` constructor. ## Disable system statistics If you serve the dashboard at the default URI, pass `false` as the second argument: ```cpp ESPDash dashboard(server, false); ``` If you use a [custom URI](/docs/espdash/v5/features/custom-uri), pass `false` as the third argument: ```cpp ESPDash dashboard(server, "/", false); ``` ## Hide the Statistics tab When system statistics are disabled and the sketch defines no custom statistics, the dashboard hides the Statistics tab from the sidebar automatically. # Statistics (/docs/espdash/v5/statistics) > Display device information and key metrics on the ESP-DASH Statistics page with StatisticValue and StatisticProvider. Library: ESP-DASH v5.x.x Statistics display key values on a dedicated Statistics page of the dashboard. Use them for device information, sensor readings or any other single data point you want to highlight. ![Statistics page](https://docs.softt.io/img/v5/statistics-light.png) ## Types ESP-DASH provides two statistic classes: - **`dash::StatisticValue`**: displays a value that you set from your sketch. - **`dash::StatisticProvider`**: displays a value returned by a function that ESP-DASH calls when it generates an update. ## Constructor Declare statistics in global scope. Both classes take the dashboard instance and a name, which is shown as the label of the statistic on the dashboard. ```cpp dash::StatisticValue hardwareStat(dashboard, "Hardware"); dash::StatisticValue freeHeapStat(dashboard, "Free Heap"); dash::StatisticProvider heapProvider(dashboard, "Free Heap (SRAM)"); ``` Template parameters: ## Methods ### setValue Available on `dash::StatisticValue`. Sets the value of the statistic and returns `true` if the value changed, or `false` if it is identical to the current value. ```cpp hardwareStat.setValue("ESP32"); freeHeapStat.setValue(ESP.getFreeHeap()); ``` ### setProvider Available on `dash::StatisticProvider`. Registers a function that returns the current value. ESP-DASH calls the provider whenever it generates the dashboard layout or an update, so you do not need to set the value yourself. ```cpp heapProvider.setProvider([]() { return ESP.getFreeHeap(); }); ``` The provider must return a value of type `T`. ## Example The following sketch uses both statistic classes. Default statistics are disabled because they already use the names "Hardware", "SDK Version" and "Free Heap (SRAM)"; see [Disable Statistics](/docs/espdash/v5/security/disable-statistics). ```cpp title="main.cpp" // ... /* Disable the default statistics */ ESPDash dashboard(server, false); // Statistic Value dash::StatisticValue hardware(dashboard, "Hardware"); dash::StatisticValue sdkVersion(dashboard, "SDK Version"); // Statistic Provider dash::StatisticProvider freeHeap(dashboard, "Free Heap (SRAM)"); void setup() { // ... // Set values for Statistic Value hardware.setValue("my-custom-hardware"); sdkVersion.setValue(ESP.getSdkVersion()); // Set provider for Statistic Provider freeHeap.setProvider([]() { return ESP.getFreeHeap(); }); } void loop() { // StatisticProvider values are read automatically when the dashboard updates. // StatisticValue values must be set whenever the data changes, for example: // if (some_condition) { // hardware.setValue("New Hardware Value"); // dashboard.sendUpdates(); // } } ``` ## Notes - By default, ESP-DASH adds the following system statistics: Hardware, SDK Version, Free Heap (SRAM), WiFi Mode and WiFi Signal. To remove them, see [Disable Statistics](/docs/espdash/v5/security/disable-statistics). - `ESP.getFreeHeap()` and `ESP.getSdkVersion()` are available on ESP32. On RP2040 and RP2350, use `rp2040.getFreeHeap()` to read the free heap. - Updated values reach connected clients when you call `dashboard.sendUpdates()`; this also applies to values returned by a provider. # Tabs (/docs/espdash/v5/tabs) > Organize ESP-DASH Pro dashboards into multiple pages with tabs, each with an optional sidebar icon. Library: ESP-DASH v5.x.x Availability: ESP-DASH Pro only Tabs split the dashboard into multiple pages so that you can group related widgets. They are useful when a dashboard contains a large number of cards and charts. ![Tabs in the dashboard sidebar](https://docs.softt.io/img/v5/tabs-light.png) By default, ESP-DASH places all widgets in the Overview tab. If every card and chart is assigned to a custom tab, the dashboard hides the Overview tab from the sidebar. ## Constructor Declare tabs in global scope, outside of any function. ```cpp dash::Tab tab1(dashboard, "Custom Tab 1", dash::Icon::COG_ICON); ``` ## Icons Since v5, you can assign an icon to a tab with the `dash::Icon` enum. The icon is displayed in the sidebar next to the tab name. | Value | Icon | | --- | --- | | `UNASSIGNED_ICON` | None (default) | | `HOME_ICON` | Home | | `COG_ICON` | Cog | | `SUN_ICON` | Sun | | `MOON_ICON` | Moon | | `SUN_MOON_ICON` | Sun and moon | | `DATABASE_ICON` | Database | | `COMPUTER_ICON` | Computer | | `ACTIVITY_ICON` | Activity | | `ALARM_ICON` | Alarm clock | | `AUDIO_ICON` | Audio waveform | | `JOYSTICK_ICON` | Joystick | ## Assign a widget to a tab After you create a tab, assign cards and charts to it with their `setTab()` method. `setTab()` takes a reference to the tab. ```cpp card1.setTab(tab1); ``` ```cpp chart1.setTab(tab1); ``` To move a widget back to the Overview tab, call `removeTab()`: ```cpp card1.removeTab(); ``` Both methods return `true` if the assignment changed and `false` otherwise. # Using with Other SOFTT Libraries (/docs/espdash/v5/using-with-other-libraries) > Combine ESP-DASH with ElegantOTA, NetWizard and WebSerial in the same project, sharing one AsyncWebServer. Library: ESP-DASH v5.x.x ESP-DASH, ElegantOTA, NetWizard and WebSerial are designed to work together in the same project. A single sketch can provide WiFi provisioning (NetWizard), a real-time dashboard (ESP-DASH), over-the-air updates (ElegantOTA) and a remote serial terminal (WebSerial), all served by one `AsyncWebServer` instance. The Lite and Pro editions of each library can be mixed freely. ## Requirements - **One shared `AsyncWebServer`.** Create a single `AsyncWebServer server(80);` and pass it to every library. Call `server.begin()` once, after all libraries are initialized. - **Async mode for ElegantOTA and NetWizard.** ESP-DASH and WebSerial only run on `ESPAsyncWebServer`. ElegantOTA and NetWizard default to the core `WebServer`, so enable their async mode by setting `ELEGANTOTA_USE_ASYNC_WEBSERVER=1` and `NETWIZARD_USE_ASYNC_WEBSERVER=1`. See [ElegantOTA async mode](/docs/elegantota/getting-started/async-mode) and [NetWizard async mode](/docs/netwizard/getting-started/async-mode). - **One copy of each dependency.** Install a single copy of `ESPAsyncWebServer` and `AsyncTCP` (ESP32) or `RPAsyncTCP` (RP2040/RP2350) whose version satisfies the highest minimum listed on the installation page of each library you use. Do not install two forks of `ESPAsyncWebServer` side by side. - **A board that every library supports.** ESP32 is supported by all four libraries. Check each library's supported boards before targeting RP2040, RP2350 or ESP8266: for example, ESP-DASH v5 and NetWizard do not support ESP8266. ## Default paths Each library registers its own routes on the shared server. The defaults do not collide, except for ESP-DASH and the NetWizard portal, which both use `/`: | Library | Default path | How to change it | | --- | --- | --- | | ESP-DASH | `/` | Pass a URI to the constructor: `ESPDash dashboard(server, "/dashboard", true);` ([custom URI](/docs/espdash/v5/features/custom-uri)) | | ElegantOTA | `/update` | Fixed | | WebSerial | `/webserial` | Pass a path to `begin()`: `WebSerial.begin(&server, "/terminal");` | | NetWizard | `/` (captive portal, access point clients only) | Fixed | When NetWizard is part of the project, serve ESP-DASH at `/dashboard` and add `server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER);` so that clients on the local network still see the dashboard at the device's root URL. See [NetWizard with ESP-DASH](/docs/netwizard/getting-started/using-with-espdash). ## Example The sketch below combines all four libraries on an ESP32. Build it with `-D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 -D NETWIZARD_USE_ASYNC_WEBSERVER=1`, or set both macros in the library headers as described on the async mode pages. ```cpp title="main.cpp" #include #include #include #include #include // NetWizard Pro: #include #include // ElegantOTA Pro: #include AsyncWebServer server(80); // WiFi provisioning: captive portal at / for access point clients NetWizard NW(&server); // Dashboard at /dashboard, so it does not conflict with the portal ESPDash dashboard(server, "/dashboard", true); dash::TemperatureCard temperature(dashboard, "Temperature"); unsigned long lastUpdate = 0; void setup() { Serial.begin(115200); // Connect to the saved network, or start the captive portal NW.setStrategy(NetWizardStrategy::BLOCKING); NW.autoConnect("My Device", ""); // Over-the-air updates at /update ElegantOTA.begin(&server); // Remote serial terminal at /webserial WebSerial.begin(&server); WebSerial.onMessage([](const String& msg) { Serial.println(msg); }); // Show the dashboard at / for clients on the local network server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER); server.begin(); } void loop() { NW.loop(); ElegantOTA.loop(); WebSerial.loop(); if (millis() - lastUpdate > 3000) { lastUpdate = millis(); temperature.setValue((int)random(0, 50)); dashboard.sendUpdates(); WebSerial.println("Dashboard updated"); } } ``` Each library's `loop()` method must be called on every iteration, and `loop()` must not block, so that the portal, OTA updates and buffered terminal output keep running alongside the dashboard. # AI Tools (/docs/netwizard/ai-tools) > Use the NetWizard documentation from AI coding assistants through the MCP server, llms.txt and Markdown page exports. Library: NetWizard The NetWizard documentation is available in machine-readable formats so that AI coding assistants can answer questions and generate code from the current API instead of outdated training data. ## Ask AI Select **Ask AI** at the bottom of any documentation page to open the documentation assistant. It searches these pages before answering and links to the sources it used. Answers are grounded in the documentation only; verify generated code against the referenced pages. ## MCP server The documentation is served as a [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP. No authentication is required. One server covers every SOFTT library (ESP-DASH, ElegantOTA, NetWizard and WebSerial); pass `library: "netwizard"` to limit results to NetWizard. ```text https://docs.softt.io/api/mcp ``` The server exposes three read-only tools: | Tool | Description | | --- | --- | | `search_docs` | Full-text search across the documentation. Accepts `query`, an optional `library` (`espdash`, `elegantota`, `netwizard`, `webserial` or `all`), an optional `version` for ESP-DASH (`v5`, `v4` or `all`) and `limit`. | | `get_page` | Returns the complete Markdown of a page, such as `/docs/netwizard/parameters/input`. | | `list_pages` | Lists pages with their title, description and URL, filtered by `library` and `version`. | ### Connect a client ```bash claude mcp add --transport http softt https://docs.softt.io/api/mcp ``` Add the server to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for all projects: ```json title=".cursor/mcp.json" { "mcpServers": { "softt": { "url": "https://docs.softt.io/api/mcp" } } } ``` Add the server to `.vscode/mcp.json` in your workspace: ```json title=".vscode/mcp.json" { "servers": { "softt": { "type": "http", "url": "https://docs.softt.io/api/mcp" } } } ``` Clients that support Streamable HTTP connect directly to the server URL. For clients that only support stdio, use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge: ```json { "softt": { "command": "npx", "args": ["-y", "mcp-remote", "https://docs.softt.io/api/mcp"] } } ``` Once connected, ask your assistant questions such as "Add a NetWizard captive portal with an MQTT host input parameter" and it will retrieve the relevant pages before writing code. ## llms.txt The site publishes [llms.txt](https://llmstxt.org) files for tools that ingest documentation directly: | URL | Contents | | --- | --- | | [`/llms.txt`](/llms.txt) | Index of every page with its title, description and link. | | [`/llms-full.txt`](/llms-full.txt) | The complete documentation of every library and version as a single Markdown file. | ## Markdown pages Every page is also available as Markdown: - Append `.md` to a page URL, for example [`/docs/netwizard/parameters/input.md`](/docs/netwizard/parameters/input.md). - Requests with an `Accept: text/markdown` header receive Markdown instead of HTML. - Use **Copy Markdown** at the top of a page to copy it to the clipboard, or **Open** to send it to an assistant such as ChatGPT or Claude. # Callbacks (/docs/netwizard/api/callbacks) > React to connection status changes, captive portal state changes and submitted configuration with NetWizard callbacks. Library: NetWizard NetWizard notifies your sketch of changes through callbacks. Register them in `setup()` before calling [`autoConnect()`](/docs/netwizard/api/connection#autoconnect). Each method stores one callback; registering another replaces the previous one. `onConnectionStatus` and `onPortalState` are invoked from `NW.loop()` (also while `autoConnect()` is running) when the value differs from the last value reported. ## Methods ### `onConnectionStatus()` ```cpp void onConnectionStatus(std::function callback); ``` Called when the connection status changes. The new status is passed as a [`NetWizardConnectionStatus`](/docs/netwizard/api/network-info#connection-statuses) value. ```cpp void setup() { // ... NW.onConnectionStatus([](NetWizardConnectionStatus status) { if (status == NetWizardConnectionStatus::CONNECTED) { Serial.printf("Connected, IP: %s\n", NW.localIP().toString().c_str()); } }); // ... } ``` ### `onPortalState()` ```cpp void onPortalState(std::function callback); ``` Called when the captive portal state changes. The new state is passed as a [`NetWizardPortalState`](/docs/netwizard/api/portal#portal-states) value. Use it to reflect the setup progress outside the portal, for example by turning an LED green when the configuration succeeds. ```cpp void setup() { // ... NW.onPortalState([](NetWizardPortalState state) { if (state == NetWizardPortalState::SUCCESS) { // indicate successful setup } }); // ... } ``` ### `onConfig()` ```cpp void onConfig(std::function callback); ``` Called when the user submits the configuration parameters in the portal, after the submitted values have been written to the `NetWizardParameter` objects. Return `true` to accept the submission or `false` to reject it. If no callback is registered, every submission is accepted. ```cpp void setup() { // ... NW.onConfig([&]() { // read, validate and store the parameter values return true; }); // ... } ``` See [Parameters](/docs/netwizard/parameters#receive-submitted-values) for details. # Configuration (/docs/netwizard/api/configuration) > Configure the NetWizard constructor, portal strategy, hostname, timeouts and compile-time options. Library: NetWizard This page covers the `NetWizard` constructor and the settings that control how NetWizard connects and runs the captive portal. Call the setters in `setup()` before [`autoConnect()`](/docs/netwizard/api/connection#autoconnect). ## Constructor ```cpp NetWizard(NETWIZARD_WEBSERVER* server); ``` ```cpp WebServer server(80); NetWizard NW(&server); ``` ## Methods ### `setStrategy()` ```cpp void setStrategy(NetWizardStrategy strategy); ``` Sets the captive portal strategy, which determines whether `autoConnect()` waits for the portal to close. | Strategy | Behavior | | --- | --- | | `NetWizardStrategy::BLOCKING` (default) | If `autoConnect()` starts the portal, it does not return until the portal closes: the user exits the portal, the portal times out, or the portal is stopped by [`reset()`](/docs/netwizard/api/credentials#reset). | | `NetWizardStrategy::NON_BLOCKING` | `autoConnect()` returns immediately after starting the portal. The portal runs in the background while `NW.loop()` is called. | Use `NON_BLOCKING` when your application must perform important tasks while the portal is active, and handle connection status changes at runtime with the [callbacks](/docs/netwizard/api/callbacks). Otherwise, the blocking strategy is recommended. ```cpp void setup() { // ... NW.setStrategy(NetWizardStrategy::NON_BLOCKING); NW.autoConnect("NetWizard Demo", ""); // ... } ``` ### `setHostname()` ```cpp void setHostname(const char* hostname); void setHostname(String& hostname); ``` Sets the hostname the device uses to identify itself on the network. The default is `netwizard`. NetWizard applies the hostname each time it connects to a network. ```cpp NW.setHostname("my-device"); ``` ### `setConnectTimeout()` ```cpp void setConnectTimeout(unsigned long timeout); ``` Sets how long, in milliseconds, `autoConnect()` tries to connect to the saved network before it starts the captive portal. Within this period, NetWizard retries the connection every 10 seconds. The default is `30000` (30 seconds). ```cpp NW.setConnectTimeout(15000); ``` This timeout does not apply to credentials submitted in the portal; the portal waits a fixed 30 seconds for those connections. ### `setPortalTimeout()` ```cpp void setPortalTimeout(unsigned long timeout); ``` Sets how long, in milliseconds, the captive portal stays active. The default is `300000` (5 minutes). ```cpp NW.setPortalTimeout(150000); ``` The timer restarts while at least one client is connected to the access point, and the portal does not time out while it is testing submitted credentials. When the portal times out, it stops and NetWizard connects to the saved network, if any. ## Compile-time options Define these macros before the library is compiled, for example with PlatformIO `build_flags` (`-DNETWIZARD_DEBUG=1`), or edit their defaults in the library header. ## Related - [Authentication](/docs/netwizard/authentication): `setAuthentication()`. - [Custom Title](/docs/netwizard/custom-title): `setTitle()` (NetWizard Pro). # Connection (/docs/netwizard/api/connection) > Connect to the saved network, start the captive portal on demand, connect with new credentials and disconnect with the NetWizard API. Library: NetWizard These methods start NetWizard and control the station (client) connection. ## Methods ### `autoConnect()` ```cpp void autoConnect(const char* ssid, const char* password); ``` Loads the saved credentials and connects to the saved network. NetWizard starts the captive portal if either of the following is true: 1. The device is not configured, that is, no credentials are saved. 2. The device cannot connect to the saved network within the [connect timeout](/docs/netwizard/api/configuration#setconnecttimeout), for example because the credentials are invalid or the network is out of range. Whether `autoConnect()` returns before the portal closes depends on the [strategy](/docs/netwizard/api/configuration#setstrategy). ```cpp void setup() { // ... NW.autoConnect("NetWizard Demo", ""); // ... } ``` Call `autoConnect()` once in `setup()`, after configuring NetWizard and registering callbacks. The access point credentials are also used by [`startPortal()`](/docs/netwizard/api/portal#startportal). ### `loop()` ```cpp void loop(); ``` Runs NetWizard: processes DNS and, with the synchronous `WebServer`, HTTP requests; updates the connection status; advances the portal state; checks the portal timeout; and invokes the callbacks. Call it on every iteration of your sketch's `loop()`. ```cpp void loop() { NW.loop(); } ``` ### `connect()` ```cpp bool connect(); bool connect(const char* ssid, const char* password); ``` Without arguments, connects to the saved network. Returns `false` if no credentials are saved, and `true` otherwise. With `ssid` and `password`, overwrites the saved credentials, stores them persistently and connects to the new network. Returns `true`. The return value indicates that a connection attempt was started, not that it succeeded. Monitor the result with [`onConnectionStatus()`](/docs/netwizard/api/callbacks#onconnectionstatus) or [`getConnectionStatus()`](/docs/netwizard/api/network-info#getconnectionstatus). ```cpp NW.connect(); // connect to the saved network NW.connect("ssid", "password"); // save and connect to a new network ``` ### `disconnect()` ```cpp void disconnect(); ``` Disconnects from the current WiFi network. The saved credentials are kept, so you can reconnect later with `connect()` or by restarting the device. ```cpp NW.disconnect(); ``` ## Related - [Portal](/docs/netwizard/api/portal): start and stop the captive portal on demand. - [Credentials](/docs/netwizard/api/credentials): manage the saved credentials. # Credentials (/docs/netwizard/api/credentials) > Read, replace, erase and reset the WiFi credentials that NetWizard stores on the device. Library: NetWizard NetWizard stores the SSID and password of the configured network in persistent storage (`Preferences`, in the `nw_creds` namespace) and loads them in [`autoConnect()`](/docs/netwizard/api/connection#autoconnect). The methods on this page read and modify these credentials. ## Methods ### `isConfigured()` ```cpp bool isConfigured(); ``` Returns `true` if the device has saved credentials, otherwise `false`. ```cpp if (NW.isConfigured()) { // ... } ``` ### `getSSID()` ```cpp const char* getSSID(); ``` Returns the SSID of the configured network. ```cpp String ssid = NW.getSSID(); ``` ### `getPassword()` ```cpp const char* getPassword(); ``` Returns the password of the configured network. ```cpp String password = NW.getPassword(); ``` ### `setCredentials()` ```cpp void setCredentials(const char* ssid, const char* password); ``` Replaces the configured credentials and saves them to persistent storage. It does not start a connection or the captive portal. To save and connect in one step, use [`connect(ssid, password)`](/docs/netwizard/api/connection#connect). ```cpp NW.setCredentials("ssid", "password"); ``` ### `erase()` ```cpp void erase(); ``` Erases the saved credentials from persistent storage, so the device starts unconfigured after the next reboot. It does not disconnect the device, stop the captive portal or change the credentials currently in use; `isConfigured()` keeps returning its current value until the next reboot. ```cpp NW.erase(); ``` ### `reset()` ```cpp void reset(); ``` Returns NetWizard to its initial state: clears the credentials in memory and in persistent storage, disconnects from the network and, if the captive portal is active, stops it after a delay of about 5 seconds. ```cpp NW.reset(); ``` | Method | Clears stored credentials | Clears credentials in use | Disconnects | Stops the portal | | --- | --- | --- | --- | --- | | `erase()` | Yes | No | No | No | | `reset()` | Yes | Yes | Yes | Yes | # Network Info (/docs/netwizard/api/network-info) > Read the connection status, IP configuration, BSSID and channel of the network NetWizard is connected to. Library: NetWizard These methods return information about the current station connection. Except for `getConnectionStatus()`, they return meaningful values only while the connection status is `CONNECTED`; check it with [`getConnectionStatus()`](#getconnectionstatus) first. ## Methods ### `getConnectionStatus()` ```cpp NetWizardConnectionStatus getConnectionStatus(); ``` Returns the current connection status. See [Connection statuses](#connection-statuses). ```cpp NetWizardConnectionStatus status = NW.getConnectionStatus(); ``` ### `localIP()` ```cpp IPAddress localIP(); ``` Returns the IP address of the device on the connected network. ```cpp IPAddress ip = NW.localIP(); String ipStr = NW.localIP().toString(); ``` ### `gatewayIP()` ```cpp IPAddress gatewayIP(); ``` Returns the gateway IP address of the connected network. ```cpp IPAddress gateway = NW.gatewayIP(); String gatewayStr = NW.gatewayIP().toString(); ``` ### `subnetMask()` ```cpp IPAddress subnetMask(); ``` Returns the subnet mask of the connected network. ```cpp IPAddress subnet = NW.subnetMask(); String subnetStr = NW.subnetMask().toString(); ``` ### `getBSSID()` ```cpp void getBSSID(uint8_t* bssid); ``` Writes the 6-byte BSSID (MAC address of the access point) of the connected network into `bssid`. ```cpp uint8_t bssid[6]; NW.getBSSID(bssid); ``` ### `getChannel()` ```cpp uint8_t getChannel(); ``` Returns the WiFi channel of the connected network. ```cpp uint8_t channel = NW.getChannel(); ``` ## Connection statuses `NetWizardConnectionStatus` is a scoped enum that reflects the state of the station connection: | Value | Meaning | | --- | --- | | `NetWizardConnectionStatus::DISCONNECTED` | Not connected to a network. | | `NetWizardConnectionStatus::CONNECTING` | A connection attempt has started. | | `NetWizardConnectionStatus::CONNECTED` | Connected to the network. | | `NetWizardConnectionStatus::CONNECTION_FAILED` | The connection attempt failed, for example because of an invalid password. | | `NetWizardConnectionStatus::CONNECTION_LOST` | An established connection was lost. | | `NetWizardConnectionStatus::NOT_FOUND` | The configured network was not found. | To react to status changes, register [`onConnectionStatus()`](/docs/netwizard/api/callbacks#onconnectionstatus). ## Related - [Credentials](/docs/netwizard/api/credentials): `getSSID()` and `getPassword()` return the configured network's credentials. # Portal (/docs/netwizard/api/portal) > Start and stop the NetWizard captive portal on demand and read its current state. Library: NetWizard `autoConnect()` starts the captive portal automatically when it is needed. The methods on this page control the portal directly, for example to let the user reconfigure the device from a button press. ## Methods ### `startPortal()` ```cpp void startPortal(); ``` Starts the captive portal on demand. The access point uses the SSID and password passed to [`autoConnect()`](/docs/netwizard/api/connection#autoconnect), so call `autoConnect()` first. If the device is connected to its saved network, it stays connected while the portal runs. Otherwise, the portal runs in access point mode only. `startPortal()` returns immediately regardless of the [strategy](/docs/netwizard/api/configuration#setstrategy); keep calling `NW.loop()`. The portal is subject to the [portal timeout](/docs/netwizard/api/configuration#setportaltimeout) and stops when it expires. ```cpp NW.startPortal(); ``` ### `stopPortal()` ```cpp void stopPortal(); ``` Stops the captive portal if it is active: removes the portal routes, stops the DNS server and closes the access point. If credentials are saved, NetWizard then connects to the saved network; otherwise, it turns WiFi off. ```cpp NW.stopPortal(); ``` ### `getPortalState()` ```cpp NetWizardPortalState getPortalState(); ``` Returns the current portal state. See [Portal states](#portal-states). ```cpp NetWizardPortalState state = NW.getPortalState(); ``` ## Portal states The portal tracks the progress of the setup flow so that the web interface stays in sync with the device, even after the device reconnects. `NetWizardPortalState` is a scoped enum: | Value | Meaning | | --- | --- | | `NetWizardPortalState::IDLE` | No setup is in progress. This is also the state after the portal stops. | | `NetWizardPortalState::CONNECTING_WIFI` | The user submitted credentials; NetWizard is starting the connection. | | `NetWizardPortalState::WAITING_FOR_CONNECTION` | NetWizard is waiting for the device to connect to the submitted network, for up to 30 seconds. | | `NetWizardPortalState::SUCCESS` | The device connected and the credentials were saved, or a submission containing only parameters was accepted. | | `NetWizardPortalState::FAILED` | Reserved. The current release does not enter this state. | | `NetWizardPortalState::TIMEOUT` | The device did not connect to the submitted network within 30 seconds. The user can try again. | To react to state changes, register [`onPortalState()`](/docs/netwizard/api/callbacks#onportalstate). # Authentication (/docs/netwizard/authentication) > Protect the NetWizard captive portal with HTTP basic authentication. Library: NetWizard By default, anyone who joins the device's access point can open the captive portal. `setAuthentication()` protects the portal with HTTP basic authentication: the browser prompts for a user name and password before it shows the portal. ## Method ### `setAuthentication()` ```cpp void setAuthentication(const char* username, const char* password); void setAuthentication(String& username, String& password); ``` Authentication is enabled only when both `username` and `password` are non-empty; otherwise the call has no effect. Call it before `autoConnect()`. ```cpp void setup() { // ... NW.setAuthentication("admin", "change-me"); NW.autoConnect("NetWizard Demo", "setup-password"); // ... } ``` Authentication applies to the portal page and to every portal API route (`/netwizard/*`). ## Notes - Authentication protects the portal page, not the access point. To restrict who can join the access point, pass a password as the second argument of [`autoConnect()`](/docs/netwizard/api/connection#autoconnect). - HTTP basic authentication sends credentials without encryption over the access point connection. Use it to prevent casual access, not as a substitute for a secured access point. # Branding (/docs/netwizard/branding) > Replace the NetWizard logo in the captive portal with your own company or product logo. Library: NetWizard Availability: NetWizard Pro only NetWizard Pro lets you replace the logo at the top of the captive portal with your own company or product logo. The logo is compiled into the firmware as a byte array. ![Captive portal with a custom logo](https://docs.softt.io/img/netwizard/branding.png) ## Requirements - A logo image in a web-compatible format, such as SVG, PNG, JPEG or GIF. SVG is recommended because of its small file size. Optimize the image before converting it; the logo is stored in flash memory. - The MIME type of the image, for example `image/svg+xml` or `image/png`. You can determine it with a tool such as the [MIME File Type Checker](https://www.htmlstrip.com/mime-file-type-checker). ## Replace the logo ### Convert the logo to a byte array 1. Open the [File2Raw](https://file2raw.labrat.one/) utility, which converts files into C++ byte arrays. 2. Select your logo file. 3. Enable **Gzip Compress** and **Use PROGMEM (Arduino)**. 4. Click **Convert**. The portal always serves the logo with `Content-Encoding: gzip`. The byte array must be gzip-compressed, or the browser cannot display the logo. ### Update `logo.h` Open `src/logo.h` in the NetWizard Pro library folder and update the following definitions: ```cpp title="logo.h" #define NETWIZARD_LOGO_WIDTH 220 // display width in px #define NETWIZARD_LOGO_HEIGHT 60 // display height in px #define NETWIZARD_LOGO_MIME "image/svg+xml" // MIME type of your image extern const uint8_t NETWIZARD_LOGO[940]; // set to the new array length ``` - Set `NETWIZARD_LOGO_MIME` to the MIME type of your image. - Set `NETWIZARD_LOGO_WIDTH` and `NETWIZARD_LOGO_HEIGHT` to the size at which the logo is displayed on the page. - Change the array length of `NETWIZARD_LOGO` to the length of the generated array. ### Update `logo.cpp` Open `src/logo.cpp` and replace the contents of `NETWIZARD_LOGO` with the generated byte array. Keep the declaration in sync with `logo.h`: ```cpp title="logo.cpp" const uint8_t NETWIZARD_LOGO[/* new length */] PROGMEM = { // generated bytes }; ``` ### Rebuild the firmware Compile and upload the firmware. The portal now shows your logo. ## Notes - `logo.h` also defines `NETWIZARD_LOGO_GZIPPED`. The current release does not read this macro; the logo is always served as gzip-compressed data. - The changes apply to the library files, so they affect every project that uses this copy of NetWizard Pro. Keep a copy of your modified files when you update the library. # Commercial License (/docs/netwizard/commercial-license) > Compare the licensing, support and features of NetWizard Lite (AGPL-3.0) and NetWizard Pro (commercial license). Library: NetWizard NetWizard is available in two editions of the same WiFi manager and captive portal library: NetWizard Lite, which is open source and licensed under AGPL-3.0, and NetWizard Pro, which is distributed under the SOFTT Commercial License. The editions differ in features, support and licensing terms. ## Comparison | | NetWizard Lite | NetWizard Pro | | --- | --- | --- | | License | Open source, AGPL-3.0 | SOFTT Commercial License | | Source-disclosure obligations | Free to use, modify and distribute. If you use NetWizard Lite in a commercial product, AGPL-3.0 requires you to make the source code of your product, including any modifications to NetWizard Lite, publicly available. | You can use NetWizard Pro in a commercial product without open-sourcing your proprietary code. | | Support | Community-driven, through online forums, documentation and user-contributed resources. Professional support may be limited. | Dedicated support and assistance. | | Features | WiFi management, captive portal, input parameters, authentication. | Adds [statistics](/docs/netwizard/statistics), [links](/docs/netwizard/links), [password input](/docs/netwizard/parameters/password-input), [toggle](/docs/netwizard/parameters/toggle) and [textarea](/docs/netwizard/parameters/textarea) parameters, integer and floating-point parameter values, a [custom title](/docs/netwizard/custom-title) and [custom branding](/docs/netwizard/branding). | | Cost model | Free | One-time license fee | ## Choosing an edition - **NetWizard Lite** fits open-source projects, and projects in which the AGPL-3.0 requirements are acceptable and basic WiFi manager functionality is sufficient. - **NetWizard Pro** fits commercial products. It provides a more comprehensive, professionally supported solution and lets you keep your code proprietary, in exchange for a one-time commercial license fee. ## Get NetWizard Pro NetWizard Pro is available on the official product page at [store.softt.io](https://store.softt.io/products/netwizard-pro). # Custom Title (/docs/netwizard/custom-title) > Change the page title of the NetWizard captive portal. Library: NetWizard Availability: NetWizard Pro only Some browsers and captive-portal windows display the page title of the portal. By default, the title is `NetWizard`. Use `setTitle()` to replace it with your product name. ![Custom captive portal title](https://docs.softt.io/img/netwizard/custom-title.png) ## Method ### `setTitle()` ```cpp void setTitle(const char* title); void setTitle(String& title); ``` Sets the portal title. Call it before `autoConnect()` or before the portal is started. ```cpp void setup() { // ... NW.setTitle("Acme Thermostat Setup"); NW.autoConnect("Acme Thermostat", ""); // ... } ``` # Async Mode (/docs/netwizard/getting-started/async-mode) > Compile NetWizard against ESPAsyncWebServer so the captive portal runs on an asynchronous web server. Library: NetWizard By default, NetWizard serves the captive portal with the core synchronous `WebServer`. In async mode, it uses `ESPAsyncWebServer` instead. Enable async mode only if your sketch uses `ESPAsyncWebServer`; the server you pass to the `NetWizard` constructor must then be an `AsyncWebServer`. ## Dependencies Async mode requires the following libraries in addition to those listed in [Installation](/docs/netwizard/getting-started/installation). - (ESP32Async) [AsyncTCP](https://github.com/ESP32Async/AsyncTCP) >= **v3.3.5** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) >= **v3.9.6** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP) >= **v1.3.1** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) >= **v3.9.6** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP) >= **v1.3.1** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer) >= **v3.9.6** Since v1.2.0, NetWizard depends on `ESP32Async/ESPAsyncWebServer`. The `ESP32Async` organization was formed by the core collaborators to maintain a single version of the library, and the improvements from the `mathieucarbou` fork have been merged into it. If you have the deprecated `me-no-dev` or `mathieucarbou` forks installed, remove them and install the `ESP32Async` libraries instead. ## Enable async mode Async mode is controlled by the `NETWIZARD_USE_ASYNC_WEBSERVER` macro. It defaults to `0`; set it to `1`. 1. Open the Arduino libraries folder and go to `NetWizard/src` (NetWizard Pro: `NetWizardPro/src`). 2. Open `NetWizard.h` (NetWizard Pro: `NetWizardPro.h`) and set the macro to `1`: ```cpp #define NETWIZARD_USE_ASYNC_WEBSERVER 1 ``` 3. Save the file. The change applies to every sketch that uses this copy of the library. Add a build flag to the environment in `platformio.ini`. The setting applies only to this project. ```ini build_flags = -DNETWIZARD_USE_ASYNC_WEBSERVER=1 ``` ## Usage In async mode, include the TCP library for your platform and `ESPAsyncWebServer.h`, then pass an `AsyncWebServer` to NetWizard. `NW.loop()` is still required. ```cpp title="AsyncDemo.ino" #if defined(ESP32) #include #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) #include #endif #include #include // NetWizard Pro: #include AsyncWebServer server(80); NetWizard NW(&server); void setup() { Serial.begin(115200); NW.autoConnect("NetWizard Demo", ""); server.on("/demo", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Hi! This is NetWizard Demo."); }); server.begin(); } void loop() { NW.loop(); } ``` ## Benefits - **Requests are handled in the background**: the asynchronous server processes HTTP requests outside your main loop instead of in `NW.loop()`. DNS processing, status updates and callbacks still run in `NW.loop()`, so keep calling it frequently. - **Responsiveness**: the device remains responsive to other tasks while requests are served. - **Compatibility**: many projects and libraries, such as [ESP-DASH](/docs/netwizard/getting-started/using-with-espdash), are built on `ESPAsyncWebServer`. Async mode lets NetWizard share the same server instance. # Example (/docs/netwizard/getting-started/example) > A complete NetWizard sketch with a synchronous web server, status callbacks and configuration parameters. Library: NetWizard The sketch below is based on the `Demo` example that ships with the library. It starts NetWizard with the blocking strategy, logs connection and portal changes, adds two configuration parameters and serves an additional route from the same web server. More examples are available in the [repository](https://github.com/ayushsharma82/NetWizard/tree/main/examples). ## Demo sketch ```cpp title="Demo.ino" #include #include // NetWizard Pro: #include WebServer server(80); // Initialize NetWizard NetWizard NW(&server); // Configuration parameters shown in the captive portal NetWizardParameter nw_header(&NW, NW_HEADER, "MQTT"); NetWizardParameter nw_divider1(&NW, NW_DIVIDER); NetWizardParameter nw_mqtt_host(&NW, NW_INPUT, "Host", "", "mqtt.example.com"); NetWizardParameter nw_mqtt_port(&NW, NW_INPUT, "Port", "", "1883"); void setup(void) { delay(3000); Serial.begin(115200); Serial.println(""); // BLOCKING: autoConnect() returns only after the captive portal has closed. // NON_BLOCKING: autoConnect() returns immediately; the portal runs in the background. NW.setStrategy(NetWizardStrategy::BLOCKING); // Called whenever the connection status changes NW.onConnectionStatus([](NetWizardConnectionStatus status) { String status_str = ""; switch (status) { case NetWizardConnectionStatus::DISCONNECTED: status_str = "Disconnected"; break; case NetWizardConnectionStatus::CONNECTING: status_str = "Connecting"; break; case NetWizardConnectionStatus::CONNECTED: status_str = "Connected"; break; case NetWizardConnectionStatus::CONNECTION_FAILED: status_str = "Connection Failed"; break; case NetWizardConnectionStatus::CONNECTION_LOST: status_str = "Connection Lost"; break; case NetWizardConnectionStatus::NOT_FOUND: status_str = "Not Found"; break; default: status_str = "Unknown"; } Serial.printf("NW connection status changed: %s\n", status_str.c_str()); }); // Called whenever the captive portal state changes NW.onPortalState([](NetWizardPortalState state) { String state_str = ""; switch (state) { case NetWizardPortalState::IDLE: state_str = "Idle"; break; case NetWizardPortalState::CONNECTING_WIFI: state_str = "Connecting to WiFi"; break; case NetWizardPortalState::WAITING_FOR_CONNECTION: state_str = "Waiting for Connection"; break; case NetWizardPortalState::SUCCESS: state_str = "Success"; break; case NetWizardPortalState::FAILED: state_str = "Failed"; break; case NetWizardPortalState::TIMEOUT: state_str = "Timeout"; break; default: state_str = "Unknown"; } Serial.printf("NW portal state changed: %s\n", state_str.c_str()); }); // Called when the user submits the configuration parameters NW.onConfig([&]() { Serial.printf("Host: %s\n", nw_mqtt_host.getValueStr().c_str()); Serial.printf("Port: %s\n", nw_mqtt_port.getValueStr().c_str()); return true; // accept the submitted values }); // Connect to the saved network, or start the captive portal NW.autoConnect("NetWizard Demo", ""); if (NW.isConfigured()) { Serial.println("Device is configured"); if (NW.getConnectionStatus() == NetWizardConnectionStatus::CONNECTED) { Serial.print("Connected to "); Serial.println(NW.getSSID()); Serial.print("IP address: "); Serial.println(NW.localIP()); } else { Serial.println("Not connected to any WiFi"); } } else { Serial.println("Device is not configured"); } // Application route server.on("/demo", HTTP_GET, []() { server.send(200, "text/plain", "Hi! This is NetWizard Demo."); }); server.begin(); } void loop(void) { server.handleClient(); // NetWizard loop task NW.loop(); } ``` ## What the sketch does - `NetWizard NW(&server)` attaches NetWizard to the web server. NetWizard registers the portal routes on this server only while the portal is active. - The four `NetWizardParameter` objects build the configuration page. See [Parameters](/docs/netwizard/parameters). - `autoConnect("NetWizard Demo", "")` connects to the saved network. If none is saved or the connection fails, it starts an open access point named `NetWizard Demo`. With the blocking strategy, the call returns after the portal closes. - `NW.loop()` must run continuously. With the synchronous `WebServer`, it also calls `server.handleClient()`, so the explicit call in `loop()` is optional. ## Notes - On RP2040 and RP2350, a LittleFS partition is required to persist credentials. See [Installation](/docs/netwizard/getting-started/installation). - For an `ESPAsyncWebServer` version of this sketch, see the `AsyncDemo` example and [Async Mode](/docs/netwizard/getting-started/async-mode). # Installation (/docs/netwizard/getting-started/installation) > Install NetWizard and its platform-specific dependencies in the Arduino IDE or PlatformIO. Library: NetWizard This page lists the dependencies NetWizard requires on each platform and describes how to install the library in the Arduino IDE and PlatformIO. ## Dependencies Install only the versions listed below. Other versions can cause NetWizard to fail to compile or to work only partially. - [ESP32 Arduino Core](https://github.com/espressif/arduino-esp32) >= **v3.3.7** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson) >= **v7.4.2** On ESP32, credentials are stored with the `Preferences` library that ships with the core. Raspberry Pi Pico W. - [Arduino Pico Core](https://github.com/earlephilhower/arduino-pico) >= **v5.5.0** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson) >= **v7.4.2** - [Preferences](https://github.com/vshymanskyy/Preferences) >= **v2.2.2** Raspberry Pi Pico 2 W. - [Arduino Pico Core](https://github.com/earlephilhower/arduino-pico) >= **v5.5.0** - [ArduinoJson](https://github.com/bblanchon/ArduinoJson) >= **v7.4.2** - [Preferences](https://github.com/vshymanskyy/Preferences) >= **v2.2.2** [Async mode](/docs/netwizard/getting-started/async-mode) requires additional libraries. On RP2040 and RP2350, credentials are stored in a LittleFS partition. Without one, NetWizard cannot persist any data. In the Arduino IDE, select a flash layout with a file system, for example **Tools > Flash Size > 2MB (Sketch 1MB, FS 1MB)**. ## Install NetWizard NetWizard Lite is published as `NetWizard`. NetWizard Pro is distributed separately; install it with the manual or ZIP method using the archive you received with your license. ### Library Manager [#library-manager] Open **Sketch > Include Library > Manage Libraries**, search for "NetWizard" and click **Install**. ### Import a ZIP library [#import-a-zip-library] 1. Download the [NetWizard repository archive](https://github.com/ayushsharma82/NetWizard/archive/master.zip). 2. Open **Sketch > Include Library > Add .ZIP Library** and select the downloaded file. ### Manual installation [#manual-installation] 1. Download the [NetWizard repository archive](https://github.com/ayushsharma82/NetWizard/archive/master.zip). 2. Extract the archive into your Arduino libraries folder: - Windows: `Documents/Arduino/libraries/` - Linux: `/libraries/` NetWizard supports several architectures. Add the following lines to your environment so that PlatformIO installs only the dependencies of your platform: ```ini lib_compat_mode = strict lib_ldf_mode = deep ; may not be required in every project ``` For ESP32, use the community-maintained `pioarduino` platform. The official PlatformIO ESP32 platform ships Arduino Core v2, which lacks features NetWizard requires: ```ini platform = https://github.com/pioarduino/platform-espressif32/releases/download/stable/platform-espressif32.zip ``` ### Library Manager [#library-manager-1] Open the PlatformIO menu, go to **Libraries**, search for "NetWizard" and add it to your project. ### Manual installation [#manual-installation-1] 1. Download the [NetWizard repository archive](https://github.com/ayushsharma82/NetWizard/archive/master.zip). 2. Extract the archive into the `lib` folder of your project. ## Include the library The header depends on the edition: ```cpp #include ``` ```cpp #include ``` Both editions expose the same `NetWizard` class, so the rest of your sketch is identical. ## Next steps - [Example](/docs/netwizard/getting-started/example): a complete sketch with callbacks and parameters. - [Integration Guide](/docs/netwizard/getting-started/integration): add NetWizard to an existing sketch. - [Async Mode](/docs/netwizard/getting-started/async-mode): use NetWizard with `ESPAsyncWebServer`. # Integration Guide (/docs/netwizard/getting-started/integration) > Add NetWizard to an existing sketch that already uses a web server. Library: NetWizard This guide adds NetWizard to an existing sketch with the minimum number of changes. It assumes you have installed NetWizard and its dependencies as described in [Installation](/docs/netwizard/getting-started/installation). ## Prepare the sketch Remove every call to `WiFi.begin()` and `WiFi.softAP()` from your sketch. NetWizard manages the WiFi mode, the station connection and the access point; direct calls interfere with its state. After integration, prefer the NetWizard methods over the `WiFi` object for connection details, for example [`NW.getSSID()`](/docs/netwizard/api/credentials#getssid), [`NW.getPassword()`](/docs/netwizard/api/credentials#getpassword) and [`NW.localIP()`](/docs/netwizard/api/network-info#localip). ## Integrate NetWizard ### Include the library Add the header at the top of the sketch: ```cpp #include // NetWizard Pro: #include ``` ### Create the NetWizard instance Create a global `NetWizard` object and pass a pointer to your web server: ```cpp NetWizard NW(&server); ``` The server type must match the mode NetWizard is compiled in: `WebServer` by default, or `AsyncWebServer` in [async mode](/docs/netwizard/getting-started/async-mode). ### Call `autoConnect()` Call [`autoConnect()`](/docs/netwizard/api/connection#autoconnect) in `setup()`. NetWizard connects to the saved network; if none exists or the connection fails, it starts the captive portal and waits for user input until the portal times out. ```cpp void setup() { // ... NW.autoConnect("NetWizard Demo", ""); // ... } ``` The two arguments are the SSID and password of the access point the device opens for the portal. An empty password creates an open access point. ### Call `loop()` Call `NW.loop()` on every iteration of `loop()`. It processes portal requests, updates the connection status and invokes callbacks. ```cpp void loop() { // ... NW.loop(); // ... } ``` This is the minimal setup. Add [callbacks](/docs/netwizard/api/callbacks), [parameters](/docs/netwizard/parameters) and [configuration](/docs/netwizard/api/configuration) as required. ## Routes used by NetWizard While the portal is active, NetWizard registers the following routes on your server. They respond only to clients connected to the device's access point, so clients on your local network continue to reach your own routes. | Route | Purpose | | --- | --- | | `/` | Portal page | | `/netwizard/*` | Portal API (status, schema, scan, save, clear, exit) and, in NetWizard Pro, the logo | NetWizard also replaces the server's not-found handler so that captive-portal detection requests are redirected to the portal. When the portal stops, NetWizard removes its routes and clears the not-found handler. If your sketch relies on a custom not-found handler, register it again after the portal has closed. ## Next steps - [Example](/docs/netwizard/getting-started/example): a complete sketch with callbacks and parameters. - [Using with ESP-DASH](/docs/netwizard/getting-started/using-with-espdash): run NetWizard alongside an ESP-DASH dashboard. # Using with ESP-DASH (/docs/netwizard/getting-started/using-with-espdash) > Run a NetWizard captive portal and an ESP-DASH dashboard on the same AsyncWebServer. Library: NetWizard [ESP-DASH](/docs/espdash/v5) is an on-device dashboard library for wireless microcontrollers that lets you display and control values in your firmware from a web interface. NetWizard and ESP-DASH can share one `AsyncWebServer`: NetWizard provides WiFi setup, and ESP-DASH provides the dashboard once the device is on the network. ESP-DASH requires `ESPAsyncWebServer`. Enable [async mode](/docs/netwizard/getting-started/async-mode) in NetWizard before compiling. ## Required changes Two changes let the libraries coexist: 1. **Serve ESP-DASH at a separate URI.** Initialize the dashboard at `/dashboard` with the [custom URI](/docs/espdash/v5/features/custom-uri) constructor, so it does not conflict with the portal page that NetWizard serves at `/` in access point mode. 2. **Rewrite `/` for station clients.** Add `server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER)` so that users on the local network who open the device's root URL see the dashboard. ## Example ```cpp title="main.cpp" #include #if defined(ESP32) #include #include #elif defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2350) #include #include #endif #include #include // NetWizard Pro: #include #include AsyncWebServer server(80); // Initialize NetWizard NetWizard NW(&server); // Configuration parameters NetWizardParameter nw_header(&NW, NW_HEADER, "MQTT"); NetWizardParameter nw_divider1(&NW, NW_DIVIDER); NetWizardParameter nw_mqtt_host(&NW, NW_INPUT, "Host", "", "mqtt.example.com"); NetWizardParameter nw_mqtt_port(&NW, NW_INPUT, "Port", "", "1883"); // Serve ESP-DASH at /dashboard so that it does not conflict with the portal ESPDash dashboard(server, "/dashboard", true); // Dashboard cards dash::TemperatureCard tempInt(dashboard, "Temperature", "°C"); dash::HumidityCard humInt(dashboard, "Humidity", "%"); void setup(void) { delay(3000); Serial.begin(115200); Serial.println(""); Serial.println("Starting NetWizard & ESP-DASH Demo..."); NW.setStrategy(NetWizardStrategy::BLOCKING); NW.onConnectionStatus([](NetWizardConnectionStatus status) { if (status == NetWizardConnectionStatus::CONNECTED) { Serial.printf("Local IP: %s\n", NW.localIP().toString().c_str()); Serial.printf("Gateway IP: %s\n", NW.gatewayIP().toString().c_str()); Serial.printf("Subnet mask: %s\n", NW.subnetMask().toString().c_str()); } }); NW.onPortalState([](NetWizardPortalState state) { Serial.printf("NW portal state changed: %d\n", (int)state); }); NW.onConfig([&]() { Serial.printf("Host: %s\n", nw_mqtt_host.getValueStr().c_str()); Serial.printf("Port: %s\n", nw_mqtt_port.getValueStr().c_str()); return true; // accept the submitted values }); // Connect to the saved network, or start the captive portal NW.autoConnect("NetWizard Demo", ""); if (NW.isConfigured()) { Serial.println("Device is configured"); } else { Serial.println("Device is not configured"); } // Application route server.on("/demo", HTTP_GET, [](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Hi! This is NetWizard Demo."); }); // Show the dashboard at / for clients on the local network server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER); server.begin(); } void loop(void) { NW.loop(); } ``` The [Example](/docs/netwizard/getting-started/example) page shows complete `onConnectionStatus` and `onPortalState` handlers that convert each value to a readable string. ## Next steps - [ESP-DASH Cards](/docs/espdash/v5/cards/generic): add widgets to the dashboard. - [Callbacks](/docs/netwizard/api/callbacks): react to connection and portal changes. # Introduction (/docs/netwizard) > NetWizard is a WiFi manager and captive portal library for ESP32, RP2040 and RP2350 that removes the need to hard-code WiFi credentials. Library: NetWizard NetWizard is a WiFi manager library for wireless microcontrollers. Instead of compiling WiFi credentials into the firmware, the device starts a captive portal when it has no saved network or cannot reach it. The user connects to the device's access point, selects a network, enters the password and the device stores the credentials for subsequent boots. NetWizard handles the connection, reconnection and portal lifecycle; your sketch interacts with it through a small C++ API. GitHub repository: https://github.com/ayushsharma82/NetWizard ![NetWizard captive portal setup flow](https://docs.softt.io/img/netwizard/netwizard-lite-preview.gif) ## Why use a WiFi manager Hard-coded credentials are impractical when you: - develop a product that end users connect to their own networks, - move a device between different WiFi networks frequently, or - want to change networks without recompiling and re-flashing the firmware. ## Key capabilities - **No hard-coded credentials**: credentials entered in the portal are stored on the device and reused at boot. - **Captive portal**: the setup page opens automatically when a phone or computer joins the device's access point. - **Step-by-step setup flow**: the portal scans for networks, tests the submitted credentials and reports the result to the user. - **Dynamic configuration page**: declare [parameters](/docs/netwizard/parameters) in C++, such as MQTT host and port, and they appear in the portal for the user to fill in. No HTML, CSS or JavaScript is required. - **Blocking and non-blocking strategies**: wait for setup to finish, or keep your application running while the portal is active. See [`setStrategy()`](/docs/netwizard/api/configuration#setstrategy). - **Synchronous and asynchronous web servers**: works with the core `WebServer` or, in [async mode](/docs/netwizard/getting-started/async-mode), with `ESPAsyncWebServer`. - **Minimal integration**: a working setup needs a constructor, a call to `autoConnect()` and a call to `loop()`. ## Supported boards - **ESP32** - **RP2040** (Raspberry Pi Pico W) - **RP2350** (Raspberry Pi Pico 2 W) A bare RP2040 or RP2350 without a wireless chip is not supported. See [Installation](/docs/netwizard/getting-started/installation) for the required core and library versions. ## How it works 1. `autoConnect()` loads the saved credentials. If credentials exist, NetWizard connects to that network and retries until the connect timeout (30 seconds by default) expires. 2. If no credentials are saved, or the connection fails, NetWizard starts an access point with the SSID and password you pass to `autoConnect()`, together with a DNS server and the portal web interface. 3. The user selects a network and submits the password and any configuration parameters. NetWizard tests the connection and, on success, saves the credentials. 4. The portal closes when the user exits it, when it times out (5 minutes without a connected client by default) or when you stop it. NetWizard then connects to the saved network in station mode. 5. `NW.loop()`, called from your `loop()`, keeps the portal, the connection status and the callbacks running. ## NetWizard Lite and NetWizard Pro This documentation covers both editions of NetWizard. NetWizard Lite is open source and licensed under AGPL-3.0. NetWizard Pro is a commercial edition that adds [statistics](/docs/netwizard/statistics), [links](/docs/netwizard/links), additional parameter types, a [custom portal title](/docs/netwizard/custom-title) and [custom branding](/docs/netwizard/branding). Pages for features that are available only in NetWizard Pro are marked accordingly. See [Commercial License](/docs/netwizard/commercial-license) for a comparison, or visit [store.softt.io](https://store.softt.io/products/netwizard-pro). ![NetWizard Pro captive portal with custom logo, statistics and a link](https://docs.softt.io/img/netwizard/pro-preview.png) ## Explore the documentation # Links (/docs/netwizard/links) > Add buttons to the NetWizard captive portal that open other pages served by the device in access point mode. Library: NetWizard Availability: NetWizard Pro only Links add buttons to the home page of the captive portal that navigate to other pages served by your device. Use them to give users access to, for example, an [ElegantOTA](/docs/elegantota) firmware update page or your application's own setup page while the device is in access point mode. Links appear in declaration order between the **Setup** and **Exit** buttons. You can add as many links as you need. ![Links in the NetWizard captive portal](https://docs.softt.io/img/netwizard/links.png) ## Constructor ```cpp NetWizardLink(NetWizard* parent, const char* label, const char* url = "#"); ``` ```cpp NetWizard NW(&server); NetWizardLink nw_link(&NW, "Firmware Update", "/update"); ``` The label and URL are copied. A link removes itself from the portal when it is destroyed. ## Methods ### `setLabel()` ```cpp void setLabel(const char* label); ``` Changes the button text. ### `setURL()` ```cpp void setURL(const char* url); ``` Changes the destination URL. ## Notes - The destination route must be reachable from clients connected to the access point. NetWizard does not register it; serve it from your own sketch on the same web server. # Parameters (/docs/netwizard/parameters) > Build a configuration page for the captive portal in C++ with NetWizardParameter, and read the submitted values in the onConfig callback. Library: NetWizard Parameters let you collect application settings during WiFi setup, such as MQTT credentials, user data or device options. Each `NetWizardParameter` you declare appears as a field on the configuration page of the captive portal, in declaration order. You can add as many parameters as you need and control the layout with headers, dividers and spacers. ![Configuration page in the NetWizard captive portal](https://docs.softt.io/img/netwizard/parameters.png) The configuration page is hidden automatically when the sketch defines no parameters. ## Parameter types | Type | Constant | Edition | | --- | --- | --- | | [Header](/docs/netwizard/parameters/layout#header) | `NW_HEADER` | Lite and Pro | | [Divider](/docs/netwizard/parameters/layout#divider) | `NW_DIVIDER` | Lite and Pro | | [Spacer](/docs/netwizard/parameters/layout#spacer) | `NW_SPACER` | Lite and Pro | | [Input](/docs/netwizard/parameters/input) | `NW_INPUT` | Lite and Pro | | [Password Input](/docs/netwizard/parameters/password-input) | `NW_PASSWORD_INPUT` | Pro | | [Toggle](/docs/netwizard/parameters/toggle) | `NW_TOGGLE` | Pro | | [Textarea](/docs/netwizard/parameters/textarea) | `NW_TEXTAREA` | Pro | ## Constructor ```cpp NetWizardParameter(NetWizard* parent, const int type, const char* name = "", const char* value = "", const char* placeholder = "", bool required = true); ``` The constructor is overloaded so that trailing arguments can be omitted; the defaults are shown above. NetWizard Pro adds overloads that take an `int` or `float` initial value instead of `const char*`, for example for [toggles](/docs/netwizard/parameters/toggle): ```cpp NetWizardParameter(NetWizard* parent, const int type, const char* name, int value, const char* placeholder = "", bool required = true); NetWizardParameter(NetWizard* parent, const int type, const char* name, float value, const char* placeholder = "", bool required = true); ``` Declare parameters globally, after the `NetWizard` instance: ```cpp NetWizard NW(&server); NetWizardParameter nw_host(&NW, NW_INPUT, "MQTT Host", "", "mqtt.example.com"); void setup() { // ... } ``` A parameter registers itself with `NetWizard` when it is constructed and removes itself when it is destroyed. To add or remove a parameter at runtime, create it with `new` and remove it with `delete`. ## Required and optional parameters Optional parameters are available in NetWizard v1.2.0 and later. All parameters are required by default: the user must fill in every input before finishing the setup. To make a parameter optional, pass `false` as the sixth constructor argument: ```cpp NetWizardParameter nw_name(&NW, NW_INPUT, "Name", "Value", "Placeholder", false); ``` You can also change the setting at runtime with `setRequired()`: ```cpp nw_name.setRequired(false); ``` ## Receive submitted values When the user finishes the setup in the portal, NetWizard writes the submitted values to the corresponding `NetWizardParameter` objects and then calls the `onConfig` callback. ```cpp void onConfig(std::function callback); ``` Return `true` to accept the submission or `false` to reject it. When the callback returns `false`, the portal reports the request as invalid. If no callback is registered, every submission is accepted. ```cpp void setup() { // ... NW.onConfig([&]() { String host = nw_host.getValueStr(); if (host.length() == 0) { return false; // reject the submission } // validate, process or store the values return true; // accept the submission }); // ... } ``` Parameter values are held in RAM only and are lost when the device restarts. Save them to persistent storage in your `onConfig` callback if your application needs them after a reboot. The submitted values are written to the parameters before the callback runs, and they are not reverted when the callback returns `false`. ## Methods ### `getValueStr()` ```cpp const String& getValueStr(); ``` Returns the value of a text-based parameter. ### `getValue()` ```cpp void getValue(String& value); void getValue(int& value); // NetWizard Pro void getValue(float& value); // NetWizard Pro ``` Copies the value into `value`. The no-argument overload `const String& getValue()` is deprecated; use `getValueStr()` instead. ### `getValueInt()` and `getValueFloat()` ```cpp int getValueInt(); // NetWizard Pro float getValueFloat(); // NetWizard Pro ``` Return the value of a parameter created with an `int` or `float` value. ### `setValue()` ```cpp void setValue(const String& value); void setValue(const char* value); void setValue(int value); // NetWizard Pro void setValue(float value); // NetWizard Pro ``` Sets the value shown in the portal. ### `getPlaceholder()` and `setPlaceholder()` ```cpp String getPlaceholder(); void setPlaceholder(const char* placeholder); ``` Read or change the placeholder text. ### `setRequired()` ```cpp void setRequired(bool required); ``` Marks the parameter as required (`true`) or optional (`false`). ### `getType()` ```cpp int getType(); ``` Returns the parameter type constant, for example `NW_INPUT`. ## Notes - The parameter stores the `name` pointer and does not copy the string. Pass a string literal or a buffer that outlives the parameter. - Only parameters you declare appear on the configuration page; WiFi credentials are handled separately by the portal. # Input (/docs/netwizard/parameters/input) > Collect a single line of text, such as a host name or user name, on the NetWizard configuration page. Library: NetWizard An input parameter displays a single-line text field on the configuration page. Use it to collect application settings such as server addresses, user names or identifiers. ![Input parameter](https://docs.softt.io/img/netwizard/nw-input.png) ## Constructor ```cpp NetWizardParameter nw_input(&NW, NW_INPUT, name, value, placeholder, required); ``` ```cpp NetWizardParameter nw_host(&NW, NW_INPUT, "MQTT Host", "", "mqtt.example.com"); ``` ## Methods ### `getValueStr()` ```cpp String val = nw_host.getValueStr(); ``` Returns the current value. ### `getValue()` ```cpp String val; nw_host.getValue(val); ``` Copies the current value into a `String`. ### `setValue()` ```cpp nw_host.setValue("broker.example.com"); ``` Sets the value shown in the portal. See [Parameters](/docs/netwizard/parameters#methods) for the methods shared by all parameter types. ## Example ```cpp title="main.cpp" #include #include WebServer server(80); NetWizard NW(&server); NetWizardParameter nw_host(&NW, NW_INPUT, "MQTT Host", "", "mqtt.example.com"); void setup() { Serial.begin(115200); NW.onConfig([&]() { Serial.printf("MQTT host: %s\n", nw_host.getValueStr().c_str()); return true; }); NW.autoConnect("NetWizard Demo", ""); server.begin(); } void loop() { NW.loop(); } ``` # Header, Divider and Spacer (/docs/netwizard/parameters/layout) > Structure the NetWizard configuration page with header, divider and spacer parameters. Library: NetWizard Header, divider and spacer parameters do not collect input or hold a value. Use them to split the configuration page into sections. Like all parameters, they appear in declaration order. ## Header A header displays a section title on the configuration page. ![Header parameter](https://docs.softt.io/img/netwizard/nw-header.png) ```cpp NetWizardParameter nw_header(&NW, NW_HEADER, "MQTT"); ``` ## Divider A divider draws a horizontal line between parameters. ![Divider parameter](https://docs.softt.io/img/netwizard/nw-divider.png) ```cpp NetWizardParameter nw_divider(&NW, NW_DIVIDER); ``` ## Spacer A spacer adds empty vertical space between parameters. ```cpp NetWizardParameter nw_spacer(&NW, NW_SPACER); ``` ## Example ```cpp title="main.cpp" #include #include WebServer server(80); NetWizard NW(&server); NetWizardParameter nw_header(&NW, NW_HEADER, "MQTT"); NetWizardParameter nw_divider(&NW, NW_DIVIDER); NetWizardParameter nw_host(&NW, NW_INPUT, "Host", "", "mqtt.example.com"); NetWizardParameter nw_spacer(&NW, NW_SPACER); NetWizardParameter nw_port(&NW, NW_INPUT, "Port", "", "1883"); void setup() { NW.autoConnect("NetWizard Demo", ""); server.begin(); } void loop() { NW.loop(); } ``` # Password Input (/docs/netwizard/parameters/password-input) > Collect sensitive text, such as passwords or API keys, in a masked field on the NetWizard configuration page. Library: NetWizard Availability: NetWizard Pro only A password input works like an [input](/docs/netwizard/parameters/input) but masks the text the user enters. Use it for passwords, tokens and other sensitive settings. ![Password input parameter](https://docs.softt.io/img/netwizard/nw-password-input.png) ## Constructor ```cpp NetWizardParameter nw_password(&NW, NW_PASSWORD_INPUT, name, value, placeholder, required); ``` ```cpp NetWizardParameter nw_mqtt_password(&NW, NW_PASSWORD_INPUT, "MQTT Password", "", "Enter password"); ``` ## Methods ### `getValueStr()` ```cpp String val = nw_mqtt_password.getValueStr(); ``` Returns the current value. ### `getValue()` ```cpp String val; nw_mqtt_password.getValue(val); ``` Copies the current value into a `String`. ### `setValue()` ```cpp nw_mqtt_password.setValue("secret"); ``` Sets the value of the field. See [Parameters](/docs/netwizard/parameters#methods) for the methods shared by all parameter types. ## Example ```cpp title="main.cpp" #include #include WebServer server(80); NetWizard NW(&server); NetWizardParameter nw_mqtt_user(&NW, NW_INPUT, "MQTT User", "", "user"); NetWizardParameter nw_mqtt_password(&NW, NW_PASSWORD_INPUT, "MQTT Password", "", "Enter password"); void setup() { NW.onConfig([&]() { // store nw_mqtt_user.getValueStr() and nw_mqtt_password.getValueStr() return true; }); NW.autoConnect("NetWizard Demo", ""); server.begin(); } void loop() { NW.loop(); } ``` # Textarea (/docs/netwizard/parameters/textarea) > Collect multi-line text, such as certificates or keys, on the NetWizard configuration page. Library: NetWizard Availability: NetWizard Pro only A textarea parameter displays a multi-line text field on the configuration page. Use it for input that is best entered over several lines, such as TLS certificates, keys or other structured data. ![Textarea parameter](https://docs.softt.io/img/netwizard/nw-textarea.png) ## Constructor ```cpp NetWizardParameter nw_textarea(&NW, NW_TEXTAREA, name, value, placeholder, required); ``` ```cpp NetWizardParameter nw_root_ca(&NW, NW_TEXTAREA, "Root CA", "", "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"); ``` ## Methods ### `getValueStr()` ```cpp String val = nw_root_ca.getValueStr(); ``` Returns the current value. ### `getValue()` ```cpp String val; nw_root_ca.getValue(val); ``` Copies the current value into a `String`. ### `setValue()` ```cpp nw_root_ca.setValue("-----BEGIN CERTIFICATE-----\n..."); ``` Sets the value of the field. See [Parameters](/docs/netwizard/parameters#methods) for the methods shared by all parameter types. ## Example ```cpp title="main.cpp" #include #include WebServer server(80); NetWizard NW(&server); NetWizardParameter nw_root_ca(&NW, NW_TEXTAREA, "Root CA", "", "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"); void setup() { NW.onConfig([&]() { // validate and store nw_root_ca.getValueStr() return true; }); NW.autoConnect("NetWizard Demo", ""); server.begin(); } void loop() { NW.loop(); } ``` # Toggle (/docs/netwizard/parameters/toggle) > Add an on/off switch to the NetWizard configuration page and read its state as an integer. Library: NetWizard Availability: NetWizard Pro only A toggle parameter displays a switch on the configuration page. Its value is an integer: `1` when the switch is on and `0` when it is off. Use it for boolean settings such as enabling TLS or a feature flag. ![Toggle parameter](https://docs.softt.io/img/netwizard/nw-toggle.png) ## Constructor ```cpp NetWizardParameter nw_toggle(&NW, NW_TOGGLE, name, value); ``` ```cpp NetWizardParameter nw_use_tls(&NW, NW_TOGGLE, "Use TLS", 0); ``` Pass the initial state as an `int` literal (`0` or `1`) so that the integer overload of the constructor is selected. ## Methods ### `getValueInt()` ```cpp int val = nw_use_tls.getValueInt(); ``` Returns the current state. ### `getValue()` ```cpp int val; nw_use_tls.getValue(val); ``` Copies the current state into an `int`. ### `setValue()` ```cpp nw_use_tls.setValue(1); ``` Sets the state of the switch. Read toggle values with `getValueInt()` or `getValue(int&)`. Do not use `getValueStr()` on a toggle: for integer values it returns a reference to a temporary `String`. ## Example ```cpp title="main.cpp" #include #include WebServer server(80); NetWizard NW(&server); NetWizardParameter nw_use_tls(&NW, NW_TOGGLE, "Use TLS", 1); void setup() { Serial.begin(115200); NW.onConfig([&]() { Serial.printf("Use TLS: %d\n", nw_use_tls.getValueInt()); return true; }); NW.autoConnect("NetWizard Demo", ""); server.begin(); } void loop() { NW.loop(); } ``` # Statistics (/docs/netwizard/statistics) > Show application-specific key-value pairs on the home page of the NetWizard captive portal. Library: NetWizard Availability: NetWizard Pro only Statistics display key-value pairs on the home page of the captive portal, below the built-in connection details. Use them to show application-specific information during setup, such as the firmware version, a serial number or sensor readings. Statistics appear in declaration order, and you can add as many as you need. ![Statistics in the NetWizard captive portal](https://docs.softt.io/img/netwizard/statistics.png) ## Constructor ```cpp NetWizardStatistic(NetWizard* parent, const char* label, const char* value = ""); ``` ```cpp NetWizard NW(&server); NetWizardStatistic nw_stat(&NW, "Firmware", "v1.0.0"); ``` The label and value are copied, so you can pass temporary strings. A statistic removes itself from the portal when it is destroyed. ## Methods ### `setLabel()` ```cpp void setLabel(const char* label); ``` Changes the key. ### `setValue()` ```cpp void setValue(const char* value); ``` Changes the value. ## Example ```cpp title="main.cpp" #include #include WebServer server(80); NetWizard NW(&server); NetWizardStatistic nw_firmware(&NW, "Firmware", "v1.0.0"); NetWizardStatistic nw_uptime(&NW, "Uptime"); void setup() { NW.setStrategy(NetWizardStrategy::NON_BLOCKING); NW.autoConnect("NetWizard Demo", ""); server.begin(); } void loop() { static unsigned long last = 0; if (millis() - last > 1000) { last = millis(); nw_uptime.setValue(String(millis() / 1000).c_str()); } NW.loop(); } ``` # Using with Other SOFTT Libraries (/docs/netwizard/using-with-other-libraries) > Combine NetWizard with ESP-DASH, ElegantOTA and WebSerial in the same project, sharing one AsyncWebServer. Library: NetWizard ESP-DASH, ElegantOTA, NetWizard and WebSerial are designed to work together in the same project. A single sketch can provide WiFi provisioning (NetWizard), a real-time dashboard (ESP-DASH), over-the-air updates (ElegantOTA) and a remote serial terminal (WebSerial), all served by one `AsyncWebServer` instance. The Lite and Pro editions of each library can be mixed freely. ## Requirements - **One shared `AsyncWebServer`.** Create a single `AsyncWebServer server(80);` and pass it to every library. Call `server.begin()` once, after all libraries are initialized. - **Async mode for ElegantOTA and NetWizard.** ESP-DASH and WebSerial only run on `ESPAsyncWebServer`. ElegantOTA and NetWizard default to the core `WebServer`, so enable their async mode by setting `ELEGANTOTA_USE_ASYNC_WEBSERVER=1` and `NETWIZARD_USE_ASYNC_WEBSERVER=1`. See [ElegantOTA async mode](/docs/elegantota/getting-started/async-mode) and [NetWizard async mode](/docs/netwizard/getting-started/async-mode). - **One copy of each dependency.** Install a single copy of `ESPAsyncWebServer` and `AsyncTCP` (ESP32) or `RPAsyncTCP` (RP2040/RP2350) whose version satisfies the highest minimum listed on the installation page of each library you use. Do not install two forks of `ESPAsyncWebServer` side by side. - **A board that every library supports.** ESP32 is supported by all four libraries. Check each library's supported boards before targeting RP2040, RP2350 or ESP8266: for example, ESP-DASH v5 and NetWizard do not support ESP8266. ## Default paths Each library registers its own routes on the shared server. The defaults do not collide, except for ESP-DASH and the NetWizard portal, which both use `/`: | Library | Default path | How to change it | | --- | --- | --- | | ESP-DASH | `/` | Pass a URI to the constructor: `ESPDash dashboard(server, "/dashboard", true);` ([custom URI](/docs/espdash/v5/features/custom-uri)) | | ElegantOTA | `/update` | Fixed | | WebSerial | `/webserial` | Pass a path to `begin()`: `WebSerial.begin(&server, "/terminal");` | | NetWizard | `/` (captive portal, access point clients only) | Fixed | When NetWizard is part of the project, serve ESP-DASH at `/dashboard` and add `server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER);` so that clients on the local network still see the dashboard at the device's root URL. See [NetWizard with ESP-DASH](/docs/netwizard/getting-started/using-with-espdash). ## Example The sketch below combines all four libraries on an ESP32. Build it with `-D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 -D NETWIZARD_USE_ASYNC_WEBSERVER=1`, or set both macros in the library headers as described on the async mode pages. ```cpp title="main.cpp" #include #include #include #include #include // NetWizard Pro: #include #include // ElegantOTA Pro: #include AsyncWebServer server(80); // WiFi provisioning: captive portal at / for access point clients NetWizard NW(&server); // Dashboard at /dashboard, so it does not conflict with the portal ESPDash dashboard(server, "/dashboard", true); dash::TemperatureCard temperature(dashboard, "Temperature"); unsigned long lastUpdate = 0; void setup() { Serial.begin(115200); // Connect to the saved network, or start the captive portal NW.setStrategy(NetWizardStrategy::BLOCKING); NW.autoConnect("My Device", ""); // Over-the-air updates at /update ElegantOTA.begin(&server); // Remote serial terminal at /webserial WebSerial.begin(&server); WebSerial.onMessage([](const String& msg) { Serial.println(msg); }); // Show the dashboard at / for clients on the local network server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER); server.begin(); } void loop() { NW.loop(); ElegantOTA.loop(); WebSerial.loop(); if (millis() - lastUpdate > 3000) { lastUpdate = millis(); temperature.setValue((int)random(0, 50)); dashboard.sendUpdates(); WebSerial.println("Dashboard updated"); } } ``` Each library's `loop()` method must be called on every iteration, and `loop()` must not block, so that the portal, OTA updates and buffered terminal output keep running alongside the dashboard. # AI Tools (/docs/webserial/ai-tools) > Use the WebSerial documentation from AI coding assistants through the MCP server, llms.txt and Markdown page exports. Library: WebSerial The WebSerial documentation is available in machine-readable formats so that AI coding assistants can answer questions and generate code from the current API instead of outdated training data. ## Ask AI Select **Ask AI** at the bottom of any documentation page to open the documentation assistant. It searches these pages before answering and links to the sources it used. Answers are grounded in the documentation only; verify generated code against the referenced pages. ## MCP server The documentation is served as a [Model Context Protocol](https://modelcontextprotocol.io) server over Streamable HTTP. No authentication is required. One server covers every SOFTT library (ESP-DASH, ElegantOTA, NetWizard and WebSerial); pass `library: "webserial"` to limit results to WebSerial. ```text https://docs.softt.io/api/mcp ``` The server exposes three read-only tools: | Tool | Description | | --- | --- | | `search_docs` | Full-text search across the documentation. Accepts `query`, an optional `library` (`espdash`, `elegantota`, `netwizard`, `webserial` or `all`), an optional `version` for ESP-DASH (`v5`, `v4` or `all`) and `limit`. | | `get_page` | Returns the complete Markdown of a page, such as `/docs/webserial/callbacks`. | | `list_pages` | Lists pages with their title, description and URL, filtered by `library` and `version`. | ### Connect a client ```bash claude mcp add --transport http softt https://docs.softt.io/api/mcp ``` Add the server to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` for all projects: ```json title=".cursor/mcp.json" { "mcpServers": { "softt": { "url": "https://docs.softt.io/api/mcp" } } } ``` Add the server to `.vscode/mcp.json` in your workspace: ```json title=".vscode/mcp.json" { "servers": { "softt": { "type": "http", "url": "https://docs.softt.io/api/mcp" } } } ``` Clients that support Streamable HTTP connect directly to the server URL. For clients that only support stdio, use the [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) bridge: ```json { "softt": { "command": "npx", "args": ["-y", "mcp-remote", "https://docs.softt.io/api/mcp"] } } ``` Once connected, ask your assistant questions such as "Add WebSerial to my sketch and handle a "reboot" command from the terminal" and it will retrieve the relevant pages before writing code. ## llms.txt The site publishes [llms.txt](https://llmstxt.org) files for tools that ingest documentation directly: | URL | Contents | | --- | --- | | [`/llms.txt`](/llms.txt) | Index of every page with its title, description and link. | | [`/llms-full.txt`](/llms-full.txt) | The complete documentation of every library and version as a single Markdown file. | ## Markdown pages Every page is also available as Markdown: - Append `.md` to a page URL, for example [`/docs/webserial/callbacks.md`](/docs/webserial/callbacks.md). - Requests with an `Accept: text/markdown` header receive Markdown instead of HTML. - Use **Copy Markdown** at the top of a page to copy it to the clipboard, or **Open** to send it to an assistant such as ChatGPT or Claude. # API Reference (/docs/webserial/api-reference) > Reference for the public methods, callback types and configuration macros of the WebSerial global object in WebSerial Lite and WebSerial Pro. Library: WebSerial WebSerial provides a single global object, `WebSerial`, declared by `WebSerial.h` (WebSerial Lite) or `WebSerialPro.h` (WebSerial Pro). The object derives from the Arduino `Print` class. This page lists every public method and configuration macro. ## Setup ### `begin()` Attaches WebSerial to an `AsyncWebServer` instance and registers the terminal page and the WebSocket handler. Call it in `setup()` before `server.begin()`. ```cpp void begin(AsyncWebServer *server, const char* url = "/webserial"); ``` In WebSerial Lite, the WebSocket endpoint is always `/wserial`. In WebSerial Pro, the WebSocket and the logo routes are derived from `url`: a leading `/` is added and a trailing `/` is removed if necessary, the WebSocket uses the same path, and the logos are served at `/l/light` and `/l/dark`. ### `loop()` Performs housekeeping in the default (buffered) mode: flushes buffered output and periodically removes disconnected clients. Call it on every iteration of `loop()`, after `begin()` has been called. It has no effect in [High Performance Mode](/docs/webserial/high-performance). ```cpp void loop(); ``` ## Printing `WebSerial` inherits `print()`, `println()` and `printf()` from `Print` and implements both `write()` overloads. See [Printing](/docs/webserial/printing). ```cpp size_t write(uint8_t); size_t write(const uint8_t* buffer, size_t size); ``` ## Receiving messages ### `onMessage()` Registers the callback invoked when a message is received from the terminal. See [Callbacks](/docs/webserial/callbacks). ```cpp void onMessage(WSLMessageHandler recv); void onMessage(WSLStringMessageHandler recv); ``` | Type | Definition | | --- | --- | | `WSLMessageHandler` | `std::function` | | `WSLStringMessageHandler` | `std::function` | ## Security ### `setAuthentication()` Enables HTTP Basic Authentication for the terminal page and the WebSocket connection. See [Authentication](/docs/webserial/authentication). ```cpp void setAuthentication(const char* username, const char* password); void setAuthentication(const String& username, const String& password); ``` ## Connection status ### `getConnectionCount()` Available in WebSerial Lite only. Reports whether clients are connected to the terminal. ```cpp bool getConnectionCount(); ``` Despite its name, `getConnectionCount()` returns a `bool`: `true` when at least one client is connected and `false` otherwise (including before `begin()` has been called). ## High Performance Mode These methods are used with [High Performance Mode](/docs/webserial/high-performance). | Method | Description | | --- | --- | | `void setBuffer(size_t initialCapacity)` | Enables a line buffer with the given initial capacity (maximum 65535 bytes; `0` disables it). Has no effect unless `WSL_HIGH_PERF` is defined. | | `AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0)` | Allocates a WebSocket message buffer. Only declared when `WSL_HIGH_PERF` is defined. | | `void send(AsyncWebSocketMessageBuffer* buffer)` | Sends a buffer allocated with `makeBuffer()` to all connected clients. Only declared when `WSL_HIGH_PERF` is defined. | ## WebSerial Pro methods The following methods are available in WebSerial Pro only. | Method | Description | | --- | --- | | `void setTitle(const char* title)` | Sets the title displayed at the top of the terminal. See [Custom Title](/docs/webserial/custom-title). | | `void setID(const char* id)` | Sets the hardware ID displayed at the bottom of the terminal. See [Hardware ID](/docs/webserial/hardware-id). | | `void enableInput()` | Enables the terminal input bar. See [Toggle Input](/docs/webserial/toggle-input). | | `void disableInput()` | Disables the terminal input bar. | | `void setInput(bool enable)` | Enables or disables the terminal input bar. | ## Configuration macros Define these macros as build flags (for example `-D WSL_BUFFER_SIZE=4096` in `platformio.ini`) to change the default behavior. Buffer and timing macros apply to the default (buffered) mode only. WebSerial reports a compile-time error if a buffer or timing macro is outside its allowed range. # Authentication (/docs/webserial/authentication) > Restrict access to the WebSerial terminal with HTTP Basic Authentication using a username and password. Library: WebSerial WebSerial supports HTTP Basic Authentication. Enable it to ensure that only authorized users can view the logs of your device and send commands to it. ## Enable authentication Call `setAuthentication()` with a username and password: ```cpp WebSerial.setAuthentication("username", "password"); ``` When authentication is enabled, WebSerial requires the credentials both for the terminal page and for the WebSocket connection that carries the log output. ## Example ```cpp title="main.cpp" #include // ... void setup() { // ... WebSerial.begin(&server); // Require credentials to access the terminal WebSerial.setAuthentication("myUsername", "myPassword"); server.begin(); } void loop() { // ... WebSerial.loop(); } ``` ## Notes - Authentication is enabled only when both the username and the password are non-empty. Pass empty strings to disable it. - `setAuthentication()` can be called before or after `begin()`. - HTTP Basic Authentication does not encrypt traffic. Use strong, unique credentials for each device and do not reuse default or easily guessable credentials. # Branding (/docs/webserial/branding) > Replace the default WebSerial Pro terminal logo with your own light and dark logos by editing the logo.h and logo.cpp library files. Library: WebSerial Availability: WebSerial Pro only Branding refers to the logo displayed on the WebSerial Pro terminal. The logo is stored as byte arrays in the `logo.h` and `logo.cpp` files of the library source, so you can replace it without modifying any HTML or CSS. ![Custom logo on the terminal](https://docs.softt.io/img/webserial/branding.png) ## Replace the logo ### Prepare the logos Create or obtain the logo you want to display. Use SVG (recommended), PNG or JPG, and optimize the image to keep the file size small. Prepare two versions of the logo, one for each theme. The light-theme logo should use darker colors and the dark-theme logo lighter colors, so that the logo remains legible in both themes. ### Convert the logos Convert both logo images into byte arrays. The converter below generates ready-to-use `logo.h` and `logo.cpp` files with gzip-compressed data: (Interactive logo converter available at https://docs.softt.io/docs/webserial/branding) To convert the images manually instead, use the [File2Raw](https://ayushsharma82.github.io/file2raw/) utility: 1. Open [File2Raw](https://ayushsharma82.github.io/file2raw/) and upload the image file. 2. Select **Gzip Compress** and **Use PROGMEM (Arduino)**. 3. Click **Convert** and copy the generated data. Repeat the procedure for the light-theme and the dark-theme logo. The conversion utility is provided as is, without warranty or liability of any kind. ### Replace the logo data in the WebSerial Pro library Update the logo files in the `src` folder of the WebSerial Pro library: 1. In `logo.cpp`, replace the contents of `WEBSERIAL_LIGHT_LOGO` and `WEBSERIAL_DARK_LOGO` with the generated data. 2. In `logo.h`, update the array sizes in the `extern` declarations of `WEBSERIAL_LIGHT_LOGO` and `WEBSERIAL_DARK_LOGO` to match the new data. 3. In `logo.h`, set `WEBSERIAL_LIGHT_LOGO_MIME` and `WEBSERIAL_DARK_LOGO_MIME` to the MIME type of each image, for example `"image/svg+xml"` or `"image/png"`. 4. Keep `WEBSERIAL_LOGO_GZIPPED` set to `1` when the data is gzip-compressed, or set it to `0` for uncompressed data. ```cpp title="logo.h" #define WEBSERIAL_LOGO_GZIPPED 1 #define WEBSERIAL_LOGO_WIDTH 200u #define WEBSERIAL_LOGO_HEIGHT 25u #define WEBSERIAL_LIGHT_LOGO_MIME "image/svg+xml" extern const uint8_t WEBSERIAL_LIGHT_LOGO[1075]; #define WEBSERIAL_DARK_LOGO_MIME "image/svg+xml" extern const uint8_t WEBSERIAL_DARK_LOGO[1075]; ``` ### Upload the firmware Compile the sketch with the modified WebSerial Pro library and upload the firmware to your device. Browsers cache the logo for up to 15 minutes. Clear the browser cache or perform a hard refresh to see the new logo immediately. ### Review the changes Open the terminal in a browser. The terminal switches between the light and dark logos according to the active theme. To adjust the logo size, change `WEBSERIAL_LOGO_WIDTH` and `WEBSERIAL_LOGO_HEIGHT` (in pixels) in `logo.h` and upload the firmware again. Clear the browser cache after each change. Logos and other branding elements may be subject to copyright and trademark law. Ensure that you have the necessary rights or permissions to use any branding elements in your project. # Callbacks (/docs/webserial/callbacks) > Handle commands and messages sent from the WebSerial terminal with the onMessage callback, as a String or as raw bytes. Library: WebSerial WebSerial provides a single callback, `onMessage()`. It notifies your firmware whenever a user sends a command or message from the terminal input bar. If no callback is registered, received messages are discarded. ## `onMessage()` Register the callback in `setup()`. You can receive messages either as a `String` or as a byte buffer. Only one callback is active at a time. If you register both variants, the callback registered last takes precedence. ### Receive messages as a `String` ```cpp WebSerial.onMessage([](const String& msg) { Serial.println(msg); }); ``` The `String` variant is invoked only for non-empty messages. ### Receive messages as raw bytes ```cpp WebSerial.onMessage([](uint8_t *data, size_t len) { Serial.printf("Received %u bytes from WebSerial: ", len); Serial.write(data, len); Serial.println(); }); ``` ## Callback types | Type | Definition | | --- | --- | | `WSLMessageHandler` | `std::function` | | `WSLStringMessageHandler` | `std::function` | ## Example The following callback parses simple commands and responds in the terminal: ```cpp WebSerial.onMessage([](const String& msg) { if (msg.startsWith("uptime")) { WebSerial.printf("Uptime: %lums\n", millis()); } else if (msg.startsWith("ip")) { WebSerial.println(WiFi.localIP()); } else { WebSerial.println("Unknown command: " + msg); } }); ``` The terminal appends a null byte (`\0`) to each message, and this byte is included in `len` and in the `String`. Compare commands with `startsWith()` or strip the trailing null byte before using `==`. In WebSerial Pro, the callback is not invoked while the input bar is disabled. See [Toggle Input](/docs/webserial/toggle-input). # Commercial License (/docs/webserial/commercial-license) > Compare the licensing, support and features of WebSerial Lite (AGPL-3.0) and WebSerial Pro (commercial license). Library: WebSerial WebSerial is available in two editions of the same remote terminal library: WebSerial Lite, which is open source and licensed under AGPL-3.0, and WebSerial Pro, which is distributed under the SOFTT Commercial License. The editions differ in features, support and licensing terms. ## Comparison | | WebSerial Lite | WebSerial Pro | | --- | --- | --- | | License | Open source, AGPL-3.0 | Commercial license | | Source-disclosure obligations | Free to use, modify and distribute. If you use WebSerial Lite in a commercial product, AGPL-3.0 requires you to make the source code of your product, including any modifications to WebSerial Lite, publicly available. | You can use WebSerial Pro in a commercial product without open-sourcing your proprietary code. | | Support | Community-driven, through online forums, documentation and user-contributed resources. Professional support may be limited. | Dedicated support and assistance. | | Features | Core remote terminal functionality. | Extended functionality. Features available only in WebSerial Pro are marked in this documentation. | | Cost model | Free | One-time license fee | ## Choosing an edition - **WebSerial Lite** fits open-source projects, and projects in which the AGPL-3.0 requirements are acceptable and core logging and debugging functionality is sufficient. - **WebSerial Pro** fits commercial products. It provides a more comprehensive, professionally supported solution and lets you keep your code proprietary, in exchange for a one-time commercial license fee. ## Get WebSerial Pro WebSerial Pro is available on the official product page at [store.softt.io](https://store.softt.io/products/webserial-pro). # Custom Title (/docs/webserial/custom-title) > Set a custom title for the WebSerial Pro terminal with setTitle(). Library: WebSerial Availability: WebSerial Pro only `setTitle()` sets the title displayed at the top of the WebSerial Pro terminal. Use it to identify the product or application the terminal belongs to. ![Custom terminal title](https://docs.softt.io/img/webserial/custom-title.png) ## Set the title Call `setTitle()` in `setup()`: ```cpp WebSerial.setTitle("Custom Title Here"); ``` ## Example ```cpp title="main.cpp" #include // ... void setup() { // ... WebSerial.begin(&server); // Set the terminal title WebSerial.setTitle("WebSerial Pro Demo"); server.begin(); } void loop() { // ... WebSerial.loop(); } ``` ## Notes - The title is sent to the browser when the terminal connects. Reload the terminal page to see a title changed while the page is open. # Examples (/docs/webserial/examples) > Example sketches in the WebSerial repository that demonstrate standard, access point, high-performance and logging setups. Library: WebSerial The WebSerial repository includes four example sketches. Each one builds on the setup described in the [Integration Guide](/docs/webserial/integration). WebSerial Pro ships the same four examples, using `WebSerialPro.h`. ## Available examples | Example | Description | | --- | --- | | [Demo](https://github.com/ayushsharma82/WebSerial/tree/master/examples/Demo) | The standard integration. Connects to an existing Wi-Fi network, prints the IP address, uptime and free heap every 2 seconds, and echoes received messages. Suitable for most use cases. | | [Demo_AP](https://github.com/ayushsharma82/WebSerial/tree/master/examples/Demo_AP) | Same as Demo, but starts the board as an access point (SoftAP) instead of connecting to an existing network. Use it to connect to the device directly. | | [HighPerf](https://github.com/ayushsharma82/WebSerial/tree/master/examples/HighPerf) | Sends more than 20 messages per second. When built with `WSL_HIGH_PERF`, it writes directly into the WebSocket message buffer with `makeBuffer()` and `send()`. See [High Performance Mode](/docs/webserial/high-performance). | | [Logging](https://github.com/ayushsharma82/WebSerial/tree/master/examples/Logging) | Streams log lines to the terminal. Calls `setBuffer()` so that line-oriented `print()`, `println()` and `printf()` calls can be used in High Performance Mode. | All examples are available in the [examples folder](https://github.com/ayushsharma82/WebSerial/tree/master/examples) of the repository. In the Arduino IDE, you can also open them from **File > Examples** after installing the library. # Export Logs (/docs/webserial/export) > Export the log output of the WebSerial Pro terminal as Text, JSON or CSV files for analysis and sharing. Library: WebSerial Availability: WebSerial Pro only Export Logs saves the log output received by the WebSerial Pro terminal to a file in your browser. Use it to archive logs, process them with other tools, or share them with your team or customers for troubleshooting. ![Export Logs dialog](https://docs.softt.io/img/webserial/export-window.png) ## Export the logs ### Open the export dialog Click the **Export Logs** button in the toolbar of the terminal. ![Export Logs button](https://docs.softt.io/img/webserial/export-logs.png) ### Choose the content Select whether packet numbers and timestamps are included in the export. Both are included by default. ### Choose the format and download Select the output format and download the file. The file is named `export-` with the extension of the selected format. | Format | Extension | Use | | --- | --- | --- | | Text | `.txt` | Human-readable log file. | | JSON | `.json` | Machine-readable log data for scripts and tools. | | CSV | `.csv` | Tabular log data for spreadsheets. | The export is generated in the browser from the logs the terminal has received; no firmware changes are required. # Font Customization (/docs/webserial/font) > Change the font family and font size of the WebSerial Pro terminal from its settings panel for better readability. Library: WebSerial Availability: WebSerial Pro only WebSerial Pro lets each user choose the font family and font size of the terminal from its settings panel. The settings are applied immediately and saved in the browser; no firmware changes are required. ## Font family ![Font family selector](https://docs.softt.io/img/webserial/font-selector.png) By default, the terminal uses `monospace`, a fixed-width font suited to code and terminal output. The following font families are available: `monospace`, `sans-serif`, `cursive`, Arial, Verdana, Tahoma, Trebuchet MS, Times New Roman, Georgia, Garamond, Courier New and Brush Script MT. ## Font size ![Font size selector](https://docs.softt.io/img/webserial/font-size-selector.png) Adjust the font size to improve readability and accessibility. The font size accepts values from 1 to 32. ![Terminal with a small font size](https://docs.softt.io/img/webserial/font-tiny-demo.png) ![Terminal with a large font size](https://docs.softt.io/img/webserial/font-large-demo.png) # Hardware ID (/docs/webserial/hardware-id) > Display a hardware ID on the WebSerial Pro terminal to identify individual devices on your network. Library: WebSerial Availability: WebSerial Pro only The hardware ID is displayed at the bottom of the WebSerial Pro terminal. It helps you identify a specific device when several devices on your network run WebSerial Pro. ![Hardware ID in the terminal footer](https://docs.softt.io/img/webserial/hardware-id.png) ## Set the hardware ID Call `setID()` in `setup()`: ```cpp WebSerial.setID("my_device_001"); ``` If you do not set an ID before calling `begin()`, WebSerial Pro generates one from the chip ID: | Board | Default ID | | --- | --- | | ESP8266 | `ESP8266-` followed by the chip ID in hexadecimal | | ESP32 | `ESP32-` followed by a chip ID derived from the eFuse MAC address, in decimal | ## Example ```cpp title="main.cpp" #include // ... void setup() { // ... // Set the hardware ID WebSerial.setID("my_device_001"); WebSerial.begin(&server); server.begin(); } void loop() { // ... WebSerial.loop(); } ``` ## Best practices - Use an ID that is unique for each device. - Avoid special characters and spaces, which can cause problems in network communication or file names. - Keep a record of the IDs assigned to your devices. - Set the ID during initialization, in `setup()`, before any network communication takes place. # High Performance Mode (/docs/webserial/high-performance) > Enable WebSerial High Performance Mode to send log messages immediately with low latency and high throughput. Library: WebSerial High Performance Mode changes how WebSerial transmits output. In the default mode, WebSerial collects printed data in internal buffers and sends it in batches. In High Performance Mode, each message is queued on the WebSocket immediately, without the global buffer, which reduces memory use and latency and supports more than 20 messages per second. Enable High Performance Mode only when your application needs it, for example when it produces a large, continuous stream of log messages. In High Performance Mode you are responsible for keeping messages neither too large nor too small and frequent. ## Enable High Performance Mode High Performance Mode is enabled with the `WSL_HIGH_PERF` macro. The macro must be visible to the WebSerial source files, so define it as a build flag or in the library header, not in your sketch. 1. Open `WebSerial.h` (or `WebSerialPro.h`) in your Arduino libraries folder. 2. At the top of the file, after the `#include` statements, add the following line: ```cpp #define WSL_HIGH_PERF 1 ``` 3. Save the file and restart the Arduino IDE. 1. Open the `platformio.ini` file of your project. 2. Add the build flag to your environment: ```ini build_flags = -DWSL_HIGH_PERF=1 ``` 3. Optionally, tune AsyncTCP and ESPAsyncWebServer for high throughput with the following additional build flags: ```ini -DCONFIG_ASYNC_TCP_QUEUE_SIZE=128 -DCONFIG_ASYNC_TCP_RUNNING_CORE=1 -DWS_MAX_QUEUED_MESSAGES=128 ``` ## Behavior in High Performance Mode - `WebSerial.loop()` has no effect. You do not need to call it. - Each call to `write(const uint8_t* buffer, size_t size)` is sent as one message. A trailing newline is removed. - Single-byte writes, `write(uint8_t)`, are not supported unless a line buffer is enabled with `setBuffer()`. Without a buffer, a single-byte write logs an error and triggers an assertion. Because many `Print` methods (for example `print(char)` and `printf()`) can emit single bytes, either enable a buffer or send complete buffers with `write(buffer, size)` or `print(String)`. ## Line buffer `setBuffer()` enables a line buffer so that you can use all `Print` methods in High Performance Mode. Output is accumulated until a newline (`\n`) is written; the line is then sent as one message and the buffer is cleared. If the buffer grew beyond its initial capacity, it is reallocated at the initial capacity. ```cpp WebSerial.setBuffer(100); ``` The buffer is disabled by default. Configure it before you start printing; calling `setBuffer()` again discards any content in the buffer. `setBuffer()` has no effect when High Performance Mode is disabled. ## Send pre-allocated WebSocket buffers In High Performance Mode, WebSerial exposes the WebSocket message buffer of ESPAsyncWebServer. Writing into this buffer avoids an additional copy when the message is queued. ```cpp String line = "sensor=42"; AsyncWebSocketMessageBuffer* wsBuffer = WebSerial.makeBuffer(line.length()); memmove(wsBuffer->get(), line.c_str(), line.length()); WebSerial.send(wsBuffer); ``` | Method | Description | | --- | --- | | `AsyncWebSocketMessageBuffer* makeBuffer(size_t size = 0)` | Allocates a WebSocket message buffer of `size` bytes. Returns `nullptr` if `begin()` has not been called. | | `void send(AsyncWebSocketMessageBuffer* buffer)` | Sends the buffer to all connected clients. Does nothing if `buffer` is `nullptr` or no client is connected. | WebSerial Pro currently compiles `makeBuffer()` and `send()` only when the mathieucarbou fork of ESPAsyncWebServer is installed, so they are not available with the ESP32Async library. Use `print()`, `printf()` or `write()` in high performance mode with WebSerial Pro. ## Example The [HighPerf](https://github.com/ayushsharma82/WebSerial/tree/master/examples/HighPerf) example sends random strings at a 50 ms interval (500 ms on ESP8266), and the [Logging](https://github.com/ayushsharma82/WebSerial/tree/master/examples/Logging) example shows how to use `setBuffer()` for line-oriented logging. ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) #include #include #endif #include #include AsyncWebServer server(80); static uint32_t last = millis(); void setup() { Serial.begin(115200); WiFi.softAP("WSLDemo"); Serial.print("IP Address: "); Serial.println(WiFi.softAPIP().toString()); WebSerial.onMessage([](const String& msg) { Serial.println(msg); }); WebSerial.begin(&server); // Enable the line buffer so that all Print methods can be used WebSerial.setBuffer(100); server.onNotFound([](AsyncWebServerRequest* request) { request->redirect("/webserial"); }); server.begin(); } void loop() { if (millis() - last > 500) { WebSerial.print(F("IP address: ")); WebSerial.println(WiFi.softAPIP()); WebSerial.printf("Uptime: %lums\n", millis()); last = millis(); } } ``` # Introduction (/docs/webserial) > WebSerial is a remote terminal library that lets you log, monitor and debug firmware on wireless microcontrollers from a web browser. Library: WebSerial WebSerial is a remote terminal that lives on your wireless microcontroller. It serves a terminal page from the device and streams your log output to every connected browser over a WebSocket, so you can log, monitor and debug firmware without a wired serial connection. GitHub repository: https://github.com/ayushsharma82/WebSerial ![WebSerial terminal](https://docs.softt.io/img/webserial/feature.png) ## Key capabilities - **Remote terminal in the browser**: the terminal page is generated and served by the library at `/webserial`. You do not need to write HTML, CSS or JavaScript. - **Familiar printing API**: the `WebSerial` object derives from the Arduino `Print` class, so `print()`, `println()`, `printf()` and `write()` work as they do with `Serial`. - **Two-way communication**: commands typed into the terminal are delivered to your firmware through the `onMessage()` callback. - **Built on ESPAsyncWebServer**: WebSerial attaches to an existing `AsyncWebServer` instance and coexists with your own routes. - **Buffered and high-performance modes**: the default mode batches output to reduce network overhead; [High Performance Mode](/docs/webserial/high-performance) sends messages immediately for high-throughput logging. - **Authentication**: protect the terminal with HTTP Basic Authentication. ## Supported boards | Board | WebSerial Lite | WebSerial Pro | | --- | --- | --- | | **ESP8266** | Yes | Yes | | **ESP32** | Yes | Yes | | **RP2040** (Raspberry Pi Pico W) | Yes | No | | **RP2350** (Raspberry Pi Pico 2 W) | Yes | No | See [Installation](/docs/webserial/installation) for the dependencies required on each platform. ## WebSerial Lite and WebSerial Pro This documentation covers both editions of WebSerial. WebSerial Lite is open source and licensed under AGPL-3.0. WebSerial Pro is a commercial edition that includes everything in WebSerial Lite plus the following: - [Export logs](/docs/webserial/export) as Text, JSON or CSV files. - [Timestamps](/docs/webserial/timestamps) synchronized with the device. - Lock scroll, to pause automatic scrolling while you read the output. - [Toggle input](/docs/webserial/toggle-input), to enable or disable the command input bar from firmware. - [Font customization](/docs/webserial/font) of the terminal font family and size from the UI. - [Custom title](/docs/webserial/custom-title) and [hardware ID](/docs/webserial/hardware-id). - [Branding](/docs/webserial/branding) with your own logo. Pages for features that are available only in WebSerial Pro are marked accordingly. See [Commercial License](/docs/webserial/commercial-license) for a comparison, or visit [store.softt.io](https://store.softt.io/products/webserial-pro). ![WebSerial Lite terminal](https://docs.softt.io/img/webserial/lite-preview.png) ![WebSerial Pro terminal](https://docs.softt.io/img/webserial/pro-preview.png) ## Explore the documentation # Installation (/docs/webserial/installation) > Install WebSerial Lite or WebSerial Pro and their platform-specific dependencies in the Arduino IDE or PlatformIO. Library: WebSerial This page lists the dependencies WebSerial requires on each platform and describes how to install the library in the Arduino IDE and PlatformIO. ## Dependencies WebSerial requires the following libraries in your libraries folder. Install only the versions listed below; other versions can cause WebSerial to fail to compile or to work only partially. ### WebSerial Lite - [ESP8266 Arduino Core](https://github.com/esp8266/Arduino) >= **v3.1.2** - (ESP32Async) [ESPAsyncTCP](https://github.com/ESP32Async/ESPAsyncTCP#v2.0.0) @ **v2.0.0** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.9.0) >= **v3.9.0** - [ESP32 Arduino Core](https://github.com/espressif/arduino-esp32) >= **v3.3.2** - (ESP32Async) [AsyncTCP](https://github.com/ESP32Async/AsyncTCP#v3.4.9) >= **v3.4.9** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.9.0) >= **v3.9.0** - [Pico Arduino Core](https://github.com/earlephilhower/arduino-pico) >= **v5.4.1** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP#v1.3.2) >= **v1.3.2** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.9.0) >= **v3.9.0** - [Pico Arduino Core](https://github.com/earlephilhower/arduino-pico) >= **v5.4.1** - (ayushsharma82) [RPAsyncTCP](https://github.com/ayushsharma82/RPAsyncTCP#v1.3.2) >= **v1.3.2** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.9.0) >= **v3.9.0** RP2040 refers to the Raspberry Pi Pico W and RP2350 to the Raspberry Pi Pico 2 W. ### WebSerial Pro WebSerial Pro supports ESP8266 and ESP32 and uses the same ESP32Async libraries as WebSerial Lite: - [ESP8266 Arduino Core](https://github.com/esp8266/Arduino) >= **v3.1.2** - (ESP32Async) [ESPAsyncTCP](https://github.com/ESP32Async/ESPAsyncTCP#v2.0.0) @ **v2.0.0** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.9.0) >= **v3.9.0** - [ESP32 Arduino Core](https://github.com/espressif/arduino-esp32) >= **v3.3.2** - (ESP32Async) [AsyncTCP](https://github.com/ESP32Async/AsyncTCP#v3.4.9) >= **v3.4.9** - (ESP32Async) [ESPAsyncWebServer](https://github.com/ESP32Async/ESPAsyncWebServer#v3.9.0) >= **v3.9.0** Earlier WebSerial Pro releases were built against the mathieucarbou forks of `AsyncTCP` and `ESPAsyncWebServer`. Remove those forks from your libraries folder before installing the ESP32Async libraries; two copies of `ESPAsyncWebServer` cannot be installed side by side. ## Install WebSerial Lite WebSerial Lite can be installed in the Arduino IDE or in PlatformIO. ### Library Manager [#library-manager] Open **Sketch > Include Library > Manage Libraries**, search for "WebSerial" and click **Install**. ### Manual installation [#manual-installation] 1. Download the [WebSerial repository archive](https://github.com/ayushsharma82/WebSerial/archive/master.zip). 2. Extract the archive into your Arduino libraries folder: - Windows: `Documents/Arduino/libraries/` - Linux: `/libraries/` ### Import a ZIP library [#import-a-zip-library] 1. Download the [WebSerial repository archive](https://github.com/ayushsharma82/WebSerial/archive/master.zip). 2. Open **Sketch > Include Library > Add .ZIP Library** and select the downloaded file. WebSerial supports multiple architectures (ESP8266, ESP32 and RP2040/RP2350). Without strict compatibility checks, PlatformIO tries to install the dependencies of every platform, which often causes compilation errors. Before installing WebSerial: 1. Delete the `.pio/libdeps` folder of your project, if it exists. 2. Add the following line to the environment in your `platformio.ini` file: ```ini lib_compat_mode = strict ``` 3. Save `platformio.ini`. ### Library Manager [#library-manager-1] Open the PlatformIO menu, go to **Libraries**, search for "WebSerial" and add it to your project. ### Manual installation [#manual-installation-1] 1. Download the [WebSerial repository archive](https://github.com/ayushsharma82/WebSerial/archive/master.zip). 2. Extract the archive into the `lib` folder of your project. ## Install WebSerial Pro WebSerial Pro is distributed as a ZIP archive after purchase. The archive contains a `WebSerialPro-x.x.x` folder with the library files. 1. Extract the downloaded ZIP archive. 2. Copy the `WebSerialPro-x.x.x` folder (the folder that contains the library files). 3. In the Arduino IDE, open **File > Preferences** (Arduino IDE 1.8) or **File > Settings** (Arduino IDE 2) and note the **Sketchbook location**. 4. Open the `libraries` folder inside the sketchbook location. 5. Paste the `WebSerialPro-x.x.x` folder into the `libraries` folder. Place the `WebSerialPro-x.x.x` folder in the `lib` folder of your project. In your sketch, include `WebSerialPro.h` instead of `WebSerial.h`. See [Upgrading to WebSerial Pro](/docs/webserial/upgrading-to-pro). ## Next steps - [Integration Guide](/docs/webserial/integration): add WebSerial to an existing web server sketch. - [Examples](/docs/webserial/examples): browse the example sketches. - [High Performance Mode](/docs/webserial/high-performance): enable unbuffered, high-throughput logging. # Integration Guide (/docs/webserial/integration) > Add a WebSerial remote terminal to an existing ESPAsyncWebServer sketch on ESP8266, ESP32, RP2040 or RP2350. Library: WebSerial This guide shows how to add WebSerial to a sketch that already runs an `AsyncWebServer`. Each step lists the lines to add; the complete sketch is at the end of the page. ### Include the dependencies Include the Wi-Fi, async TCP and web server headers for your platform, followed by `WebSerial.h`. The preprocessor conditions below select the correct headers for each supported board, so the same sketch compiles on every platform. ```cpp #include #if defined(ESP8266) /* ESP8266 Dependencies */ #include #include #elif defined(ESP32) /* ESP32 Dependencies */ #include #include #elif defined(TARGET_RP2040) || defined(PICO_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2350) /* RP2040 or RP2350 Dependencies */ #include #include #endif #include #include ``` With WebSerial Pro, include `WebSerialPro.h` instead of `WebSerial.h`. WebSerial Pro supports ESP8266 and ESP32 only. ### Create the web server WebSerial uses `AsyncWebServer` to serve the terminal page and to handle the WebSocket connection. Create an instance on port 80, or on any other port you prefer. ```cpp AsyncWebServer server(80); ``` ### Initialize WebSerial in `setup()` Pass a pointer to the `AsyncWebServer` instance to `WebSerial.begin()`. Register an `onMessage()` callback to receive commands typed into the terminal, then start the server. ```cpp void setup() { // ... existing setup code // Attach WebSerial to the AsyncWebServer instance WebSerial.begin(&server); // Handle incoming messages from the terminal WebSerial.onMessage([](uint8_t *data, size_t len) { Serial.printf("Received %u bytes from WebSerial: ", len); Serial.write(data, len); Serial.println(); }); // Start AsyncWebServer server.begin(); } ``` The terminal is served at `/webserial` by default. Pass a different path as the second argument of `begin()` to change it, for example `WebSerial.begin(&server, "/terminal")`. ### Call `WebSerial.loop()` in `loop()` In the default (buffered) mode, `WebSerial.loop()` flushes buffered output and cleans up disconnected clients. Call it on every iteration and keep `loop()` free of blocking code. ```cpp void loop() { // ... existing loop code WebSerial.loop(); } ``` ### Open the terminal Upload the sketch and open `http:///webserial` in a browser on the same network. ## Complete example ```cpp title="main.cpp" #include #if defined(ESP8266) #include #include #elif defined(ESP32) #include #include #elif defined(TARGET_RP2040) || defined(TARGET_RP2350) || defined(PICO_RP2040) || defined(PICO_RP2350) #include #include #endif #include #include AsyncWebServer server(80); const char* ssid = ""; // Your Wi-Fi SSID const char* password = ""; // Your Wi-Fi password unsigned long last_print_time = millis(); void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.printf("WiFi Failed!\n"); return; } Serial.print("IP Address: "); Serial.println(WiFi.localIP()); // WebSerial is accessible at "/webserial" WebSerial.begin(&server); // Echo received messages back to the terminal WebSerial.onMessage([](uint8_t *data, size_t len) { Serial.printf("Received %u bytes from WebSerial: ", len); Serial.write(data, len); Serial.println(); WebSerial.println("Received Data..."); String d = ""; for (size_t i = 0; i < len; i++) { d += char(data[i]); } WebSerial.println(d); }); server.begin(); } void loop() { // Print every 2 seconds (non-blocking) if ((unsigned long)(millis() - last_print_time) > 2000) { WebSerial.print(F("IP address: ")); WebSerial.println(WiFi.localIP()); WebSerial.printf("Uptime: %lums\n", millis()); last_print_time = millis(); } WebSerial.loop(); } ``` ## Next steps - [Printing](/docs/webserial/printing): methods for sending output to the terminal. - [Callbacks](/docs/webserial/callbacks): handle commands received from the terminal. - [Authentication](/docs/webserial/authentication): restrict access to the terminal. # Printing (/docs/webserial/printing) > Send log output to the WebSerial terminal with print, println, printf and write, and understand how WebSerial buffers output. Library: WebSerial The `WebSerial` object derives from the Arduino `Print` class, so it provides the same printing methods as `Serial`. Output is sent over a WebSocket to every browser that has the terminal open. Do not send large amounts of data in a single call (more than about 1 KB). WebSockets on microcontrollers have limited throughput. WebSerial buffers output to reduce the load, but you should still keep individual writes small for stable operation. In the default mode, a single write must not exceed `WSL_PRINT_BUFFER_SIZE` (1024 bytes by default). ## Methods ### `print()` Prints data to the terminal as human-readable text. ```cpp WebSerial.print("Temperature: "); WebSerial.print(23.5); ``` ### `println()` Prints data to the terminal, followed by a carriage return and a newline. ```cpp WebSerial.println(WiFi.localIP()); ``` ### `printf()` Prints formatted data to the terminal. ```cpp WebSerial.printf("Uptime: %lums\n", millis()); ``` ### `write()` Writes raw bytes to the terminal. ```cpp WebSerial.write('A'); WebSerial.write(buffer, size); ``` `WebSerial` implements both `write()` overloads of `Print`: | Signature | Description | | --- | --- | | `size_t write(uint8_t)` | Writes a single byte. Not supported in [High Performance Mode](/docs/webserial/high-performance) unless a line buffer is enabled. | | `size_t write(const uint8_t* buffer, size_t size)` | Writes `size` bytes from `buffer`. Returns `0` if `begin()` has not been called or `size` is `0`. | ## Output buffering In the default mode, WebSerial does not send each write as a separate WebSocket message: 1. Writes are collected in a print buffer of `WSL_PRINT_BUFFER_SIZE` bytes (default 1024). 2. When no data has been written for `WSL_PRINT_FLUSH_TIME_US` microseconds (default 100), or when the print buffer is full, its content is moved into a global buffer of `WSL_BUFFER_SIZE` bytes (default 2048) as one row. Rows longer than `WSL_MAX_ROW_PACKET_PAYLOAD_SIZE` bytes (default 512) are split into several packets. 3. The global buffer is sent to all clients when it is full, or `WSL_GLOBAL_FLUSH_TIME_MS` milliseconds (default 100) after the last flush. These steps run in `WebSerial.loop()` and at the start of every write. Call `WebSerial.loop()` on every iteration of `loop()` so that output is delivered even when nothing else is printed. The buffer sizes and intervals can be changed with build flags; see [API Reference](/docs/webserial/api-reference#configuration-macros). To send each message immediately instead, use [High Performance Mode](/docs/webserial/high-performance). # Timestamps (/docs/webserial/timestamps) > Show device-synchronized timestamps and packet numbers on each line of the WebSerial Pro terminal. Library: WebSerial Availability: WebSerial Pro only Timestamps add the time at which each message was logged to the start of the line. Use them to track when events occur and to analyze scheduled tasks, timers and task execution times. ![Timestamps in the terminal](https://docs.softt.io/img/webserial/timestamps.png) ## How timestamps are synchronized Timestamps are generated on the device, not in the browser. The terminal periodically sends the current time of the browser to the device, and WebSerial Pro stamps each message with the device time when it is written, at a resolution of one second. No RTC, NTP client or other external dependency is required. Because the time is recorded on the device, timestamps are consistent across multiple open terminal windows and are not affected by transmission latency. ## Enable timestamps Timestamps are enabled from the settings panel of the terminal; no firmware changes are required. ![Timestamp settings](https://docs.softt.io/img/webserial/timestamps-settings.png) The settings panel provides the following options: | Setting | Description | | --- | --- | | **Timestamps > Enable** | Shows the timestamp at the start of each line. | | **Timestamps > Prevent Copying** | Excludes timestamps from text selection, so that copying from the terminal copies only the log output. | | **Packet Numbers > Enable** | Shows a sequential packet number on each line. | | **Packet Numbers > Prevent Copying** | Excludes packet numbers from text selection. | Settings are applied immediately and saved in the browser for future sessions. # Toggle Input (/docs/webserial/toggle-input) > Enable or disable the WebSerial Pro terminal input bar from firmware to control whether users can send commands. Library: WebSerial Availability: WebSerial Pro only WebSerial Pro lets you enable or disable the command input bar of the terminal from your firmware. Use it to prevent user interaction during critical operations, such as a firmware update, or to make the terminal permanently read-only. User input is enabled by default. ## Disable user input ```cpp WebSerial.disableInput(); ``` ![Disabled input bar](https://docs.softt.io/img/webserial/input-bar-disabled.png) While input is disabled, messages received from the terminal are ignored and the [`onMessage()`](/docs/webserial/callbacks) callback is not invoked. ## Enable user input ```cpp WebSerial.enableInput(); ``` ![Enabled input bar](https://docs.softt.io/img/webserial/input-bar.png) ## Set the input state `setInput()` enables or disables the input bar from a boolean value: ```cpp WebSerial.setInput(false); ``` ## Notes - The input state is sent to the browser when the terminal connects. Reload the terminal page to see a change made while the page is open. # Upgrading to WebSerial Pro (/docs/webserial/upgrading-to-pro) > Move an existing project from WebSerial Lite to WebSerial Pro, or from WebSerial Pro v1 to WebSerial Pro v2. Library: WebSerial Availability: WebSerial Pro only This page describes the changes required to move an existing project to WebSerial Pro v2, either from WebSerial Lite or from WebSerial Pro v1. ## From WebSerial Lite WebSerial Pro exposes the same `WebSerial` object and API as WebSerial Lite. After you [install WebSerial Pro](/docs/webserial/installation#install-webserial-pro), replace the include in your sketch: ```cpp // Before #include // After #include ``` WebSerial Pro supports ESP8266 and ESP32. RP2040 and RP2350 are supported by WebSerial Lite only. ## From WebSerial Pro v1 ### Update the dependencies WebSerial Pro v2 uses different forks of its dependencies. Replace your existing dependencies with the versions listed in [Installation](/docs/webserial/installation#webserial-pro). ### Rename the global object The global object is now named `WebSerial` instead of `WebSerialPro`. Rename all calls accordingly. ```cpp // Before WebSerialPro.print("Hello"); // After WebSerial.print("Hello"); ``` ### Call `WebSerial.loop()` Add `WebSerial.loop()` to your `loop()` function, and keep `loop()` free of blocking code. ```cpp void loop() { // ... WebSerial.loop(); } ``` ### Rename the message callback The message callback has been renamed from `msgCallback()` to `onMessage()`. See [Callbacks](/docs/webserial/callbacks). # Using with Other SOFTT Libraries (/docs/webserial/using-with-other-libraries) > Combine WebSerial with ESP-DASH, ElegantOTA and NetWizard in the same project, sharing one AsyncWebServer. Library: WebSerial ESP-DASH, ElegantOTA, NetWizard and WebSerial are designed to work together in the same project. A single sketch can provide WiFi provisioning (NetWizard), a real-time dashboard (ESP-DASH), over-the-air updates (ElegantOTA) and a remote serial terminal (WebSerial), all served by one `AsyncWebServer` instance. The Lite and Pro editions of each library can be mixed freely. ## Requirements - **One shared `AsyncWebServer`.** Create a single `AsyncWebServer server(80);` and pass it to every library. Call `server.begin()` once, after all libraries are initialized. - **Async mode for ElegantOTA and NetWizard.** ESP-DASH and WebSerial only run on `ESPAsyncWebServer`. ElegantOTA and NetWizard default to the core `WebServer`, so enable their async mode by setting `ELEGANTOTA_USE_ASYNC_WEBSERVER=1` and `NETWIZARD_USE_ASYNC_WEBSERVER=1`. See [ElegantOTA async mode](/docs/elegantota/getting-started/async-mode) and [NetWizard async mode](/docs/netwizard/getting-started/async-mode). - **One copy of each dependency.** Install a single copy of `ESPAsyncWebServer` and `AsyncTCP` (ESP32) or `RPAsyncTCP` (RP2040/RP2350) whose version satisfies the highest minimum listed on the installation page of each library you use. Do not install two forks of `ESPAsyncWebServer` side by side. - **A board that every library supports.** ESP32 is supported by all four libraries. Check each library's supported boards before targeting RP2040, RP2350 or ESP8266: for example, ESP-DASH v5 and NetWizard do not support ESP8266. ## Default paths Each library registers its own routes on the shared server. The defaults do not collide, except for ESP-DASH and the NetWizard portal, which both use `/`: | Library | Default path | How to change it | | --- | --- | --- | | ESP-DASH | `/` | Pass a URI to the constructor: `ESPDash dashboard(server, "/dashboard", true);` ([custom URI](/docs/espdash/v5/features/custom-uri)) | | ElegantOTA | `/update` | Fixed | | WebSerial | `/webserial` | Pass a path to `begin()`: `WebSerial.begin(&server, "/terminal");` | | NetWizard | `/` (captive portal, access point clients only) | Fixed | When NetWizard is part of the project, serve ESP-DASH at `/dashboard` and add `server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER);` so that clients on the local network still see the dashboard at the device's root URL. See [NetWizard with ESP-DASH](/docs/netwizard/getting-started/using-with-espdash). ## Example The sketch below combines all four libraries on an ESP32. Build it with `-D ELEGANTOTA_USE_ASYNC_WEBSERVER=1 -D NETWIZARD_USE_ASYNC_WEBSERVER=1`, or set both macros in the library headers as described on the async mode pages. ```cpp title="main.cpp" #include #include #include #include #include // NetWizard Pro: #include #include // ElegantOTA Pro: #include AsyncWebServer server(80); // WiFi provisioning: captive portal at / for access point clients NetWizard NW(&server); // Dashboard at /dashboard, so it does not conflict with the portal ESPDash dashboard(server, "/dashboard", true); dash::TemperatureCard temperature(dashboard, "Temperature"); unsigned long lastUpdate = 0; void setup() { Serial.begin(115200); // Connect to the saved network, or start the captive portal NW.setStrategy(NetWizardStrategy::BLOCKING); NW.autoConnect("My Device", ""); // Over-the-air updates at /update ElegantOTA.begin(&server); // Remote serial terminal at /webserial WebSerial.begin(&server); WebSerial.onMessage([](const String& msg) { Serial.println(msg); }); // Show the dashboard at / for clients on the local network server.rewrite("/", "/dashboard").setFilter(ON_STA_FILTER); server.begin(); } void loop() { NW.loop(); ElegantOTA.loop(); WebSerial.loop(); if (millis() - lastUpdate > 3000) { lastUpdate = millis(); temperature.setValue((int)random(0, 50)); dashboard.sendUpdates(); WebSerial.println("Dashboard updated"); } } ``` Each library's `loop()` method must be called on every iteration, and `loop()` must not block, so that the portal, OTA updates and buffered terminal output keep running alongside the dashboard.