diff --git a/firmware/main/Kconfig.projbuild b/firmware/main/Kconfig.projbuild index 9f7aa9d..3560c5e 100644 --- a/firmware/main/Kconfig.projbuild +++ b/firmware/main/Kconfig.projbuild @@ -51,4 +51,29 @@ menu "ESPresso Frame Configuration" How long to wait for an HTTP response from the tools server when checking reachability on the post-connect status screen. + config FRAME_FETCH_TIMEOUT_MS + int "Image fetch HTTP timeout (ms)" + default 15000 + help + How long to wait on the GET /frame/image request before giving + up. The server does real work per request (Immich download + + resize + dither), so this is longer than the reachability + check's timeout. + + config FRAME_SLEEP_INTERVAL_S + int "Deep sleep interval between refreshes (seconds)" + default 3600 + help + How long the device deep-sleeps between successful + fetch-and-display cycles. Lower this for bench testing so you + don't have to wait an hour per iteration. + + config FRAME_RETRY_INTERVAL_S + int "Deep sleep interval after a failed cycle (seconds)" + default 300 + help + How long the device deep-sleeps before retrying after the + server was unreachable or the fetch/display failed, instead of + waiting the full FRAME_SLEEP_INTERVAL_S. + endmenu diff --git a/firmware/main/frame_client.c b/firmware/main/frame_client.c index 819cd0b..8fd09ef 100644 --- a/firmware/main/frame_client.c +++ b/firmware/main/frame_client.c @@ -6,9 +6,9 @@ #include "esp_wifi_default.h" #include "esp_netif.h" #include "esp_http_client.h" +#include "esp_sleep.h" #include "freertos/FreeRTOS.h" -#include "freertos/task.h" #include "freertos/event_groups.h" #include "epd7in3e.h" @@ -134,27 +134,94 @@ static bool check_server_reachable(const char *toolsserver) return err == ESP_OK; } +typedef struct { + esp_http_client_handle_t client; +} http_read_ctx_t; + +/* Pulls the next chunk straight out of the in-progress HTTP response -- + * epd_display_stream() calls this to feed the panel without ever holding + * the full ~192KB frame in RAM. */ +static size_t http_read_fn(uint8_t *chunk, size_t chunk_size, void *ctx_) +{ + http_read_ctx_t *ctx = (http_read_ctx_t *)ctx_; + int n = esp_http_client_read(ctx->client, (char *)chunk, (int)chunk_size); + return n > 0 ? (size_t)n : 0; +} + +/* GETs /frame/image and streams the response directly into the panel. */ +static esp_err_t fetch_and_display(const frame_config_t *cfg) +{ + char url[160]; + snprintf(url, sizeof(url), "http://%s/frame/image", cfg->toolsserver); + + esp_http_client_config_t config = { + .url = url, + .method = HTTP_METHOD_GET, + .timeout_ms = CONFIG_FRAME_FETCH_TIMEOUT_MS, + }; + esp_http_client_handle_t client = esp_http_client_init(&config); + + esp_err_t err = esp_http_client_open(client, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to open '%s': %s", url, esp_err_to_name(err)); + esp_http_client_cleanup(client); + return err; + } + + int content_length = esp_http_client_fetch_headers(client); + int status = esp_http_client_get_status_code(client); + if (status != 200) { + ESP_LOGE(TAG, "'%s' returned HTTP %d", url, status); + esp_http_client_close(client); + esp_http_client_cleanup(client); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Fetching frame (%d bytes) from '%s'", content_length, url); + + http_read_ctx_t ctx = { .client = client }; + err = epd_display_stream(http_read_fn, &ctx); + + esp_http_client_close(client); + esp_http_client_cleanup(client); + + return err; +} + void frame_client_run(const frame_config_t *cfg) { esp_err_t epd_err = epd_init(); - if (epd_err != ESP_OK) { + bool have_display = (epd_err == ESP_OK); + if (!have_display) { 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); + uint32_t sleep_seconds = CONFIG_FRAME_SLEEP_INTERVAL_S; + bool server_reachable = check_server_reachable(cfg->toolsserver); - while (1) { - ESP_LOGI(TAG, "heartbeat: still connected"); - vTaskDelay(pdMS_TO_TICKS(60000)); + if (!server_reachable) { + /* Nothing's been drawn yet this cycle, so this is the only + * refresh -- cheap diagnostics without compounding flashing. */ + ESP_LOGW(TAG, "Tools server not reachable, retrying sooner"); + sleep_seconds = CONFIG_FRAME_RETRY_INTERVAL_S; + if (have_display) { + status_screen_show(cfg->sta_ssid, STATUS_OK, cfg->toolsserver, STATUS_FAILED); + } + } else if (have_display) { + esp_err_t fetch_err = fetch_and_display(cfg); + if (fetch_err != ESP_OK) { + /* The panel may have already partially refreshed with a + * truncated frame by this point -- log and retry sooner + * rather than drawing a second status screen on top of it. */ + ESP_LOGW(TAG, "Fetch/display failed (%s), retrying sooner", esp_err_to_name(fetch_err)); + sleep_seconds = CONFIG_FRAME_RETRY_INTERVAL_S; + } } + + if (have_display) { + epd_sleep(); + } + + ESP_LOGI(TAG, "Deep sleeping for %u seconds", (unsigned)sleep_seconds); + esp_sleep_enable_timer_wakeup((uint64_t)sleep_seconds * 1000000ULL); + esp_deep_sleep_start(); }