# ESP32 Device Framework A reusable ESP32 firmware base with Wi-Fi, web configuration UI, authentication, MQTT / Home Assistant integration, and factory reset. Add device-specific logic on top without re-implementing the boilerplate. ## Table of contents - [Features](#features) - [Building and flashing](#building-and-flashing) - [First-time Wi-Fi setup](#first-time-wi-fi-setup) - [Web UI](#web-ui) - [MQTT / Home Assistant](#mqtt--home-assistant) - [HTTP API](#http-api) - [Configuration reference](#configuration-reference) - [Extending the framework](#extending-the-framework) --- ## Features - **Wi-Fi** via WiFiManager — captive-portal setup on first boot, credentials stored in flash - **mDNS** — device reachable as `.local` - **Web UI** — responsive configuration page served from the device, with logo support - **Authentication** — optional HTTP Basic Auth to protect the web UI - **MQTT** — connects to any broker, publishes an availability topic, reconnects automatically without blocking the web server - **Home Assistant discovery** — stub ready for your entities - **Factory reset** — rapid power-cycling clears settings (5× config, 10× config + Wi-Fi credentials) - **Config persistence** — all settings stored as JSON in LittleFS (`/config.json`) --- ## Building and flashing The project uses [PlatformIO](https://platformio.org/). ```bash # Build pio run -e esp32dev # Flash firmware pio run -e esp32dev --target upload # Upload filesystem image (logo, etc.) — only needed when data/ changes pio run -e esp32dev --target uploadfs # Open serial monitor (115 200 baud) pio device monitor ``` > **Note:** close the serial monitor before uploading — it holds the serial port exclusively. Board targets: `esp32dev`, `esp32-s3-devkitc-1` --- ## First-time Wi-Fi setup On first boot (or when stored Wi-Fi credentials are missing), the device starts an access point named **ESP32-Device**. Connect to it with any phone or laptop — a captive portal will appear automatically. 1. Enter your Wi-Fi SSID and password. 2. Optionally change the **Device hostname** (default: `esp32-device`, max 63 characters). 3. Click **Save**. The device connects to your network and restarts. After connecting, the device is reachable at: - `http://.local` (default `http://esp32-device.local`) — mDNS, works on most local networks - `http://` — shown in the serial monitor on boot ### Factory reset Power-cycle (or press EN) rapidly before the 3-second boot window expires. The reset counter accumulates across rapid reboots: | Cycles | Effect | |--------|--------| | 5 – 9 | Clears hostname, auth, and MQTT config; restarts into normal Wi-Fi | | 10+ | Clears all of the above **plus** stored Wi-Fi credentials; restarts into captive portal | Serial output shows the accumulated count: ``` [BOOT] reset count 3 [BOOT] reset count 4 ... [BOOT] reset count 5, triggering factory reset [BOOT] factory reset triggered (clearWifi=0)! [BOOT] factory reset complete, restarting... ``` --- ## Web UI Browse to the device address to open the configuration page. ### Logo Place a `logo.png` in the `data/` directory and upload the filesystem image once. The logo appears in the top-left of the configuration page. The `data/logo.png` included in this repo is a 200×200 px transparent PNG. ### Info panel Shows the current hostname and IP address. ### Hostname Sets the mDNS name (`.local`). Changing the hostname automatically restarts the device so mDNS re-registers with the new name. ### Security | Field | Description | |-------|-------------| | Require password | Enables HTTP Basic Auth on the web UI | | Password | Password for the `admin` account. Leave blank to keep the existing password | ### MQTT | Field | Description | |-------|-------------| | Enable | Toggle MQTT on/off | | Broker | Hostname or IP of your MQTT broker | | Port | Default `1883` | | User / Pass | Broker credentials (leave Pass blank to keep the existing value) | | Prefix | Topic prefix (default `device`) | Click **Save** to persist all settings. Click **Reset Device** to restart the device immediately. --- ## MQTT / Home Assistant ### Connection behaviour On connect the device publishes `online` to `/status` (retained) and sets `offline` as the LWT. If the broker is unreachable, the firmware probes the TCP port before attempting a full MQTT connect and retries every 30 seconds without blocking the web server. ### Extending with discovery and topics Three stubs in `main.cpp` are the intended extension points: | Function | Purpose | |----------|---------| | `mqttSubscribe()` | Subscribe to command topics after connecting | | `mqttCallback()` | Handle incoming messages | | `mqttPublishDiscovery()` | Publish Home Assistant discovery payloads | --- ## HTTP API | Method | Path | Description | |--------|------|-------------| | `GET` | `/` | HTML configuration page | | `GET` | `/logo.png` | Logo image served from LittleFS | | `POST` | `/config` | Save configuration; redirects to `/`. Restarts if hostname changed | | `POST` | `/reset` | Restart the device immediately | --- ## Configuration reference Config is stored as JSON in LittleFS at `/config.json`. ```json { "hostname": "esp32-device", "mqtt": { "en": true, "host": "192.168.1.10", "port": 1883, "user": "ha", "pass": "secret", "prefix": "device" }, "auth": { "en": true, "pass": "secret" } } ``` The file is written by the web UI. To erase everything including flash, use `pio run --target erase`. --- ## Extending the framework 1. Add your hardware setup in `setup()` after the framework initialises. 2. Add your per-loop logic in `loop()` alongside `server.handleClient()` and `mqttLoop()`. 3. Fill in the three MQTT stubs to subscribe, receive, and publish discovery payloads. 4. Add extra web routes with `server.on(...)` in `startServer()` if you need device-specific endpoints. 5. Persist extra config fields by adding keys to `loadConfig()` and `saveConfig()`. 6. Place static files (images, etc.) in `data/` and upload the filesystem image with `pio run --target uploadfs`.