From b49fe146ad0c119618c2186fb290cc1c767d6d11 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 09:44:20 -0500 Subject: [PATCH] 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")