190 lines
6.1 KiB
Markdown
190 lines
6.1 KiB
Markdown
# 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 `<hostname>.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://<hostname>.local` (default `http://esp32-device.local`) — mDNS, works on most local networks
|
||
- `http://<IP address>` — 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 (`<hostname>.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 `<prefix>/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`.
|