diff --git a/esphome/components/http_request/http_request_idf.cpp b/esphome/components/http_request/http_request_idf.cpp index b449f046ee..66f064c2ce 100644 --- a/esphome/components/http_request/http_request_idf.cpp +++ b/esphome/components/http_request/http_request_idf.cpp @@ -12,6 +12,8 @@ #include "esp_crt_bundle.h" #endif +#include "esp_task_wdt.h" + namespace esphome { namespace http_request { @@ -117,11 +119,11 @@ std::shared_ptr HttpRequestIDF::start(std::string url, std::strin return nullptr; } - App.feed_wdt(); + container->feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); - App.feed_wdt(); + container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); - App.feed_wdt(); + container->feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; @@ -151,11 +153,11 @@ std::shared_ptr HttpRequestIDF::start(std::string url, std::strin return nullptr; } - App.feed_wdt(); + container->feed_wdt(); container->content_length = esp_http_client_fetch_headers(client); - App.feed_wdt(); + container->feed_wdt(); container->status_code = esp_http_client_get_status_code(client); - App.feed_wdt(); + container->feed_wdt(); if (is_success(container->status_code)) { container->duration_ms = millis() - start; return container; @@ -185,8 +187,9 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { return 0; } - App.feed_wdt(); + this->feed_wdt(); int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize); + this->feed_wdt(); this->bytes_read_ += read_len; this->duration_ms += (millis() - start); @@ -201,6 +204,13 @@ void HttpContainerIDF::end() { esp_http_client_cleanup(this->client_); } +void HttpContainerIDF::feed_wdt() { + // Tests to see if the executing task has a watchdog timer attached + if (esp_task_wdt_status(nullptr) == ESP_OK) { + App.feed_wdt(); + } +} + } // namespace http_request } // namespace esphome diff --git a/esphome/components/http_request/http_request_idf.h b/esphome/components/http_request/http_request_idf.h index 431794924b..2ed50698b9 100644 --- a/esphome/components/http_request/http_request_idf.h +++ b/esphome/components/http_request/http_request_idf.h @@ -18,6 +18,9 @@ class HttpContainerIDF : public HttpContainer { int read(uint8_t *buf, size_t max_len) override; void end() override; + /// @brief Feeds the watchdog timer if the executing task has one attached + void feed_wdt(); + protected: esp_http_client_handle_t client_; }; diff --git a/esphome/components/http_request/update/http_request_update.cpp b/esphome/components/http_request/update/http_request_update.cpp index 0e0966c22b..d683495ac6 100644 --- a/esphome/components/http_request/update/http_request_update.cpp +++ b/esphome/components/http_request/update/http_request_update.cpp @@ -9,6 +9,13 @@ namespace esphome { namespace http_request { +// The update function runs in a task only on ESP32s. +#ifdef USE_ESP32 +#define UPDATE_RETURN vTaskDelete(nullptr) // Delete the current update task +#else +#define UPDATE_RETURN return +#endif + static const char *const TAG = "http_request.update"; static const size_t MAX_READ_SIZE = 256; @@ -29,113 +36,131 @@ void HttpRequestUpdate::setup() { } void HttpRequestUpdate::update() { - auto container = this->request_parent_->get(this->source_url_); +#ifdef USE_ESP32 + xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_); +#else + this->update_task(this); +#endif +} + +void HttpRequestUpdate::update_task(void *params) { + HttpRequestUpdate *this_update = (HttpRequestUpdate *) params; + + auto container = this_update->request_parent_->get(this_update->source_url_); if (container == nullptr || container->status_code != HTTP_STATUS_OK) { - std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str()); - this->status_set_error(msg.c_str()); - return; + std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str()); + this_update->status_set_error(msg.c_str()); + UPDATE_RETURN; } ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); uint8_t *data = allocator.allocate(container->content_length); if (data == nullptr) { std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length); - this->status_set_error(msg.c_str()); + this_update->status_set_error(msg.c_str()); container->end(); - return; + UPDATE_RETURN; } size_t read_index = 0; while (container->get_bytes_read() < container->content_length) { int read_bytes = container->read(data + read_index, MAX_READ_SIZE); - App.feed_wdt(); yield(); read_index += read_bytes; } - std::string response((char *) data, read_index); - allocator.deallocate(data, container->content_length); + bool valid = false; + { // Ensures the response string falls out of scope and deallocates before the task ends + std::string response((char *) data, read_index); + allocator.deallocate(data, container->content_length); - container->end(); + container->end(); + container.reset(); // Release ownership of the container's shared_ptr - bool valid = json::parse_json(response, [this](JsonObject root) -> bool { - if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { - ESP_LOGE(TAG, "Manifest does not contain required fields"); - return false; - } - this->update_info_.title = root["name"].as(); - this->update_info_.latest_version = root["version"].as(); - - for (auto build : root["builds"].as()) { - if (!build.containsKey("chipFamily")) { + valid = json::parse_json(response, [this_update](JsonObject root) -> bool { + if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - if (build["chipFamily"] == ESPHOME_VARIANT) { - if (!build.containsKey("ota")) { + this_update->update_info_.title = root["name"].as(); + this_update->update_info_.latest_version = root["version"].as(); + + for (auto build : root["builds"].as()) { + if (!build.containsKey("chipFamily")) { ESP_LOGE(TAG, "Manifest does not contain required fields"); return false; } - auto ota = build["ota"]; - if (!ota.containsKey("path") || !ota.containsKey("md5")) { - ESP_LOGE(TAG, "Manifest does not contain required fields"); - return false; + if (build["chipFamily"] == ESPHOME_VARIANT) { + if (!build.containsKey("ota")) { + ESP_LOGE(TAG, "Manifest does not contain required fields"); + return false; + } + auto ota = build["ota"]; + if (!ota.containsKey("path") || !ota.containsKey("md5")) { + ESP_LOGE(TAG, "Manifest does not contain required fields"); + return false; + } + this_update->update_info_.firmware_url = ota["path"].as(); + this_update->update_info_.md5 = ota["md5"].as(); + + if (ota.containsKey("summary")) + this_update->update_info_.summary = ota["summary"].as(); + if (ota.containsKey("release_url")) + this_update->update_info_.release_url = ota["release_url"].as(); + + return true; } - this->update_info_.firmware_url = ota["path"].as(); - this->update_info_.md5 = ota["md5"].as(); - - if (ota.containsKey("summary")) - this->update_info_.summary = ota["summary"].as(); - if (ota.containsKey("release_url")) - this->update_info_.release_url = ota["release_url"].as(); - - return true; } - } - return false; - }); + return false; + }); + } if (!valid) { - std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str()); - this->status_set_error(msg.c_str()); - return; + std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str()); + this_update->status_set_error(msg.c_str()); + UPDATE_RETURN; } - // Merge source_url_ and this->update_info_.firmware_url - if (this->update_info_.firmware_url.find("http") == std::string::npos) { - std::string path = this->update_info_.firmware_url; + // Merge source_url_ and this_update->update_info_.firmware_url + if (this_update->update_info_.firmware_url.find("http") == std::string::npos) { + std::string path = this_update->update_info_.firmware_url; if (path[0] == '/') { - std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8)); - this->update_info_.firmware_url = domain + path; + std::string domain = this_update->source_url_.substr(0, this_update->source_url_.find('/', 8)); + this_update->update_info_.firmware_url = domain + path; } else { - std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1); - this->update_info_.firmware_url = domain + path; + std::string domain = this_update->source_url_.substr(0, this_update->source_url_.rfind('/') + 1); + this_update->update_info_.firmware_url = domain + path; } } - std::string current_version; + { // Ensures the current version string falls out of scope and deallocates before the task ends + std::string current_version; #ifdef ESPHOME_PROJECT_VERSION - current_version = ESPHOME_PROJECT_VERSION; + current_version = ESPHOME_PROJECT_VERSION; #else - current_version = ESPHOME_VERSION; + current_version = ESPHOME_VERSION; #endif - this->update_info_.current_version = current_version; - - if (this->update_info_.latest_version.empty() || this->update_info_.latest_version == update_info_.current_version) { - this->state_ = update::UPDATE_STATE_NO_UPDATE; - } else { - this->state_ = update::UPDATE_STATE_AVAILABLE; + this_update->update_info_.current_version = current_version; } - this->update_info_.has_progress = false; - this->update_info_.progress = 0.0f; + if (this_update->update_info_.latest_version.empty() || + this_update->update_info_.latest_version == this_update->update_info_.current_version) { + this_update->state_ = update::UPDATE_STATE_NO_UPDATE; + } else { + this_update->state_ = update::UPDATE_STATE_AVAILABLE; + } - this->status_clear_error(); - this->publish_state(); + this_update->update_info_.has_progress = false; + this_update->update_info_.progress = 0.0f; + + this_update->status_clear_error(); + this_update->publish_state(); + + UPDATE_RETURN; } void HttpRequestUpdate::perform(bool force) { diff --git a/esphome/components/http_request/update/http_request_update.h b/esphome/components/http_request/update/http_request_update.h index 45c7e6a447..e05fdb0cc2 100644 --- a/esphome/components/http_request/update/http_request_update.h +++ b/esphome/components/http_request/update/http_request_update.h @@ -7,6 +7,10 @@ #include "esphome/components/http_request/ota/ota_http_request.h" #include "esphome/components/update/update_entity.h" +#ifdef USE_ESP32 +#include +#endif + namespace esphome { namespace http_request { @@ -29,6 +33,11 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { HttpRequestComponent *request_parent_; OtaHttpRequestComponent *ota_parent_; std::string source_url_; + + static void update_task(void *params); +#ifdef USE_ESP32 + TaskHandle_t update_task_handle_{nullptr}; +#endif }; } // namespace http_request diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index ceeb30baf5..a090f42aa7 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -147,7 +147,7 @@ class LibreTinyPreferences : public ESPPreferences { ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", to_save.key.c_str()); return true; } - stored_data.data.reserve(kv.value_len); + stored_data.data.resize(kv.value_len); fdb_blob_make(&blob, stored_data.data.data(), kv.value_len); size_t actual_len = fdb_kv_get_blob(db, to_save.key.c_str(), &blob); if (actual_len != kv.value_len) { diff --git a/esphome/const.py b/esphome/const.py index 10ad9454fd..a86e20c2e5 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2024.12.2" +__version__ = "2024.12.3" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (