diff --git a/esphome/components/ota/__init__.py b/esphome/components/ota/__init__.py new file mode 100644 index 0000000000..627c55e910 --- /dev/null +++ b/esphome/components/ota/__init__.py @@ -0,0 +1,122 @@ +from esphome import automation +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.const import ( + CONF_ESPHOME, + CONF_ON_ERROR, + CONF_OTA, + CONF_PLATFORM, + CONF_TRIGGER_ID, +) +from esphome.core import CORE, coroutine_with_priority + +CODEOWNERS = ["@esphome/core"] +AUTO_LOAD = ["md5", "safe_mode"] + +IS_PLATFORM_COMPONENT = True + +CONF_ON_ABORT = "on_abort" +CONF_ON_BEGIN = "on_begin" +CONF_ON_END = "on_end" +CONF_ON_PROGRESS = "on_progress" +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()) +OTAProgressTrigger = ota_ns.class_("OTAProgressTrigger", automation.Trigger.template()) +OTAStartTrigger = ota_ns.class_("OTAStartTrigger", automation.Trigger.template()) +OTAStateChangeTrigger = ota_ns.class_( + "OTAStateChangeTrigger", automation.Trigger.template() +) + + +def _ota_final_validate(config): + if len(config) < 1: + raise cv.Invalid( + f"At least one platform must be specified for '{CONF_OTA}'; add '{CONF_PLATFORM}: {CONF_ESPHOME}' for original OTA functionality" + ) + + +FINAL_VALIDATE_SCHEMA = _ota_final_validate + +BASE_OTA_SCHEMA = cv.Schema( + { + cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAStateChangeTrigger), + } + ), + cv.Optional(CONF_ON_ABORT): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAAbortTrigger), + } + ), + cv.Optional(CONF_ON_BEGIN): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAStartTrigger), + } + ), + cv.Optional(CONF_ON_END): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAEndTrigger), + } + ), + cv.Optional(CONF_ON_ERROR): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAErrorTrigger), + } + ), + cv.Optional(CONF_ON_PROGRESS): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OTAProgressTrigger), + } + ), + } +) + + +@coroutine_with_priority(54.0) +async def to_code(config): + cg.add_define("USE_OTA") + + 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() + use_state_callback = False + for conf in config.get(CONF_ON_STATE_CHANGE, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(OTAState, "state")], conf) + use_state_callback = True + for conf in config.get(CONF_ON_ABORT, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + use_state_callback = True + for conf in config.get(CONF_ON_BEGIN, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + use_state_callback = True + for conf in config.get(CONF_ON_PROGRESS, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(float, "x")], conf) + use_state_callback = True + for conf in config.get(CONF_ON_END, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + use_state_callback = True + for conf in config.get(CONF_ON_ERROR, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [(cg.uint8, "x")], conf) + use_state_callback = True + if use_state_callback: + cg.add_define("USE_OTA_STATE_CALLBACK") diff --git a/esphome/components/ota/automation.h b/esphome/components/ota/automation.h new file mode 100644 index 0000000000..7e1a60f3ce --- /dev/null +++ b/esphome/components/ota/automation.h @@ -0,0 +1,78 @@ +#pragma once +#ifdef USE_OTA_STATE_CALLBACK +#include "ota_backend.h" + +#include "esphome/core/automation.h" + +namespace esphome { +namespace ota { + +class OTAStateChangeTrigger : public Trigger { + public: + explicit OTAStateChangeTrigger(OTAComponent *parent) { + parent->add_on_state_callback([this, parent](OTAState state, float progress, uint8_t error) { + if (!parent->is_failed()) { + trigger(state); + } + }); + } +}; + +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()) { + trigger(); + } + }); + } +}; + +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()) { + trigger(progress); + } + }); + } +}; + +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()) { + trigger(); + } + }); + } +}; + +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()) { + trigger(); + } + }); + } +}; + +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()) { + trigger(error); + } + }); + } +}; + +} // namespace ota +} // namespace esphome +#endif diff --git a/esphome/components/ota/ota_backend.cpp b/esphome/components/ota/ota_backend.cpp new file mode 100644 index 0000000000..30de4ec4b3 --- /dev/null +++ b/esphome/components/ota/ota_backend.cpp @@ -0,0 +1,20 @@ +#include "ota_backend.h" + +namespace esphome { +namespace ota { + +#ifdef USE_OTA_STATE_CALLBACK +OTAGlobalCallback *global_ota_callback{nullptr}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +OTAGlobalCallback *get_global_ota_callback() { + if (global_ota_callback == nullptr) { + global_ota_callback = new OTAGlobalCallback(); // NOLINT(cppcoreguidelines-owning-memory) + } + return global_ota_callback; +} + +void register_ota_platform(OTAComponent *ota_caller) { get_global_ota_callback()->register_ota(ota_caller); } +#endif + +} // namespace ota +} // namespace esphome diff --git a/esphome/components/ota/ota_backend.h b/esphome/components/ota/ota_backend.h new file mode 100644 index 0000000000..bc8ab46643 --- /dev/null +++ b/esphome/components/ota/ota_backend.h @@ -0,0 +1,96 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +#ifdef USE_OTA_STATE_CALLBACK +#include "esphome/core/automation.h" +#endif + +namespace esphome { +namespace ota { + +enum OTAResponseTypes { + OTA_RESPONSE_OK = 0x00, + OTA_RESPONSE_REQUEST_AUTH = 0x01, + + OTA_RESPONSE_HEADER_OK = 0x40, + OTA_RESPONSE_AUTH_OK = 0x41, + OTA_RESPONSE_UPDATE_PREPARE_OK = 0x42, + OTA_RESPONSE_BIN_MD5_OK = 0x43, + OTA_RESPONSE_RECEIVE_OK = 0x44, + OTA_RESPONSE_UPDATE_END_OK = 0x45, + OTA_RESPONSE_SUPPORTS_COMPRESSION = 0x46, + OTA_RESPONSE_CHUNK_OK = 0x47, + + OTA_RESPONSE_ERROR_MAGIC = 0x80, + OTA_RESPONSE_ERROR_UPDATE_PREPARE = 0x81, + OTA_RESPONSE_ERROR_AUTH_INVALID = 0x82, + OTA_RESPONSE_ERROR_WRITING_FLASH = 0x83, + OTA_RESPONSE_ERROR_UPDATE_END = 0x84, + OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING = 0x85, + OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG = 0x86, + OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG = 0x87, + OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE = 0x88, + OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE = 0x89, + OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION = 0x8A, + OTA_RESPONSE_ERROR_MD5_MISMATCH = 0x8B, + OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE = 0x8C, + OTA_RESPONSE_ERROR_UNKNOWN = 0xFF, +}; + +enum OTAState { + OTA_COMPLETED = 0, + OTA_STARTED, + OTA_IN_PROGRESS, + OTA_ABORT, + OTA_ERROR, +}; + +class OTABackend { + public: + virtual ~OTABackend() = default; + virtual OTAResponseTypes begin(size_t image_size) = 0; + virtual void set_update_md5(const char *md5) = 0; + virtual OTAResponseTypes write(uint8_t *data, size_t len) = 0; + virtual OTAResponseTypes end() = 0; + virtual void abort() = 0; + virtual bool supports_compression() = 0; +}; + +class OTAComponent : public Component { +#ifdef USE_OTA_STATE_CALLBACK + public: + void add_on_state_callback(std::function &&callback) { + this->state_callback_.add(std::move(callback)); + } + + protected: + CallbackManager state_callback_{}; +#endif +}; + +#ifdef USE_OTA_STATE_CALLBACK +class OTAGlobalCallback { + public: + void register_ota(OTAComponent *ota_caller) { + ota_caller->add_on_state_callback([this, ota_caller](OTAState state, float progress, uint8_t error) { + this->state_callback_.call(state, progress, error, ota_caller); + }); + } + void add_on_state_callback(std::function &&callback) { + this->state_callback_.add(std::move(callback)); + } + + protected: + CallbackManager state_callback_{}; +}; + +OTAGlobalCallback *get_global_ota_callback(); +void register_ota_platform(OTAComponent *ota_caller); +#endif +std::unique_ptr make_ota_backend(); + +} // namespace ota +} // namespace esphome diff --git a/esphome/components/ota/ota_backend_arduino_esp32.cpp b/esphome/components/ota/ota_backend_arduino_esp32.cpp new file mode 100644 index 0000000000..15dfc98a6c --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp32.cpp @@ -0,0 +1,62 @@ +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#include "esphome/core/defines.h" +#include "esphome/core/log.h" + +#include "ota_backend.h" +#include "ota_backend_arduino_esp32.h" + +#include + +namespace esphome { +namespace ota { + +static const char *const TAG = "ota.arduino_esp32"; + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes ArduinoESP32OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_SIZE) + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + + ESP_LOGE(TAG, "Begin error: %d", error); + + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoESP32OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoESP32OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written == len) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + ESP_LOGE(TAG, "Write error: %d", error); + + return OTA_RESPONSE_ERROR_WRITING_FLASH; +} + +OTAResponseTypes ArduinoESP32OTABackend::end() { + if (Update.end()) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + ESP_LOGE(TAG, "End error: %d", error); + + return OTA_RESPONSE_ERROR_UPDATE_END; +} + +void ArduinoESP32OTABackend::abort() { Update.abort(); } + +} // namespace ota +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_esp32.h b/esphome/components/ota/ota_backend_arduino_esp32.h new file mode 100644 index 0000000000..ac7fe9f14f --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp32.h @@ -0,0 +1,24 @@ +#pragma once +#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#include "ota_backend.h" + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace ota { + +class ArduinoESP32OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_ESP32_FRAMEWORK_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.cpp b/esphome/components/ota/ota_backend_arduino_esp8266.cpp new file mode 100644 index 0000000000..42edbf5d2b --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp8266.cpp @@ -0,0 +1,75 @@ +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 +#include "ota_backend_arduino_esp8266.h" +#include "ota_backend.h" + +#include "esphome/components/esp8266/preferences.h" +#include "esphome/core/defines.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace ota { + +static const char *const TAG = "ota.arduino_esp8266"; + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes ArduinoESP8266OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + esp8266::preferences_prevent_write(true); + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_BOOTSTRAP) + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; + if (error == UPDATE_ERROR_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + if (error == UPDATE_ERROR_SPACE) + return OTA_RESPONSE_ERROR_ESP8266_NOT_ENOUGH_SPACE; + + ESP_LOGE(TAG, "Begin error: %d", error); + + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoESP8266OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoESP8266OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written == len) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + ESP_LOGE(TAG, "Write error: %d", error); + + return OTA_RESPONSE_ERROR_WRITING_FLASH; +} + +OTAResponseTypes ArduinoESP8266OTABackend::end() { + if (Update.end()) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + ESP_LOGE(TAG, "End error: %d", error); + + return OTA_RESPONSE_ERROR_UPDATE_END; +} + +void ArduinoESP8266OTABackend::abort() { + Update.end(); + esp8266::preferences_prevent_write(false); +} + +} // namespace ota +} // namespace esphome + +#endif +#endif diff --git a/esphome/components/ota/ota_backend_arduino_esp8266.h b/esphome/components/ota/ota_backend_arduino_esp8266.h new file mode 100644 index 0000000000..7f44d7c965 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_esp8266.h @@ -0,0 +1,30 @@ +#pragma once +#ifdef USE_ARDUINO +#ifdef USE_ESP8266 +#include "ota_backend.h" + +#include "esphome/core/defines.h" +#include "esphome/core/macros.h" + +namespace esphome { +namespace ota { + +class ArduinoESP8266OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) + bool supports_compression() override { return true; } +#else + bool supports_compression() override { return false; } +#endif +}; + +} // namespace ota +} // namespace esphome + +#endif +#endif diff --git a/esphome/components/ota/ota_backend_arduino_libretiny.cpp b/esphome/components/ota/ota_backend_arduino_libretiny.cpp new file mode 100644 index 0000000000..6b2cf80684 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_libretiny.cpp @@ -0,0 +1,62 @@ +#ifdef USE_LIBRETINY +#include "ota_backend_arduino_libretiny.h" +#include "ota_backend.h" + +#include "esphome/core/defines.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace ota { + +static const char *const TAG = "ota.arduino_libretiny"; + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes ArduinoLibreTinyOTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_SIZE) + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + + ESP_LOGE(TAG, "Begin error: %d", error); + + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoLibreTinyOTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoLibreTinyOTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written == len) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + ESP_LOGE(TAG, "Write error: %d", error); + + return OTA_RESPONSE_ERROR_WRITING_FLASH; +} + +OTAResponseTypes ArduinoLibreTinyOTABackend::end() { + if (Update.end()) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + ESP_LOGE(TAG, "End error: %d", error); + + return OTA_RESPONSE_ERROR_UPDATE_END; +} + +void ArduinoLibreTinyOTABackend::abort() { Update.abort(); } + +} // namespace ota +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/ota/ota_backend_arduino_libretiny.h b/esphome/components/ota/ota_backend_arduino_libretiny.h new file mode 100644 index 0000000000..11deb6e2f2 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_libretiny.h @@ -0,0 +1,23 @@ +#pragma once +#ifdef USE_LIBRETINY +#include "ota_backend.h" + +#include "esphome/core/defines.h" + +namespace esphome { +namespace ota { + +class ArduinoLibreTinyOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.cpp b/esphome/components/ota/ota_backend_arduino_rp2040.cpp new file mode 100644 index 0000000000..ffeab2e93f --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_rp2040.cpp @@ -0,0 +1,75 @@ +#ifdef USE_ARDUINO +#ifdef USE_RP2040 +#include "ota_backend_arduino_rp2040.h" +#include "ota_backend.h" + +#include "esphome/components/rp2040/preferences.h" +#include "esphome/core/defines.h" +#include "esphome/core/log.h" + +#include + +namespace esphome { +namespace ota { + +static const char *const TAG = "ota.arduino_rp2040"; + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes ArduinoRP2040OTABackend::begin(size_t image_size) { + bool ret = Update.begin(image_size, U_FLASH); + if (ret) { + rp2040::preferences_prevent_write(true); + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + if (error == UPDATE_ERROR_BOOTSTRAP) + return OTA_RESPONSE_ERROR_INVALID_BOOTSTRAPPING; + if (error == UPDATE_ERROR_NEW_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_NEW_FLASH_CONFIG; + if (error == UPDATE_ERROR_FLASH_CONFIG) + return OTA_RESPONSE_ERROR_WRONG_CURRENT_FLASH_CONFIG; + if (error == UPDATE_ERROR_SPACE) + return OTA_RESPONSE_ERROR_RP2040_NOT_ENOUGH_SPACE; + + ESP_LOGE(TAG, "Begin error: %d", error); + + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void ArduinoRP2040OTABackend::set_update_md5(const char *md5) { Update.setMD5(md5); } + +OTAResponseTypes ArduinoRP2040OTABackend::write(uint8_t *data, size_t len) { + size_t written = Update.write(data, len); + if (written == len) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + ESP_LOGE(TAG, "Write error: %d", error); + + return OTA_RESPONSE_ERROR_WRITING_FLASH; +} + +OTAResponseTypes ArduinoRP2040OTABackend::end() { + if (Update.end()) { + return OTA_RESPONSE_OK; + } + + uint8_t error = Update.getError(); + ESP_LOGE(TAG, "End error: %d", error); + + return OTA_RESPONSE_ERROR_UPDATE_END; +} + +void ArduinoRP2040OTABackend::abort() { + Update.end(); + rp2040::preferences_prevent_write(false); +} + +} // namespace ota +} // namespace esphome + +#endif // USE_RP2040 +#endif // USE_ARDUINO diff --git a/esphome/components/ota/ota_backend_arduino_rp2040.h b/esphome/components/ota/ota_backend_arduino_rp2040.h new file mode 100644 index 0000000000..b189964ab3 --- /dev/null +++ b/esphome/components/ota/ota_backend_arduino_rp2040.h @@ -0,0 +1,26 @@ +#pragma once +#ifdef USE_ARDUINO +#ifdef USE_RP2040 +#include "ota_backend.h" + +#include "esphome/core/defines.h" +#include "esphome/core/macros.h" + +namespace esphome { +namespace ota { + +class ArduinoRP2040OTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } +}; + +} // namespace ota +} // namespace esphome + +#endif // USE_RP2040 +#endif // USE_ARDUINO diff --git a/esphome/components/ota/ota_backend_esp_idf.cpp b/esphome/components/ota/ota_backend_esp_idf.cpp new file mode 100644 index 0000000000..6f45fb75e4 --- /dev/null +++ b/esphome/components/ota/ota_backend_esp_idf.cpp @@ -0,0 +1,116 @@ +#ifdef USE_ESP_IDF +#include "ota_backend_esp_idf.h" + +#include "esphome/components/md5/md5.h" +#include "esphome/core/defines.h" + +#include +#include + +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#endif + +namespace esphome { +namespace ota { + +std::unique_ptr make_ota_backend() { return make_unique(); } + +OTAResponseTypes IDFOTABackend::begin(size_t image_size) { + this->partition_ = esp_ota_get_next_update_partition(nullptr); + if (this->partition_ == nullptr) { + return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION; + } + +#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_, image_size, &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; + if (err == ESP_ERR_INVALID_SIZE) { + return OTA_RESPONSE_ERROR_ESP32_NOT_ENOUGH_SPACE; + } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + this->md5_.init(); + return OTA_RESPONSE_OK; +} + +void IDFOTABackend::set_update_md5(const char *expected_md5) { memcpy(this->expected_bin_md5_, expected_md5, 32); } + +OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) { + esp_err_t err = esp_ota_write(this->update_handle_, data, len); + this->md5_.add(data, len); + if (err != ESP_OK) { + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_MAGIC; + } else if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; + } + return OTA_RESPONSE_OK; +} + +OTAResponseTypes IDFOTABackend::end() { + this->md5_.calculate(); + if (!this->md5_.equals_hex(this->expected_bin_md5_)) { + this->abort(); + return OTA_RESPONSE_ERROR_MD5_MISMATCH; + } + esp_err_t err = esp_ota_end(this->update_handle_); + this->update_handle_ = 0; + if (err == ESP_OK) { + err = esp_ota_set_boot_partition(this->partition_); + if (err == ESP_OK) { + return OTA_RESPONSE_OK; + } + } + if (err == ESP_ERR_OTA_VALIDATE_FAILED) { + return OTA_RESPONSE_ERROR_UPDATE_END; + } + if (err == ESP_ERR_FLASH_OP_TIMEOUT || err == ESP_ERR_FLASH_OP_FAIL) { + return OTA_RESPONSE_ERROR_WRITING_FLASH; + } + return OTA_RESPONSE_ERROR_UNKNOWN; +} + +void IDFOTABackend::abort() { + esp_ota_abort(this->update_handle_); + this->update_handle_ = 0; +} + +} // namespace ota +} // namespace esphome +#endif diff --git a/esphome/components/ota/ota_backend_esp_idf.h b/esphome/components/ota/ota_backend_esp_idf.h new file mode 100644 index 0000000000..ed66d9b970 --- /dev/null +++ b/esphome/components/ota/ota_backend_esp_idf.h @@ -0,0 +1,31 @@ +#pragma once +#ifdef USE_ESP_IDF +#include "ota_backend.h" + +#include "esphome/components/md5/md5.h" +#include "esphome/core/defines.h" + +#include + +namespace esphome { +namespace ota { + +class IDFOTABackend : public OTABackend { + public: + OTAResponseTypes begin(size_t image_size) override; + void set_update_md5(const char *md5) override; + OTAResponseTypes write(uint8_t *data, size_t len) override; + OTAResponseTypes end() override; + void abort() override; + bool supports_compression() override { return false; } + + private: + esp_ota_handle_t update_handle_{0}; + const esp_partition_t *partition_; + md5::MD5Digest md5_{}; + char expected_bin_md5_[32]; +}; + +} // namespace ota +} // namespace esphome +#endif