After a successful home WiFi connect, frame_client_run() now redraws the panel as a two-row checklist (WiFi row with a checkmark, server row) so the connection sequence is visible on-device rather than only in serial logs. Refreshes once with the server row pending, probes the tools server with a plain HTTP HEAD (any response, even 404, confirms the socket-level connection works -- there's no real server yet), then refreshes again with the final result. Two refreshes rather than one to actually show staged progress, at the cost of the extra refresh time inherent to this panel. Also fixes a second hardware-verified bug in the same area: on a failed STA connect falling back to provisioning, wifi_init_softap()'s esp_wifi_init() call was aborting with ESP_ERR_INVALID_STATE, because frame_wifi_connect_sta() only stopped the WiFi driver on failure rather than fully deinitializing it (and destroying the STA netif) before handing back control.
161 lines
6.0 KiB
C
161 lines
6.0 KiB
C
#include <string.h>
|
|
|
|
#include "esp_event.h"
|
|
#include "esp_log.h"
|
|
#include "esp_wifi.h"
|
|
#include "esp_wifi_default.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_http_client.h"
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "freertos/event_groups.h"
|
|
|
|
#include "epd7in3e.h"
|
|
#include "status_screen.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_t *sta_netif = 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) {
|
|
/* Fully tear the WiFi driver back down on failure -- the caller
|
|
* falls back to provisioning, which calls esp_wifi_init() again
|
|
* for AP mode. Leaving the driver merely stopped (rather than
|
|
* deinitialized) made that second esp_wifi_init() call fail with
|
|
* ESP_ERR_INVALID_STATE and abort, confirmed on hardware. */
|
|
esp_wifi_stop();
|
|
esp_wifi_deinit();
|
|
esp_netif_destroy_default_wifi(sta_netif);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Probes cfg->toolsserver with a plain HTTP HEAD -- any HTTP response
|
|
* (even a 404, since there's no server yet to define real routes) means
|
|
* the socket-level connection succeeded, which is all this is checking. */
|
|
static bool check_server_reachable(const char *toolsserver)
|
|
{
|
|
char url[160];
|
|
snprintf(url, sizeof(url), "http://%s/", toolsserver);
|
|
|
|
esp_http_client_config_t config = {
|
|
.url = url,
|
|
.method = HTTP_METHOD_HEAD,
|
|
.timeout_ms = CONFIG_FRAME_SERVER_CHECK_TIMEOUT_MS,
|
|
};
|
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
|
esp_err_t err = esp_http_client_perform(client);
|
|
esp_http_client_cleanup(client);
|
|
|
|
if (err != ESP_OK) {
|
|
ESP_LOGW(TAG, "Server '%s' not reachable: %s", toolsserver, esp_err_to_name(err));
|
|
}
|
|
return err == ESP_OK;
|
|
}
|
|
|
|
void frame_client_run(const frame_config_t *cfg)
|
|
{
|
|
esp_err_t epd_err = epd_init();
|
|
if (epd_err != ESP_OK) {
|
|
ESP_LOGW(TAG, "EPD init failed (%s), continuing without display", esp_err_to_name(epd_err));
|
|
} else {
|
|
status_screen_show(cfg->sta_ssid, STATUS_OK, cfg->toolsserver, STATUS_PENDING);
|
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
|
|
|
bool reachable = check_server_reachable(cfg->toolsserver);
|
|
status_screen_show(cfg->sta_ssid, STATUS_OK, cfg->toolsserver, reachable ? STATUS_OK : STATUS_FAILED);
|
|
}
|
|
|
|
/* 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));
|
|
}
|
|
}
|