mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	[http_request] Bugfix: run update function in a task (#8018)
This commit is contained in:
		
				
					committed by
					
						 Jesse Hills
						Jesse Hills
					
				
			
			
				
	
			
			
			
						parent
						
							abdd6b232f
						
					
				
				
					commit
					03c36920ff
				
			| @@ -12,6 +12,8 @@ | |||||||
| #include "esp_crt_bundle.h" | #include "esp_crt_bundle.h" | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #include "esp_task_wdt.h" | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace http_request { | namespace http_request { | ||||||
|  |  | ||||||
| @@ -117,11 +119,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin | |||||||
|     return nullptr; |     return nullptr; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   App.feed_wdt(); |   container->feed_wdt(); | ||||||
|   container->content_length = esp_http_client_fetch_headers(client); |   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); |   container->status_code = esp_http_client_get_status_code(client); | ||||||
|   App.feed_wdt(); |   container->feed_wdt(); | ||||||
|   if (is_success(container->status_code)) { |   if (is_success(container->status_code)) { | ||||||
|     container->duration_ms = millis() - start; |     container->duration_ms = millis() - start; | ||||||
|     return container; |     return container; | ||||||
| @@ -151,11 +153,11 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::start(std::string url, std::strin | |||||||
|         return nullptr; |         return nullptr; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       App.feed_wdt(); |       container->feed_wdt(); | ||||||
|       container->content_length = esp_http_client_fetch_headers(client); |       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); |       container->status_code = esp_http_client_get_status_code(client); | ||||||
|       App.feed_wdt(); |       container->feed_wdt(); | ||||||
|       if (is_success(container->status_code)) { |       if (is_success(container->status_code)) { | ||||||
|         container->duration_ms = millis() - start; |         container->duration_ms = millis() - start; | ||||||
|         return container; |         return container; | ||||||
| @@ -185,8 +187,9 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) { | |||||||
|     return 0; |     return 0; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   App.feed_wdt(); |   this->feed_wdt(); | ||||||
|   int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize); |   int read_len = esp_http_client_read(this->client_, (char *) buf, bufsize); | ||||||
|  |   this->feed_wdt(); | ||||||
|   this->bytes_read_ += read_len; |   this->bytes_read_ += read_len; | ||||||
|  |  | ||||||
|   this->duration_ms += (millis() - start); |   this->duration_ms += (millis() - start); | ||||||
| @@ -201,6 +204,13 @@ void HttpContainerIDF::end() { | |||||||
|   esp_http_client_cleanup(this->client_); |   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 http_request | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,6 +18,9 @@ class HttpContainerIDF : public HttpContainer { | |||||||
|   int read(uint8_t *buf, size_t max_len) override; |   int read(uint8_t *buf, size_t max_len) override; | ||||||
|   void end() override; |   void end() override; | ||||||
|  |  | ||||||
|  |   /// @brief Feeds the watchdog timer if the executing task has one attached | ||||||
|  |   void feed_wdt(); | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   esp_http_client_handle_t client_; |   esp_http_client_handle_t client_; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -9,6 +9,13 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace http_request { | 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 char *const TAG = "http_request.update"; | ||||||
|  |  | ||||||
| static const size_t MAX_READ_SIZE = 256; | static const size_t MAX_READ_SIZE = 256; | ||||||
| @@ -29,113 +36,131 @@ void HttpRequestUpdate::setup() { | |||||||
| } | } | ||||||
|  |  | ||||||
| void HttpRequestUpdate::update() { | 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) { |   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()); |     std::string msg = str_sprintf("Failed to fetch manifest from %s", this_update->source_url_.c_str()); | ||||||
|     this->status_set_error(msg.c_str()); |     this_update->status_set_error(msg.c_str()); | ||||||
|     return; |     UPDATE_RETURN; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); |   ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); | ||||||
|   uint8_t *data = allocator.allocate(container->content_length); |   uint8_t *data = allocator.allocate(container->content_length); | ||||||
|   if (data == nullptr) { |   if (data == nullptr) { | ||||||
|     std::string msg = str_sprintf("Failed to allocate %d bytes for manifest", container->content_length); |     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(); |     container->end(); | ||||||
|     return; |     UPDATE_RETURN; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   size_t read_index = 0; |   size_t read_index = 0; | ||||||
|   while (container->get_bytes_read() < container->content_length) { |   while (container->get_bytes_read() < container->content_length) { | ||||||
|     int read_bytes = container->read(data + read_index, MAX_READ_SIZE); |     int read_bytes = container->read(data + read_index, MAX_READ_SIZE); | ||||||
|  |  | ||||||
|     App.feed_wdt(); |  | ||||||
|     yield(); |     yield(); | ||||||
|  |  | ||||||
|     read_index += read_bytes; |     read_index += read_bytes; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   std::string response((char *) data, read_index); |   bool valid = false; | ||||||
|   allocator.deallocate(data, container->content_length); |   {  // 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 { |     valid = json::parse_json(response, [this_update](JsonObject root) -> bool { | ||||||
|     if (!root.containsKey("name") || !root.containsKey("version") || !root.containsKey("builds")) { |       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<std::string>(); |  | ||||||
|     this->update_info_.latest_version = root["version"].as<std::string>(); |  | ||||||
|  |  | ||||||
|     for (auto build : root["builds"].as<JsonArray>()) { |  | ||||||
|       if (!build.containsKey("chipFamily")) { |  | ||||||
|         ESP_LOGE(TAG, "Manifest does not contain required fields"); |         ESP_LOGE(TAG, "Manifest does not contain required fields"); | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       if (build["chipFamily"] == ESPHOME_VARIANT) { |       this_update->update_info_.title = root["name"].as<std::string>(); | ||||||
|         if (!build.containsKey("ota")) { |       this_update->update_info_.latest_version = root["version"].as<std::string>(); | ||||||
|  |  | ||||||
|  |       for (auto build : root["builds"].as<JsonArray>()) { | ||||||
|  |         if (!build.containsKey("chipFamily")) { | ||||||
|           ESP_LOGE(TAG, "Manifest does not contain required fields"); |           ESP_LOGE(TAG, "Manifest does not contain required fields"); | ||||||
|           return false; |           return false; | ||||||
|         } |         } | ||||||
|         auto ota = build["ota"]; |         if (build["chipFamily"] == ESPHOME_VARIANT) { | ||||||
|         if (!ota.containsKey("path") || !ota.containsKey("md5")) { |           if (!build.containsKey("ota")) { | ||||||
|           ESP_LOGE(TAG, "Manifest does not contain required fields"); |             ESP_LOGE(TAG, "Manifest does not contain required fields"); | ||||||
|           return false; |             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<std::string>(); | ||||||
|  |           this_update->update_info_.md5 = ota["md5"].as<std::string>(); | ||||||
|  |  | ||||||
|  |           if (ota.containsKey("summary")) | ||||||
|  |             this_update->update_info_.summary = ota["summary"].as<std::string>(); | ||||||
|  |           if (ota.containsKey("release_url")) | ||||||
|  |             this_update->update_info_.release_url = ota["release_url"].as<std::string>(); | ||||||
|  |  | ||||||
|  |           return true; | ||||||
|         } |         } | ||||||
|         this->update_info_.firmware_url = ota["path"].as<std::string>(); |  | ||||||
|         this->update_info_.md5 = ota["md5"].as<std::string>(); |  | ||||||
|  |  | ||||||
|         if (ota.containsKey("summary")) |  | ||||||
|           this->update_info_.summary = ota["summary"].as<std::string>(); |  | ||||||
|         if (ota.containsKey("release_url")) |  | ||||||
|           this->update_info_.release_url = ota["release_url"].as<std::string>(); |  | ||||||
|  |  | ||||||
|         return true; |  | ||||||
|       } |       } | ||||||
|     } |       return false; | ||||||
|     return false; |     }); | ||||||
|   }); |   } | ||||||
|  |  | ||||||
|   if (!valid) { |   if (!valid) { | ||||||
|     std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str()); |     std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str()); | ||||||
|     this->status_set_error(msg.c_str()); |     this_update->status_set_error(msg.c_str()); | ||||||
|     return; |     UPDATE_RETURN; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Merge source_url_ and this->update_info_.firmware_url |   // Merge source_url_ and this_update->update_info_.firmware_url | ||||||
|   if (this->update_info_.firmware_url.find("http") == std::string::npos) { |   if (this_update->update_info_.firmware_url.find("http") == std::string::npos) { | ||||||
|     std::string path = this->update_info_.firmware_url; |     std::string path = this_update->update_info_.firmware_url; | ||||||
|     if (path[0] == '/') { |     if (path[0] == '/') { | ||||||
|       std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8)); |       std::string domain = this_update->source_url_.substr(0, this_update->source_url_.find('/', 8)); | ||||||
|       this->update_info_.firmware_url = domain + path; |       this_update->update_info_.firmware_url = domain + path; | ||||||
|     } else { |     } else { | ||||||
|       std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1); |       std::string domain = this_update->source_url_.substr(0, this_update->source_url_.rfind('/') + 1); | ||||||
|       this->update_info_.firmware_url = domain + path; |       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 | #ifdef ESPHOME_PROJECT_VERSION | ||||||
|   current_version = ESPHOME_PROJECT_VERSION; |     current_version = ESPHOME_PROJECT_VERSION; | ||||||
| #else | #else | ||||||
|   current_version = ESPHOME_VERSION; |     current_version = ESPHOME_VERSION; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   this->update_info_.current_version = current_version; |     this_update->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_info_.has_progress = false; |   if (this_update->update_info_.latest_version.empty() || | ||||||
|   this->update_info_.progress = 0.0f; |       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_update->update_info_.has_progress = false; | ||||||
|   this->publish_state(); |   this_update->update_info_.progress = 0.0f; | ||||||
|  |  | ||||||
|  |   this_update->status_clear_error(); | ||||||
|  |   this_update->publish_state(); | ||||||
|  |  | ||||||
|  |   UPDATE_RETURN; | ||||||
| } | } | ||||||
|  |  | ||||||
| void HttpRequestUpdate::perform(bool force) { | void HttpRequestUpdate::perform(bool force) { | ||||||
|   | |||||||
| @@ -7,6 +7,10 @@ | |||||||
| #include "esphome/components/http_request/ota/ota_http_request.h" | #include "esphome/components/http_request/ota/ota_http_request.h" | ||||||
| #include "esphome/components/update/update_entity.h" | #include "esphome/components/update/update_entity.h" | ||||||
|  |  | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  | #include <freertos/FreeRTOS.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace http_request { | namespace http_request { | ||||||
|  |  | ||||||
| @@ -29,6 +33,11 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent { | |||||||
|   HttpRequestComponent *request_parent_; |   HttpRequestComponent *request_parent_; | ||||||
|   OtaHttpRequestComponent *ota_parent_; |   OtaHttpRequestComponent *ota_parent_; | ||||||
|   std::string source_url_; |   std::string source_url_; | ||||||
|  |  | ||||||
|  |   static void update_task(void *params); | ||||||
|  | #ifdef USE_ESP32 | ||||||
|  |   TaskHandle_t update_task_handle_{nullptr}; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace http_request | }  // namespace http_request | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user