From 58de53123aa9dfb571104ae9c5af6a97af2b501c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 30 Jun 2025 12:41:55 -0500 Subject: [PATCH] move more --- .../components/http_request/ota/__init__.py | 3 +- .../http_request/ota/ota_http_request.cpp | 25 ++++---- .../http_request/ota/ota_http_request.h | 6 +- .../update/http_request_update.cpp | 7 ++- esphome/components/ota/__init__.py | 4 +- esphome/components/ota/automation.h | 15 +++-- esphome/components/ota/ota.h | 61 +------------------ esphome/components/ota_base/__init__.py | 2 + esphome/components/ota_base/ota_backend.h | 35 ++++++++--- .../web_server_base/web_server_base.h | 2 +- 10 files changed, 65 insertions(+), 95 deletions(-) diff --git a/esphome/components/http_request/ota/__init__.py b/esphome/components/http_request/ota/__init__.py index a3f6d5840c..a1c9dba455 100644 --- a/esphome/components/http_request/ota/__init__.py +++ b/esphome/components/http_request/ota/__init__.py @@ -1,6 +1,7 @@ from esphome import automation import esphome.codegen as cg -from esphome.components.ota import BASE_OTA_SCHEMA, OTAComponent, ota_to_code +from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code +from esphome.components.ota_base import OTAComponent 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 diff --git a/esphome/components/http_request/ota/ota_http_request.cpp b/esphome/components/http_request/ota/ota_http_request.cpp index 57e65e6c03..23caa6fbd3 100644 --- a/esphome/components/http_request/ota/ota_http_request.cpp +++ b/esphome/components/http_request/ota/ota_http_request.cpp @@ -6,8 +6,7 @@ #include "esphome/components/md5/md5.h" #include "esphome/components/watchdog/watchdog.h" -#include "esphome/components/ota/ota.h" // For OTAComponent and callbacks -#include "esphome/components/ota_base/ota_backend.h" // For OTABackend class +#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" @@ -51,15 +50,15 @@ void OtaHttpRequestComponent::flash() { ESP_LOGI(TAG, "Starting update"); #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_STARTED, 0.0f, 0); + this->state_callback_.call(ota_base::OTA_STARTED, 0.0f, 0); #endif auto ota_status = this->do_ota_(); switch (ota_status) { - case ota::OTA_RESPONSE_OK: + case ota_base::OTA_RESPONSE_OK: #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_COMPLETED, 100.0f, ota_status); + this->state_callback_.call(ota_base::OTA_COMPLETED, 100.0f, ota_status); #endif delay(10); App.safe_reboot(); @@ -67,7 +66,7 @@ void OtaHttpRequestComponent::flash() { default: #ifdef USE_OTA_STATE_CALLBACK - this->state_callback_.call(ota::OTA_ERROR, 0.0f, ota_status); + this->state_callback_.call(ota_base::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 @@ -75,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"); @@ -118,7 +117,7 @@ uint8_t OtaHttpRequestComponent::do_ota_() { ESP_LOGV(TAG, "OTA backend begin"); auto backend = ota_base::make_ota_backend(); auto error_code = backend->begin(container->content_length); - if (error_code != ota::OTA_RESPONSE_OK) { + if (error_code != ota_base::OTA_RESPONSE_OK) { ESP_LOGW(TAG, "backend->begin error: %d", error_code); this->cleanup_(std::move(backend), container); return error_code; @@ -145,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::OTA_RESPONSE_OK) { + if (error_code != ota_base::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, @@ -161,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::OTA_IN_PROGRESS, percentage, 0); + this->state_callback_.call(ota_base::OTA_IN_PROGRESS, percentage, 0); #endif } } // while @@ -175,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::OTA_RESPONSE_ERROR_MD5_MISMATCH; + return ota_base::OTA_RESPONSE_ERROR_MD5_MISMATCH; } else { backend->set_update_md5(md5_receive_str.get()); } @@ -188,14 +187,14 @@ uint8_t OtaHttpRequestComponent::do_ota_() { delay(100); // NOLINT error_code = backend->end(); - if (error_code != ota::OTA_RESPONSE_OK) { + if (error_code != ota_base::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::OTA_RESPONSE_OK; + return ota_base::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 20a7abba71..138731fc5c 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/ota.h" +#include "esphome/components/ota_base/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::OTAComponent, public Parented { +class OtaHttpRequestComponent : public ota_base::OTAComponent, public Parented { public: void setup() override; void dump_config() override; @@ -40,7 +40,7 @@ class OtaHttpRequestComponent : public ota::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 828fb5bd8b..3a4c83f398 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -5,6 +5,7 @@ #include "esphome/components/json/json_util.h" #include "esphome/components/network/util.h" +#include "esphome/components/ota_base/ota_backend.h" namespace esphome { namespace http_request { @@ -21,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::OTAState state, float progress, uint8_t err) { - if (state == ota::OTAState::OTA_IN_PROGRESS) { + 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->state_ = update::UPDATE_STATE_INSTALLING; this->update_info_.has_progress = true; this->update_info_.progress = progress; this->publish_state(); - } else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) { + } else if (state == ota_base::OTAState::OTA_ABORT || state == ota_base::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/ota/__init__.py b/esphome/components/ota/__init__.py index e990256969..1fa9bfa410 100644 --- a/esphome/components/ota/__init__.py +++ b/esphome/components/ota/__init__.py @@ -1,5 +1,6 @@ from esphome import automation import esphome.codegen as cg +from esphome.components.ota_base import OTAState import esphome.config_validation as cv from esphome.const import ( CONF_ESPHOME, @@ -23,8 +24,7 @@ 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") +# OTAComponent and OTAState are imported from ota_base OTAAbortTrigger = ota_ns.class_("OTAAbortTrigger", automation.Trigger.template()) OTAEndTrigger = ota_ns.class_("OTAEndTrigger", automation.Trigger.template()) OTAErrorTrigger = ota_ns.class_("OTAErrorTrigger", automation.Trigger.template()) diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h index c3ff8e33d7..2dbf0c70e1 100644 --- a/esphome/components/ota/automation.h +++ b/esphome/components/ota/automation.h @@ -1,12 +1,17 @@ #pragma once #ifdef USE_OTA_STATE_CALLBACK #include "ota.h" +#include "esphome/components/ota_base/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) { @@ -22,7 +27,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_STARTED && !parent->is_failed()) { + if (state == ota_base::OTA_STARTED && !parent->is_failed()) { trigger(); } }); @@ -33,7 +38,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_IN_PROGRESS && !parent->is_failed()) { + if (state == ota_base::OTA_IN_PROGRESS && !parent->is_failed()) { trigger(progress); } }); @@ -44,7 +49,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_COMPLETED && !parent->is_failed()) { + if (state == ota_base::OTA_COMPLETED && !parent->is_failed()) { trigger(); } }); @@ -55,7 +60,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_ABORT && !parent->is_failed()) { + if (state == ota_base::OTA_ABORT && !parent->is_failed()) { trigger(); } }); @@ -66,7 +71,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_ERROR && !parent->is_failed()) { + if (state == ota_base::OTA_ERROR && !parent->is_failed()) { trigger(error); } }); diff --git a/esphome/components/ota/ota.h b/esphome/components/ota/ota.h index 42a6cdbe85..141f99c87b 100644 --- a/esphome/components/ota/ota.h +++ b/esphome/components/ota/ota.h @@ -1,69 +1,12 @@ #pragma once #include "esphome/core/defines.h" -#include "esphome/components/ota_base/ota_backend.h" namespace esphome { namespace ota { -// Import types from ota_base namespace for backward compatibility -using ota_base::OTABackend; -using ota_base::OTAComponent; -using ota_base::OTAResponseTypes; -using ota_base::OTAState; - -// Re-export specific enum values for backward compatibility -// OTAState values -static constexpr auto OTA_COMPLETED = ota_base::OTA_COMPLETED; -static constexpr auto OTA_STARTED = ota_base::OTA_STARTED; -static constexpr auto OTA_IN_PROGRESS = ota_base::OTA_IN_PROGRESS; -static constexpr auto OTA_ABORT = ota_base::OTA_ABORT; -static constexpr auto OTA_ERROR = ota_base::OTA_ERROR; - -// OTAResponseTypes values -static constexpr auto OTA_RESPONSE_OK = ota_base::OTA_RESPONSE_OK; -static constexpr auto OTA_RESPONSE_REQUEST_AUTH = ota_base::OTA_RESPONSE_REQUEST_AUTH; -static constexpr auto OTA_RESPONSE_HEADER_OK = ota_base::OTA_RESPONSE_HEADER_OK; -static constexpr auto OTA_RESPONSE_AUTH_OK = ota_base::OTA_RESPONSE_AUTH_OK; -static constexpr auto OTA_RESPONSE_UPDATE_PREPARE_OK = ota_base::OTA_RESPONSE_UPDATE_PREPARE_OK; -static constexpr auto OTA_RESPONSE_BIN_MD5_OK = ota_base::OTA_RESPONSE_BIN_MD5_OK; -static constexpr auto OTA_RESPONSE_RECEIVE_OK = ota_base::OTA_RESPONSE_RECEIVE_OK; -static constexpr auto OTA_RESPONSE_UPDATE_END_OK = ota_base::OTA_RESPONSE_UPDATE_END_OK; -static constexpr auto OTA_RESPONSE_SUPPORTS_COMPRESSION = ota_base::OTA_RESPONSE_SUPPORTS_COMPRESSION; -static constexpr auto OTA_RESPONSE_CHUNK_OK = ota_base::OTA_RESPONSE_CHUNK_OK; -static constexpr auto OTA_RESPONSE_ERROR_MAGIC = ota_base::OTA_RESPONSE_ERROR_MAGIC; -static constexpr auto OTA_RESPONSE_ERROR_UPDATE_PREPARE = ota_base::OTA_RESPONSE_ERROR_UPDATE_PREPARE; -static constexpr auto OTA_RESPONSE_ERROR_AUTH_INVALID = ota_base::OTA_RESPONSE_ERROR_AUTH_INVALID; -static constexpr auto OTA_RESPONSE_ERROR_WRITING_FLASH = ota_base::OTA_RESPONSE_ERROR_WRITING_FLASH; -static constexpr auto OTA_RESPONSE_ERROR_UPDATE_END = ota_base::OTA_RESPONSE_ERROR_UPDATE_END; -static constexpr auto OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = ota_base::OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; -static constexpr auto OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = - ota_base::OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; -static constexpr auto OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = ota_base::OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; -static constexpr auto OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = - ota_base::OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; -static constexpr auto OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = ota_base::OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; -static constexpr auto OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = ota_base::OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; -static constexpr auto OTA_RESPONSE_ERROR_MD5_MISMATCH = ota_base::OTA_RESPONSE_ERROR_MD5_MISMATCH; -static constexpr auto OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = ota_base::OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE; -static constexpr auto OTA_RESPONSE_ERROR_UNKNOWN = ota_base::OTA_RESPONSE_ERROR_UNKNOWN; - -#ifdef USE_OTA_STATE_CALLBACK -using ota_base::OTAGlobalCallback; - -// Deprecated: Use ota_base::get_global_ota_callback() instead -// Will be removed after 2025-12-30 (6 months from 2025-06-30) -[[deprecated("Use ota_base::get_global_ota_callback() instead")]] inline OTAGlobalCallback *get_global_ota_callback() { - return ota_base::get_global_ota_callback(); -} - -// Deprecated: Use ota_base::register_ota_platform() instead -// Will be removed after 2025-12-30 (6 months from 2025-06-30) -[[deprecated("Use ota_base::register_ota_platform() instead")]] inline void register_ota_platform( - OTAComponent *ota_caller) { - ota_base::register_ota_platform(ota_caller); -} -#endif +// All OTA backend functionality has been moved to the ota_base component. +// This file remains for the high-level OTA automation triggers defined in automation.h } // namespace ota } // namespace esphome diff --git a/esphome/components/ota_base/__init__.py b/esphome/components/ota_base/__init__.py index 7a1f233d26..2203785953 100644 --- a/esphome/components/ota_base/__init__.py +++ b/esphome/components/ota_base/__init__.py @@ -5,6 +5,8 @@ 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) diff --git a/esphome/components/ota_base/ota_backend.h b/esphome/components/ota_base/ota_backend.h index 7f4c89a540..f60019ce5a 100644 --- a/esphome/components/ota_base/ota_backend.h +++ b/esphome/components/ota_base/ota_backend.h @@ -69,7 +69,29 @@ class OTAComponent : public Component { } protected: - CallbackManager state_callback_{}; + /** Thread-safe callback manager that automatically defers to main loop. + * + * This ensures all OTA callbacks are executed in the main loop task, + * making them safe to call from any context (including web_server's OTA task). + * Existing code doesn't need changes - callbacks are automatically deferred. + */ + class DeferredCallbackManager : public CallbackManager { + public: + DeferredCallbackManager(OTAComponent *component) : component_(component) {} + + /// Override call to automatically defer to main loop + void call(OTAState state, float progress, uint8_t error) { + // Always defer to main loop for thread safety + component_->defer([this, state, progress, error]() { + CallbackManager::call(state, progress, error); + }); + } + + private: + OTAComponent *component_; + }; + + DeferredCallbackManager state_callback_{this}; #endif }; @@ -92,13 +114,10 @@ class OTAGlobalCallback { OTAGlobalCallback *get_global_ota_callback(); void register_ota_platform(OTAComponent *ota_caller); -// TODO: When web_server is updated to use ota_base, we need to add thread-safe -// callback execution. The web_server OTA runs in a separate task, so callbacks -// need to be deferred to the main loop task to avoid race conditions. -// This could be implemented using: -// - A queue of callback events that the main loop processes -// - Or using App.schedule() to defer callback execution to the main loop -// Example: App.schedule([=]() { state_callback_.call(state, progress, error); }); +// Thread-safe callback execution is automatically provided by DeferredCallbackManager +// which overrides call() to use Component::defer(). This ensures all OTA callbacks +// run in the main loop task, making them safe to call from any context including +// web_server's separate OTA task. No code changes needed. #endif } // namespace ota_base diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index a1e3added0..7e339dadab 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -111,7 +111,7 @@ class WebServerBase : public Component { void add_handler(AsyncWebHandler *handler); // TODO: In future PR, update this to use ota_base instead of duplicating OTA code - // Important: OTA callbacks must be thread-safe as web server OTA runs in a separate task + // Note: OTA callbacks in ota_base are automatically thread-safe via defer() void add_ota_handler(); void set_port(uint16_t port) { port_ = port; }