diff --git a/esphome/components/ota_base/ota_backend_arduino_esp32.cpp b/esphome/components/ota_base/ota_backend_arduino_esp32.cpp index b89c61472a..f239544cfe 100644 --- a/esphome/components/ota_base/ota_backend_arduino_esp32.cpp +++ b/esphome/components/ota_base/ota_backend_arduino_esp32.cpp @@ -34,7 +34,10 @@ OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoESP32OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } +void ArduinoESP32OTABackend::set_update_md5(const char *md5) { + Update.setMD5(md5); + this->md5_set_ = true; +} OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); @@ -49,7 +52,9 @@ OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes ArduinoESP32OTABackend::end() { - if (Update.end()) { + // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 + // This matches the behavior of the old web_server OTA implementation + if (Update.end(!this->md5_set_)) { return OTA_RESPONSE_OK; } diff --git a/esphome/components/ota_base/ota_backend_arduino_esp32.h b/esphome/components/ota_base/ota_backend_arduino_esp32.h index 6fb9454c64..e3966eb2f6 100644 --- a/esphome/components/ota_base/ota_backend_arduino_esp32.h +++ b/esphome/components/ota_base/ota_backend_arduino_esp32.h @@ -16,6 +16,9 @@ class ArduinoESP32OTABackend : public OTABackend { OTAResponseTypes end() override; void abort() override; bool supports_compression() override { return false; } + + private: + bool md5_set_{false}; }; } // namespace ota_base diff --git a/esphome/components/ota_base/ota_backend_arduino_esp8266.cpp b/esphome/components/ota_base/ota_backend_arduino_esp8266.cpp index 1133ce7b05..5df2ed0a58 100644 --- a/esphome/components/ota_base/ota_backend_arduino_esp8266.cpp +++ b/esphome/components/ota_base/ota_backend_arduino_esp8266.cpp @@ -43,7 +43,10 @@ OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } +void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { + Update.setMD5(md5); + this->md5_set_ = true; +} OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); @@ -58,7 +61,9 @@ OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes ArduinoESP8266OTABackend::end() { - if (Update.end()) { + // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 + // This matches the behavior of the old web_server OTA implementation + if (Update.end(!this->md5_set_)) { return OTA_RESPONSE_OK; } diff --git a/esphome/components/ota_base/ota_backend_arduino_esp8266.h b/esphome/components/ota_base/ota_backend_arduino_esp8266.h index 3f9982a514..f399013121 100644 --- a/esphome/components/ota_base/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota_base/ota_backend_arduino_esp8266.h @@ -21,6 +21,9 @@ class ArduinoESP8266OTABackend : public OTABackend { #else bool supports_compression() override { return false; } #endif + + private: + bool md5_set_{false}; }; } // namespace ota_base diff --git a/esphome/components/ota_base/ota_backend_arduino_libretiny.cpp b/esphome/components/ota_base/ota_backend_arduino_libretiny.cpp index 052a9faed9..2596e3c2a3 100644 --- a/esphome/components/ota_base/ota_backend_arduino_libretiny.cpp +++ b/esphome/components/ota_base/ota_backend_arduino_libretiny.cpp @@ -34,7 +34,10 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } +void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { + Update.setMD5(md5); + this->md5_set_ = true; +} OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); @@ -49,7 +52,9 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes ArduinoLibreTinyOTABackend::end() { - if (Update.end()) { + // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 + // This matches the behavior of the old web_server OTA implementation + if (Update.end(!this->md5_set_)) { return OTA_RESPONSE_OK; } diff --git a/esphome/components/ota_base/ota_backend_arduino_libretiny.h b/esphome/components/ota_base/ota_backend_arduino_libretiny.h index b1cf1df738..33eebeb95a 100644 --- a/esphome/components/ota_base/ota_backend_arduino_libretiny.h +++ b/esphome/components/ota_base/ota_backend_arduino_libretiny.h @@ -15,6 +15,9 @@ class ArduinoLibreTinyOTABackend : public OTABackend { OTAResponseTypes end() override; void abort() override; bool supports_compression() override { return false; } + + private: + bool md5_set_{false}; }; } // namespace ota_base diff --git a/esphome/components/ota_base/ota_backend_arduino_rp2040.cpp b/esphome/components/ota_base/ota_backend_arduino_rp2040.cpp index bcb87f3547..589187f615 100644 --- a/esphome/components/ota_base/ota_backend_arduino_rp2040.cpp +++ b/esphome/components/ota_base/ota_backend_arduino_rp2040.cpp @@ -43,7 +43,10 @@ OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) { return OTA_RESPONSE_ERROR_UNKNOWN; } -void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } +void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { + Update.setMD5(md5); + this->md5_set_ = true; +} OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) { size_t written = Update.write(data, len); @@ -58,7 +61,9 @@ OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) { } OTAResponseTypes ArduinoRP2040OTABackend::end() { - if (Update.end()) { + // Use strict validation (false) when MD5 is set, lenient validation (true) when no MD5 + // This matches the behavior of the old web_server OTA implementation + if (Update.end(!this->md5_set_)) { return OTA_RESPONSE_OK; } diff --git a/esphome/components/ota_base/ota_backend_arduino_rp2040.h b/esphome/components/ota_base/ota_backend_arduino_rp2040.h index fb6e90bb53..6d622d4a5a 100644 --- a/esphome/components/ota_base/ota_backend_arduino_rp2040.h +++ b/esphome/components/ota_base/ota_backend_arduino_rp2040.h @@ -17,6 +17,9 @@ class ArduinoRP2040OTABackend : public OTABackend { OTAResponseTypes end() override; void abort() override; bool supports_compression() override { return false; } + + private: + bool md5_set_{false}; }; } // namespace ota_base diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 1503ee3bdd..a683ee85eb 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -67,6 +67,7 @@ void OTARequestHandler::schedule_ota_reboot_() { void OTARequestHandler::ota_init_(const char *filename) { ESP_LOGI(TAG, "OTA Update Start: %s", filename); this->ota_read_length_ = 0; + this->ota_success_ = false; } void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, @@ -140,8 +141,15 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin // Finalize if (final) { + ESP_LOGD(TAG, "OTA final chunk: index=%u, len=%u, total_read=%u, contentLength=%u", index, len, + this->ota_read_length_, request->contentLength()); + + // For Arduino framework, the Update library tracks expected size from firmware header + // If we haven't received enough data, calling end() will fail + // This can happen if the upload is interrupted or the client disconnects error_code = this->ota_backend_->end(); if (error_code == ota_base::OTA_RESPONSE_OK) { + this->ota_success_ = true; #ifdef USE_OTA_STATE_CALLBACK // Report completion before reboot - use call_deferred since we're in web server task this->parent_->state_callback_.call_deferred(ota_base::OTA_COMPLETED, 100.0f, 0); @@ -159,8 +167,9 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { AsyncWebServerResponse *response; - // Send response based on whether backend still exists (error) or was reset (success) - response = request->beginResponse(200, "text/plain", !this->ota_backend_ ? "Update Successful!" : "Update Failed!"); + // Use the ota_success_ flag to determine the actual result + const char *msg = this->ota_success_ ? "Update Successful!" : "Update Failed!"; + response = request->beginResponse(200, "text/plain", msg); response->addHeader("Connection", "close"); request->send(response); } diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index 6f3a770e42..99087ddfa8 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -159,6 +159,7 @@ class OTARequestHandler : public AsyncWebHandler { uint32_t last_ota_progress_{0}; uint32_t ota_read_length_{0}; WebServerBase *parent_; + bool ota_success_{false}; private: std::unique_ptr ota_backend_{nullptr};