# Captive Portal Implementation Guide
This document describes how to implement the captive portal functionality on the ESP32 side to work with `captive.html`.
## Overview
When the ESP32 has no WiFi credentials stored (or connection fails), it should start in Access Point (AP) mode and serve a captive portal that allows users to configure WiFi settings.
## How Captive Portal Detection Works
Operating systems automatically send HTTP requests to known URLs to check for internet connectivity:
| OS | Detection URL | Expected Response |
|---|---|---|
| **iOS/macOS** | `http://captive.apple.com/hotspot-detect.html` | `
SuccessSuccess` |
| **Android** | `http://connectivitycheck.gstatic.com/generate_204` | HTTP 204 No Content |
| **Windows** | `http://www.msftconnecttest.com/connecttest.txt` | `Microsoft Connect Test` |
If the response doesn't match, the OS assumes there's a captive portal and opens a browser.
## ESP32 Implementation Steps
### 1. Start Access Point Mode
```c
wifi_config_t ap_config = {
.ap = {
.ssid = "SystemControl-Setup",
.ssid_len = 0,
.password = "", // Open network for easy access
.max_connection = 4,
.authmode = WIFI_AUTH_OPEN
}
};
esp_wifi_set_mode(WIFI_MODE_AP);
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
esp_wifi_start();
```
### 2. Start DNS Server (DNS Hijacking)
Redirect ALL DNS queries to the ESP32's IP address:
```c
// Simplified example - use a proper DNS server component
void dns_server_task(void *pvParameters) {
// Listen on UDP port 53
// For any DNS query, respond with ESP32's AP IP (e.g., 192.168.4.1)
}
```
### 3. Configure HTTP Server with Redirects
```c
// Handler for captive portal detection URLs
esp_err_t captive_redirect_handler(httpd_req_t *req) {
httpd_resp_set_status(req, "302 Found");
httpd_resp_set_hdr(req, "Location", "http://192.168.4.1/captive.html");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
// Register handlers for detection URLs
httpd_uri_t apple_detect = {
.uri = "/hotspot-detect.html",
.method = HTTP_GET,
.handler = captive_redirect_handler
};
httpd_uri_t android_detect = {
.uri = "/generate_204",
.method = HTTP_GET,
.handler = captive_redirect_handler
};
// Catch-all for any unknown paths
httpd_uri_t catch_all = {
.uri = "/*",
.method = HTTP_GET,
.handler = captive_redirect_handler
};
```
### 4. Serve Static Files
Serve the captive portal files from SPIFFS/LittleFS:
- `/captive.html` - Main captive portal page
- `/favicon.svg` - Favicon
- `/css/shared.css` - Shared styles
- `/css/captive.css` - Captive-specific styles
- `/js/i18n.js` - Internationalization
- `/js/wifi-shared.js` - WiFi configuration logic
### 5. Implement WiFi Configuration API
```c
// POST /api/wifi/config
// Body: { "ssid": "NetworkName", "password": "SecretPassword" }
esp_err_t wifi_config_handler(httpd_req_t *req) {
// 1. Parse JSON body
// 2. Store credentials in NVS
// 3. Send success response
// 4. Schedule restart/reconnect
return ESP_OK;
}
// GET /api/wifi/scan
// Returns: [{ "ssid": "Network1", "rssi": -45 }, ...]
esp_err_t wifi_scan_handler(httpd_req_t *req) {
// 1. Perform WiFi scan
// 2. Return JSON array of networks
return ESP_OK;
}
```
## Flow After User Submits WiFi Credentials
```
1. User enters SSID + Password, clicks "Connect"
↓
2. Frontend sends POST /api/wifi/config
↓
3. ESP32 stores credentials in NVS (Non-Volatile Storage)
↓
4. ESP32 sends HTTP 200 OK response
↓
5. Frontend shows countdown (10 seconds)
↓
6. ESP32 stops AP mode
↓
7. ESP32 connects to configured WiFi
↓
8. ESP32 gets new IP from router (e.g., 192.168.1.42)
↓
9. User connects phone/PC to normal WiFi
↓
10. User accesses ESP32 via new IP or mDNS (e.g., http://system-control.local)
```
## Recommended: mDNS Support
Register an mDNS hostname so users can access the device without knowing the IP:
```c
mdns_init();
mdns_hostname_set("system-control");
mdns_instance_name_set("System Control");
```
Then the device is accessible at: `http://system-control.local`
## Error Handling / Fallback
If WiFi connection fails after credentials are saved:
1. Wait for connection timeout (e.g., 30 seconds)
2. If connection fails, restart in AP mode
3. Show error message on captive portal
4. Allow user to re-enter credentials
```c
// Pseudo-code
if (wifi_connect_timeout()) {
nvs_erase_key("wifi_ssid");
nvs_erase_key("wifi_password");
esp_restart(); // Will boot into AP mode again
}
```
## API Endpoints Summary
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/wifi/scan` | Scan for available networks |
| POST | `/api/wifi/config` | Save WiFi credentials |
| GET | `/api/wifi/status` | Get current connection status |
### Request/Response Examples
**GET /api/wifi/scan**
```json
[
{ "ssid": "HomeNetwork", "rssi": -45, "secure": true },
{ "ssid": "GuestWiFi", "rssi": -67, "secure": false }
]
```
**POST /api/wifi/config**
```json
{ "ssid": "HomeNetwork", "password": "MySecretPassword" }
```
**GET /api/wifi/status**
```json
{
"connected": true,
"ssid": "HomeNetwork",
"ip": "192.168.1.42",
"rssi": -52
}
```
## Security Considerations
1. **Open AP**: The setup AP is intentionally open for easy access. Keep setup time short.
2. **HTTPS**: Consider using HTTPS for the main interface (after WiFi setup).
3. **Timeout**: Auto-disable AP mode after successful connection.
4. **Button Reset**: Implement a physical button to reset WiFi credentials and re-enter AP mode.