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");
147 this->
set_interval(INITIAL_CHECK_INTERVAL_ID, 10000, [
this]() {
162 "ESP32 Hosted Update:\n"
163 " Host Library Version: %s\n"
164 " Coprocessor Version: %s\n"
165 " Latest Version: %s",
167 this->update_info_.latest_version.c_str());
168#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
176 " Firmware Size: %zu bytes",
182#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
184 ESP_LOGD(TAG,
"Network not connected, skipping update check");
196 this->update_info_.latest_version == this->update_info_.current_version) {
212#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
214 ESP_LOGD(TAG,
"Fetching manifest");
217 if (container ==
nullptr || container->status_code != 200) {
218 ESP_LOGE(TAG,
"Failed to fetch manifest from %s", this->
source_url_.c_str());
226 std::string json_str;
227 json_str.reserve(container->content_length);
231 while (container->get_bytes_read() < container->content_length) {
232 int read_or_error = container->read(buf,
sizeof(buf));
243 json_str.append(
reinterpret_cast<char *
>(buf), read_or_error);
251 if (!root[
"versions"].is<JsonArray>()) {
252 ESP_LOGE(TAG,
"Manifest does not contain 'versions' array");
256 JsonArray versions = root[
"versions"].as<JsonArray>();
257 if (versions.size() == 0) {
258 ESP_LOGE(TAG,
"Manifest 'versions' array is empty");
264 int best_major = -1, best_minor = -1, best_patch = -1;
265 std::string best_version, best_url, best_sha256;
267 for (JsonObject entry : versions) {
268 if (!entry[
"version"].is<const char *>() || !entry[
"url"].is<const char *>() ||
269 !entry[
"sha256"].is<const char *>()) {
273 std::string ver_str = entry[
"version"].as<std::string>();
274 int major, minor, patch;
275 if (!parse_version(ver_str, major, minor, patch)) {
276 ESP_LOGW(TAG,
"Failed to parse version: %s", ver_str.c_str());
281 if (compare_versions(major, minor, patch, ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1,
282 ESP_HOSTED_VERSION_PATCH_1) > 0) {
287 if (best_major < 0 || compare_versions(major, minor, patch, best_major, best_minor, best_patch) > 0) {
291 best_version = ver_str;
292 best_url = entry[
"url"].as<std::string>();
293 best_sha256 = entry[
"sha256"].as<std::string>();
297 if (best_major < 0) {
298 ESP_LOGW(TAG,
"No compatible firmware version found (host is %s)", ESP_HOSTED_VERSION_STR);
307 ESP_LOGE(TAG,
"Invalid SHA256: %s", best_sha256.c_str());
317 ESP_LOGE(TAG,
"Failed to parse manifest JSON");
326 ESP_LOGI(TAG,
"Downloading firmware");
329 if (container ==
nullptr || container->status_code != 200) {
330 ESP_LOGE(TAG,
"Failed to fetch firmware");
335 size_t total_size = container->content_length;
336 ESP_LOGI(TAG,
"Firmware size: %zu bytes", total_size);
339 esp_err_t err = esp_hosted_slave_ota_begin();
341 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
356 while (container->get_bytes_read() < total_size) {
357 int read_or_error = container->read(buffer,
sizeof(buffer));
373 ESP_LOGE(TAG,
"Timeout reading firmware data");
375 ESP_LOGE(TAG,
"Error reading firmware data: %d", read_or_error);
377 esp_hosted_slave_ota_end();
383 hasher.
add(buffer, read_or_error);
384 err = esp_hosted_slave_ota_write(buffer, read_or_error);
386 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
387 esp_hosted_slave_ota_end();
397 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
398 ESP_LOGE(TAG,
"SHA256 mismatch");
399 esp_hosted_slave_ota_end();
404 ESP_LOGI(TAG,
"SHA256 verified successfully");
410 ESP_LOGE(TAG,
"No firmware data available");
420 if (!hasher.
equals_bytes(this->firmware_sha256_.data())) {
421 ESP_LOGE(TAG,
"SHA256 mismatch");
426 ESP_LOGI(TAG,
"Starting OTA update (%zu bytes)", this->
firmware_size_);
428 esp_err_t err = esp_hosted_slave_ota_begin();
430 ESP_LOGE(TAG,
"Failed to begin OTA: %s", esp_err_to_name(err));
438 while (remaining > 0) {
439 size_t chunk_size = std::min(remaining,
static_cast<size_t>(
CHUNK_SIZE));
440 memcpy(chunk, data_ptr, chunk_size);
441 err = esp_hosted_slave_ota_write(chunk, chunk_size);
443 ESP_LOGE(TAG,
"Failed to write OTA data: %s", esp_err_to_name(err));
444 esp_hosted_slave_ota_end();
448 data_ptr += chunk_size;
449 remaining -= chunk_size;
459 ESP_LOGW(TAG,
"Update not available");
463#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
465 ESP_LOGW(TAG,
"No firmware URL available, run check first");
477#ifdef USE_ESP32_HOSTED_HTTP_UPDATE
483 this->
state_ = prev_state;
489 esp_err_t end_err = esp_hosted_slave_ota_end();
490 if (end_err != ESP_OK) {
491 ESP_LOGE(TAG,
"Failed to end OTA: %s", esp_err_to_name(end_err));
492 this->
state_ = prev_state;
498 esp_err_t activate_err = esp_hosted_slave_ota_activate();
499 if (activate_err != ESP_OK) {
500 ESP_LOGE(TAG,
"Failed to activate OTA: %s", esp_err_to_name(activate_err));
501 this->
state_ = prev_state;
508 ESP_LOGI(TAG,
"OTA update successful");
513#ifdef USE_OTA_ROLLBACK
516 esp_ota_mark_app_valid_cancel_rollback();
520 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_()
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
std::unique_ptr< Trigger< const UpdateInfo & > > update_available_trigger_
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