diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index 2a6dcbd..635665c 100644 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -1,3 +1,3 @@ -idf_component_register(SRCS main.c wifi_provisioning.c frame_client.c qr_onboarding.c - PRIV_REQUIRES esp_event nvs_flash esp_wifi esp_netif esp_http_server dns_server epd7in3e qrcode epaper_fonts +idf_component_register(SRCS main.c wifi_provisioning.c frame_client.c qr_onboarding.c status_screen.c epd_draw.c + PRIV_REQUIRES esp_event nvs_flash esp_wifi esp_netif esp_http_server esp_http_client dns_server epd7in3e qrcode epaper_fonts EMBED_FILES root.html) diff --git a/firmware/main/Kconfig.projbuild b/firmware/main/Kconfig.projbuild index 8e8c79a..9f7aa9d 100644 --- a/firmware/main/Kconfig.projbuild +++ b/firmware/main/Kconfig.projbuild @@ -44,4 +44,11 @@ menu "ESPresso Frame Configuration" How long to wait for an IP address on each connection attempt to the stored home WiFi network before counting it as a failed retry. + config FRAME_SERVER_CHECK_TIMEOUT_MS + int "Tools server reachability check timeout (ms)" + default 3000 + help + How long to wait for an HTTP response from the tools server + when checking reachability on the post-connect status screen. + endmenu diff --git a/firmware/main/frame_client.c b/firmware/main/frame_client.c index 4d3ebf7..819cd0b 100644 --- a/firmware/main/frame_client.c +++ b/firmware/main/frame_client.c @@ -3,12 +3,17 @@ #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"; @@ -50,7 +55,7 @@ esp_err_t frame_wifi_connect_sta(const frame_config_t *cfg) { s_sta_event_group = xEventGroupCreate(); - esp_netif_create_default_wifi_sta(); + 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)); @@ -93,14 +98,55 @@ esp_err_t frame_wifi_connect_sta(const frame_config_t *cfg) 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. */ diff --git a/firmware/main/status_screen.c b/firmware/main/status_screen.c new file mode 100644 index 0000000..d82d612 --- /dev/null +++ b/firmware/main/status_screen.c @@ -0,0 +1,62 @@ +#include +#include + +#include "esp_check.h" + +#include "epd7in3e.h" +#include "epd_draw.h" +#include "fonts.h" + +#include "status_screen.h" + +static const char *TAG = "status_screen"; + +#define TITLE_Y 40 +#define ROW1_Y 160 +#define ROW2_Y 240 +#define LABEL_X 80 +#define MARKER_X 640 +#define CHECKMARK_SIZE 40 + +static void draw_status_marker(uint8_t *frame, int x, int y, status_state_t state) +{ + switch (state) { + case STATUS_OK: + epd_draw_checkmark(frame, x, y - 8, CHECKMARK_SIZE); + break; + case STATUS_FAILED: + epd_draw_text(frame, &Font24, "FAILED", x, y); + break; + case STATUS_PENDING: + default: + epd_draw_text(frame, &Font24, "...", x, y); + break; + } +} + +esp_err_t status_screen_show(const char *wifi_ssid, status_state_t wifi_state, const char *server_addr, + status_state_t server_state) +{ + /* Allocated on demand rather than statically reserved -- see + * qr_onboarding.c for why that's fine memory-budget-wise here. */ + uint8_t *frame = malloc(EPD_FRAME_BYTES); + ESP_RETURN_ON_FALSE(frame != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate frame buffer"); + memset(frame, (EPD_COLOR_WHITE << 4) | EPD_COLOR_WHITE, EPD_FRAME_BYTES); + + epd_draw_text_centered(frame, &Font24, "CONNECTING", EPD_WIDTH / 2, TITLE_Y); + + char wifi_line[64]; + snprintf(wifi_line, sizeof(wifi_line), "WIFI: %s", wifi_ssid); + epd_draw_text(frame, &Font24, wifi_line, LABEL_X, ROW1_Y); + draw_status_marker(frame, MARKER_X, ROW1_Y, wifi_state); + + char server_line[96]; + snprintf(server_line, sizeof(server_line), "SERVER: %s", server_addr); + epd_draw_text(frame, &Font24, server_line, LABEL_X, ROW2_Y); + draw_status_marker(frame, MARKER_X, ROW2_Y, server_state); + + esp_err_t err = epd_display_buffer(frame, EPD_FRAME_BYTES); + free(frame); + + return err; +} diff --git a/firmware/main/status_screen.h b/firmware/main/status_screen.h new file mode 100644 index 0000000..50e284d --- /dev/null +++ b/firmware/main/status_screen.h @@ -0,0 +1,20 @@ +#pragma once + +#include "esp_err.h" + +typedef enum { + STATUS_PENDING, + STATUS_OK, + STATUS_FAILED, +} status_state_t; + +/** + * Renders a two-row connection checklist onto the e-ink panel: a WiFi row + * (SSID + a checkmark once connected) and a server row (toolsserver + * address + a checkmark once reachable), each showing a checkmark on + * STATUS_OK, "FAILED" on STATUS_FAILED, or "..." while STATUS_PENDING. + * Call once per stage as connection progress is confirmed, so the panel + * visibly updates as each step completes. + */ +esp_err_t status_screen_show(const char *wifi_ssid, status_state_t wifi_state, const char *server_addr, + status_state_t server_state);