From ccdb5a3bfb3161eca67f7782f576a70882d502e9 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 13 Apr 2026 12:49:11 -1000 Subject: [PATCH] Track request pointer to avoid re-init on web_server_idf's double index==0 call web_server_idf invokes handleUpload twice with index==0 at the start of every upload (once as a 'Start' signal with nullptr data, then with the first data chunk). The previous fix treated the second call as a stale-session retry, wastefully aborting and re-initializing the backend on every single upload. Store the AsyncWebServerRequest pointer that owns the current session and only tear down the backend when a different request arrives at index==0. Clear the tracked pointer on every backend reset path so a stack-reused request pointer from a later upload cannot collide. --- .../components/web_server/ota/ota_web_server.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp index d5c6f9e7a4..747bbca544 100644 --- a/esphome/components/web_server/ota/ota_web_server.cpp +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -72,6 +72,11 @@ class OTARequestHandler : public AsyncWebHandler { private: ota::OTABackendPtr ota_backend_{nullptr}; + // Tracks the request that owns the current ota_backend_ session so we can + // detect when a new (retry) request arrives while a previous session is + // still open -- without re-triggering on the multiple index==0 callbacks + // web_server_idf makes for a single upload (Start + first data chunk). + AsyncWebServerRequest *ota_request_{nullptr}; }; void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) { @@ -114,8 +119,8 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf uint8_t *data, size_t len, bool final) { ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK; - if (index == 0) { - // A new multipart upload is starting. If a previous upload was interrupted + if (index == 0 && this->ota_request_ != request) { + // A new request is starting an upload. If a previous upload was interrupted // (e.g. TCP reset) the backend from that session may still be open; tear it // down so flash state doesn't get concatenated with the new image (which // can produce a technically-valid-sized but corrupted firmware that @@ -125,6 +130,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf this->ota_backend_->abort(); this->ota_backend_.reset(); } + this->ota_request_ = request; // Initialize OTA on first call this->ota_init_(filename.c_str()); @@ -160,6 +166,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf if (error_code != ota::OTA_RESPONSE_OK) { ESP_LOGE(TAG, "OTA begin failed: %d", error_code); this->ota_backend_.reset(); + this->ota_request_ = nullptr; #ifdef USE_OTA_STATE_LISTENER this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif @@ -178,6 +185,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf ESP_LOGE(TAG, "OTA write failed: %d", error_code); this->ota_backend_->abort(); this->ota_backend_.reset(); + this->ota_request_ = nullptr; #ifdef USE_OTA_STATE_LISTENER this->parent_->notify_state_deferred_(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif @@ -210,6 +218,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Platf #endif } this->ota_backend_.reset(); + this->ota_request_ = nullptr; } }