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 "Firmware version: %s\n"
114 app_desc->version, app_desc->project_name, app_desc->date, app_desc->time, app_desc->idf_ver);
122 ESP_LOGW(TAG,
"Invalid app description magic word: 0x%08x (expected 0x%08x)", app_desc->magic_word,
123 ESP_APP_DESC_MAGIC_WORD);
127 ESP_LOGW(TAG,
"Firmware too small to contain app description");
139 this->
set_interval(INITIAL_CHECK_INTERVAL_ID, 10000, [
this]() {
154 "ESP32 Hosted Update:\n"
155 " Host Library Version: %s\n"
156 " Coprocessor Version: %s\n"
157 " Latest Version: %s",
159 this->update_info_.latest_version.c_str());
160#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
168 " Firmware Size: %zu bytes",
174#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
176 ESP_LOGD(TAG,
"Network not connected, skipping update check");
186 this->update_info_.latest_version == this->update_info_.current_version) {
199#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
201 ESP_LOGD(TAG,
"Fetching manifest");
204 if (container ==
nullptr || container->status_code != 200) {
205 ESP_LOGE(TAG,
"Failed to fetch manifest from %s", this->
source_url_.c_str());
213 std::string json_str;
214 json_str.reserve(container->content_length);
216 uint32_t last_data_time =
millis();
218 while (container->get_bytes_read() < container->content_length) {
219 int read_or_error = container->read(buf,
sizeof(buf));
230 json_str.append(
reinterpret_cast<char *
>(buf), read_or_error);
238 if (!root[
"versions"].is<JsonArray>()) {
239 ESP_LOGE(TAG,
"Manifest does not contain 'versions' array");
243 JsonArray versions = root[
"versions"].as<JsonArray>();
244 if (versions.size() == 0) {
245 ESP_LOGE(TAG,
"Manifest 'versions' array is empty");
251 int best_major = -1, best_minor = -1, best_patch = -1;
252 std::string best_version, best_url, best_sha256;
254 for (JsonObject entry : versions) {
255 if (!entry[
"version"].is<const char *>() || !entry[
"url"].is<const char *>() ||
256 !entry[
"sha256"].is<const char *>()) {
260 std::string ver_str = entry[
"version"].as<std::string>();
261 int major, minor, patch;
262 if (!parse_version(ver_str, major, minor, patch)) {
263 ESP_LOGW(TAG,
"Failed to parse version: %s", ver_str.c_str());
268 if (compare_versions(major, minor, patch, ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1,
269 ESP_HOSTED_VERSION_PATCH_1) > 0) {
274 if (best_major < 0 || compare_versions(major, minor, patch, best_major, best_minor, best_patch) > 0) {
278 best_version = ver_str;
279 best_url = entry[
"url"].as<std::string>();
280 best_sha256 = entry[
"sha256"].as<std::string>();
284 if (best_major < 0) {
285 ESP_LOGW(TAG,
"No compatible firmware version found (host is %s)", ESP_HOSTED_VERSION_STR);
294 ESP_LOGE(TAG,
"Invalid SHA256: %s", best_sha256.c_str());
304 ESP_LOGE(TAG,
"Failed to parse manifest JSON");
313 ESP_LOGI(TAG,
"Downloading firmware");
316 if (container ==
nullptr || container->status_code != 200) {
317 ESP_LOGE(TAG,
"Failed to fetch firmware");
322 size_t total_size = container->content_length;
323 ESP_LOGI(TAG,
"Firmware size: %zu bytes", total_size);
326 esp_err_t err = esp_hosted_slave_ota_begin();
328 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
341 uint32_t last_data_time =
millis();
343 while (container->get_bytes_read() < total_size) {
344 int read_or_error = container->read(buffer,
sizeof(buffer));
360 ESP_LOGE(TAG,
"Timeout reading firmware data");
362 ESP_LOGE(TAG,
"Error reading firmware data: %d", read_or_error);
364 esp_hosted_slave_ota_end();
370 hasher.
add(buffer, read_or_error);
371 err = esp_hosted_slave_ota_write(buffer, read_or_error);
373 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
374 esp_hosted_slave_ota_end();
384 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
385 ESP_LOGE(TAG,
"SHA256 mismatch");
386 esp_hosted_slave_ota_end();
391 ESP_LOGI(TAG,
"SHA256 verified successfully");
397 ESP_LOGE(TAG,
"No firmware data available");
407 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
408 ESP_LOGE(TAG,
"SHA256 mismatch");
413 ESP_LOGI(TAG,
"Starting OTA update (%zu bytes)", this->
firmware_size_);
415 esp_err_t err = esp_hosted_slave_ota_begin();
417 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
425 while (remaining > 0) {
426 size_t chunk_size = std::min(remaining,
static_cast<size_t>(
CHUNK_SIZE));
427 memcpy(chunk, data_ptr, chunk_size);
428 err = esp_hosted_slave_ota_write(chunk, chunk_size);
430 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
431 esp_hosted_slave_ota_end();
435 data_ptr += chunk_size;
436 remaining -= chunk_size;
446 ESP_LOGW(TAG,
"Update not available");
457#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
463 this->
state_ = prev_state;
469 esp_err_t end_err = esp_hosted_slave_ota_end();
470 if (end_err != ESP_OK) {
471 ESP_LOGE(TAG,
"Failed to end OTA: %s", esp_err_to_name(end_err));
472 this->
state_ = prev_state;
478 esp_err_t activate_err = esp_hosted_slave_ota_activate();
479 if (activate_err != ESP_OK) {
480 ESP_LOGE(TAG,
"Failed to activate OTA: %s", esp_err_to_name(activate_err));
481 this->
state_ = prev_state;
488 ESP_LOGI(TAG,
"OTA update successful");
493#ifdef USE_OTA_ROLLBACK
496 esp_ota_mark_app_valid_cancel_rollback();
500 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.
void IRAM_ATTR HOT yield()
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