mirror of
https://github.com/esphome/esphome.git
synced 2026-02-21 08:55:36 -07:00
Merge branch 'light_lazy_callbacks' into integration
This commit is contained in:
@@ -120,46 +120,54 @@ template<typename... Ts> class LightIsOffCondition : public Condition<Ts...> {
|
||||
LightState *state_;
|
||||
};
|
||||
|
||||
class LightTurnOnTrigger : public Trigger<> {
|
||||
class LightTurnOnTrigger : public Trigger<>, public LightRemoteValuesListener {
|
||||
public:
|
||||
LightTurnOnTrigger(LightState *a_light) {
|
||||
a_light->add_new_remote_values_callback([this, a_light]() {
|
||||
// using the remote value because of transitions we need to trigger as early as possible
|
||||
auto is_on = a_light->remote_values.is_on();
|
||||
// only trigger when going from off to on
|
||||
auto should_trigger = is_on && !this->last_on_;
|
||||
// Set new state immediately so that trigger() doesn't devolve
|
||||
// into infinite loop
|
||||
this->last_on_ = is_on;
|
||||
if (should_trigger) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
explicit LightTurnOnTrigger(LightState *a_light) : light_(a_light) {
|
||||
a_light->add_remote_values_listener(this);
|
||||
this->last_on_ = a_light->current_values.is_on();
|
||||
}
|
||||
|
||||
void on_light_remote_values_update() override {
|
||||
// using the remote value because of transitions we need to trigger as early as possible
|
||||
auto is_on = this->light_->remote_values.is_on();
|
||||
// only trigger when going from off to on
|
||||
auto should_trigger = is_on && !this->last_on_;
|
||||
// Set new state immediately so that trigger() doesn't devolve
|
||||
// into infinite loop
|
||||
this->last_on_ = is_on;
|
||||
if (should_trigger) {
|
||||
this->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
LightState *light_;
|
||||
bool last_on_;
|
||||
};
|
||||
|
||||
class LightTurnOffTrigger : public Trigger<> {
|
||||
class LightTurnOffTrigger : public Trigger<>, public LightTargetStateReachedListener {
|
||||
public:
|
||||
LightTurnOffTrigger(LightState *a_light) {
|
||||
a_light->add_new_target_state_reached_callback([this, a_light]() {
|
||||
auto is_on = a_light->current_values.is_on();
|
||||
// only trigger when going from on to off
|
||||
if (!is_on) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
explicit LightTurnOffTrigger(LightState *a_light) : light_(a_light) {
|
||||
a_light->add_target_state_reached_listener(this);
|
||||
}
|
||||
|
||||
void on_light_target_state_reached() override {
|
||||
auto is_on = this->light_->current_values.is_on();
|
||||
// only trigger when going from on to off
|
||||
if (!is_on) {
|
||||
this->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
LightState *light_;
|
||||
};
|
||||
|
||||
class LightStateTrigger : public Trigger<> {
|
||||
class LightStateTrigger : public Trigger<>, public LightRemoteValuesListener {
|
||||
public:
|
||||
LightStateTrigger(LightState *a_light) {
|
||||
a_light->add_new_remote_values_callback([this]() { this->trigger(); });
|
||||
}
|
||||
explicit LightStateTrigger(LightState *a_light) { a_light->add_remote_values_listener(this); }
|
||||
|
||||
void on_light_remote_values_update() override { this->trigger(); }
|
||||
};
|
||||
|
||||
// This is slightly ugly, but we can't log in headers, and can't make this a static method on AddressableSet
|
||||
|
||||
@@ -174,8 +174,10 @@ void LightCall::perform() {
|
||||
this->parent_->set_immediately_(v, publish);
|
||||
}
|
||||
|
||||
if (!this->has_transition_()) {
|
||||
this->parent_->target_state_reached_callback_.call();
|
||||
if (!this->has_transition_() && this->parent_->target_state_reached_listeners_) {
|
||||
for (auto *listener : *this->parent_->target_state_reached_listeners_) {
|
||||
listener->on_light_target_state_reached();
|
||||
}
|
||||
}
|
||||
if (publish) {
|
||||
this->parent_->publish_state();
|
||||
|
||||
@@ -127,7 +127,11 @@ void LightState::loop() {
|
||||
this->transformer_->stop();
|
||||
this->is_transformer_active_ = false;
|
||||
this->transformer_ = nullptr;
|
||||
this->target_state_reached_callback_.call();
|
||||
if (this->target_state_reached_listeners_) {
|
||||
for (auto *listener : *this->target_state_reached_listeners_) {
|
||||
listener->on_light_target_state_reached();
|
||||
}
|
||||
}
|
||||
|
||||
// Disable loop if idle (no transformer and no effect)
|
||||
this->disable_loop_if_idle_();
|
||||
@@ -146,7 +150,11 @@ void LightState::loop() {
|
||||
float LightState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
|
||||
|
||||
void LightState::publish_state() {
|
||||
this->remote_values_callback_.call();
|
||||
if (this->remote_values_listeners_) {
|
||||
for (auto *listener : *this->remote_values_listeners_) {
|
||||
listener->on_light_remote_values_update();
|
||||
}
|
||||
}
|
||||
#if defined(USE_LIGHT) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_light_update(this);
|
||||
#endif
|
||||
@@ -171,11 +179,17 @@ StringRef LightState::get_effect_name_ref() {
|
||||
return EFFECT_NONE_REF;
|
||||
}
|
||||
|
||||
void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) {
|
||||
this->remote_values_callback_.add(std::move(send_callback));
|
||||
void LightState::add_remote_values_listener(LightRemoteValuesListener *listener) {
|
||||
if (!this->remote_values_listeners_) {
|
||||
this->remote_values_listeners_ = make_unique<std::vector<LightRemoteValuesListener *>>();
|
||||
}
|
||||
this->remote_values_listeners_->push_back(listener);
|
||||
}
|
||||
void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) {
|
||||
this->target_state_reached_callback_.add(std::move(send_callback));
|
||||
void LightState::add_target_state_reached_listener(LightTargetStateReachedListener *listener) {
|
||||
if (!this->target_state_reached_listeners_) {
|
||||
this->target_state_reached_listeners_ = make_unique<std::vector<LightTargetStateReachedListener *>>();
|
||||
}
|
||||
this->target_state_reached_listeners_->push_back(listener);
|
||||
}
|
||||
|
||||
void LightState::set_default_transition_length(uint32_t default_transition_length) {
|
||||
|
||||
@@ -18,6 +18,29 @@
|
||||
namespace esphome::light {
|
||||
|
||||
class LightOutput;
|
||||
class LightState;
|
||||
|
||||
/** Listener interface for light remote value changes.
|
||||
*
|
||||
* Components can implement this interface to receive notifications
|
||||
* when the light's remote values change (state, brightness, color, etc.)
|
||||
* without the overhead of std::function callbacks.
|
||||
*/
|
||||
class LightRemoteValuesListener {
|
||||
public:
|
||||
virtual void on_light_remote_values_update() = 0;
|
||||
};
|
||||
|
||||
/** Listener interface for light target state reached.
|
||||
*
|
||||
* Components can implement this interface to receive notifications
|
||||
* when the light finishes a transition and reaches its target state
|
||||
* without the overhead of std::function callbacks.
|
||||
*/
|
||||
class LightTargetStateReachedListener {
|
||||
public:
|
||||
virtual void on_light_target_state_reached() = 0;
|
||||
};
|
||||
|
||||
enum LightRestoreMode : uint8_t {
|
||||
LIGHT_RESTORE_DEFAULT_OFF,
|
||||
@@ -121,21 +144,17 @@ class LightState : public EntityBase, public Component {
|
||||
/// Return the name of the current effect as StringRef (for API usage)
|
||||
StringRef get_effect_name_ref();
|
||||
|
||||
/**
|
||||
* This lets front-end components subscribe to light change events. This callback is called once
|
||||
* when the remote color values are changed.
|
||||
*
|
||||
* @param send_callback The callback.
|
||||
/** Add a listener for remote values changes.
|
||||
* Listener is notified when the light's remote values change (state, brightness, color, etc.)
|
||||
* Lazily allocates the listener vector on first registration.
|
||||
*/
|
||||
void add_new_remote_values_callback(std::function<void()> &&send_callback);
|
||||
void add_remote_values_listener(LightRemoteValuesListener *listener);
|
||||
|
||||
/**
|
||||
* The callback is called once the state of current_values and remote_values are equal (when the
|
||||
* transition is finished).
|
||||
*
|
||||
* @param send_callback
|
||||
/** Add a listener for target state reached.
|
||||
* Listener is notified when the light finishes a transition and reaches its target state.
|
||||
* Lazily allocates the listener vector on first registration.
|
||||
*/
|
||||
void add_new_target_state_reached_callback(std::function<void()> &&send_callback);
|
||||
void add_target_state_reached_listener(LightTargetStateReachedListener *listener);
|
||||
|
||||
/// Set the default transition length, i.e. the transition length when no transition is provided.
|
||||
void set_default_transition_length(uint32_t default_transition_length);
|
||||
@@ -279,19 +298,24 @@ class LightState : public EntityBase, public Component {
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_ = false;
|
||||
|
||||
/** Callback to call when new values for the frontend are available.
|
||||
/** Listeners for remote values changes.
|
||||
*
|
||||
* "Remote values" are light color values that are reported to the frontend and have a lower
|
||||
* publish frequency than the "real" color values. For example, during transitions the current
|
||||
* color value may change continuously, but the remote values will be reported as the target values
|
||||
* starting with the beginning of the transition.
|
||||
*
|
||||
* Lazily allocated - only created when a listener is actually registered.
|
||||
*/
|
||||
CallbackManager<void()> remote_values_callback_{};
|
||||
std::unique_ptr<std::vector<LightRemoteValuesListener *>> remote_values_listeners_;
|
||||
|
||||
/** Callback to call when the state of current_values and remote_values are equal
|
||||
* This should be called once the state of current_values changed and equals the state of remote_values
|
||||
/** Listeners for target state reached.
|
||||
* Notified when the state of current_values and remote_values are equal
|
||||
* (when the transition is finished).
|
||||
*
|
||||
* Lazily allocated - only created when a listener is actually registered.
|
||||
*/
|
||||
CallbackManager<void()> target_state_reached_callback_{};
|
||||
std::unique_ptr<std::vector<LightTargetStateReachedListener *>> target_state_reached_listeners_;
|
||||
|
||||
/// Initial state of the light.
|
||||
optional<LightStateRTCState> initial_state_{};
|
||||
|
||||
@@ -25,8 +25,11 @@ void MQTTJSONLightComponent::setup() {
|
||||
call.perform();
|
||||
});
|
||||
|
||||
auto f = std::bind(&MQTTJSONLightComponent::publish_state_, this);
|
||||
this->state_->add_new_remote_values_callback([this, f]() { this->defer("send", f); });
|
||||
this->state_->add_remote_values_listener(this);
|
||||
}
|
||||
|
||||
void MQTTJSONLightComponent::on_light_remote_values_update() {
|
||||
this->defer("send", [this]() { this->publish_state_(); });
|
||||
}
|
||||
|
||||
MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTJSONLightComponent : public mqtt::MQTTComponent {
|
||||
class MQTTJSONLightComponent : public mqtt::MQTTComponent, public light::LightRemoteValuesListener {
|
||||
public:
|
||||
explicit MQTTJSONLightComponent(light::LightState *state);
|
||||
|
||||
@@ -25,6 +25,9 @@ class MQTTJSONLightComponent : public mqtt::MQTTComponent {
|
||||
|
||||
bool send_initial_state() override;
|
||||
|
||||
// LightRemoteValuesListener interface
|
||||
void on_light_remote_values_update() override;
|
||||
|
||||
protected:
|
||||
std::string component_type() const override;
|
||||
const EntityBase *get_entity() const override;
|
||||
|
||||
Reference in New Issue
Block a user