From c008e6aa1c1338aa334ca69c3d69c4be22a8ce5b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 1 Jul 2025 15:28:11 -0500 Subject: [PATCH] revert ota_base changes, move to platform --- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 8 +- esphome/components/esphome/ota/__init__.py | 3 +- .../components/esphome/ota/ota_esphome.cpp | 60 ++--- esphome/components/esphome/ota/ota_esphome.h | 4 +- .../components/http_request/ota/__init__.py | 5 +- .../http_request/ota/ota_http_request.cpp | 36 +-- .../http_request/ota/ota_http_request.h | 6 +- .../update/http_request_update.cpp | 8 +- .../micro_wake_word/micro_wake_word.cpp | 10 +- esphome/components/ota/__init__.py | 16 +- esphome/components/ota/automation.h | 16 +- .../{ota_base => ota}/ota_backend.cpp | 4 +- .../{ota_base => ota}/ota_backend.h | 4 +- .../ota_backend_arduino_esp32.cpp | 4 +- .../ota_backend_arduino_esp32.h | 4 +- .../ota_backend_arduino_esp8266.cpp | 4 +- .../ota_backend_arduino_esp8266.h | 4 +- .../ota_backend_arduino_libretiny.cpp | 4 +- .../ota_backend_arduino_libretiny.h | 4 +- .../ota_backend_arduino_rp2040.cpp | 4 +- .../ota_backend_arduino_rp2040.h | 4 +- .../{ota_base => ota}/ota_backend_esp_idf.cpp | 4 +- .../{ota_base => ota}/ota_backend_esp_idf.h | 4 +- esphome/components/ota_base/__init__.py | 23 -- .../media_player/speaker_media_player.cpp | 10 +- esphome/components/web_server/__init__.py | 26 ++- esphome/components/web_server/ota/__init__.py | 29 +++ .../web_server/ota/ota_web_server.cpp | 206 ++++++++++++++++++ .../web_server/ota/ota_web_server.h | 26 +++ esphome/components/web_server/web_server.cpp | 11 +- esphome/components/web_server/web_server.h | 6 - .../components/web_server/web_server_v1.cpp | 9 +- .../components/web_server_base/__init__.py | 1 + .../web_server_base/web_server_base.cpp | 164 +------------- .../web_server_base/web_server_base.h | 48 +--- 35 files changed, 411 insertions(+), 368 deletions(-) rename esphome/components/{ota_base => ota}/ota_backend.cpp (93%) rename esphome/components/{ota_base => ota}/ota_backend.h (98%) rename esphome/components/{ota_base => ota}/ota_backend_arduino_esp32.cpp (97%) rename esphome/components/{ota_base => ota}/ota_backend_arduino_esp32.h (92%) rename esphome/components/{ota_base => ota}/ota_backend_arduino_esp8266.cpp (98%) rename esphome/components/{ota_base => ota}/ota_backend_arduino_esp8266.h (93%) rename esphome/components/{ota_base => ota}/ota_backend_arduino_libretiny.cpp (97%) rename esphome/components/{ota_base => ota}/ota_backend_arduino_libretiny.h (92%) rename esphome/components/{ota_base => ota}/ota_backend_arduino_rp2040.cpp (98%) rename esphome/components/{ota_base => ota}/ota_backend_arduino_rp2040.h (93%) rename esphome/components/{ota_base => ota}/ota_backend_esp_idf.cpp (98%) rename esphome/components/{ota_base => ota}/ota_backend_esp_idf.h (93%) delete mode 100644 esphome/components/ota_base/__init__.py create mode 100644 esphome/components/web_server/ota/__init__.py create mode 100644 esphome/components/web_server/ota/ota_web_server.cpp create mode 100644 esphome/components/web_server/ota/ota_web_server.h diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 8e785da4be..d950ccb5f1 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -18,7 +18,7 @@ #include #ifdef USE_OTA -#include "esphome/components/ota_base/ota_backend.h" +#include "esphome/components/ota/ota_backend.h" #endif #ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE @@ -61,9 +61,9 @@ void ESP32BLETracker::setup() { global_esp32_ble_tracker = this; #ifdef USE_OTA - ota_base::get_global_ota_callback()->add_on_state_callback( - [this](ota_base::OTAState state, float progress, uint8_t error, ota_base::OTAComponent *comp) { - if (state == ota_base::OTA_STARTED) { + ota::get_global_ota_callback()->add_on_state_callback( + [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { this->stop_scan(); for (auto *client : this->clients_) { client->disconnect(); diff --git a/esphome/components/esphome/ota/__init__.py b/esphome/components/esphome/ota/__init__.py index bf5c438f9b..901657ec82 100644 --- a/esphome/components/esphome/ota/__init__.py +++ b/esphome/components/esphome/ota/__init__.py @@ -1,8 +1,7 @@ import logging import esphome.codegen as cg -from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code -from esphome.components.ota_base import OTAComponent +from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code from esphome.config_helpers import merge_config import esphome.config_validation as cv from esphome.const import ( diff --git a/esphome/components/esphome/ota/ota_esphome.cpp b/esphome/components/esphome/ota/ota_esphome.cpp index 5f8d1baf49..e250e5dc99 100644 --- a/esphome/components/esphome/ota/ota_esphome.cpp +++ b/esphome/components/esphome/ota/ota_esphome.cpp @@ -2,12 +2,12 @@ #ifdef USE_OTA #include "esphome/components/md5/md5.h" #include "esphome/components/network/util.h" -#include "esphome/components/ota_base/ota_backend.h" // For OTAComponent and callbacks -#include "esphome/components/ota_base/ota_backend_arduino_esp32.h" -#include "esphome/components/ota_base/ota_backend_arduino_esp8266.h" -#include "esphome/components/ota_base/ota_backend_arduino_libretiny.h" -#include "esphome/components/ota_base/ota_backend_arduino_rp2040.h" -#include "esphome/components/ota_base/ota_backend_esp_idf.h" +#include "esphome/components/ota/ota_backend.h" // For OTAComponent and callbacks +#include "esphome/components/ota/ota_backend_arduino_esp32.h" +#include "esphome/components/ota/ota_backend_arduino_esp8266.h" +#include "esphome/components/ota/ota_backend_arduino_libretiny.h" +#include "esphome/components/ota/ota_backend_arduino_rp2040.h" +#include "esphome/components/ota/ota_backend_esp_idf.h" #include "esphome/core/application.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" @@ -23,7 +23,7 @@ static constexpr u_int16_t OTA_BLOCK_SIZE = 8192; void ESPHomeOTAComponent::setup() { #ifdef USE_OTA_STATE_CALLBACK - ota_base::register_ota_platform(this); + ota::register_ota_platform(this); #endif this->server_ = socket::socket_ip_loop_monitored(SOCK_STREAM, 0); // monitored for incoming connections @@ -94,7 +94,7 @@ void ESPHomeOTAComponent::loop() { static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01; void ESPHomeOTAComponent::handle_() { - ota_base::OTAResponseTypes error_code = ota_base::OTA_RESPONSE_ERROR_UNKNOWN; + ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_ERROR_UNKNOWN; bool update_started = false; size_t total = 0; uint32_t last_progress = 0; @@ -102,7 +102,7 @@ void ESPHomeOTAComponent::handle_() { char *sbuf = reinterpret_cast(buf); size_t ota_size; uint8_t ota_features; - std::unique_ptr backend; + std::unique_ptr backend; (void) ota_features; #if USE_OTA_VERSION == 2 size_t size_acknowledged = 0; @@ -129,7 +129,7 @@ void ESPHomeOTAComponent::handle_() { ESP_LOGD(TAG, "Starting update from %s", this->client_->getpeername().c_str()); this->status_set_warning(); #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota_base::OTA_STARTED, 0.0f, 0); + this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0); #endif if (!this->readall_(buf, 5)) { @@ -140,16 +140,16 @@ void ESPHomeOTAComponent::handle_() { if (buf[0] != 0x6C || buf[1] != 0x26 || buf[2] != 0xF7 || buf[3] != 0x5C || buf[4] != 0x45) { ESP_LOGW(TAG, "Magic bytes do not match! 0x%02X-0x%02X-0x%02X-0x%02X-0x%02X", buf[0], buf[1], buf[2], buf[3], buf[4]); - error_code = ota_base::OTA_RESPONSE_ERROR_MAGIC; + error_code = ota::OTA_RESPONSE_ERROR_MAGIC; goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Send OK and version - 2 bytes - buf[0] = ota_base::OTA_RESPONSE_OK; + buf[0] = ota::OTA_RESPONSE_OK; buf[1] = USE_OTA_VERSION; this->writeall_(buf, 2); - backend = ota_base::make_ota_backend(); + backend = ota::make_ota_backend(); // Read features - 1 byte if (!this->readall_(buf, 1)) { @@ -160,16 +160,16 @@ void ESPHomeOTAComponent::handle_() { ESP_LOGV(TAG, "Features: 0x%02X", ota_features); // Acknowledge header - 1 byte - buf[0] = ota_base::OTA_RESPONSE_HEADER_OK; + buf[0] = ota::OTA_RESPONSE_HEADER_OK; if ((ota_features & FEATURE_SUPPORTS_COMPRESSION) != 0 && backend->supports_compression()) { - buf[0] = ota_base::OTA_RESPONSE_SUPPORTS_COMPRESSION; + buf[0] = ota::OTA_RESPONSE_SUPPORTS_COMPRESSION; } this->writeall_(buf, 1); #ifdef USE_OTA_PASSWORD if (!this->password_.empty()) { - buf[0] = ota_base::OTA_RESPONSE_REQUEST_AUTH; + buf[0] = ota::OTA_RESPONSE_REQUEST_AUTH; this->writeall_(buf, 1); md5::MD5Digest md5{}; md5.init(); @@ -220,14 +220,14 @@ void ESPHomeOTAComponent::handle_() { if (!matches) { ESP_LOGW(TAG, "Auth failed! Passwords do not match"); - error_code = ota_base::OTA_RESPONSE_ERROR_AUTH_INVALID; + error_code = ota::OTA_RESPONSE_ERROR_AUTH_INVALID; goto error; // NOLINT(cppcoreguidelines-avoid-goto) } } #endif // USE_OTA_PASSWORD // Acknowledge auth OK - 1 byte - buf[0] = ota_base::OTA_RESPONSE_AUTH_OK; + buf[0] = ota::OTA_RESPONSE_AUTH_OK; this->writeall_(buf, 1); // Read size, 4 bytes MSB first @@ -243,12 +243,12 @@ void ESPHomeOTAComponent::handle_() { ESP_LOGV(TAG, "Size is %u bytes", ota_size); error_code = backend->begin(ota_size); - if (error_code != ota_base::OTA_RESPONSE_OK) + if (error_code != ota::OTA_RESPONSE_OK) goto error; // NOLINT(cppcoreguidelines-avoid-goto) update_started = true; // Acknowledge prepare OK - 1 byte - buf[0] = ota_base::OTA_RESPONSE_UPDATE_PREPARE_OK; + buf[0] = ota::OTA_RESPONSE_UPDATE_PREPARE_OK; this->writeall_(buf, 1); // Read binary MD5, 32 bytes @@ -261,7 +261,7 @@ void ESPHomeOTAComponent::handle_() { backend->set_update_md5(sbuf); // Acknowledge MD5 OK - 1 byte - buf[0] = ota_base::OTA_RESPONSE_BIN_MD5_OK; + buf[0] = ota::OTA_RESPONSE_BIN_MD5_OK; this->writeall_(buf, 1); while (total < ota_size) { @@ -285,14 +285,14 @@ void ESPHomeOTAComponent::handle_() { } error_code = backend->write(buf, read); - if (error_code != ota_base::OTA_RESPONSE_OK) { + if (error_code != ota::OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error writing binary data to flash!, error_code: %d", error_code); goto error; // NOLINT(cppcoreguidelines-avoid-goto) } total += read; #if USE_OTA_VERSION == 2 while (size_acknowledged + OTA_BLOCK_SIZE <= total || (total == ota_size && size_acknowledged < ota_size)) { - buf[0] = ota_base::OTA_RESPONSE_CHUNK_OK; + buf[0] = ota::OTA_RESPONSE_CHUNK_OK; this->writeall_(buf, 1); size_acknowledged += OTA_BLOCK_SIZE; } @@ -304,7 +304,7 @@ void ESPHomeOTAComponent::handle_() { float percentage = (total * 100.0f) / ota_size; ESP_LOGD(TAG, "Progress: %0.1f%%", percentage); #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota_base::OTA_IN_PROGRESS, percentage, 0); + this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0); #endif // feed watchdog and give other tasks a chance to run App.feed_wdt(); @@ -313,21 +313,21 @@ void ESPHomeOTAComponent::handle_() { } // Acknowledge receive OK - 1 byte - buf[0] = ota_base::OTA_RESPONSE_RECEIVE_OK; + buf[0] = ota::OTA_RESPONSE_RECEIVE_OK; this->writeall_(buf, 1); error_code = backend->end(); - if (error_code != ota_base::OTA_RESPONSE_OK) { + if (error_code != ota::OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code); goto error; // NOLINT(cppcoreguidelines-avoid-goto) } // Acknowledge Update end OK - 1 byte - buf[0] = ota_base::OTA_RESPONSE_UPDATE_END_OK; + buf[0] = ota::OTA_RESPONSE_UPDATE_END_OK; this->writeall_(buf, 1); // Read ACK - if (!this->readall_(buf, 1) || buf[0] != ota_base::OTA_RESPONSE_OK) { + if (!this->readall_(buf, 1) || buf[0] != ota::OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Reading back acknowledgement failed"); // do not go to error, this is not fatal } @@ -338,7 +338,7 @@ void ESPHomeOTAComponent::handle_() { ESP_LOGI(TAG, "Update complete"); this->status_clear_warning(); #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota_base::OTA_COMPLETED, 100.0f, 0); + this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, 0); #endif delay(100); // NOLINT App.safe_reboot(); @@ -355,7 +355,7 @@ error: this->status_momentary_error("onerror", 5000); #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota_base::OTA_ERROR, 0.0f, static_cast(error_code)); + this->state_callback_.call(ota::OTA_ERROR, 0.0f, static_cast(error_code)); #endif } diff --git a/esphome/components/esphome/ota/ota_esphome.h b/esphome/components/esphome/ota/ota_esphome.h index 08266122d6..e0d09ff37e 100644 --- a/esphome/components/esphome/ota/ota_esphome.h +++ b/esphome/components/esphome/ota/ota_esphome.h @@ -4,13 +4,13 @@ #ifdef USE_OTA #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" -#include "esphome/components/ota_base/ota_backend.h" +#include "esphome/components/ota/ota_backend.h" #include "esphome/components/socket/socket.h" namespace esphome { /// ESPHomeOTAComponent provides a simple way to integrate Over-the-Air updates into your app using ArduinoOTA. -class ESPHomeOTAComponent : public ota_base::OTAComponent { +class ESPHomeOTAComponent : public ota::OTAComponent { public: #ifdef USE_OTA_PASSWORD void set_auth_password(const std::string &password) { password_ = password; } diff --git a/esphome/components/http_request/ota/__init__.py b/esphome/components/http_request/ota/__init__.py index d3a54c699b..a3f6d5840c 100644 --- a/esphome/components/http_request/ota/__init__.py +++ b/esphome/components/http_request/ota/__init__.py @@ -1,6 +1,6 @@ from esphome import automation import esphome.codegen as cg -from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code +from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code import esphome.config_validation as cv from esphome.const import CONF_ID, CONF_PASSWORD, CONF_URL, CONF_USERNAME from esphome.core import coroutine_with_priority @@ -15,9 +15,6 @@ DEPENDENCIES = ["network", "http_request"] CONF_MD5 = "md5" CONF_MD5_URL = "md5_url" -ota_base_ns = cg.esphome_ns.namespace("ota_base") -OTAComponent = ota_base_ns.class_("OTAComponent", cg.Component) - OtaHttpRequestComponent = http_request_ns.class_( "OtaHttpRequestComponent", OTAComponent ) diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 23caa6fbd3..4d9e868c74 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -6,11 +6,11 @@ #include "esphome/components/md5/md5.h" #include "esphome/components/watchdog/watchdog.h" -#include "esphome/components/ota_base/ota_backend.h" -#include "esphome/components/ota_base/ota_backend_arduino_esp32.h" -#include "esphome/components/ota_base/ota_backend_arduino_esp8266.h" -#include "esphome/components/ota_base/ota_backend_arduino_rp2040.h" -#include "esphome/components/ota_base/ota_backend_esp_idf.h" +#include "esphome/components/ota/ota_backend.h" +#include "esphome/components/ota/ota_backend_arduino_esp32.h" +#include "esphome/components/ota/ota_backend_arduino_esp8266.h" +#include "esphome/components/ota/ota_backend_arduino_rp2040.h" +#include "esphome/components/ota/ota_backend_esp_idf.h" namespace esphome { namespace http_request { @@ -19,7 +19,7 @@ static const char *const TAG = "http_request.ota"; void OtaHttpRequestComponent::setup() { #ifdef USE_OTA_STATE_CALLBACK - ota_base::register_ota_platform(this); + ota::register_ota_platform(this); #endif } @@ -50,15 +50,15 @@ void OtaHttpRequestComponent::flash() { ESP_LOGI(TAG, "Starting update"); #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota_base::OTA_STARTED, 0.0f, 0); + this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0); #endif auto ota_status = this->do_ota_(); switch (ota_status) { - case ota_base::OTA_RESPONSE_OK: + case ota::OTA_RESPONSE_OK: #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota_base::OTA_COMPLETED, 100.0f, ota_status); + this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status); #endif delay(10); App.safe_reboot(); @@ -66,7 +66,7 @@ void OtaHttpRequestComponent::flash() { default: #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota_base::OTA_ERROR, 0.0f, ota_status); + this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status); #endif this->md5_computed_.clear(); // will be reset at next attempt this->md5_expected_.clear(); // will be reset at next attempt @@ -74,7 +74,7 @@ void OtaHttpRequestComponent::flash() { } } -void OtaHttpRequestComponent::cleanup_(std::unique_ptr backend, +void OtaHttpRequestComponent::cleanup_(std::unique_ptr backend, const std::shared_ptr &container) { if (this->update_started_) { ESP_LOGV(TAG, "Aborting OTA backend"); @@ -115,9 +115,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() { ESP_LOGV(TAG, "MD5Digest initialized"); ESP_LOGV(TAG, "OTA backend begin"); - auto backend = ota_base::make_ota_backend(); + auto backend = ota::make_ota_backend(); auto error_code = backend->begin(container->content_length); - if (error_code != ota_base::OTA_RESPONSE_OK) { + if (error_code != ota::OTA_RESPONSE_OK) { ESP_LOGW(TAG, "backend->begin error: %d", error_code); this->cleanup_(std::move(backend), container); return error_code; @@ -144,7 +144,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() { // write bytes to OTA backend this->update_started_ = true; error_code = backend->write(buf, bufsize); - if (error_code != ota_base::OTA_RESPONSE_OK) { + if (error_code != ota::OTA_RESPONSE_OK) { // error code explanation available at // https://github.com/esphome/esphome/blob/dev/esphome/components/ota/ota_backend.h ESP_LOGE(TAG, "Error code (%02X) writing binary data to flash at offset %d and size %d", error_code, @@ -160,7 +160,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() { float percentage = container->get_bytes_read() * 100.0f / container->content_length; ESP_LOGD(TAG, "Progress: %0.1f%%", percentage); #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota_base::OTA_IN_PROGRESS, percentage, 0); + this->state_callback_.call(ota::OTA_IN_PROGRESS, percentage, 0); #endif } } // while @@ -174,7 +174,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() { if (strncmp(this->md5_computed_.c_str(), this->md5_expected_.c_str(), MD5_SIZE) != 0) { ESP_LOGE(TAG, "MD5 computed: %s - Aborting due to MD5 mismatch", this->md5_computed_.c_str()); this->cleanup_(std::move(backend), container); - return ota_base::OTA_RESPONSE_ERROR_MD5_MISMATCH; + return ota::OTA_RESPONSE_ERROR_MD5_MISMATCH; } else { backend->set_update_md5(md5_receive_str.get()); } @@ -187,14 +187,14 @@ uint8_t OtaHttpRequestComponent::do_ota_() { delay(100); // NOLINT error_code = backend->end(); - if (error_code != ota_base::OTA_RESPONSE_OK) { + if (error_code != ota::OTA_RESPONSE_OK) { ESP_LOGW(TAG, "Error ending update! error_code: %d", error_code); this->cleanup_(std::move(backend), container); return error_code; } ESP_LOGI(TAG, "Update complete"); - return ota_base::OTA_RESPONSE_OK; + return ota::OTA_RESPONSE_OK; } std::string OtaHttpRequestComponent::get_url_with_auth_(const std::string &url) { diff --git a/esphome/components/http_request/ota/ota_http_request.h b/esphome/components/http_request/ota/ota_http_request.h index 138731fc5c..6a86b4ab43 100644 --- a/esphome/components/http_request/ota/ota_http_request.h +++ b/esphome/components/http_request/ota/ota_http_request.h @@ -1,6 +1,6 @@ #pragma once -#include "esphome/components/ota_base/ota_backend.h" +#include "esphome/components/ota/ota_backend.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/helpers.h" @@ -22,7 +22,7 @@ enum OtaHttpRequestError : uint8_t { OTA_CONNECTION_ERROR = 0x12, }; -class OtaHttpRequestComponent : public ota_base::OTAComponent, public Parented { +class OtaHttpRequestComponent : public ota::OTAComponent, public Parented { public: void setup() override; void dump_config() override; @@ -40,7 +40,7 @@ class OtaHttpRequestComponent : public ota_base::OTAComponent, public Parented backend, const std::shared_ptr &container); + void cleanup_(std::unique_ptr backend, const std::shared_ptr &container); uint8_t do_ota_(); std::string get_url_with_auth_(const std::string &url); bool http_get_md5_(); diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 9f14d53eb9..2e2ca47345 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -5,7 +5,7 @@ #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" -#include "esphome/components/ota_base/ota_backend.h" +#include "esphome/components/ota/ota_backend.h" namespace esphome { namespace http_request { @@ -22,13 +22,13 @@ static const char *const TAG = "http_request.update"; static const size_t MAX_READ_SIZE = 256; void HttpRequestUpdate::setup() { - this->ota_parent_->add_on_state_callback([this](ota_base::OTAState state, float progress, uint8_t err) { - if (state == ota_base::OTAState::OTA_IN_PROGRESS) { + this->ota_parent_->add_on_state_callback([this](ota::OTAState state, float progress, uint8_t err) { + if (state == ota::OTAState::OTA_IN_PROGRESS) { this->state_ = update::UPDATE_STATE_INSTALLING; this->update_info_.has_progress = true; this->update_info_.progress = progress; this->publish_state(); - } else if (state == ota_base::OTAState::OTA_ABORT || state == ota_base::OTAState::OTA_ERROR) { + } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) { this->state_ = update::UPDATE_STATE_AVAILABLE; this->status_set_error("Failed to install firmware"); this->publish_state(); diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index 583a4b2fe2..201d956a37 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -9,7 +9,7 @@ #include "esphome/components/audio/audio_transfer_buffer.h" #ifdef USE_OTA -#include "esphome/components/ota_base/ota_backend.h" +#include "esphome/components/ota/ota_backend.h" #endif namespace esphome { @@ -121,11 +121,11 @@ void MicroWakeWord::setup() { }); #ifdef USE_OTA - ota_base::get_global_ota_callback()->add_on_state_callback( - [this](ota_base::OTAState state, float progress, uint8_t error, ota_base::OTAComponent *comp) { - if (state == ota_base::OTA_STARTED) { + ota::get_global_ota_callback()->add_on_state_callback( + [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { this->suspend_task_(); - } else if (state == ota_base::OTA_ERROR) { + } else if (state == ota::OTA_ERROR) { this->resume_task_(); } }); diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py index 2ac09607be..8f0f4fbb05 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -8,12 +8,10 @@ from esphome.const import ( CONF_PLATFORM, CONF_TRIGGER_ID, ) -from esphome.core import coroutine_with_priority - -from ..ota_base import OTAState +from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] -AUTO_LOAD = ["safe_mode", "ota_base"] +AUTO_LOAD = ["safe_mode", "md5"] IS_PLATFORM_COMPONENT = True @@ -25,6 +23,9 @@ CONF_ON_STATE_CHANGE = "on_state_change" ota_ns = cg.esphome_ns.namespace("ota") +OTAComponent = ota_ns.class_("OTAComponent", cg.Component) +OTAState = ota_ns.enum("OTAState") + OTAAbortTrigger = ota_ns.class_("OTAAbortTrigger", automation.Trigger.template()) OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template()) OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template()) @@ -84,6 +85,13 @@ BASE_OTA_SCHEMA = cv.Schema( async def to_code(config): cg.add_define("USE_OTA") + # Libraries for OTA backends + if CORE.is_esp32 and CORE.using_arduino: + cg.add_library("Update", None) + + if CORE.is_rp2040 and CORE.using_arduino: + cg.add_library("Updater", None) + async def ota_to_code(var, config): await cg.past_safe_mode() diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h index 5c71859d43..dbb4b84e63 100644 --- a/esphome/components/ota/automation.h +++ b/esphome/components/ota/automation.h @@ -1,16 +1,12 @@ #pragma once #ifdef USE_OTA_STATE_CALLBACK -#include "esphome/components/ota_base/ota_backend.h" +#include "esphome/components/ota/ota_backend.h" #include "esphome/core/automation.h" namespace esphome { namespace ota { -// Import types from ota_base for the automation triggers -using ota_base::OTAComponent; -using ota_base::OTAState; - class OTAStateChangeTrigger : public Trigger { public: explicit OTAStateChangeTrigger(OTAComponent *parent) { @@ -26,7 +22,7 @@ class OTAStartTrigger : public Trigger<> { public: explicit OTAStartTrigger(OTAComponent *parent) { parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == ota_base::OTA_STARTED && !parent->is_failed()) { + if (state == ota::OTA_STARTED && !parent->is_failed()) { trigger(); } }); @@ -37,7 +33,7 @@ class OTAProgressTrigger : public Trigger { public: explicit OTAProgressTrigger(OTAComponent *parent) { parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == ota_base::OTA_IN_PROGRESS && !parent->is_failed()) { + if (state == ota::OTA_IN_PROGRESS && !parent->is_failed()) { trigger(progress); } }); @@ -48,7 +44,7 @@ class OTAEndTrigger : public Trigger<> { public: explicit OTAEndTrigger(OTAComponent *parent) { parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == ota_base::OTA_COMPLETED && !parent->is_failed()) { + if (state == ota::OTA_COMPLETED && !parent->is_failed()) { trigger(); } }); @@ -59,7 +55,7 @@ class OTAAbortTrigger : public Trigger<> { public: explicit OTAAbortTrigger(OTAComponent *parent) { parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == ota_base::OTA_ABORT && !parent->is_failed()) { + if (state == ota::OTA_ABORT && !parent->is_failed()) { trigger(); } }); @@ -70,7 +66,7 @@ class OTAErrorTrigger : public Trigger { public: explicit OTAErrorTrigger(OTAComponent *parent) { parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { - if (state == ota_base::OTA_ERROR && !parent->is_failed()) { + if (state == ota::OTA_ERROR && !parent->is_failed()) { trigger(error); } }); diff --git a/esphome/components/ota_base/ota_backend.cpp b/esphome/components/ota/ota_backend.cpp similarity index 93% rename from esphome/components/ota_base/ota_backend.cpp rename to esphome/components/ota/ota_backend.cpp index 7cbc795866..e7d2a2bcce 100644 --- a/esphome/components/ota_base/ota_backend.cpp +++ b/esphome/components/ota/ota_backend.cpp @@ -1,7 +1,7 @@ #include "ota_backend.h" namespace esphome { -namespace ota_base { +namespace ota { // The make_ota_backend() implementation is provided by each platform-specific backend @@ -18,5 +18,5 @@ OTAGlobalCallback *get_global_ota_callback() { void register_ota_platform(OTAComponent *ota_caller) { get_global_ota_callback()->register_ota(ota_caller); } #endif -} // namespace ota_base +} // namespace ota } // namespace esphome diff --git a/esphome/components/ota_base/ota_backend.h b/esphome/components/ota/ota_backend.h similarity index 98% rename from esphome/components/ota_base/ota_backend.h rename to esphome/components/ota/ota_backend.h index 27637a9af2..c0487e9dac 100644 --- a/esphome/components/ota_base/ota_backend.h +++ b/esphome/components/ota/ota_backend.h @@ -9,7 +9,7 @@ #endif namespace esphome { -namespace ota_base { +namespace ota { enum OTAResponseTypes { OTA_RESPONSE_OK = 0x00, @@ -119,5 +119,5 @@ void register_ota_platform(OTAComponent *ota_caller); // This ensures proper callback execution in all contexts. #endif -} // namespace ota_base +} // namespace ota } // namespace esphome diff --git a/esphome/components/ota_base/ota_backend_arduino_esp32.cpp b/esphome/components/ota/ota_backend_arduino_esp32.cpp similarity index 97% rename from esphome/components/ota_base/ota_backend_arduino_esp32.cpp rename to esphome/components/ota/ota_backend_arduino_esp32.cpp index f239544cfe..008cd45df4 100644 --- a/esphome/components/ota_base/ota_backend_arduino_esp32.cpp +++ b/esphome/components/ota/ota_backend_arduino_esp32.cpp @@ -8,7 +8,7 @@ #include namespace esphome { -namespace ota_base { +namespace ota { static const char *const TAG = "ota.arduino_esp32"; @@ -66,7 +66,7 @@ OTAResponseTypes ArduinoESP32OTABackend::end() { void ArduinoESP32OTABackend::abort() { Update.abort(); } -} // namespace ota_base +} // namespace ota } // namespace esphome #endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota_base/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h similarity index 92% rename from esphome/components/ota_base/ota_backend_arduino_esp32.h rename to esphome/components/ota/ota_backend_arduino_esp32.h index e3966eb2f6..6615cf3dc0 100644 --- a/esphome/components/ota_base/ota_backend_arduino_esp32.h +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -6,7 +6,7 @@ #include "esphome/core/helpers.h" namespace esphome { -namespace ota_base { +namespace ota { class ArduinoESP32OTABackend : public OTABackend { public: @@ -21,7 +21,7 @@ class ArduinoESP32OTABackend : public OTABackend { bool md5_set_{false}; }; -} // namespace ota_base +} // namespace ota } // namespace esphome #endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota_base/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp similarity index 98% rename from esphome/components/ota_base/ota_backend_arduino_esp8266.cpp rename to esphome/components/ota/ota_backend_arduino_esp8266.cpp index a9d48b59df..3ad429b48c 100644 --- a/esphome/components/ota_base/ota_backend_arduino_esp8266.cpp +++ b/esphome/components/ota/ota_backend_arduino_esp8266.cpp @@ -10,7 +10,7 @@ #include namespace esphome { -namespace ota_base { +namespace ota { static const char *const TAG = "ota.arduino_esp8266"; @@ -82,7 +82,7 @@ void ArduinoESP8266OTABackend::abort() { esp8266::preferences_prevent_write(false); } -} // namespace ota_base +} // namespace ota } // namespace esphome #endif diff --git a/esphome/components/ota_base/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h similarity index 93% rename from esphome/components/ota_base/ota_backend_arduino_esp8266.h rename to esphome/components/ota/ota_backend_arduino_esp8266.h index f399013121..e1b9015cc7 100644 --- a/esphome/components/ota_base/ota_backend_arduino_esp8266.h +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -7,7 +7,7 @@ #include "esphome/core/macros.h" namespace esphome { -namespace ota_base { +namespace ota { class ArduinoESP8266OTABackend : public OTABackend { public: @@ -26,7 +26,7 @@ class ArduinoESP8266OTABackend : public OTABackend { bool md5_set_{false}; }; -} // namespace ota_base +} // namespace ota } // namespace esphome #endif diff --git a/esphome/components/ota_base/ota_backend_arduino_libretiny.cpp b/esphome/components/ota/ota_backend_arduino_libretiny.cpp similarity index 97% rename from esphome/components/ota_base/ota_backend_arduino_libretiny.cpp rename to esphome/components/ota/ota_backend_arduino_libretiny.cpp index 2596e3c2a3..8ef0f0005d 100644 --- a/esphome/components/ota_base/ota_backend_arduino_libretiny.cpp +++ b/esphome/components/ota/ota_backend_arduino_libretiny.cpp @@ -8,7 +8,7 @@ #include namespace esphome { -namespace ota_base { +namespace ota { static const char *const TAG = "ota.arduino_libretiny"; @@ -66,7 +66,7 @@ OTAResponseTypes ArduinoLibreTinyOTABackend::end() { void ArduinoLibreTinyOTABackend::abort() { Update.abort(); } -} // namespace ota_base +} // namespace ota } // namespace esphome #endif // USE_LIBRETINY diff --git a/esphome/components/ota_base/ota_backend_arduino_libretiny.h b/esphome/components/ota/ota_backend_arduino_libretiny.h similarity index 92% rename from esphome/components/ota_base/ota_backend_arduino_libretiny.h rename to esphome/components/ota/ota_backend_arduino_libretiny.h index 33eebeb95a..6d9b7a96d5 100644 --- a/esphome/components/ota_base/ota_backend_arduino_libretiny.h +++ b/esphome/components/ota/ota_backend_arduino_libretiny.h @@ -5,7 +5,7 @@ #include "esphome/core/defines.h" namespace esphome { -namespace ota_base { +namespace ota { class ArduinoLibreTinyOTABackend : public OTABackend { public: @@ -20,7 +20,7 @@ class ArduinoLibreTinyOTABackend : public OTABackend { bool md5_set_{false}; }; -} // namespace ota_base +} // namespace ota } // namespace esphome #endif // USE_LIBRETINY diff --git a/esphome/components/ota_base/ota_backend_arduino_rp2040.cpp b/esphome/components/ota/ota_backend_arduino_rp2040.cpp similarity index 98% rename from esphome/components/ota_base/ota_backend_arduino_rp2040.cpp rename to esphome/components/ota/ota_backend_arduino_rp2040.cpp index 160c529231..fc4d70249c 100644 --- a/esphome/components/ota_base/ota_backend_arduino_rp2040.cpp +++ b/esphome/components/ota/ota_backend_arduino_rp2040.cpp @@ -10,7 +10,7 @@ #include namespace esphome { -namespace ota_base { +namespace ota { static const char *const TAG = "ota.arduino_rp2040"; @@ -84,7 +84,7 @@ void ArduinoRP2040OTABackend::abort() { rp2040::preferences_prevent_write(false); } -} // namespace ota_base +} // namespace ota } // namespace esphome #endif // USE_RP2040 diff --git a/esphome/components/ota_base/ota_backend_arduino_rp2040.h b/esphome/components/ota/ota_backend_arduino_rp2040.h similarity index 93% rename from esphome/components/ota_base/ota_backend_arduino_rp2040.h rename to esphome/components/ota/ota_backend_arduino_rp2040.h index 6d622d4a5a..b9e10d506c 100644 --- a/esphome/components/ota_base/ota_backend_arduino_rp2040.h +++ b/esphome/components/ota/ota_backend_arduino_rp2040.h @@ -7,7 +7,7 @@ #include "esphome/core/macros.h" namespace esphome { -namespace ota_base { +namespace ota { class ArduinoRP2040OTABackend : public OTABackend { public: @@ -22,7 +22,7 @@ class ArduinoRP2040OTABackend : public OTABackend { bool md5_set_{false}; }; -} // namespace ota_base +} // namespace ota } // namespace esphome #endif // USE_RP2040 diff --git a/esphome/components/ota_base/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp similarity index 98% rename from esphome/components/ota_base/ota_backend_esp_idf.cpp rename to esphome/components/ota/ota_backend_esp_idf.cpp index e5cc1efaf6..7638ef1752 100644 --- a/esphome/components/ota_base/ota_backend_esp_idf.cpp +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -9,7 +9,7 @@ #include namespace esphome { -namespace ota_base { +namespace ota { std::unique_ptr make_ota_backend() { return make_unique(); } @@ -105,6 +105,6 @@ void IDFOTABackend::abort() { this->update_handle_ = 0; } -} // namespace ota_base +} // namespace ota } // namespace esphome #endif diff --git a/esphome/components/ota_base/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h similarity index 93% rename from esphome/components/ota_base/ota_backend_esp_idf.h rename to esphome/components/ota/ota_backend_esp_idf.h index 3c760df1c8..6e93982131 100644 --- a/esphome/components/ota_base/ota_backend_esp_idf.h +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -8,7 +8,7 @@ #include namespace esphome { -namespace ota_base { +namespace ota { class IDFOTABackend : public OTABackend { public: @@ -27,6 +27,6 @@ class IDFOTABackend : public OTABackend { bool md5_set_{false}; }; -} // namespace ota_base +} // namespace ota } // namespace esphome #endif diff --git a/esphome/components/ota_base/__init__.py b/esphome/components/ota_base/__init__.py deleted file mode 100644 index 2203785953..0000000000 --- a/esphome/components/ota_base/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -import esphome.codegen as cg -from esphome.core import CORE, coroutine_with_priority - -CODEOWNERS = ["@esphome/core"] -AUTO_LOAD = ["md5"] - -ota_base_ns = cg.esphome_ns.namespace("ota_base") -OTAComponent = ota_base_ns.class_("OTAComponent", cg.Component) -OTAState = ota_base_ns.enum("OTAState") - - -@coroutine_with_priority(52.0) -async def to_code(config): - # Note: USE_OTA_STATE_CALLBACK is not defined here - # Components that need OTA callbacks (like esp32_ble_tracker, speaker, etc.) - # define USE_OTA_STATE_CALLBACK themselves in their own __init__.py files - # This ensures the callback functionality is only compiled when actually needed - - if CORE.is_esp32 and CORE.using_arduino: - cg.add_library("Update", None) - - if CORE.is_rp2040 and CORE.using_arduino: - cg.add_library("Updater", None) diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index c6f6c91760..2c30f17c78 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -6,7 +6,7 @@ #include "esphome/components/audio/audio.h" #ifdef USE_OTA -#include "esphome/components/ota_base/ota_backend.h" +#include "esphome/components/ota/ota_backend.h" #endif namespace esphome { @@ -67,16 +67,16 @@ void SpeakerMediaPlayer::setup() { } #ifdef USE_OTA - ota_base::get_global_ota_callback()->add_on_state_callback( - [this](ota_base::OTAState state, float progress, uint8_t error, ota_base::OTAComponent *comp) { - if (state == ota_base::OTA_STARTED) { + ota::get_global_ota_callback()->add_on_state_callback( + [this](ota::OTAState state, float progress, uint8_t error, ota::OTAComponent *comp) { + if (state == ota::OTA_STARTED) { if (this->media_pipeline_ != nullptr) { this->media_pipeline_->suspend_tasks(); } if (this->announcement_pipeline_ != nullptr) { this->announcement_pipeline_->suspend_tasks(); } - } else if (state == ota_base::OTA_ERROR) { + } else if (state == ota::OTA_ERROR) { if (this->media_pipeline_ != nullptr) { this->media_pipeline_->resume_tasks(); } diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 8c77866540..72612a5641 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -34,7 +34,7 @@ from esphome.const import ( from esphome.core import CORE, coroutine_with_priority import esphome.final_validate as fv -AUTO_LOAD = ["json", "web_server_base", "ota_base"] +AUTO_LOAD = ["json", "web_server_base"] CONF_SORTING_GROUP_ID = "sorting_group_id" CONF_SORTING_GROUPS = "sorting_groups" @@ -73,6 +73,20 @@ def validate_local(config): return config +def validate_ota_removed(config): + # Only raise error if OTA is explicitly enabled (True) + # If it's False or not specified, we can safely ignore it + if config.get(CONF_OTA): + raise cv.Invalid( + f"The '{CONF_OTA}' option has been removed from 'web_server'. " + f"Please use the new OTA platform structure instead:\n\n" + f"ota:\n" + f" - platform: web_server\n\n" + f"See https://esphome.io/components/ota for more information." + ) + return config + + def validate_sorting_groups(config): if CONF_SORTING_GROUPS in config and config[CONF_VERSION] != 3: raise cv.Invalid( @@ -170,7 +184,7 @@ CONFIG_SCHEMA = cv.All( web_server_base.WebServerBase ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, - cv.Optional(CONF_OTA, default=True): cv.boolean, + cv.Optional(CONF_OTA, default=False): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, cv.Optional(CONF_SORTING_GROUPS): cv.ensure_list(sorting_group), @@ -188,6 +202,7 @@ CONFIG_SCHEMA = cv.All( default_url, validate_local, validate_sorting_groups, + validate_ota_removed, ) @@ -271,11 +286,8 @@ async def to_code(config): else: 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]: - # Define USE_WEBSERVER_OTA based only on web_server OTA config - # Web server OTA now uses ota_base backend for consistency - cg.add_define("USE_WEBSERVER_OTA") + # OTA is now handled by the web_server OTA platform + # The CONF_OTA option is kept only for backwards compatibility validation cg.add(var.set_expose_log(config[CONF_LOG])) if config[CONF_ENABLE_PRIVATE_NETWORK_ACCESS]: cg.add_define("USE_WEBSERVER_PRIVATE_NETWORK_ACCESS") diff --git a/esphome/components/web_server/ota/__init__.py b/esphome/components/web_server/ota/__init__.py new file mode 100644 index 0000000000..a8d01cb9c1 --- /dev/null +++ b/esphome/components/web_server/ota/__init__.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.core import coroutine_with_priority + +CODEOWNERS = ["@esphome/core"] +DEPENDENCIES = ["network", "web_server_base"] + +web_server_ns = cg.esphome_ns.namespace("web_server") +WebServerOTAComponent = web_server_ns.class_("WebServerOTAComponent", OTAComponent) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(WebServerOTAComponent), + } + ) + .extend(BASE_OTA_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +@coroutine_with_priority(52.0) +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await ota_to_code(var, config) + await cg.register_component(var, config) + cg.add_define("USE_WEBSERVER_OTA") diff --git a/esphome/components/web_server/ota/ota_web_server.cpp b/esphome/components/web_server/ota/ota_web_server.cpp new file mode 100644 index 0000000000..c778297135 --- /dev/null +++ b/esphome/components/web_server/ota/ota_web_server.cpp @@ -0,0 +1,206 @@ +#include "ota_web_server.h" +#ifdef USE_WEBSERVER_OTA + +#include "esphome/components/ota/ota_backend.h" +#include "esphome/components/web_server_base/web_server_base.h" +#include "esphome/core/application.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace web_server { + +static const char *const TAG = "web_server.ota"; + +class OTARequestHandler : public AsyncWebHandler { + public: + OTARequestHandler(WebServerOTAComponent *parent) : parent_(parent) {} + void handleRequest(AsyncWebServerRequest *request) override; + void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, + bool final) override; + bool canHandle(AsyncWebServerRequest *request) const override { + return request->url() == "/update" && request->method() == HTTP_POST; + } + + // NOLINTNEXTLINE(readability-identifier-naming) + bool isRequestHandlerTrivial() const override { return false; } + + protected: + void report_ota_progress_(AsyncWebServerRequest *request); + void schedule_ota_reboot_(); + void ota_init_(const char *filename); + + uint32_t last_ota_progress_{0}; + uint32_t ota_read_length_{0}; + WebServerOTAComponent *parent_; + bool ota_success_{false}; + + private: + std::unique_ptr ota_backend_{nullptr}; +}; + +void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) { + const uint32_t now = millis(); + if (now - this->last_ota_progress_ > 1000) { + float percentage = 0.0f; + if (request->contentLength() != 0) { + // Note: Using contentLength() for progress calculation is technically wrong as it includes + // multipart headers/boundaries, but it's only off by a small amount and we don't have + // access to the actual firmware size until the upload is complete. This is intentional + // as it still gives the user a reasonable progress indication. + percentage = (this->ota_read_length_ * 100.0f) / request->contentLength(); + ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage); + } else { + ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_); + } +#ifdef USE_OTA_STATE_CALLBACK + // Report progress - use call_deferred since we're in web server task + this->parent_->state_callback_.call_deferred(ota::OTA_IN_PROGRESS, percentage, 0); +#endif + this->last_ota_progress_ = now; + } +} + +void OTARequestHandler::schedule_ota_reboot_() { + ESP_LOGI(TAG, "OTA update successful!"); + this->parent_->set_timeout(100, []() { + ESP_LOGI(TAG, "Performing OTA reboot now"); + App.safe_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, + uint8_t *data, size_t len, bool final) { + ota::OTAResponseTypes error_code = ota::OTA_RESPONSE_OK; + + if (index == 0 && !this->ota_backend_) { + // Initialize OTA on first call + this->ota_init_(filename.c_str()); + +#ifdef USE_OTA_STATE_CALLBACK + // Notify OTA started - use call_deferred since we're in web server task + this->parent_->state_callback_.call_deferred(ota::OTA_STARTED, 0.0f, 0); +#endif + + // Platform-specific pre-initialization +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 + Update.runAsync(true); +#endif +#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_LIBRETINY) + if (Update.isRunning()) { + Update.abort(); + } +#endif +#endif // USE_ARDUINO + + this->ota_backend_ = ota::make_ota_backend(); + if (!this->ota_backend_) { + ESP_LOGE(TAG, "Failed to create OTA backend"); +#ifdef USE_OTA_STATE_CALLBACK + this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, + static_cast(ota::OTA_RESPONSE_ERROR_UNKNOWN)); +#endif + return; + } + + // Web server OTA uses multipart uploads where the actual firmware size + // is unknown (contentLength includes multipart overhead) + // Pass 0 to indicate unknown size + error_code = this->ota_backend_->begin(0); + if (error_code != ota::OTA_RESPONSE_OK) { + ESP_LOGE(TAG, "OTA begin failed: %d", error_code); + this->ota_backend_.reset(); +#ifdef USE_OTA_STATE_CALLBACK + this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#endif + return; + } + } + + if (!this->ota_backend_) { + return; + } + + // Process data + if (len > 0) { + error_code = this->ota_backend_->write(data, len); + if (error_code != ota::OTA_RESPONSE_OK) { + ESP_LOGE(TAG, "OTA write failed: %d", error_code); + this->ota_backend_->abort(); + this->ota_backend_.reset(); +#ifdef USE_OTA_STATE_CALLBACK + this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#endif + return; + } + this->ota_read_length_ += len; + this->report_ota_progress_(request); + } + + // 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::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::OTA_COMPLETED, 100.0f, 0); +#endif + this->schedule_ota_reboot_(); + } else { + ESP_LOGE(TAG, "OTA end failed: %d", error_code); +#ifdef USE_OTA_STATE_CALLBACK + this->parent_->state_callback_.call_deferred(ota::OTA_ERROR, 0.0f, static_cast(error_code)); +#endif + } + this->ota_backend_.reset(); + } +} + +void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response; + // 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); +} + +void WebServerOTAComponent::setup() { + // Get the global web server base instance and register our handler + auto *base = web_server_base::global_web_server_base; + if (base == nullptr) { + ESP_LOGE(TAG, "WebServerBase not found. WebServer OTA requires web_server_base component"); + this->mark_failed(); + return; + } + + base->add_handler(new OTARequestHandler(this)); // NOLINT +#ifdef USE_OTA_STATE_CALLBACK + // Register with global OTA callback system + ota::register_ota_platform(this); +#endif + ESP_LOGCONFIG(TAG, "Web Server OTA initialized"); +} + +void WebServerOTAComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Web Server OTA:"); + ESP_LOGCONFIG(TAG, " Available at /update"); +} + +} // namespace web_server +} // namespace esphome + +#endif // USE_WEBSERVER_OTA \ No newline at end of file diff --git a/esphome/components/web_server/ota/ota_web_server.h b/esphome/components/web_server/ota/ota_web_server.h new file mode 100644 index 0000000000..68267bf3e8 --- /dev/null +++ b/esphome/components/web_server/ota/ota_web_server.h @@ -0,0 +1,26 @@ +#pragma once + +#include "esphome/core/defines.h" +#ifdef USE_WEBSERVER_OTA + +#include "esphome/components/ota/ota_backend.h" +#include "esphome/components/web_server_base/web_server_base.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace web_server { + +class WebServerOTAComponent : public ota::OTAComponent { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } + + protected: + friend class OTARequestHandler; +}; + +} // namespace web_server +} // namespace esphome + +#endif // USE_WEBSERVER_OTA \ No newline at end of file diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index e0027d0b27..d5ded2a02c 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -273,7 +273,11 @@ std::string WebServer::get_config_json() { return json::build_json([this](JsonObject root) { root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); root["comment"] = App.get_comment(); - root["ota"] = this->allow_ota_; +#ifdef USE_WEBSERVER_OTA + root["ota"] = true; // web_server OTA platform is configured +#else + root["ota"] = false; +#endif root["log"] = this->expose_log_; root["lang"] = "en"; }); @@ -299,10 +303,7 @@ void WebServer::setup() { #endif this->base_->add_handler(this); -#ifdef USE_WEBSERVER_OTA - if (this->allow_ota_) - this->base_->add_ota_handler(); -#endif + // OTA is now handled by the web_server OTA platform // doesn't need defer functionality - if the queue is full, the client JS knows it's alive because it's clearly // getting a lot of events diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 991bca6fa7..5f175b6bdd 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -212,11 +212,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { * @param include_internal Whether internal components should be displayed. */ void set_include_internal(bool include_internal) { include_internal_ = include_internal; } - /** Set whether or not the webserver should expose the OTA form and handler. - * - * @param allow_ota. - */ - void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; } /** Set whether or not the webserver should expose the Log. * * @param expose_log. @@ -525,7 +520,6 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #ifdef USE_WEBSERVER_JS_INCLUDE const char *js_include_{nullptr}; #endif - bool allow_ota_{true}; bool expose_log_{true}; #ifdef USE_ESP32 std::deque> to_schedule_; diff --git a/esphome/components/web_server/web_server_v1.cpp b/esphome/components/web_server/web_server_v1.cpp index c9b38a2dc4..5db0f1cae9 100644 --- a/esphome/components/web_server/web_server_v1.cpp +++ b/esphome/components/web_server/web_server_v1.cpp @@ -192,11 +192,10 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(F("

See ESPHome Web API for " "REST API documentation.

")); - if (this->allow_ota_) { - stream->print( - F("

OTA Update

")); - } +#ifdef USE_WEBSERVER_OTA + stream->print(F("

OTA Update

")); +#endif stream->print(F("

Debug Log

"));
 #ifdef USE_WEBSERVER_JS_INCLUDE
   if (this->js_include_ != nullptr) {
diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py
index c17bab2128..754bf7d433 100644
--- a/esphome/components/web_server_base/__init__.py
+++ b/esphome/components/web_server_base/__init__.py
@@ -30,6 +30,7 @@ CONFIG_SCHEMA = cv.Schema(
 async def to_code(config):
     var = cg.new_Pvariable(config[CONF_ID])
     await cg.register_component(var, config)
+    cg.add(cg.RawExpression(f"{web_server_base_ns}::global_web_server_base = {var}"))
 
     if CORE.using_arduino:
         if CORE.is_esp32:
diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp
index a683ee85eb..e1c2bc0b25 100644
--- a/esphome/components/web_server_base/web_server_base.cpp
+++ b/esphome/components/web_server_base/web_server_base.cpp
@@ -4,23 +4,13 @@
 #include "esphome/core/helpers.h"
 #include "esphome/core/log.h"
 
-#ifdef USE_WEBSERVER_OTA
-#include "esphome/components/ota_base/ota_backend.h"
-#endif
-
-#ifdef USE_ARDUINO
-#ifdef USE_ESP8266
-#include 
-#elif defined(USE_ESP32) || defined(USE_LIBRETINY)
-#include 
-#endif
-#endif
-
 namespace esphome {
 namespace web_server_base {
 
 static const char *const TAG = "web_server_base";
 
+WebServerBase *global_web_server_base = nullptr;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
 void WebServerBase::add_handler(AsyncWebHandler *handler) {
   // remove all handlers
 
@@ -33,156 +23,6 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) {
   }
 }
 
-#ifdef USE_WEBSERVER_OTA
-void OTARequestHandler::report_ota_progress_(AsyncWebServerRequest *request) {
-  const uint32_t now = millis();
-  if (now - this->last_ota_progress_ > 1000) {
-    float percentage = 0.0f;
-    if (request->contentLength() != 0) {
-      // Note: Using contentLength() for progress calculation is technically wrong as it includes
-      // multipart headers/boundaries, but it's only off by a small amount and we don't have
-      // access to the actual firmware size until the upload is complete. This is intentional
-      // as it still gives the user a reasonable progress indication.
-      percentage = (this->ota_read_length_ * 100.0f) / request->contentLength();
-      ESP_LOGD(TAG, "OTA in progress: %0.1f%%", percentage);
-    } else {
-      ESP_LOGD(TAG, "OTA in progress: %u bytes read", this->ota_read_length_);
-    }
-#ifdef USE_OTA_STATE_CALLBACK
-    // Report progress - use call_deferred since we're in web server task
-    this->parent_->state_callback_.call_deferred(ota_base::OTA_IN_PROGRESS, percentage, 0);
-#endif
-    this->last_ota_progress_ = now;
-  }
-}
-
-void OTARequestHandler::schedule_ota_reboot_() {
-  ESP_LOGI(TAG, "OTA update successful!");
-  this->parent_->set_timeout(100, []() {
-    ESP_LOGI(TAG, "Performing OTA reboot now");
-    App.safe_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,
-                                     uint8_t *data, size_t len, bool final) {
-  ota_base::OTAResponseTypes error_code = ota_base::OTA_RESPONSE_OK;
-
-  if (index == 0 && !this->ota_backend_) {
-    // Initialize OTA on first call
-    this->ota_init_(filename.c_str());
-
-#ifdef USE_OTA_STATE_CALLBACK
-    // Notify OTA started - use call_deferred since we're in web server task
-    this->parent_->state_callback_.call_deferred(ota_base::OTA_STARTED, 0.0f, 0);
-#endif
-
-    // Platform-specific pre-initialization
-#ifdef USE_ARDUINO
-#ifdef USE_ESP8266
-    Update.runAsync(true);
-#endif
-#if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_LIBRETINY)
-    if (Update.isRunning()) {
-      Update.abort();
-    }
-#endif
-#endif  // USE_ARDUINO
-
-    this->ota_backend_ = ota_base::make_ota_backend();
-    if (!this->ota_backend_) {
-      ESP_LOGE(TAG, "Failed to create OTA backend");
-#ifdef USE_OTA_STATE_CALLBACK
-      this->parent_->state_callback_.call_deferred(ota_base::OTA_ERROR, 0.0f,
-                                                   static_cast(ota_base::OTA_RESPONSE_ERROR_UNKNOWN));
-#endif
-      return;
-    }
-
-    // Web server OTA uses multipart uploads where the actual firmware size
-    // is unknown (contentLength includes multipart overhead)
-    // Pass 0 to indicate unknown size
-    error_code = this->ota_backend_->begin(0);
-    if (error_code != ota_base::OTA_RESPONSE_OK) {
-      ESP_LOGE(TAG, "OTA begin failed: %d", error_code);
-      this->ota_backend_.reset();
-#ifdef USE_OTA_STATE_CALLBACK
-      this->parent_->state_callback_.call_deferred(ota_base::OTA_ERROR, 0.0f, static_cast(error_code));
-#endif
-      return;
-    }
-  }
-
-  if (!this->ota_backend_) {
-    return;
-  }
-
-  // Process data
-  if (len > 0) {
-    error_code = this->ota_backend_->write(data, len);
-    if (error_code != ota_base::OTA_RESPONSE_OK) {
-      ESP_LOGE(TAG, "OTA write failed: %d", error_code);
-      this->ota_backend_->abort();
-      this->ota_backend_.reset();
-#ifdef USE_OTA_STATE_CALLBACK
-      this->parent_->state_callback_.call_deferred(ota_base::OTA_ERROR, 0.0f, static_cast(error_code));
-#endif
-      return;
-    }
-    this->ota_read_length_ += len;
-    this->report_ota_progress_(request);
-  }
-
-  // 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);
-#endif
-      this->schedule_ota_reboot_();
-    } else {
-      ESP_LOGE(TAG, "OTA end failed: %d", error_code);
-#ifdef USE_OTA_STATE_CALLBACK
-      this->parent_->state_callback_.call_deferred(ota_base::OTA_ERROR, 0.0f, static_cast(error_code));
-#endif
-    }
-    this->ota_backend_.reset();
-  }
-}
-
-void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) {
-  AsyncWebServerResponse *response;
-  // 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);
-}
-
-void WebServerBase::add_ota_handler() {
-  this->add_handler(new OTARequestHandler(this));  // NOLINT
-#ifdef USE_OTA_STATE_CALLBACK
-  // Register with global OTA callback system
-  ota_base::register_ota_platform(this);
-#endif
-}
-#endif
-
 float WebServerBase::get_setup_priority() const {
   // Before WiFi (captive portal)
   return setup_priority::WIFI + 2.0f;
diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h
index 99087ddfa8..a475238a37 100644
--- a/esphome/components/web_server_base/web_server_base.h
+++ b/esphome/components/web_server_base/web_server_base.h
@@ -14,13 +14,12 @@
 #include "esphome/components/web_server_idf/web_server_idf.h"
 #endif
 
-#ifdef USE_WEBSERVER_OTA
-#include "esphome/components/ota_base/ota_backend.h"
-#endif
-
 namespace esphome {
 namespace web_server_base {
 
+class WebServerBase;
+extern WebServerBase *global_web_server_base;  // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
 namespace internal {
 
 class MiddlewareHandler : public AsyncWebHandler {
@@ -83,11 +82,7 @@ class AuthMiddlewareHandler : public MiddlewareHandler {
 
 }  // namespace internal
 
-#ifdef USE_WEBSERVER_OTA
-class WebServerBase : public ota_base::OTAComponent {
-#else
 class WebServerBase : public Component {
-#endif
  public:
   void init() {
     if (this->initialized_) {
@@ -118,18 +113,10 @@ class WebServerBase : public Component {
 
   void add_handler(AsyncWebHandler *handler);
 
-#ifdef USE_WEBSERVER_OTA
-  void add_ota_handler();
-#endif
-
   void set_port(uint16_t port) { port_ = port; }
   uint16_t get_port() const { return port_; }
 
  protected:
-#ifdef USE_WEBSERVER_OTA
-  friend class OTARequestHandler;
-#endif
-
   int initialized_{0};
   uint16_t port_{80};
   std::shared_ptr server_{nullptr};
@@ -137,35 +124,6 @@ class WebServerBase : public Component {
   internal::Credentials credentials_;
 };
 
-#ifdef USE_WEBSERVER_OTA
-class OTARequestHandler : public AsyncWebHandler {
- public:
-  OTARequestHandler(WebServerBase *parent) : parent_(parent) {}
-  void handleRequest(AsyncWebServerRequest *request) override;
-  void handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len,
-                    bool final) override;
-  bool canHandle(AsyncWebServerRequest *request) const override {
-    return request->url() == "/update" && request->method() == HTTP_POST;
-  }
-
-  // NOLINTNEXTLINE(readability-identifier-naming)
-  bool isRequestHandlerTrivial() const override { return false; }
-
- protected:
-  void report_ota_progress_(AsyncWebServerRequest *request);
-  void schedule_ota_reboot_();
-  void ota_init_(const char *filename);
-
-  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};
-};
-#endif  // USE_WEBSERVER_OTA
-
 }  // namespace web_server_base
 }  // namespace esphome
 #endif