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>
32static esp_err_t ota_end_guarded() {
34 return esp_hosted_slave_ota_end();
37#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
43#define STRINGIFY_(x) #x
44#define STRINGIFY(x) STRINGIFY_(x)
45static const char *
const ESP_HOSTED_VERSION_STR = STRINGIFY(ESP_HOSTED_VERSION_MAJOR_1)
"." STRINGIFY(
46 ESP_HOSTED_VERSION_MINOR_1)
"." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1);
48#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
51static bool parse_int(
const char *&ptr,
int &value) {
53 value =
static_cast<int>(strtol(ptr, &
end, 10));
62static bool parse_version(
const std::string &version_str,
int &major,
int &minor,
int &patch) {
63 major = minor = patch = 0;
64 const char *ptr = version_str.c_str();
66 if (!parse_int(ptr, major) || *ptr !=
'.')
69 if (!parse_int(ptr, minor))
72 parse_int(++ptr, patch);
81static int compare_versions(
int major1,
int minor1,
int patch1,
int major2,
int minor2,
int patch2) {
83 return major1 < major2 ? -1 : 1;
85 return minor1 < minor2 ? -1 : 1;
87 return patch1 < patch2 ? -1 : 1;
97 esp_hosted_connect_to_slave();
101 esp_hosted_coprocessor_fwver_t ver_info;
102 if (esp_hosted_get_coprocessor_fwversion(&ver_info) == ESP_OK) {
105 snprintf(buf,
sizeof(buf),
"%" PRIu32
".%" PRIu32
".%" PRIu32, ver_info.major1, ver_info.minor1, ver_info.patch1);
112#ifndef USE_ESP32_HOSTED_HTTP_UPDATE
114 const int app_desc_offset =
sizeof(esp_image_header_t) +
sizeof(esp_image_segment_header_t);
115 if (this->
firmware_size_ >= app_desc_offset +
sizeof(esp_app_desc_t)) {
116 esp_app_desc_t *app_desc = (esp_app_desc_t *) (this->
firmware_data_ + app_desc_offset);
117 if (app_desc->magic_word == ESP_APP_DESC_MAGIC_WORD) {
119 "ESP32 Hosted firmware:\n"
120 " Firmware version: %s\n"
121 " Project name: %s\n"
125 app_desc->version, app_desc->project_name, app_desc->date, app_desc->time, app_desc->idf_ver);
133 ESP_LOGW(TAG,
"Invalid app description magic word: 0x%08" PRIx32
" (expected 0x%08" PRIx32
")",
134 app_desc->magic_word,
static_cast<uint32_t>(ESP_APP_DESC_MAGIC_WORD));
138 ESP_LOGW(TAG,
"Firmware too small to contain app description");
154 this->
set_interval(INITIAL_CHECK_INTERVAL_ID, 10000, [
this]() {
169 "ESP32 Hosted Update:\n"
170 " Host Library Version: %s\n"
171 " Coprocessor Version: %s\n"
172 " Latest Version: %s",
174 this->update_info_.latest_version.c_str());
175#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
183 " Firmware Size: %zu bytes",
189#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
191 ESP_LOGD(TAG,
"Network not connected, skipping update check");
203 this->update_info_.latest_version == this->update_info_.current_version) {
219#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
221 ESP_LOGD(TAG,
"Fetching manifest");
224 if (container ==
nullptr || container->status_code != 200) {
225 ESP_LOGE(TAG,
"Failed to fetch manifest from %s", this->
source_url_);
233 std::string json_str;
234 json_str.reserve(container->content_length);
238 while (container->get_bytes_read() < container->content_length) {
239 int read_or_error = container->read(buf,
sizeof(buf));
250 json_str.append(
reinterpret_cast<char *
>(buf), read_or_error);
258 if (!root[
"versions"].is<JsonArray>()) {
259 ESP_LOGE(TAG,
"Manifest does not contain 'versions' array");
263 JsonArray versions = root[
"versions"].as<JsonArray>();
264 if (versions.size() == 0) {
265 ESP_LOGE(TAG,
"Manifest 'versions' array is empty");
271 int best_major = -1, best_minor = -1, best_patch = -1;
272 std::string best_version, best_url, best_sha256;
274 for (JsonObject entry : versions) {
275 if (!entry[
"version"].is<const char *>() || !entry[
"url"].is<const char *>() ||
276 !entry[
"sha256"].is<const char *>()) {
280 std::string ver_str = entry[
"version"].as<std::string>();
281 int major, minor, patch;
282 if (!parse_version(ver_str, major, minor, patch)) {
283 ESP_LOGW(TAG,
"Failed to parse version: %s", ver_str.c_str());
288 if (compare_versions(major, minor, patch, ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1,
289 ESP_HOSTED_VERSION_PATCH_1) > 0) {
294 if (best_major < 0 || compare_versions(major, minor, patch, best_major, best_minor, best_patch) > 0) {
298 best_version = ver_str;
299 best_url = entry[
"url"].as<std::string>();
300 best_sha256 = entry[
"sha256"].as<std::string>();
304 if (best_major < 0) {
305 ESP_LOGW(TAG,
"No compatible firmware version found (host is %s)", ESP_HOSTED_VERSION_STR);
314 ESP_LOGE(TAG,
"Invalid SHA256: %s", best_sha256.c_str());
324 ESP_LOGE(TAG,
"Failed to parse manifest JSON");
333 ESP_LOGI(TAG,
"Downloading firmware");
336 if (container ==
nullptr || container->status_code != 200) {
337 ESP_LOGE(TAG,
"Failed to fetch firmware");
342 size_t total_size = container->content_length;
343 ESP_LOGI(TAG,
"Firmware size: %zu bytes", total_size);
349 err = esp_hosted_slave_ota_begin();
352 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
367 while (container->get_bytes_read() < total_size) {
368 int read_or_error = container->read(
buffer,
sizeof(
buffer));
384 ESP_LOGE(TAG,
"Timeout reading firmware data");
386 ESP_LOGE(TAG,
"Error reading firmware data: %d", read_or_error);
395 err = esp_hosted_slave_ota_write(
buffer, read_or_error);
397 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
408 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
409 ESP_LOGE(TAG,
"SHA256 mismatch");
415 ESP_LOGI(TAG,
"SHA256 verified successfully");
421 ESP_LOGE(TAG,
"No firmware data available");
431 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
432 ESP_LOGE(TAG,
"SHA256 mismatch");
437 ESP_LOGI(TAG,
"Starting OTA update (%zu bytes)", this->
firmware_size_);
442 err = esp_hosted_slave_ota_begin();
445 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
453 while (remaining > 0) {
454 size_t chunk_size = std::min(remaining,
static_cast<size_t>(
CHUNK_SIZE));
455 memcpy(chunk, data_ptr, chunk_size);
456 err = esp_hosted_slave_ota_write(chunk, chunk_size);
458 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
463 data_ptr += chunk_size;
464 remaining -= chunk_size;
474 ESP_LOGW(TAG,
"Update not available");
478#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
480 ESP_LOGW(TAG,
"No firmware URL available, run check first");
490#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
496 this->
state_ = prev_state;
502 esp_err_t end_err = ota_end_guarded();
503 if (end_err != ESP_OK) {
504 ESP_LOGE(TAG,
"Failed to end OTA: %s", esp_err_to_name(end_err));
505 this->
state_ = prev_state;
511 esp_err_t activate_err = esp_hosted_slave_ota_activate();
512 if (activate_err != ESP_OK) {
513 ESP_LOGE(TAG,
"Failed to activate OTA: %s", esp_err_to_name(activate_err));
514 this->
state_ = prev_state;
521 ESP_LOGI(TAG,
"OTA update successful");
526#ifdef USE_OTA_ROLLBACK
529 esp_ota_mark_app_valid_cancel_rollback();
533 ESP_LOGI(TAG,
"Restarting in 1 second");
void feed_wdt()
Feed the task watchdog.
bool cancel_interval(const char *name)
Cancel an interval function.
void status_clear_error()
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
void defer(const char *name, std::function< void()> &&f)
Defer a callback to the next loop() call with a const char* name.
void set_interval(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a const char* name.
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_()
std::shared_ptr< HttpContainer > get(const char *url)
uint32_t get_timeout() const
SHA256 hash implementation.
void calculate() override
void add(const uint8_t *data, size_t len) override
std::unique_ptr< Trigger< const UpdateInfo & > > update_available_trigger_
constexpr uint32_t INITIAL_CHECK_INTERVAL_ID
ESPHOME_LOG_TAG(TAG, "esp32_hosted.update")
constexpr uint32_t OTA_WDT_TIMEOUT_MS
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