From b49fe146ad0c119618c2186fb290cc1c767d6d11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 09:44:20 -0500 Subject: [PATCH 1/4] make sure ota still works without ota loaded --- esphome/components/web_server/__init__.py | 5 +- .../web_server_base/web_server_base.cpp | 100 ++++++++++++++++-- esphome/components/web_server_idf/__init__.py | 3 +- 3 files changed, 97 insertions(+), 11 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 54e35e301e..a7e57d6e7d 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -274,8 +274,9 @@ async def to_code(config): cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) cg.add(var.set_allow_ota(config[CONF_OTA])) - if config[CONF_OTA] and "ota" in CORE.loaded_integrations: - # Only define USE_WEBSERVER_OTA if OTA component is actually loaded + if config[CONF_OTA]: + # Define USE_WEBSERVER_OTA based only on web_server OTA config + # This allows web server OTA to work without loading the OTA component cg.add_define("USE_WEBSERVER_OTA") cg.add(var.set_expose_log(config[CONF_LOG])) if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]: diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 3a41c7db3d..868496a2fe 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -15,7 +15,8 @@ #endif #if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) -#include "esphome/components/ota/ota_backend.h" +#include +#include #endif namespace esphome { @@ -23,6 +24,90 @@ namespace web_server_base { static const char *const TAG = "web_server_base"; +#if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) +// Minimal OTA backend implementation for web server +// This allows OTA updates via web server without requiring the OTA component +class IDFWebServerOTABackend { + public: + bool begin() { + this->partition_ = esp_ota_get_next_update_partition(nullptr); + if (this->partition_ == nullptr) { + ESP_LOGE(TAG, "No OTA partition available"); + return false; + } + +#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 + // The following function takes longer than the 5 seconds timeout of WDT +#if ESP_IDF_VERSION_MAJOR >= 5 + esp_task_wdt_config_t wdtc; + wdtc.idle_core_mask = 0; +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 + wdtc.idle_core_mask |= (1 << 0); +#endif +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 + wdtc.idle_core_mask |= (1 << 1); +#endif + wdtc.timeout_ms = 15000; + wdtc.trigger_panic = false; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(15, false); +#endif +#endif + + esp_err_t err = esp_ota_begin(this->partition_, 0, &this->update_handle_); + +#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 + // Set the WDT back to the configured timeout +#if ESP_IDF_VERSION_MAJOR >= 5 + wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); +#endif +#endif + + if (err != ESP_OK) { + esp_ota_abort(this->update_handle_); + this->update_handle_ = 0; + ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err)); + return false; + } + return true; + } + + bool write(uint8_t *data, size_t len) { + esp_err_t err = esp_ota_write(this->update_handle_, data, len); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_write failed: %s", esp_err_to_name(err)); + return false; + } + return true; + } + + bool end() { + esp_err_t err = esp_ota_end(this->update_handle_); + this->update_handle_ = 0; + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_end failed: %s", esp_err_to_name(err)); + return false; + } + + err = esp_ota_set_boot_partition(this->partition_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_set_boot_partition failed: %s", esp_err_to_name(err)); + return false; + } + + return true; + } + + private: + esp_ota_handle_t update_handle_{0}; + const esp_partition_t *partition_{nullptr}; +}; +#endif + void WebServerBase::add_handler(AsyncWebHandler *handler) { // remove all handlers @@ -120,22 +205,23 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin this->ota_init_(filename.c_str()); this->ota_success_ = false; - auto backend = ota::make_ota_backend(); - if (backend->begin(0) != ota::OTA_RESPONSE_OK) { + auto *backend = new IDFWebServerOTABackend(); + if (!backend->begin()) { ESP_LOGE(TAG, "OTA begin failed"); + delete backend; return; } - this->ota_backend_ = backend.release(); + this->ota_backend_ = backend; } - auto *backend = static_cast(this->ota_backend_); + auto *backend = static_cast(this->ota_backend_); if (!backend) { return; } // Process data if (len > 0) { - if (backend->write(data, len) != ota::OTA_RESPONSE_OK) { + if (!backend->write(data, len)) { ESP_LOGE(TAG, "OTA write failed"); backend->abort(); delete backend; @@ -148,7 +234,7 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin // Finalize if (final) { - this->ota_success_ = (backend->end() == ota::OTA_RESPONSE_OK); + this->ota_success_ = backend->end(); if (this->ota_success_) { this->schedule_ota_reboot_(); } else { diff --git a/esphome/components/web_server_idf/__init__.py b/esphome/components/web_server_idf/__init__.py index 4e6f21cd03..fe1c6f2640 100644 --- a/esphome/components/web_server_idf/__init__.py +++ b/esphome/components/web_server_idf/__init__.py @@ -15,7 +15,6 @@ async def to_code(config): # Increase the maximum supported size of headers section in HTTP request packet to be processed by the server add_idf_sdkconfig_option("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024) # Check if web_server component has OTA enabled - web_server_config = CORE.config.get(CONF_WEB_SERVER, {}) - if web_server_config.get(CONF_OTA, True) and "ota" in CORE.loaded_integrations: + if CORE.config.get(CONF_WEB_SERVER, {}).get(CONF_OTA, True): # Add multipart parser component for ESP-IDF OTA support add_idf_component(name="zorxx/multipart-parser", ref="1.0.1") From 7f2f9636f5bb058f1f7ae8f84e75f5034867c09f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 09:46:33 -0500 Subject: [PATCH 2/4] make sure ota still works without ota loaded --- .../web_server_base/web_server_base.cpp | 38 ++++--------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 868496a2fe..5116fd7e7c 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -16,7 +16,6 @@ #if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) #include -#include #endif namespace esphome { @@ -36,37 +35,7 @@ class IDFWebServerOTABackend { return false; } -#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 - // The following function takes longer than the 5 seconds timeout of WDT -#if ESP_IDF_VERSION_MAJOR >= 5 - esp_task_wdt_config_t wdtc; - wdtc.idle_core_mask = 0; -#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 - wdtc.idle_core_mask |= (1 << 0); -#endif -#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 - wdtc.idle_core_mask |= (1 << 1); -#endif - wdtc.timeout_ms = 15000; - wdtc.trigger_panic = false; - esp_task_wdt_reconfigure(&wdtc); -#else - esp_task_wdt_init(15, false); -#endif -#endif - esp_err_t err = esp_ota_begin(this->partition_, 0, &this->update_handle_); - -#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 - // Set the WDT back to the configured timeout -#if ESP_IDF_VERSION_MAJOR >= 5 - wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000; - esp_task_wdt_reconfigure(&wdtc); -#else - esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); -#endif -#endif - if (err != ESP_OK) { esp_ota_abort(this->update_handle_); this->update_handle_ = 0; @@ -102,6 +71,13 @@ class IDFWebServerOTABackend { return true; } + void abort() { + if (this->update_handle_ != 0) { + esp_ota_abort(this->update_handle_); + this->update_handle_ = 0; + } + } + private: esp_ota_handle_t update_handle_{0}; const esp_partition_t *partition_{nullptr}; From 928819ffbd05b4d6a7ed43a80dbf6edf6194ce68 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 09:49:59 -0500 Subject: [PATCH 3/4] fixes --- .../web_server_base/web_server_base.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 5116fd7e7c..5bff06e7ad 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -16,6 +16,7 @@ #if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) #include +#include #endif namespace esphome { @@ -35,7 +36,37 @@ class IDFWebServerOTABackend { return false; } +#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 + // The following function takes longer than the default timeout of WDT due to flash erase +#if ESP_IDF_VERSION_MAJOR >= 5 + esp_task_wdt_config_t wdtc; + wdtc.idle_core_mask = 0; +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0 + wdtc.idle_core_mask |= (1 << 0); +#endif +#if CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1 + wdtc.idle_core_mask |= (1 << 1); +#endif + wdtc.timeout_ms = 15000; + wdtc.trigger_panic = false; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(15, false); +#endif +#endif + esp_err_t err = esp_ota_begin(this->partition_, 0, &this->update_handle_); + +#if CONFIG_ESP_TASK_WDT_TIMEOUT_S < 15 + // Set the WDT back to the configured timeout +#if ESP_IDF_VERSION_MAJOR >= 5 + wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S * 1000; + esp_task_wdt_reconfigure(&wdtc); +#else + esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); +#endif +#endif + if (err != ESP_OK) { esp_ota_abort(this->update_handle_); this->update_handle_ = 0; From 14123d25c2a8cf59cb12e9112d236b26f91bbd34 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 09:50:46 -0500 Subject: [PATCH 4/4] fixes --- esphome/components/web_server_base/web_server_base.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 5bff06e7ad..78c4b6b57a 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -27,6 +27,10 @@ static const char *const TAG = "web_server_base"; #if defined(USE_ESP_IDF) && defined(USE_WEBSERVER_OTA) // Minimal OTA backend implementation for web server // This allows OTA updates via web server without requiring the OTA component +// TODO: In the future, this should be refactored into a common ota_base component +// that both web_server and ota components can depend on, avoiding code duplication +// while keeping the components independent. This would allow both ESP-IDF and Arduino +// implementations to share the base OTA functionality without requiring the full OTA component. class IDFWebServerOTABackend { public: bool begin() {