Finish captive portal: NVS config, POST handler, STA connect, AP identity
Splits provisioning (softAP + captive portal + NVS-backed config) into wifi_provisioning.c and home-network connection into frame_client.c. /save_config now parses the form body and persists it to NVS; on boot the device goes straight to STA mode if a config exists, retrying a few times before falling back to provisioning if the home network is unreachable. The provisioning AP is now always named ESPRESSO with a random per-device password (generated once, persisted in NVS) instead of a fixed Kconfig value, drawn from a charset that avoids visually ambiguous characters since it'll be read off the e-ink panel and possibly typed by hand.
This commit is contained in:
+25
-176
@@ -1,195 +1,44 @@
|
||||
/* Captive Portal Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_mac.h"
|
||||
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_netif.h"
|
||||
#include "lwip/inet.h"
|
||||
|
||||
#include "esp_http_server.h"
|
||||
#include "dns_server.h"
|
||||
#include "wifi_provisioning.h"
|
||||
#include "frame_client.h"
|
||||
|
||||
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
|
||||
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
|
||||
#define EXAMPLE_MAX_STA_CONN CONFIG_ESP_MAX_STA_CONN
|
||||
|
||||
extern const char root_start[] asm("_binary_root_html_start");
|
||||
extern const char root_end[] asm("_binary_root_html_end");
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
|
||||
wifi_event_ap_staconnected_t *event = (wifi_event_ap_staconnected_t *)event_data;
|
||||
ESP_LOGI(TAG, "station " MACSTR " join, AID=%d",
|
||||
MAC2STR(event->mac), event->aid);
|
||||
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
|
||||
wifi_event_ap_stadisconnected_t *event = (wifi_event_ap_stadisconnected_t *)event_data;
|
||||
ESP_LOGI(TAG, "station " MACSTR " leave, AID=%d, reason=%d",
|
||||
MAC2STR(event->mac), event->aid, event->reason);
|
||||
}
|
||||
}
|
||||
|
||||
static void wifi_init_softap(void)
|
||||
{
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
|
||||
|
||||
wifi_config_t wifi_config = {
|
||||
.ap = {
|
||||
.ssid = EXAMPLE_ESP_WIFI_SSID,
|
||||
.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
|
||||
.password = EXAMPLE_ESP_WIFI_PASS,
|
||||
.max_connection = EXAMPLE_MAX_STA_CONN,
|
||||
.authmode = WIFI_AUTH_WPA_WPA2_PSK
|
||||
},
|
||||
};
|
||||
if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
|
||||
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
esp_netif_ip_info_t ip_info;
|
||||
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
|
||||
|
||||
char ip_addr[16];
|
||||
inet_ntoa_r(ip_info.ip.addr, ip_addr, 16);
|
||||
ESP_LOGI(TAG, "Set up softAP with IP: %s", ip_addr);
|
||||
|
||||
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:'%s' password:'%s'",
|
||||
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ESP_ENABLE_DHCP_CAPTIVEPORTAL
|
||||
static void dhcp_set_captiveportal_url(void) {
|
||||
// get the IP of the access point to redirect to
|
||||
esp_netif_ip_info_t ip_info;
|
||||
esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &ip_info);
|
||||
|
||||
char ip_addr[16];
|
||||
inet_ntoa_r(ip_info.ip.addr, ip_addr, 16);
|
||||
ESP_LOGI(TAG, "Set up softAP with IP: %s", ip_addr);
|
||||
|
||||
// turn the IP into a URI
|
||||
char* captiveportal_uri = (char*) malloc(32 * sizeof(char));
|
||||
assert(captiveportal_uri && "Failed to allocate captiveportal_uri");
|
||||
strcpy(captiveportal_uri, "http://");
|
||||
strcat(captiveportal_uri, ip_addr);
|
||||
|
||||
// get a handle to configure DHCP with
|
||||
esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF");
|
||||
|
||||
// set the DHCP option 114
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcps_stop(netif));
|
||||
ESP_ERROR_CHECK(esp_netif_dhcps_option(netif, ESP_NETIF_OP_SET, ESP_NETIF_CAPTIVEPORTAL_URI, captiveportal_uri, strlen(captiveportal_uri)));
|
||||
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_netif_dhcps_start(netif));
|
||||
}
|
||||
#endif // CONFIG_ESP_ENABLE_DHCP_CAPTIVEPORTAL
|
||||
|
||||
// HTTP GET Handler
|
||||
static esp_err_t root_get_handler(httpd_req_t *req)
|
||||
{
|
||||
const uint32_t root_len = root_end - root_start;
|
||||
|
||||
ESP_LOGI(TAG, "Serve root");
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, root_start, root_len);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static const httpd_uri_t root = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = root_get_handler
|
||||
};
|
||||
|
||||
// HTTP Error (404) Handler - Redirects all requests to the root page
|
||||
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
|
||||
{
|
||||
// Set status
|
||||
httpd_resp_set_status(req, "303 See Other");
|
||||
// Redirect to the "/" root directory
|
||||
httpd_resp_set_hdr(req, "Location", "/");
|
||||
// iOS requires content in the response to detect a captive portal, simply redirecting is not sufficient.
|
||||
httpd_resp_send(req, "Redirect to the captive portal", HTTPD_RESP_USE_STRLEN);
|
||||
|
||||
ESP_LOGI(TAG, "Redirecting to root");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static httpd_handle_t start_webserver(void)
|
||||
{
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.max_open_sockets = 13;
|
||||
config.lru_purge_enable = true;
|
||||
|
||||
// Start the httpd server
|
||||
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
||||
if (httpd_start(&server, &config) == ESP_OK) {
|
||||
// Set URI handlers
|
||||
ESP_LOGI(TAG, "Registering URI handlers");
|
||||
httpd_register_uri_handler(server, &root);
|
||||
httpd_register_err_handler(server, HTTPD_404_NOT_FOUND, http_404_error_handler);
|
||||
}
|
||||
return server;
|
||||
}
|
||||
static const char *TAG = "main";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/*
|
||||
Turn of warnings from HTTP server as redirecting traffic will yield
|
||||
lots of invalid requests
|
||||
*/
|
||||
/* Redirected/invalid captive-portal traffic generates a lot of noise
|
||||
* at the default log level. */
|
||||
esp_log_level_set("httpd_uri", ESP_LOG_ERROR);
|
||||
esp_log_level_set("httpd_txrx", ESP_LOG_ERROR);
|
||||
esp_log_level_set("httpd_parse", ESP_LOG_ERROR);
|
||||
|
||||
|
||||
// Initialize networking stack
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
|
||||
// Create default event loop needed by the main app
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
// Initialize NVS needed by Wi-Fi
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
esp_err_t nvs_err = nvs_flash_init();
|
||||
if (nvs_err == ESP_ERR_NVS_NO_FREE_PAGES || nvs_err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
nvs_err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(nvs_err);
|
||||
|
||||
// Initialize Wi-Fi including netif with default config
|
||||
esp_netif_create_default_wifi_ap();
|
||||
frame_config_t cfg;
|
||||
esp_err_t cfg_err = frame_config_load(&cfg);
|
||||
if (cfg_err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found stored config for '%s', connecting to home WiFi", cfg.sta_ssid);
|
||||
if (frame_wifi_connect_sta(&cfg) == ESP_OK) {
|
||||
frame_client_run(&cfg);
|
||||
return; /* frame_client_run currently never returns */
|
||||
}
|
||||
ESP_LOGW(TAG, "Could not connect to stored WiFi after %d attempts, falling back to provisioning",
|
||||
CONFIG_FRAME_STA_CONNECT_MAX_RETRIES);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "No stored config found (%s), starting provisioning", esp_err_to_name(cfg_err));
|
||||
}
|
||||
|
||||
// Initialise ESP32 in SoftAP mode
|
||||
wifi_init_softap();
|
||||
|
||||
// Configure DNS-based captive portal, if configured
|
||||
#ifdef CONFIG_ESP_ENABLE_DHCP_CAPTIVEPORTAL
|
||||
dhcp_set_captiveportal_url();
|
||||
#endif
|
||||
|
||||
// Start the server for the first time
|
||||
start_webserver();
|
||||
|
||||
// Start the DNS server that will redirect all queries to the softAP IP
|
||||
dns_server_config_t config = DNS_SERVER_CONFIG_SINGLE("*" /* all A queries */, "WIFI_AP_DEF" /* softAP netif ID */);
|
||||
start_dns_server(&config);
|
||||
wifi_provisioning_start();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user