From 0b9d1a7d2350e9f14ab765f88d68c5966d48504f Mon Sep 17 00:00:00 2001 From: Jeremy Klein Date: Wed, 20 May 2026 05:46:49 -0700 Subject: [PATCH] fix: keep wifi OTA off the heap floor (#2074) OTA install streams the full 5.8MB image over a multi-minute TLS session while wifi/LWIP already holds the big internal arena. Measured on device, the arena bottomed out at ~7.7KB free with the largest contiguous block down to ~2.4KB; for 80% of the download there wasn't even a contiguous 8KB block. It finishes on a clean heap but tips into OOM for anyone carrying more pre-OTA fragmentation. Two avoidable drains, both in OtaUpdater: - The esp_http_client RX/TX buffers were 8192/8192 on both the version check and the install. RX only has to hold response headers (bodies stream through the parser / OTA writer) and TX only carries our GET, so trim both to 4096/1024. 4096 still fits the github->CDN redirect headers; the 512 IDF default truncates them, which is why they got oversized in the first place. - installUpdate fired the progress callback every ~100ms perform iteration, waking the render task on every tick. Its framebuffer work fights the TLS session for the same arena, and epd can't really repaint faster than a percent anyway. Throttle it to whole-percent changes. On device, combined: floor 7.7KB -> 19KB, worstcase contiguous block 2.4KB -> 21KB, zero sub 8KB iterations across the whole download. KOReaderSyncClient already uses small buffers; HttpDownloader is on the Arduino HTTPClient stack with no equivalent knob, so neither changed. Did you use AI tools to help write this code? partial, heap-exploration assisted by Claude. --- src/network/OtaUpdater.cpp | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/network/OtaUpdater.cpp b/src/network/OtaUpdater.cpp index 6c05d553..658b145a 100644 --- a/src/network/OtaUpdater.cpp +++ b/src/network/OtaUpdater.cpp @@ -33,8 +33,11 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() { esp_http_client_config_t client_config = { .url = latestReleaseUrl, .event_handler = event_handler, - .buffer_size = 8192, - .buffer_size_tx = 8192, + // 4096 holds the API response headers; the 32KB body streams through the + // parser in chunks so RX needn't be larger. TX only carries our GET. + // Both free before installUpdate, so smaller leaves it less fragmentation. + .buffer_size = 4096, + .buffer_size_tx = 1024, .user_data = &releaseParser, .skip_cert_common_name_check = true, .crt_bundle_attach = esp_crt_bundle_attach, @@ -151,12 +154,11 @@ OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate(ProgressCallback onProgres esp_http_client_config_t client_config = { .url = otaUrl.c_str(), .timeout_ms = 15000, - /* Default HTTP client buffer size 512 byte only - * not sufficient to handle URL redirection cases or - * parsing of large HTTP headers. - */ - .buffer_size = 8192, - .buffer_size_tx = 8192, + // 4096 holds the github->CDN redirect headers (the 512 default truncates + // them); TX only carries our GET. Both are contiguous blocks contending + // with the TLS handshake on a tight internal arena, so keep them minimal. + .buffer_size = 4096, + .buffer_size_tx = 1024, .skip_cert_common_name_check = true, .crt_bundle_attach = esp_crt_bundle_attach, .keep_alive_enable = true, @@ -176,10 +178,21 @@ OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate(ProgressCallback onProgres return INTERNAL_UPDATE_ERROR; } + int lastReportedPct = -1; do { esp_err = esp_https_ota_perform(ota_handle); processedSize = esp_https_ota_get_image_len_read(ota_handle); - if (onProgress) onProgress(ctx); + // Fire the callback only on whole-percent change. Without this it fired + // every ~100ms perform iteration, waking the render task whose framebuffer + // work contends with TLS on the same internal arena. E-ink can't repaint + // faster than a percent tick anyway. + if (onProgress && totalSize > 0) { + const int pct = static_cast(static_cast(processedSize) * 100 / totalSize); + if (pct != lastReportedPct) { + lastReportedPct = pct; + onProgress(ctx); + } + } delay(100); // TODO: should we replace this with something better? } while (esp_err == ESP_ERR_HTTPS_OTA_IN_PROGRESS);