Files
esphome-lsm6ds/esphome/components/ota/ota_backend_esp_idf.cpp
T
1365251365 [ota] Add bootloader update functionality to ota component (#16238)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-05-08 21:36:06 +00:00

186 lines
5.9 KiB
C++

#ifdef USE_ESP32
#include "ota_backend_esp_idf.h"
#include "esphome/components/md5/md5.h"
#include "esphome/components/watchdog/watchdog.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include <esp_ota_ops.h>
#include <esp_task_wdt.h>
#include <spi_flash_mmap.h>
namespace esphome::ota {
static const char *const TAG = "ota.idf";
std::unique_ptr<IDFOTABackend> make_ota_backend() { return make_unique<IDFOTABackend>(); }
OTAResponseTypes IDFOTABackend::begin(size_t image_size, ota::OTAType ota_type) {
#ifdef USE_OTA_PARTITIONS
this->ota_type_ = ota_type;
if (this->ota_type_ == ota::OTA_TYPE_UPDATE_PARTITION_TABLE) {
// Reject any size other than ESP_PARTITION_TABLE_MAX_LEN
if (image_size != ESP_PARTITION_TABLE_MAX_LEN) {
ESP_LOGE(TAG, "Wrong partition table size: expected %u bytes, got %zu", ESP_PARTITION_TABLE_MAX_LEN, image_size);
return OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY;
}
memset(this->buf_, 0xFF, sizeof this->buf_);
this->buf_written_ = 0;
this->image_size_ = image_size;
this->md5_.init();
return OTA_RESPONSE_OK;
}
if (this->ota_type_ == ota::OTA_TYPE_UPDATE_BOOTLOADER) {
OTAResponseTypes result = this->prepare_bootloader_update_(image_size);
if (result != OTA_RESPONSE_OK) {
return result;
}
}
if (!this->is_app_or_bootloader_update_()) {
return OTA_RESPONSE_ERROR_UNSUPPORTED_OTA_TYPE;
}
#else
if (ota_type != ota::OTA_TYPE_UPDATE_APP) {
return OTA_RESPONSE_ERROR_UNSUPPORTED_OTA_TYPE;
}
#endif
#ifdef USE_OTA_ROLLBACK
// If we're starting an OTA, the current boot is good enough - mark it valid
// to prevent rollback and allow the OTA to proceed even if the safe mode
// timer hasn't expired yet.
esp_ota_mark_app_valid_cancel_rollback();
#endif
this->partition_ = esp_ota_get_next_update_partition(nullptr);
if (this->partition_ == nullptr) {
return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION;
}
watchdog::WatchdogManager watchdog(15000);
esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed (err=0x%X)", err);
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;
}
#ifdef USE_OTA_PARTITIONS
if (this->ota_type_ == ota::OTA_TYPE_UPDATE_BOOTLOADER) {
OTAResponseTypes result = this->setup_bootloader_staging_();
if (result != OTA_RESPONSE_OK) {
return result;
}
}
#endif
this->md5_.init();
return OTA_RESPONSE_OK;
}
void IDFOTABackend::set_update_md5(const char *expected_md5) {
memcpy(this->expected_bin_md5_, expected_md5, 32);
this->md5_set_ = true;
}
OTAResponseTypes IDFOTABackend::write(uint8_t *data, size_t len) {
#ifdef USE_OTA_PARTITIONS
if (this->ota_type_ == ota::OTA_TYPE_UPDATE_PARTITION_TABLE) {
if (len > PARTITION_TABLE_BUFFER_SIZE - this->buf_written_) {
ESP_LOGE(TAG, "Wrong partition table size");
return OTA_RESPONSE_ERROR_PARTITION_TABLE_VERIFY;
}
memcpy(this->buf_ + this->buf_written_, data, len);
this->buf_written_ += len;
this->md5_.add(data, len);
return OTA_RESPONSE_OK;
}
if (!this->is_app_or_bootloader_update_()) {
return OTA_RESPONSE_ERROR_UNSUPPORTED_OTA_TYPE;
}
#endif
esp_err_t err = esp_ota_write(this->update_handle_, data, len);
this->md5_.add(data, len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_write failed (err=0x%X)", err);
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() {
if (this->md5_set_) {
this->md5_.calculate();
if (!this->md5_.equals_hex(this->expected_bin_md5_)) {
this->abort();
return OTA_RESPONSE_ERROR_MD5_MISMATCH;
}
}
#ifdef USE_OTA_PARTITIONS
if (this->ota_type_ == ota::OTA_TYPE_UPDATE_PARTITION_TABLE) {
return this->update_partition_table();
}
if (!this->is_app_or_bootloader_update_()) {
return OTA_RESPONSE_ERROR_UNSUPPORTED_OTA_TYPE;
}
#endif
esp_err_t err = esp_ota_end(this->update_handle_);
this->update_handle_ = 0;
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed (err=0x%X)", err);
}
#ifdef USE_OTA_PARTITIONS
if (this->ota_type_ == ota::OTA_TYPE_UPDATE_BOOTLOADER) {
return this->finalize_bootloader_update_(err);
}
#endif
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) {
#ifdef USE_OTA_SIGNED_VERIFICATION
ESP_LOGE(TAG, "OTA validation failed (err=0x%X) - possible signature verification failure", err);
return OTA_RESPONSE_ERROR_SIGNATURE_INVALID;
#else
return OTA_RESPONSE_ERROR_UPDATE_END;
#endif
}
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() {
#ifdef USE_OTA_PARTITIONS
if (this->partition_table_part_ != nullptr) {
esp_partition_deregister_external(this->partition_table_part_);
this->partition_table_part_ = nullptr;
}
if (this->bootloader_part_ != nullptr) {
esp_partition_deregister_external(this->bootloader_part_);
this->bootloader_part_ = nullptr;
}
#endif
// esp_ota_abort with handle 0 returns ESP_ERR_INVALID_ARG harmlessly, so this is safe whether
// or not an update is in flight.
esp_ota_abort(this->update_handle_);
this->update_handle_ = 0;
}
} // namespace esphome::ota
#endif // USE_ESP32