[update, http_request_update] Implement update available trigger (#9174)

This commit is contained in:
Jan-Henrik Bruhn 2025-07-07 02:36:09 +02:00 committed by GitHub
parent b6fade7339
commit 1368139f4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 38 additions and 7 deletions

View File

@ -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,19 +150,35 @@ 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;
}
// 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();
if (trigger_update_available) {
this_update->get_update_available_trigger()->trigger(this_update->update_info_);
}
});
UPDATE_RETURN;
}

View File

@ -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

View File

@ -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"

View File

@ -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"