diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index c4d3a29a1e5..35d7b3e430a 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -33,9 +33,7 @@ void BinarySensor::publish_initial_state(bool new_state) { this->publish_state(new_state); } void BinarySensor::send_state_internal(bool new_state) { - // copy the new state to the visible property for backwards compatibility, before any callbacks - this->state = new_state; - // Note that set_new_state_ de-dups and will only trigger callbacks if the state has actually changed + // set_new_state handles de-duplication, updating this->state via set_state_value_(), and triggering callbacks this->set_new_state(new_state); } diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 6ae5d04bcbf..8029ff7fba3 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -32,7 +32,9 @@ void log_binary_sensor(const char *tag, const char *prefix, const char *type, Bi */ class BinarySensor : public StatefulEntityBase { public: - explicit BinarySensor(){}; + explicit BinarySensor() { this->flags_.trigger_on_initial_state = true; } + + const bool &get_state() const override { return this->state; } /** Publish a new state to the front-end. * @@ -59,11 +61,11 @@ class BinarySensor : public StatefulEntityBase { /// Return whether this binary sensor has outputted a state. virtual bool is_status_binary_sensor() const; - // For backward compatibility, provide an accessible property - bool state{}; protected: + void set_state_value_(const bool &value) override { this->state = value; } + #ifdef USE_BINARY_SENSOR_FILTER Filter *filter_list_{nullptr}; #endif @@ -73,7 +75,7 @@ class BinarySensor : public StatefulEntityBase { class BinarySensorInitiallyOff : public BinarySensor { public: - bool has_state() const override { return true; } + BinarySensorInitiallyOff() { this->set_has_state(true); } }; } // namespace esphome::binary_sensor diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 8c1f1a213e5..f3ebeb202d2 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -266,8 +266,9 @@ class EntityBase { uint8_t internal : 1; uint8_t disabled_by_default : 1; uint8_t has_state : 1; - uint8_t entity_category : 2; // Supports up to 4 categories - uint8_t reserved : 2; // Reserved for future use + uint8_t entity_category : 2; // Supports up to 4 categories + uint8_t trigger_on_initial_state : 1; // Whether callbacks fire on first state (StatefulEntityBase) + uint8_t reserved : 1; // Reserved for future use } flags_{}; // String table indices — packed into the 3 padding bytes after flags_ #ifdef USE_ENTITY_DEVICE_CLASS @@ -302,9 +303,8 @@ void log_entity_unit_of_measurement(const char *tag, const char *prefix, const E */ template 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(); } // NOLINT(bugprone-unchecked-optional-access) - virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); } + virtual const T &get_state() const = 0; + T get_state_default(T default_value) const { return this->has_state() ? this->get_state() : default_value; } void invalidate_state() { this->set_new_state({}); } template void add_full_state_callback(F &&callback) { @@ -315,11 +315,10 @@ template class StatefulEntityBase : public EntityBase { } void set_trigger_on_initial_state(bool trigger_on_initial_state) { - this->trigger_on_initial_state_ = trigger_on_initial_state; + this->flags_.trigger_on_initial_state = trigger_on_initial_state; } protected: - optional state_{}; /** * Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous. * @@ -327,20 +326,25 @@ template class StatefulEntityBase : public EntityBase { * @return True if the state was changed, false if it was the same as before. */ virtual bool set_new_state(const optional &new_state) { - if (this->state_ != new_state) { + optional old_state = this->has_state() ? optional(this->get_state()) : nullopt; + if (old_state != new_state) { // call the full state callbacks with the previous and new state - this->full_state_callbacks_.call(this->state_, new_state); + this->full_state_callbacks_.call(old_state, new_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_ = new_state; - if (new_state.has_value() && (this->trigger_on_initial_state_ || had_state)) - this->state_callbacks_.call(new_state.value()); + this->set_has_state(new_state.has_value()); + if (new_state.has_value()) { + this->set_state_value_(new_state.value()); + if (this->flags_.trigger_on_initial_state || had_state) + this->state_callbacks_.call(new_state.value()); + } return true; } return false; } - bool trigger_on_initial_state_{true}; + /// Subclasses implement this to store the actual value. + virtual void set_state_value_(const T &value) = 0; LazyCallbackManager previous, optional current)> full_state_callbacks_; LazyCallbackManager state_callbacks_; };