A production-ready WiFi provisioning and recovery library for ESP32 devices. Designed for real IoT products with robust error handling, multiple reset mechanisms, and configurable UX features.
- β Non-blocking operation - Fully asynchronous state machine
- β Captive portal - Responsive web interface for configuration
- β Persistent storage - Credentials stored in NVS (Non-Volatile Storage)
- β Automatic retry - Configurable retry logic with exponential backoff option
- β WiFi network scanning - List available networks in configuration UI
- β State callbacks - Hooks for application integration
- π Hardware button - Long-press physical button to reset
- π HTTP endpoint - Simple or authenticated remote reset
- π Double-reboot detection - Emergency recovery mechanism
- π Auto-wipe on max retries - Optional credential clearing
- π‘ LED status indicator - Visual feedback for device state
- π mDNS support - Access device via
http://device-name.local - π± Mobile-friendly UI - Responsive design works on all devices
- β‘ Fast configuration - Network scanning and one-click selection
- π Password hashing - SHA-256 hashing for reset passwords
- π Authenticated reset - Require password for remote operations
- π Configurable AP security - Open or password-protected configuration AP
- π Credential isolation - Separate storage for WiFi and reset passwords
- Download this repository as ZIP
- In Arduino IDE: Sketch β Include Library β Add .ZIP Library
- Select the downloaded ZIP file
Add to platformio.ini:
lib_deps =
https://github.com/ferdiu/ESP32ProvisionToolkit.git#include <ESP32ProvisionToolkit.h>
ESP32ProvisionToolkit provisioner;
void setup() {
Serial.begin(115200);
provisioner
.setAPName("MyDevice") // Custom AP name
.setLed(2) // Enable LED on GPIO 2
.onConnected([]() {
Serial.println("Connected!");
})
.begin();
}
void loop() {
provisioner.loop();
// Your application code here
if (provisioner.isConnected()) {
// Do something
}
}The library uses a fluent API for easy configuration:
provisioner
.setAPName("MyESP32")
.setAPPassword("config123")
.setMaxRetries(10)
.setRetryDelay(3000)
.enableHardwareReset(GPIO_NUM_0, 5000)
.enableAuthenticatedHttpReset(true)
.setLed(GPIO_NUM_2)
.enableMDNS("my-device")
.setLogLevel(LOG_INFO)
.onConnected(onConnectedCallback)
.begin();| Method | Parameters | Description |
|---|---|---|
setAPName(name) |
String | AP SSID base name (MAC suffix added) |
setAPPassword(password) |
String | AP password (empty = open network) |
setAPTimeout(ms) |
uint32_t | Timeout before exiting AP mode |
| Method | Parameters | Description |
|---|---|---|
setMaxRetries(count) |
uint8_t | Max connection attempts before action |
setRetryDelay(ms) |
uint32_t | Delay between retry attempts |
setAutoWipeOnMaxRetries(enable) |
bool | Clear credentials after max retries |
| Method | Parameters | Description |
|---|---|---|
enableHardwareReset(pin, duration, activeLow) |
int8_t, uint32_t, bool | Enable button reset |
disableHardwareReset() |
- | Disable button reset |
Example:
// BOOT button (GPIO 0), hold for 5 seconds, active low
provisioner.enableHardwareReset(0, 5000, true);| Method | Parameters | Description |
|---|---|---|
enableHttpReset(enable) |
bool | Enable simple HTTP reset |
enableAuthenticatedHttpReset(enable) |
bool | Enable password-protected reset |
HTTP Reset Usage:
Simple (unauthenticated):
curl -X POST http://device-ip/resetAuthenticated:
curl -X POST http://device-ip/reset -d "password=YOUR_RESET_PASSWORD"NOTE: If any time you reset the software you change this password it can still be considered secure to send the password in plain-text.
| Method | Parameters | Description |
|---|---|---|
setLed(pin, activeLow) |
int8_t, bool | Enable LED status indicator |
enableMDNS(name) |
String | Enable mDNS responder |
enableDoubleRebootDetect(windowMs) |
uint32_t | Enable double-reboot reset |
LED Patterns:
- Fast blink (100ms on/off): Provisioning mode
- Slow blink (100ms on, 900ms off): Connecting
- Solid on: Connected
- Off: Idle/Error
| Method | Parameters | Description |
|---|---|---|
setLogLevel(level) |
LogLevel | Set verbosity level |
Log Levels:
LOG_NONE- No outputLOG_ERROR- Errors onlyLOG_INFO- Normal operation infoLOG_DEBUG- Detailed debugging
| Method | Parameters | Description |
|---|---|---|
onConnected(callback) |
void (*callback)() | Called when WiFi connects |
onFailed(callback) |
void (*callback)(uint8_t) | Called on connection failure |
onAPMode(callback) |
void (callback)(const char, const char*) | Called when AP mode starts |
onReset(callback) |
void (*callback)() | Called before device reset |
Example:
provisioner.onConnected([]() {
Serial.println("Connected to WiFi!");
Serial.println(WiFi.localIP());
});
provisioner.onFailed([](uint8_t retryCount) {
Serial.printf("Connection failed, retry %d\n", retryCount);
});
provisioner.onAPMode([](const char* ssid, const char* ip) {
Serial.printf("AP Mode: %s at %s\n", ssid, ip);
});| Method | Returns | Description |
|---|---|---|
isConnected() |
bool | True if WiFi connected |
isProvisioning() |
bool | True if in AP provisioning mode |
getState() |
ProvisionerState | Current state machine state |
getSSID() |
String | Connected SSID |
getLocalIP() |
IPAddress | Device IP address |
getAPIP() |
String | AP mode IP address |
| Method | Parameters | Description |
|---|---|---|
setCredentials(ssid, password, reboot) |
String, String, bool | Set WiFi credentials |
clearCredentials(reboot) |
bool | Clear stored credentials |
reset() |
- | Trigger programmatic reset |
Example:
// Programmatically set credentials
provisioner.setCredentials("MyNetwork", "password123", true);
// Clear and reboot
provisioner.clearCredentials(true);
// Trigger reset
provisioner.reset();βββββββββββββββ
β INIT β
ββββββββ¬βββββββ
β
v
βββββββββββββββ No Credentials ββββββββββββββββ
β LOAD_CONFIG ββββββββββββββββββββββββββ>β PROVISIONING β
ββββββββ¬βββββββ βββββββββ¬βββββββ
β β
β Has Credentials β
v β
βββββββββββββββ Success ββββββββββββvββββββββββ
β CONNECTING ββββββββββββββββββββββ>β PROVISIONING_ACTIVE β
ββββββββ¬βββββββ βββββββββββββββββββββββ
β
β Failure
v
βββββββββββββββ Max Retries ββββββββββββββββ
β RETRY_WAIT ββββββββββββββββββββββ>β WIPE & AP β
ββββββββ¬βββββββ ββββββββββββββββ
β
β Retry
v
βββββββββββββββ
β CONNECTED βββββ Main operation state
βββββββββββββββ
Namespace: wifiprov
| Key | Type | Description |
|---|---|---|
ssid |
String | WiFi SSID |
password |
String | WiFi password |
reset_pwd |
String | SHA-256 hash of reset password |
boot_count |
uint32_t | Boot counter for double-reboot |
boot_time |
uint32_t | Last boot timestamp |
-
Always use authenticated reset in untrusted environments:
provisioner.enableAuthenticatedHttpReset(true); -
Secure the configuration AP with a strong password:
provisioner.setAPPassword("Strong!Pass123"); -
Use hardware reset for field deployment:
provisioner.enableHardwareReset(BUTTON_PIN, 10000); // 10 sec
-
Disable debug logging in production:
provisioner.setLogLevel(LOG_ERROR);
- Reset passwords are hashed using SHA-256 before storage
- Original passwords are never stored in plaintext
- Hash verification is constant-time to prevent timing attacks
- The library does not implement WPA3 or certificate pinning
- Use WPA2-PSK or better for production networks
- Consider implementing additional application-level encryption
- For highly sensitive applications, use VPN or TLS for all communications
Causes:
- Credentials are stored from previous session
- Double-reboot window not met
Solutions:
// Check if credentials exist
if (!provisioner.isProvisioning()) {
// Manually trigger reset
provisioner.clearCredentials(true);
}
// Or use hardware button
// Hold BOOT button for configured durationCauses:
- Wrong password
- Network out of range
- Router MAC filtering
Solutions:
- Check router logs
- Verify SSID and password
- Increase max retries:
provisioner.setMaxRetries(20); - Enable auto-wipe to re-enter provisioning:
provisioner.setAutoWipeOnMaxRetries(true);
Causes:
- Mobile device doesn't support captive portal detection
- DNS server not starting properly
Solutions:
- Manually navigate to AP IP address (usually
192.168.4.1) - Check Serial output for DNS server status
- Ensure DNS server is enabled in your network settings
Causes:
- Reset feature not enabled
- Using authenticated endpoint without password
Solutions:
// Enable simple reset
provisioner.enableHttpReset(true);
// OR enable authenticated reset
provisioner.enableAuthenticatedHttpReset(true);
// Then set password during configurationCauses:
- LED not enabled
- Wrong GPIO pin
- Active-low setting incorrect
Solutions:
// For most ESP32 boards (active-low built-in LED)
provisioner.setLed(2, true); // GPIO 2, active low
// For external LED (active-high)
provisioner.setLed(LED_PIN, false);The library uses the wifiprov namespace by default. To avoid conflicts:
// Currently not configurable, but you can modify the source:
// In ESP32ProvisionToolkit.cpp:
// #define NVS_NAMESPACE "myapp_wifi"#include <ESP32ProvisionToolkit.h>
#include <PubSubClient.h>
ESP32ProvisionToolkit provisioner;
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
void setup() {
provisioner
.onConnected([]() {
// Connect to MQTT when WiFi connects
mqtt.setServer("mqtt.example.com", 1883);
mqtt.connect("esp32-client");
})
.begin();
}
void loop() {
provisioner.loop();
if (provisioner.isConnected()) {
mqtt.loop();
}
}#include <ESP32ProvisionToolkit.h>
#include <ArduinoOTA.h>
void setup() {
provisioner
.onConnected([]() {
ArduinoOTA.begin();
})
.begin();
}
void loop() {
provisioner.loop();
if (provisioner.isConnected()) {
ArduinoOTA.handle();
}
}void loop() {
provisioner.loop();
if (provisioner.isConnected()) {
// Do work
sendSensorData();
// Sleep until next transmission
esp_sleep_enable_timer_wakeup(60 * 1000000); // 60 seconds
esp_light_sleep_start();
}
}The library includes four complete examples:
- Basic - Minimal setup for getting started
- SecureReset - All security features enabled
- Headless - Production IoT device configuration
- CustomRoutes - The basic example with additional user-defined routes
See /examples directory for complete code.
See the header file ESP32ProvisionToolkit.h for complete API documentation with detailed comments.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests if applicable
- Submit a pull request
MIT License - See LICENSE file for details
- Issues: GitHub Issues
- Documentation update
- Initial release
- Complete WiFi provisioning system
- Multiple reset mechanisms
- Production-ready features
- Comprehensive documentation
Developed with β€οΈ for the ESP32 community
- Espressif for the ESP32 platform
- Arduino community for the excellent ecosystem