1#if defined(USE_ESP32_VARIANT_ESP32H2) || defined(USE_ESP32_VARIANT_ESP32P4)
7#include <esp_image_format.h>
8#include <esp_app_desc.h>
10#include <esp_hosted_host_fw_ver.h>
11#include <esp_ota_ops.h>
13#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
20#include <esp_hosted_ota.h>
25static const char *
const TAG =
"esp32_hosted.update";
30#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
36#define STRINGIFY_(x) #x
37#define STRINGIFY(x) STRINGIFY_(x)
38static const char *
const ESP_HOSTED_VERSION_STR = STRINGIFY(ESP_HOSTED_VERSION_MAJOR_1)
"." STRINGIFY(
39 ESP_HOSTED_VERSION_MINOR_1)
"." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1);
41#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
44static bool parse_int(
const char *&ptr,
int &value) {
46 value =
static_cast<int>(strtol(ptr, &
end, 10));
55static bool parse_version(
const std::string &version_str,
int &major,
int &minor,
int &patch) {
56 major = minor = patch = 0;
57 const char *ptr = version_str.c_str();
59 if (!parse_int(ptr, major) || *ptr !=
'.')
62 if (!parse_int(ptr, minor))
65 parse_int(++ptr, patch);
74static int compare_versions(
int major1,
int minor1,
int patch1,
int major2,
int minor2,
int patch2) {
76 return major1 < major2 ? -1 : 1;
78 return minor1 < minor2 ? -1 : 1;
80 return patch1 < patch2 ? -1 : 1;
90 esp_hosted_connect_to_slave();
94 esp_hosted_coprocessor_fwver_t ver_info;
95 if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
98 snprintf(buf,
sizeof(buf),
"%" PRIu32
".%" PRIu32
".%" PRIu32, ver_info.major1, ver_info.minor1, ver_info.patch1);
105#ifndef USE_ESP32_HOSTED_HTTP_UPDATE
107 const int app_desc_offset =
sizeof(esp_image_header_t) +
sizeof(esp_image_segment_header_t);
108 if (this->
firmware_size_ >= app_desc_offset +
sizeof(esp_app_desc_t)) {
109 esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->
firmware_data_ + app_desc_offset);
110 if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) {
112 "ESP32 Hosted firmware:\n"
113 " Firmware version: %s\n"
114 " Project name: %s\n"
118 app_desc->version, app_desc->project_name, app_desc->date, app_desc->time, app_desc->idf_ver);
126 ESP_LOGW(TAG,
"Invalid app description magic word: 0x%08" PRIx32
" (expected 0x%08" PRIx32
")",
127 app_desc->magic_word,
static_cast<uint32_t>(ESP_APP_DESC_MAGIC_WORD));
131 ESP_LOGW(TAG,
"Firmware too small to contain app description");
143 this->
set_interval(INITIAL_CHECK_INTERVAL_ID, 10000, [
this]() {
158 "ESP32 Hosted Update:\n"
159 " Host Library Version: %s\n"
160 " Coprocessor Version: %s\n"
161 " Latest Version: %s",
163 this->update_info_.latest_version.c_str());
164#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
172 " Firmware Size: %zu bytes",
178#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
180 ESP_LOGD(TAG,
"Network not connected, skipping update check");
190 this->update_info_.latest_version == this->update_info_.current_version) {
203#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
205 ESP_LOGD(TAG,
"Fetching manifest");
208 if (container ==
nullptr || container->status_code != 200) {
209 ESP_LOGE(TAG,
"Failed to fetch manifest from %s", this->
source_url_.c_str());
217 std::string json_str;
218 json_str.reserve(container->content_length);
222 while (container->get_bytes_read() < container->content_length) {
223 int read_or_error = container->read(buf,
sizeof(buf));
234 json_str.append(
reinterpret_cast<char *
>(buf), read_or_error);
242 if (!root[
"versions"].is<JsonArray>()) {
243 ESP_LOGE(TAG,
"Manifest does not contain 'versions' array");
247 JsonArray versions = root[
"versions"].as<JsonArray>();
248 if (versions.size() == 0) {
249 ESP_LOGE(TAG,
"Manifest 'versions' array is empty");
255 int best_major = -1, best_minor = -1, best_patch = -1;
256 std::string best_version, best_url, best_sha256;
258 for (JsonObject entry : versions) {
259 if (!entry[
"version"].is<const char *>() || !entry[
"url"].is<const char *>() ||
260 !entry[
"sha256"].is<const char *>()) {
264 std::string ver_str = entry[
"version"].as<std::string>();
265 int major, minor, patch;
266 if (!parse_version(ver_str, major, minor, patch)) {
267 ESP_LOGW(TAG,
"Failed to parse version: %s", ver_str.c_str());
272 if (compare_versions(major, minor, patch, ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1,
273 ESP_HOSTED_VERSION_PATCH_1) > 0) {
278 if (best_major < 0 || compare_versions(major, minor, patch, best_major, best_minor, best_patch) > 0) {
282 best_version = ver_str;
283 best_url = entry[
"url"].as<std::string>();
284 best_sha256 = entry[
"sha256"].as<std::string>();
288 if (best_major < 0) {
289 ESP_LOGW(TAG,
"No compatible firmware version found (host is %s)", ESP_HOSTED_VERSION_STR);
298 ESP_LOGE(TAG,
"Invalid SHA256: %s", best_sha256.c_str());
308 ESP_LOGE(TAG,
"Failed to parse manifest JSON");
317 ESP_LOGI(TAG,
"Downloading firmware");
320 if (container ==
nullptr || container->status_code != 200) {
321 ESP_LOGE(TAG,
"Failed to fetch firmware");
326 size_t total_size = container->content_length;
327 ESP_LOGI(TAG,
"Firmware size: %zu bytes", total_size);
330 esp_err_t err = esp_hosted_slave_ota_begin();
332 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
347 while (container->get_bytes_read() < total_size) {
348 int read_or_error = container->read(buffer,
sizeof(buffer));
364 ESP_LOGE(TAG,
"Timeout reading firmware data");
366 ESP_LOGE(TAG,
"Error reading firmware data: %d", read_or_error);
368 esp_hosted_slave_ota_end();
374 hasher.
add(buffer, read_or_error);
375 err = esp_hosted_slave_ota_write(buffer, read_or_error);
377 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
378 esp_hosted_slave_ota_end();
388 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
389 ESP_LOGE(TAG,
"SHA256 mismatch");
390 esp_hosted_slave_ota_end();
395 ESP_LOGI(TAG,
"SHA256 verified successfully");
401 ESP_LOGE(TAG,
"No firmware data available");
411 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
412 ESP_LOGE(TAG,
"SHA256 mismatch");
417 ESP_LOGI(TAG,
"Starting OTA update (%zu bytes)", this->
firmware_size_);
419 esp_err_t err = esp_hosted_slave_ota_begin();
421 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
429 while (remaining > 0) {
430 size_t chunk_size = std::min(remaining,
static_cast<size_t>(
CHUNK_SIZE));
431 memcpy(chunk, data_ptr, chunk_size);
432 err = esp_hosted_slave_ota_write(chunk, chunk_size);
434 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
435 esp_hosted_slave_ota_end();
439 data_ptr += chunk_size;
440 remaining -= chunk_size;
450 ESP_LOGW(TAG,
"Update not available");
454#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
456 ESP_LOGW(TAG,
"No firmware URL available, run check first");
468#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
474 this->
state_ = prev_state;
480 esp_err_t end_err = esp_hosted_slave_ota_end();
481 if (end_err != ESP_OK) {
482 ESP_LOGE(TAG,
"Failed to end OTA: %s", esp_err_to_name(end_err));
483 this->
state_ = prev_state;
489 esp_err_t activate_err = esp_hosted_slave_ota_activate();
490 if (activate_err != ESP_OK) {
491 ESP_LOGE(TAG,
"Failed to activate OTA: %s", esp_err_to_name(activate_err));
492 this->
state_ = prev_state;
499 ESP_LOGI(TAG,
"OTA update successful");
504#ifdef USE_OTA_ROLLBACK
507 esp_ota_mark_app_valid_cancel_rollback();
511 ESP_LOGI(TAG,
"Restarting in 1 second");
void feed_wdt()
Feed the task watchdog.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_timeout(const std voi set_timeout)(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
void status_clear_error()
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_interval(const std voi set_interval)(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a unique name.
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_interval(const std boo cancel_interval)(const char *name)
Cancel an interval function.
bool equals_bytes(const uint8_t *expected)
Compare the hash against a provided byte-encoded hash.
virtual uint32_t get_update_interval() const
Get the update interval in ms of this sensor.
void dump_config() override
bool stream_firmware_to_coprocessor_()
uint8_t initial_check_remaining_
http_request::HttpRequestComponent * http_request_parent_
std::array< uint8_t, 32 > firmware_sha256_
std::string firmware_url_
const uint8_t * firmware_data_
bool write_embedded_firmware_to_coprocessor_()
uint32_t get_timeout() const
std::shared_ptr< HttpContainer > get(const std::string &url)
SHA256 hash implementation.
void calculate() override
void add(const uint8_t *data, size_t len) override
constexpr uint32_t INITIAL_CHECK_INTERVAL_ID
constexpr size_t CHUNK_SIZE
@ TIMEOUT
Timeout waiting for data, caller should exit loop.
@ COMPLETE
All content has been read, caller should exit loop.
@ RETRY
No data yet, already delayed, caller should continue loop.
@ DATA
Data was read, process it.
HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time, uint32_t timeout_ms, bool is_read_complete)
Process a read result with timeout tracking and delay handling.
bool parse_json(const std::string &data, const json_parse_t &f)
Parse a JSON string and run the provided json parse function if it's valid.
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
@ UPDATE_STATE_INSTALLING
size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count)
Parse bytes from a hex-encoded string into a byte array.
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.
std::string current_version
std::string latest_version