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:
@@ -0,0 +1,114 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_netif.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#include "frame_client.h"
|
||||
|
||||
static const char *TAG = "frame_client";
|
||||
|
||||
#define STA_CONNECTED_BIT BIT0
|
||||
#define STA_FAILED_BIT BIT1
|
||||
|
||||
static EventGroupHandle_t s_sta_event_group;
|
||||
|
||||
/* wifi_sta_config_t's ssid/password fields are fixed-size byte arrays, not
|
||||
* necessarily null-terminated (a full 32-char SSID fills the field exactly).
|
||||
* snprintf() flags that as a possible truncation at -Werror, so copy by
|
||||
* hand instead. */
|
||||
static void copy_wifi_field(uint8_t *dst, size_t dst_size, const char *src)
|
||||
{
|
||||
size_t len = strnlen(src, dst_size);
|
||||
memcpy(dst, src, len);
|
||||
if (len < dst_size) {
|
||||
dst[len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void sta_event_handler(void *arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void *event_data)
|
||||
{
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||
esp_wifi_connect();
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
ESP_LOGW(TAG, "Disconnected from home WiFi");
|
||||
xEventGroupSetBits(s_sta_event_group, STA_FAILED_BIT);
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
xEventGroupSetBits(s_sta_event_group, STA_CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t frame_wifi_connect_sta(const frame_config_t *cfg)
|
||||
{
|
||||
s_sta_event_group = xEventGroupCreate();
|
||||
|
||||
esp_netif_create_default_wifi_sta();
|
||||
|
||||
wifi_init_config_t init_cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&init_cfg));
|
||||
|
||||
esp_event_handler_instance_t wifi_handler;
|
||||
esp_event_handler_instance_t ip_handler;
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &sta_event_handler, NULL, &wifi_handler));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &sta_event_handler, NULL, &ip_handler));
|
||||
|
||||
wifi_config_t wifi_config = {0};
|
||||
copy_wifi_field(wifi_config.sta.ssid, sizeof(wifi_config.sta.ssid), cfg->sta_ssid);
|
||||
copy_wifi_field(wifi_config.sta.password, sizeof(wifi_config.sta.password), cfg->sta_password);
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
esp_err_t result = ESP_FAIL;
|
||||
for (int attempt = 1; attempt <= CONFIG_FRAME_STA_CONNECT_MAX_RETRIES; attempt++) {
|
||||
ESP_LOGI(TAG, "Connecting to '%s' (attempt %d/%d)", cfg->sta_ssid, attempt,
|
||||
CONFIG_FRAME_STA_CONNECT_MAX_RETRIES);
|
||||
|
||||
xEventGroupClearBits(s_sta_event_group, STA_CONNECTED_BIT | STA_FAILED_BIT);
|
||||
esp_wifi_connect();
|
||||
|
||||
EventBits_t bits = xEventGroupWaitBits(s_sta_event_group, STA_CONNECTED_BIT | STA_FAILED_BIT,
|
||||
pdTRUE, pdFALSE,
|
||||
pdMS_TO_TICKS(CONFIG_FRAME_STA_CONNECT_TIMEOUT_MS));
|
||||
|
||||
if (bits & STA_CONNECTED_BIT) {
|
||||
result = ESP_OK;
|
||||
break;
|
||||
}
|
||||
ESP_LOGW(TAG, "Attempt %d/%d failed", attempt, CONFIG_FRAME_STA_CONNECT_MAX_RETRIES);
|
||||
}
|
||||
|
||||
esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_handler);
|
||||
esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, ip_handler);
|
||||
vEventGroupDelete(s_sta_event_group);
|
||||
s_sta_event_group = NULL;
|
||||
|
||||
if (result != ESP_OK) {
|
||||
esp_wifi_stop();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void frame_client_run(const frame_config_t *cfg)
|
||||
{
|
||||
/* TODO(step 6): HTTP fetch from cfg->toolsserver -> epd_write_stream ->
|
||||
* epd_refresh -> esp_deep_sleep. Idling here for now so the device
|
||||
* stays observable over serial while STA connectivity is verified. */
|
||||
ESP_LOGI(TAG, "Connected to home WiFi. toolsserver='%s' (fetch/display cycle not yet implemented)",
|
||||
cfg->toolsserver);
|
||||
|
||||
while (1) {
|
||||
ESP_LOGI(TAG, "heartbeat: still connected");
|
||||
vTaskDelay(pdMS_TO_TICKS(60000));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user