mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge remote-tracking branch 'origin/dev' into heap_scheduler_stress_component
This commit is contained in:
		| @@ -50,7 +50,8 @@ void HttpRequestUpdate::update_task(void *params) { | ||||
|  | ||||
|   if (container == nullptr || container->status_code != HTTP_STATUS_OK) { | ||||
|     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()); | ||||
|     // Defer to main loop to avoid race condition on component_state_ read-modify-write | ||||
|     this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); | ||||
|     UPDATE_RETURN; | ||||
|   } | ||||
|  | ||||
| @@ -58,7 +59,8 @@ void HttpRequestUpdate::update_task(void *params) { | ||||
|   uint8_t *data = allocator.allocate(container->content_length); | ||||
|   if (data == nullptr) { | ||||
|     std::string msg = str_sprintf("Failed to allocate %zu bytes for manifest", container->content_length); | ||||
|     this_update->status_set_error(msg.c_str()); | ||||
|     // Defer to main loop to avoid race condition on component_state_ read-modify-write | ||||
|     this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); | ||||
|     container->end(); | ||||
|     UPDATE_RETURN; | ||||
|   } | ||||
| @@ -120,7 +122,8 @@ void HttpRequestUpdate::update_task(void *params) { | ||||
|  | ||||
|   if (!valid) { | ||||
|     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()); | ||||
|     // Defer to main loop to avoid race condition on component_state_ read-modify-write | ||||
|     this_update->defer([this_update, msg]() { this_update->status_set_error(msg.c_str()); }); | ||||
|     UPDATE_RETURN; | ||||
|   } | ||||
|  | ||||
| @@ -147,18 +150,34 @@ void HttpRequestUpdate::update_task(void *params) { | ||||
|     this_update->update_info_.current_version = current_version; | ||||
|   } | ||||
|  | ||||
|   bool trigger_update_available = false; | ||||
|  | ||||
|   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 { | ||||
|     if (this_update->state_ != update::UPDATE_STATE_AVAILABLE) { | ||||
|       trigger_update_available = true; | ||||
|     } | ||||
|     this_update->state_ = update::UPDATE_STATE_AVAILABLE; | ||||
|   } | ||||
|  | ||||
|   this_update->update_info_.has_progress = false; | ||||
|   this_update->update_info_.progress = 0.0f; | ||||
|   // Defer to main loop to ensure thread-safe execution of: | ||||
|   // - status_clear_error() performs non-atomic read-modify-write on component_state_ | ||||
|   // - publish_state() triggers API callbacks that write to the shared protobuf buffer | ||||
|   //   which can be corrupted if accessed concurrently from task and main loop threads | ||||
|   // - update_available trigger to ensure consistent state when the trigger fires | ||||
|   this_update->defer([this_update, trigger_update_available]() { | ||||
|     this_update->update_info_.has_progress = false; | ||||
|     this_update->update_info_.progress = 0.0f; | ||||
|  | ||||
|   this_update->status_clear_error(); | ||||
|   this_update->publish_state(); | ||||
|     this_update->status_clear_error(); | ||||
|     this_update->publish_state(); | ||||
|  | ||||
|     if (trigger_update_available) { | ||||
|       this_update->get_update_available_trigger()->trigger(this_update->update_info_); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   UPDATE_RETURN; | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <memory> | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/entity_base.h" | ||||
| @@ -38,12 +39,19 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass { | ||||
|   const UpdateState &state = state_; | ||||
|  | ||||
|   void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); } | ||||
|   Trigger<const UpdateInfo &> *get_update_available_trigger() { | ||||
|     if (!update_available_trigger_) { | ||||
|       update_available_trigger_ = std::make_unique<Trigger<const UpdateInfo &>>(); | ||||
|     } | ||||
|     return update_available_trigger_.get(); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   UpdateState state_{UPDATE_STATE_UNKNOWN}; | ||||
|   UpdateInfo update_info_; | ||||
|  | ||||
|   CallbackManager<void()> state_callback_{}; | ||||
|   std::unique_ptr<Trigger<const UpdateInfo &>> update_available_trigger_{nullptr}; | ||||
| }; | ||||
|  | ||||
| }  // namespace update | ||||
|   | ||||
| @@ -91,3 +91,5 @@ update: | ||||
|     name: OTA Update | ||||
|     id: ota_update | ||||
|     source: http://my.ha.net:8123/local/esphome/manifest.json | ||||
|     on_update_available: | ||||
|       - logger.log: "A new update is available" | ||||
|   | ||||
| @@ -26,3 +26,5 @@ update: | ||||
|   - platform: http_request | ||||
|     name: Firmware Update | ||||
|     source: http://example.com/manifest.json | ||||
|     on_update_available: | ||||
|       - logger.log: "A new update is available" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user