diff --git a/firmware/components/epd7in3e/epd7in3e.c b/firmware/components/epd7in3e/epd7in3e.c index 9a4f846..0ed3596 100644 --- a/firmware/components/epd7in3e/epd7in3e.c +++ b/firmware/components/epd7in3e/epd7in3e.c @@ -234,7 +234,17 @@ esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx) EPD_CHECK(err); if (total != EPD_FRAME_BYTES) { - ESP_LOGW(TAG, "Stream supplied %u bytes, expected %u", (unsigned)total, (unsigned)EPD_FRAME_BYTES); + /* Whatever was received has already been clocked into the panel's + * internal RAM over SPI, but epd_turn_on_display() (the actual + * physical refresh trigger) hasn't been called yet -- returning + * here instead leaves the visible screen exactly as it was, rather + * than refreshing onto a mostly-garbage buffer. Confirmed on + * hardware: a misdirected fetch that returned a ~10KB error page + * instead of a 192,000-byte frame still triggered a refresh before + * this check existed, painting garbage over a previously-good image. */ + ESP_LOGE(TAG, "Stream supplied %u bytes, expected %u -- aborting refresh", + (unsigned)total, (unsigned)EPD_FRAME_BYTES); + return ESP_ERR_INVALID_SIZE; } return epd_turn_on_display(); diff --git a/firmware/main/frame_client.c b/firmware/main/frame_client.c index 244282d..9d0e77a 100644 --- a/firmware/main/frame_client.c +++ b/firmware/main/frame_client.c @@ -213,13 +213,12 @@ static size_t http_read_fn(uint8_t *chunk, size_t chunk_size, void *ctx_) } /* GETs /frame/image and streams the response directly into the panel. - * *out_started_stream tells the caller whether the panel was actually - * touched -- false means the failure happened before any pixel data was - * sent (bad status, connection error), so nothing was drawn this cycle. */ -static esp_err_t fetch_and_display(const frame_config_t *cfg, bool *out_started_stream) + * Returning non-ESP_OK means the panel was never actually refreshed -- + * epd_display_stream() (see epd7in3e.c) refuses to trigger a physical + * refresh on a short/wrong-size stream, so a failure here always leaves + * the visible screen exactly as it was. */ +static esp_err_t fetch_and_display(const frame_config_t *cfg) { - *out_started_stream = false; - char url[160]; snprintf(url, sizeof(url), "http://%s/frame/image", cfg->toolsserver); @@ -247,7 +246,6 @@ static esp_err_t fetch_and_display(const frame_config_t *cfg, bool *out_started_ } ESP_LOGI(TAG, "Fetching frame (%d bytes) from '%s'", content_length, url); - *out_started_stream = true; http_read_ctx_t ctx = { .client = client }; err = epd_display_stream(http_read_fn, &ctx); @@ -294,20 +292,16 @@ void frame_client_run(const frame_config_t *cfg) status_screen_show(cfg->sta_ssid, STATUS_OK, cfg->toolsserver, STATUS_OK); } if (have_display) { - bool stream_started = false; - esp_err_t fetch_err = fetch_and_display(cfg, &stream_started); + esp_err_t fetch_err = fetch_and_display(cfg); if (fetch_err != ESP_OK) { + /* epd_display_stream() never triggers a physical refresh on + * a failed/short/wrong-size stream (see epd7in3e.c), so the + * visible screen is guaranteed untouched here -- always + * safe to show what went wrong instead of leaving stale + * content with no indication anything failed. */ ESP_LOGW(TAG, "Fetch/display failed (%s), retrying sooner", esp_err_to_name(fetch_err)); sleep_seconds = CONFIG_FRAME_RETRY_INTERVAL_S; - if (!stream_started) { - /* The panel was never touched this cycle (e.g. a bad - * HTTP status before any pixel data was sent) -- show - * the failure instead of leaving whatever was on - * screen before. A mid-stream failure, by contrast, - * already refreshed the panel with something, so - * skip compounding it with a second refresh here. */ - status_screen_show(cfg->sta_ssid, STATUS_OK, cfg->toolsserver, STATUS_FAILED); - } + status_screen_show(cfg->sta_ssid, STATUS_OK, cfg->toolsserver, STATUS_FAILED); } } }