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
- Building and flashing
- First-time Wi-Fi setup
- Web UI
- MQTT / Home Assistant
- HTTP API
- Configuration reference
- 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.
# 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.
- Enter your Wi-Fi SSID and password.
- Optionally change the Device hostname (default:
esp32-device, max 63 characters). - Click Save. The device connects to your network and restarts.
After connecting, the device is reachable at:
http://<hostname>.local(defaulthttp://esp32-device.local) — mDNS, works on most local networkshttp://<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.
{
"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
- Add your hardware setup in
setup()after the framework initialises. - Add your per-loop logic in
loop()alongsideserver.handleClient()andmqttLoop(). - Fill in the three MQTT stubs to subscribe, receive, and publish discovery payloads.
- Add extra web routes with
server.on(...)instartServer()if you need device-specific endpoints. - Persist extra config fields by adding keys to
loadConfig()andsaveConfig(). - Place static files (images, etc.) in
data/and upload the filesystem image withpio run --target uploadfs.