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) { |   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()); |     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; |     UPDATE_RETURN; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -58,7 +59,8 @@ void HttpRequestUpdate::update_task(void *params) { | |||||||
|   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 %zu bytes for manifest", container->content_length); |     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(); |     container->end(); | ||||||
|     UPDATE_RETURN; |     UPDATE_RETURN; | ||||||
|   } |   } | ||||||
| @@ -120,7 +122,8 @@ void HttpRequestUpdate::update_task(void *params) { | |||||||
|  |  | ||||||
|   if (!valid) { |   if (!valid) { | ||||||
|     std::string msg = str_sprintf("Failed to parse JSON from %s", this_update->source_url_.c_str()); |     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; |     UPDATE_RETURN; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -147,18 +150,34 @@ void HttpRequestUpdate::update_task(void *params) { | |||||||
|     this_update->update_info_.current_version = current_version; |     this_update->update_info_.current_version = current_version; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   bool trigger_update_available = false; | ||||||
|  |  | ||||||
|   if (this_update->update_info_.latest_version.empty() || |   if (this_update->update_info_.latest_version.empty() || | ||||||
|       this_update->update_info_.latest_version == this_update->update_info_.current_version) { |       this_update->update_info_.latest_version == this_update->update_info_.current_version) { | ||||||
|     this_update->state_ = update::UPDATE_STATE_NO_UPDATE; |     this_update->state_ = update::UPDATE_STATE_NO_UPDATE; | ||||||
|   } else { |   } else { | ||||||
|  |     if (this_update->state_ != update::UPDATE_STATE_AVAILABLE) { | ||||||
|  |       trigger_update_available = true; | ||||||
|  |     } | ||||||
|     this_update->state_ = update::UPDATE_STATE_AVAILABLE; |     this_update->state_ = update::UPDATE_STATE_AVAILABLE; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   this_update->update_info_.has_progress = false; |   // Defer to main loop to ensure thread-safe execution of: | ||||||
|   this_update->update_info_.progress = 0.0f; |   // - 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->status_clear_error(); | ||||||
|   this_update->publish_state(); |     this_update->publish_state(); | ||||||
|  |  | ||||||
|  |     if (trigger_update_available) { | ||||||
|  |       this_update->get_update_available_trigger()->trigger(this_update->update_info_); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  |  | ||||||
|   UPDATE_RETURN; |   UPDATE_RETURN; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
| #include "esphome/core/automation.h" | #include "esphome/core/automation.h" | ||||||
| #include "esphome/core/component.h" | #include "esphome/core/component.h" | ||||||
| #include "esphome/core/entity_base.h" | #include "esphome/core/entity_base.h" | ||||||
| @@ -38,12 +39,19 @@ class UpdateEntity : public EntityBase, public EntityBase_DeviceClass { | |||||||
|   const UpdateState &state = state_; |   const UpdateState &state = state_; | ||||||
|  |  | ||||||
|   void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); } |   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: |  protected: | ||||||
|   UpdateState state_{UPDATE_STATE_UNKNOWN}; |   UpdateState state_{UPDATE_STATE_UNKNOWN}; | ||||||
|   UpdateInfo update_info_; |   UpdateInfo update_info_; | ||||||
|  |  | ||||||
|   CallbackManager<void()> state_callback_{}; |   CallbackManager<void()> state_callback_{}; | ||||||
|  |   std::unique_ptr<Trigger<const UpdateInfo &>> update_available_trigger_{nullptr}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace update | }  // namespace update | ||||||
|   | |||||||
| @@ -91,3 +91,5 @@ update: | |||||||
|     name: OTA Update |     name: OTA Update | ||||||
|     id: ota_update |     id: ota_update | ||||||
|     source: http://my.ha.net:8123/local/esphome/manifest.json |     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 |   - platform: http_request | ||||||
|     name: Firmware Update |     name: Firmware Update | ||||||
|     source: http://example.com/manifest.json |     source: http://example.com/manifest.json | ||||||
|  |     on_update_available: | ||||||
|  |       - logger.log: "A new update is available" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user