[binary_sensor] Add action to invalidate state and pass to HA (#8961)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Clyde Stubbs
2025-06-12 09:16:20 +10:00
committed by GitHub
co-authored by J. Nick Koston
parent 0228379a2e
commit 261b561bb2
27 changed files with 250 additions and 115 deletions
+4 -2
View File
@@ -7,8 +7,10 @@ namespace esphome {
void Controller::setup_controller(bool include_internal) {
#ifdef USE_BINARY_SENSOR
for (auto *obj : App.get_binary_sensors()) {
if (include_internal || !obj->is_internal())
obj->add_on_state_callback([this, obj](bool state) { this->on_binary_sensor_update(obj, state); });
if (include_internal || !obj->is_internal()) {
obj->add_full_state_callback(
[this, obj](optional<bool> previous, optional<bool> state) { this->on_binary_sensor_update(obj); });
}
}
#endif
#ifdef USE_FAN
+1 -1
View File
@@ -71,7 +71,7 @@ class Controller {
public:
void setup_controller(bool include_internal = false);
#ifdef USE_BINARY_SENSOR
virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){};
virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj){};
#endif
#ifdef USE_FAN
virtual void on_fan_update(fan::Fan *obj){};
+59 -2
View File
@@ -3,6 +3,8 @@
#include <string>
#include <cstdint>
#include "string_ref.h"
#include "helpers.h"
#include "log.h"
namespace esphome {
@@ -29,7 +31,7 @@ class EntityBase {
// Get the unique Object ID of this Entity
uint32_t get_object_id_hash();
// Get/set whether this Entity should be hidden from outside of ESPHome
// Get/set whether this Entity should be hidden outside ESPHome
bool is_internal() const;
void set_internal(bool internal);
@@ -56,11 +58,12 @@ class EntityBase {
StringRef name_;
const char *object_id_c_str_{nullptr};
const char *icon_c_str_{nullptr};
uint32_t object_id_hash_;
uint32_t object_id_hash_{};
bool has_own_name_{false};
bool internal_{false};
bool disabled_by_default_{false};
EntityCategory entity_category_{ENTITY_CATEGORY_NONE};
bool has_state_{};
};
class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming)
@@ -85,4 +88,58 @@ class EntityBase_UnitOfMeasurement { // NOLINT(readability-identifier-naming)
const char *unit_of_measurement_{nullptr}; ///< Unit of measurement override
};
/**
* An entity that has a state.
* @tparam T The type of the state
*/
template<typename T> class StatefulEntityBase : public EntityBase {
public:
virtual bool has_state() const { return this->state_.has_value(); }
virtual const T &get_state() const { return this->state_.value(); }
virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); }
void invalidate_state() { this->set_state_({}); }
void add_full_state_callback(std::function<void(optional<T> previous, optional<T> current)> &&callback) {
if (this->full_state_callbacks_ == nullptr)
this->full_state_callbacks_ = new CallbackManager<void(optional<T> previous, optional<T> current)>(); // NOLINT
this->full_state_callbacks_->add(std::move(callback));
}
void add_on_state_callback(std::function<void(T)> &&callback) {
if (this->state_callbacks_ == nullptr)
this->state_callbacks_ = new CallbackManager<void(T)>(); // NOLINT
this->state_callbacks_->add(std::move(callback));
}
void set_trigger_on_initial_state(bool trigger_on_initial_state) {
this->trigger_on_initial_state_ = trigger_on_initial_state;
}
protected:
optional<T> state_{};
/**
* Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous.
*
* @param state The new state.
* @return True if the state was changed, false if it was the same as before.
*/
bool set_state_(const optional<T> &state) {
if (this->state_ != state) {
// call the full state callbacks with the previous and new state
if (this->full_state_callbacks_ != nullptr)
this->full_state_callbacks_->call(this->state_, state);
// trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or
// the previous state was valid
auto had_state = this->has_state();
this->state_ = state;
if (this->state_callbacks_ != nullptr && state.has_value() && (this->trigger_on_initial_state_ || had_state))
this->state_callbacks_->call(state.value());
return true;
}
return false;
}
bool trigger_on_initial_state_{true};
// callbacks with full state and previous state
CallbackManager<void(optional<T> previous, optional<T> current)> *full_state_callbacks_{};
CallbackManager<void(T)> *state_callbacks_{};
};
} // namespace esphome
+2
View File
@@ -165,6 +165,8 @@ int esp_idf_log_vprintf_(const char *format, va_list args); // NOLINT
#define YESNO(b) ((b) ? "YES" : "NO")
#define ONOFF(b) ((b) ? "ON" : "OFF")
#define TRUEFALSE(b) ((b) ? "TRUE" : "FALSE")
// for use with optional values
#define ONOFFMAYBE(b) (((b).has_value()) ? ONOFF((b).value()) : "UNKNOWN")
// Helper class that identifies strings that may be stored in flash storage (similar to Arduino's __FlashStringHelper)
struct LogString;
+5
View File
@@ -52,6 +52,11 @@ template<typename T> class optional { // NOLINT
reset();
return *this;
}
bool operator==(optional<T> const &rhs) const {
if (has_value() && rhs.has_value())
return value() == rhs.value();
return !has_value() && !rhs.has_value();
}
template<class U> optional &operator=(optional<U> const &other) {
has_value_ = other.has_value();