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++ !=
'.' || !parse_int(ptr, minor))
62 parse_int(++ptr, patch);
71static int compare_versions(
int major1,
int minor1,
int patch1,
int major2,
int minor2,
int patch2) {
73 return major1 < major2 ? -1 : 1;
75 return minor1 < minor2 ? -1 : 1;
77 return patch1 < patch2 ? -1 : 1;
87 esp_hosted_connect_to_slave();
91 esp_hosted_coprocessor_fwver_t ver_info;
92 if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
95 snprintf(buf,
sizeof(buf),
"%d.%d.%d", ver_info.major1, ver_info.minor1, ver_info.patch1);
102#ifndef USE_ESP32_HOSTED_HTTP_UPDATE
104 const int app_desc_offset =
sizeof(esp_image_header_t) +
sizeof(esp_image_segment_header_t);
105 if (this->
firmware_size_ >= app_desc_offset +
sizeof(esp_app_desc_t)) {
106 esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->
firmware_data_ + app_desc_offset);
107 if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) {
109 "ESP32 Hosted firmware:\n"
110 " Firmware version: %s\n"
111 " Project name: %s\n"
115 app_desc->version, app_desc->project_name, app_desc->date, app_desc->time, app_desc->idf_ver);
123 ESP_LOGW(TAG,
"Invalid app description magic word: 0x%08x (expected 0x%08x)", app_desc->magic_word,
124 ESP_APP_DESC_MAGIC_WORD);
128 ESP_LOGW(TAG,
"Firmware too small to contain app description");
140 this->
set_interval(INITIAL_CHECK_INTERVAL_ID, 10000, [
this]() {
155 "ESP32 Hosted Update:\n"
156 " Host Library Version: %s\n"
157 " Coprocessor Version: %s\n"
158 " Latest Version: %s",
160 this->update_info_.latest_version.c_str());
161#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
169 " Firmware Size: %zu bytes",
175#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
177 ESP_LOGD(TAG,
"Network not connected, skipping update check");
187 this->update_info_.latest_version == this->update_info_.current_version) {
200#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
202 ESP_LOGD(TAG,
"Fetching manifest");
205 if (container ==
nullptr || container->status_code != 200) {
206 ESP_LOGE(TAG,
"Failed to fetch manifest from %s", this->
source_url_.c_str());
214 std::string json_str;
215 json_str.reserve(container->content_length);
217 uint32_t last_data_time =
millis();
219 while (container->get_bytes_read() < container->content_length) {
220 int read_or_error = container->read(buf,
sizeof(buf));
231 json_str.append(
reinterpret_cast<char *
>(buf), read_or_error);
239 if (!root[
"versions"].is<JsonArray>()) {
240 ESP_LOGE(TAG,
"Manifest does not contain 'versions' array");
244 JsonArray versions = root[
"versions"].as<JsonArray>();
245 if (versions.size() == 0) {
246 ESP_LOGE(TAG,
"Manifest 'versions' array is empty");
252 int best_major = -1, best_minor = -1, best_patch = -1;
253 std::string best_version, best_url, best_sha256;
255 for (JsonObject entry : versions) {
256 if (!entry[
"version"].is<const char *>() || !entry[
"url"].is<const char *>() ||
257 !entry[
"sha256"].is<const char *>()) {
261 std::string ver_str = entry[
"version"].as<std::string>();
262 int major, minor, patch;
263 if (!parse_version(ver_str, major, minor, patch)) {
264 ESP_LOGW(TAG,
"Failed to parse version: %s", ver_str.c_str());
269 if (compare_versions(major, minor, patch, ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1,
270 ESP_HOSTED_VERSION_PATCH_1) > 0) {
275 if (best_major < 0 || compare_versions(major, minor, patch, best_major, best_minor, best_patch) > 0) {
279 best_version = ver_str;
280 best_url = entry[
"url"].as<std::string>();
281 best_sha256 = entry[
"sha256"].as<std::string>();
285 if (best_major < 0) {
286 ESP_LOGW(TAG,
"No compatible firmware version found (host is %s)", ESP_HOSTED_VERSION_STR);
295 ESP_LOGE(TAG,
"Invalid SHA256: %s", best_sha256.c_str());
305 ESP_LOGE(TAG,
"Failed to parse manifest JSON");
314 ESP_LOGI(TAG,
"Downloading firmware");
317 if (container ==
nullptr || container->status_code != 200) {
318 ESP_LOGE(TAG,
"Failed to fetch firmware");
323 size_t total_size = container->content_length;
324 ESP_LOGI(TAG,
"Firmware size: %zu bytes", total_size);
327 esp_err_t err = esp_hosted_slave_ota_begin();
329 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
342 uint32_t last_data_time =
millis();
344 while (container->get_bytes_read() < total_size) {
345 int read_or_error = container->read(buffer,
sizeof(buffer));
361 ESP_LOGE(TAG,
"Timeout reading firmware data");
363 ESP_LOGE(TAG,
"Error reading firmware data: %d", read_or_error);
365 esp_hosted_slave_ota_end();
371 hasher.
add(buffer, read_or_error);
372 err = esp_hosted_slave_ota_write(buffer, read_or_error);
374 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
375 esp_hosted_slave_ota_end();
385 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
386 ESP_LOGE(TAG,
"SHA256 mismatch");
387 esp_hosted_slave_ota_end();
392 ESP_LOGI(TAG,
"SHA256 verified successfully");
398 ESP_LOGE(TAG,
"No firmware data available");
408 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
409 ESP_LOGE(TAG,
"SHA256 mismatch");
414 ESP_LOGI(TAG,
"Starting OTA update (%zu bytes)", this->
firmware_size_);
416 esp_err_t err = esp_hosted_slave_ota_begin();
418 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
426 while (remaining > 0) {
427 size_t chunk_size = std::min(remaining,
static_cast<size_t>(
CHUNK_SIZE));
428 memcpy(chunk, data_ptr, chunk_size);
429 err = esp_hosted_slave_ota_write(chunk, chunk_size);
431 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
432 esp_hosted_slave_ota_end();
436 data_ptr += chunk_size;
437 remaining -= chunk_size;
447 ESP_LOGW(TAG,
"Update not available");
458#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
464 this->
state_ = prev_state;
470 esp_err_t end_err = esp_hosted_slave_ota_end();
471 if (end_err != ESP_OK) {
472 ESP_LOGE(TAG,
"Failed to end OTA: %s", esp_err_to_name(end_err));
473 this->
state_ = prev_state;
479 esp_err_t activate_err = esp_hosted_slave_ota_activate();
480 if (activate_err != ESP_OK) {
481 ESP_LOGE(TAG,
"Failed to activate OTA: %s", esp_err_to_name(activate_err));
482 this->
state_ = prev_state;
489 ESP_LOGI(TAG,
"OTA update successful");
494#ifdef USE_OTA_ROLLBACK
497 esp_ota_mark_app_valid_cancel_rollback();
501 ESP_LOGI(TAG,
"Restarting in 1 second");
void feed_wdt(uint32_t time=0)
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.
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