mirror of
https://github.com/esphome/esphome.git
synced 2024-11-14 02:58:11 +01:00
Modular light transformers (#2124)
This commit is contained in:
parent
11477dbc03
commit
46f17bea66
8 changed files with 195 additions and 150 deletions
|
@ -24,6 +24,10 @@ void AddressableLight::call_setup() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<LightTransformer> AddressableLight::create_default_transition() {
|
||||||
|
return make_unique<AddressableLightTransformer>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
Color esp_color_from_light_color_values(LightColorValues val) {
|
Color esp_color_from_light_color_values(LightColorValues val) {
|
||||||
auto r = to_uint8_scale(val.get_color_brightness() * val.get_red());
|
auto r = to_uint8_scale(val.get_color_brightness() * val.get_red());
|
||||||
auto g = to_uint8_scale(val.get_color_brightness() * val.get_green());
|
auto g = to_uint8_scale(val.get_color_brightness() * val.get_green());
|
||||||
|
@ -37,41 +41,40 @@ void AddressableLight::write_state(LightState *state) {
|
||||||
auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state());
|
auto max_brightness = to_uint8_scale(val.get_brightness() * val.get_state());
|
||||||
this->correction_.set_local_brightness(max_brightness);
|
this->correction_.set_local_brightness(max_brightness);
|
||||||
|
|
||||||
this->last_transition_progress_ = 0.0f;
|
|
||||||
this->accumulated_alpha_ = 0.0f;
|
|
||||||
|
|
||||||
if (this->is_effect_active())
|
if (this->is_effect_active())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// don't use LightState helper, gamma correction+brightness is handled by ESPColorView
|
// don't use LightState helper, gamma correction+brightness is handled by ESPColorView
|
||||||
|
|
||||||
if (state->transformer_ == nullptr || !state->transformer_->is_transition()) {
|
|
||||||
// no transformer active or non-transition one
|
|
||||||
this->all() = esp_color_from_light_color_values(val);
|
this->all() = esp_color_from_light_color_values(val);
|
||||||
} else {
|
}
|
||||||
// transition transformer active, activate specialized transition for addressable effects
|
|
||||||
// instead of using a unified transition for all LEDs, we use the current state each LED as the
|
|
||||||
// start. Warning: ugly
|
|
||||||
|
|
||||||
// We can't use a direct lerp smoothing here though - that would require creating a copy of the original
|
void AddressableLightTransformer::start() {
|
||||||
// state of each LED at the start of the transition
|
auto end_values = this->target_values_;
|
||||||
// Instead, we "fake" the look of the LERP by using an exponential average over time and using
|
this->target_color_ = esp_color_from_light_color_values(end_values);
|
||||||
// dynamically-calculated alpha values to match the look of the
|
|
||||||
|
|
||||||
float new_progress = state->transformer_->get_progress();
|
|
||||||
float prev_smoothed = LightTransitionTransformer::smoothed_progress(last_transition_progress_);
|
|
||||||
float new_smoothed = LightTransitionTransformer::smoothed_progress(new_progress);
|
|
||||||
this->last_transition_progress_ = new_progress;
|
|
||||||
|
|
||||||
auto end_values = state->transformer_->get_end_values();
|
|
||||||
Color target_color = esp_color_from_light_color_values(end_values);
|
|
||||||
|
|
||||||
// our transition will handle brightness, disable brightness in correction.
|
// our transition will handle brightness, disable brightness in correction.
|
||||||
this->correction_.set_local_brightness(255);
|
this->light_.correction_.set_local_brightness(255);
|
||||||
target_color *= to_uint8_scale(end_values.get_brightness() * end_values.get_state());
|
this->target_color_ *= to_uint8_scale(end_values.get_brightness() * end_values.get_state());
|
||||||
|
}
|
||||||
|
|
||||||
float denom = (1.0f - new_smoothed);
|
optional<LightColorValues> AddressableLightTransformer::apply() {
|
||||||
float alpha = denom == 0.0f ? 0.0f : (new_smoothed - prev_smoothed) / denom;
|
// Don't try to transition over running effects, instead immediately use the target values. write_state() and the
|
||||||
|
// effects pick up the change from current_values.
|
||||||
|
if (this->light_.is_effect_active())
|
||||||
|
return this->target_values_;
|
||||||
|
|
||||||
|
// Use a specialized transition for addressable lights: instead of using a unified transition for
|
||||||
|
// all LEDs, we use the current state of each LED as the start.
|
||||||
|
|
||||||
|
// We can't use a direct lerp smoothing here though - that would require creating a copy of the original
|
||||||
|
// state of each LED at the start of the transition.
|
||||||
|
// Instead, we "fake" the look of the LERP by using an exponential average over time and using
|
||||||
|
// dynamically-calculated alpha values to match the look.
|
||||||
|
|
||||||
|
float smoothed_progress = LightTransitionTransformer::smoothed_progress(this->get_progress_());
|
||||||
|
|
||||||
|
float denom = (1.0f - smoothed_progress);
|
||||||
|
float alpha = denom == 0.0f ? 0.0f : (smoothed_progress - this->last_transition_progress_) / denom;
|
||||||
|
|
||||||
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
|
// We need to use a low-resolution alpha here which makes the transition set in only after ~half of the length
|
||||||
// We solve this by accumulating the fractional part of the alpha over time.
|
// We solve this by accumulating the fractional part of the alpha over time.
|
||||||
|
@ -89,14 +92,16 @@ void AddressableLight::write_state(LightState *state) {
|
||||||
|
|
||||||
if (alpha8 != 0) {
|
if (alpha8 != 0) {
|
||||||
uint8_t inv_alpha8 = 255 - alpha8;
|
uint8_t inv_alpha8 = 255 - alpha8;
|
||||||
Color add = target_color * alpha8;
|
Color add = this->target_color_ * alpha8;
|
||||||
|
|
||||||
for (auto led : *this)
|
for (auto led : this->light_)
|
||||||
led = add + led.get() * inv_alpha8;
|
led.set(add + led.get() * inv_alpha8);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->schedule_show();
|
this->last_transition_progress_ = smoothed_progress;
|
||||||
|
this->light_.schedule_show();
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "esp_range_view.h"
|
#include "esp_range_view.h"
|
||||||
#include "light_output.h"
|
#include "light_output.h"
|
||||||
#include "light_state.h"
|
#include "light_state.h"
|
||||||
|
#include "transformers.h"
|
||||||
|
|
||||||
#ifdef USE_POWER_SUPPLY
|
#ifdef USE_POWER_SUPPLY
|
||||||
#include "esphome/components/power_supply/power_supply.h"
|
#include "esphome/components/power_supply/power_supply.h"
|
||||||
|
@ -53,6 +54,7 @@ class AddressableLight : public LightOutput, public Component {
|
||||||
bool is_effect_active() const { return this->effect_active_; }
|
bool is_effect_active() const { return this->effect_active_; }
|
||||||
void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; }
|
void set_effect_active(bool effect_active) { this->effect_active_ = effect_active; }
|
||||||
void write_state(LightState *state) override;
|
void write_state(LightState *state) override;
|
||||||
|
std::unique_ptr<LightTransformer> create_default_transition() override;
|
||||||
void set_correction(float red, float green, float blue, float white = 1.0f) {
|
void set_correction(float red, float green, float blue, float white = 1.0f) {
|
||||||
this->correction_.set_max_brightness(
|
this->correction_.set_max_brightness(
|
||||||
Color(to_uint8_scale(red), to_uint8_scale(green), to_uint8_scale(blue), to_uint8_scale(white)));
|
Color(to_uint8_scale(red), to_uint8_scale(green), to_uint8_scale(blue), to_uint8_scale(white)));
|
||||||
|
@ -70,6 +72,8 @@ class AddressableLight : public LightOutput, public Component {
|
||||||
void call_setup() override;
|
void call_setup() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
friend class AddressableLightTransformer;
|
||||||
|
|
||||||
bool should_show_() const { return this->effect_active_ || this->next_show_; }
|
bool should_show_() const { return this->effect_active_ || this->next_show_; }
|
||||||
void mark_shown_() {
|
void mark_shown_() {
|
||||||
this->next_show_ = false;
|
this->next_show_ = false;
|
||||||
|
@ -92,6 +96,18 @@ class AddressableLight : public LightOutput, public Component {
|
||||||
power_supply::PowerSupplyRequester power_;
|
power_supply::PowerSupplyRequester power_;
|
||||||
#endif
|
#endif
|
||||||
LightState *state_parent_{nullptr};
|
LightState *state_parent_{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
class AddressableLightTransformer : public LightTransitionTransformer {
|
||||||
|
public:
|
||||||
|
AddressableLightTransformer(AddressableLight &light) : light_(light) {}
|
||||||
|
|
||||||
|
void start() override;
|
||||||
|
optional<LightColorValues> apply() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AddressableLight &light_;
|
||||||
|
Color target_color_{};
|
||||||
float last_transition_progress_{0.0f};
|
float last_transition_progress_{0.0f};
|
||||||
float accumulated_alpha_{0.0f};
|
float accumulated_alpha_{0.0f};
|
||||||
};
|
};
|
||||||
|
|
12
esphome/components/light/light_output.cpp
Normal file
12
esphome/components/light/light_output.cpp
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#include "light_output.h"
|
||||||
|
#include "transformers.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
std::unique_ptr<LightTransformer> LightOutput::create_default_transition() {
|
||||||
|
return make_unique<LightTransitionTransformer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
|
@ -3,6 +3,7 @@
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "light_traits.h"
|
#include "light_traits.h"
|
||||||
#include "light_state.h"
|
#include "light_state.h"
|
||||||
|
#include "light_transformer.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace light {
|
namespace light {
|
||||||
|
@ -13,6 +14,9 @@ class LightOutput {
|
||||||
/// Return the LightTraits of this LightOutput.
|
/// Return the LightTraits of this LightOutput.
|
||||||
virtual LightTraits get_traits() = 0;
|
virtual LightTraits get_traits() = 0;
|
||||||
|
|
||||||
|
/// Return the default transformer used for transitions.
|
||||||
|
virtual std::unique_ptr<LightTransformer> create_default_transition();
|
||||||
|
|
||||||
virtual void setup_state(LightState *state) {}
|
virtual void setup_state(LightState *state) {}
|
||||||
|
|
||||||
virtual void write_state(LightState *state) = 0;
|
virtual void write_state(LightState *state) = 0;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "light_state.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "light_state.h"
|
||||||
#include "light_output.h"
|
#include "light_output.h"
|
||||||
|
#include "transformers.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace light {
|
namespace light {
|
||||||
|
@ -105,19 +106,19 @@ void LightState::loop() {
|
||||||
|
|
||||||
// Apply transformer (if any)
|
// Apply transformer (if any)
|
||||||
if (this->transformer_ != nullptr) {
|
if (this->transformer_ != nullptr) {
|
||||||
|
auto values = this->transformer_->apply();
|
||||||
|
this->next_write_ = values.has_value(); // don't write if transformer doesn't want us to
|
||||||
|
if (values.has_value())
|
||||||
|
this->current_values = *values;
|
||||||
|
|
||||||
if (this->transformer_->is_finished()) {
|
if (this->transformer_->is_finished()) {
|
||||||
this->remote_values = this->current_values = this->transformer_->get_end_values();
|
this->transformer_->stop();
|
||||||
this->target_state_reached_callback_.call();
|
|
||||||
if (this->transformer_->publish_at_end())
|
|
||||||
this->publish_state();
|
|
||||||
this->transformer_ = nullptr;
|
this->transformer_ = nullptr;
|
||||||
} else {
|
this->target_state_reached_callback_.call();
|
||||||
this->current_values = this->transformer_->get_values();
|
|
||||||
this->remote_values = this->transformer_->get_remote_values();
|
|
||||||
}
|
}
|
||||||
this->next_write_ = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write state to the light
|
||||||
if (this->next_write_) {
|
if (this->next_write_) {
|
||||||
this->output_->write_state(this);
|
this->output_->write_state(this);
|
||||||
this->next_write_ = false;
|
this->next_write_ = false;
|
||||||
|
@ -217,18 +218,21 @@ void LightState::stop_effect_() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightState::start_transition_(const LightColorValues &target, uint32_t length) {
|
void LightState::start_transition_(const LightColorValues &target, uint32_t length) {
|
||||||
this->transformer_ = make_unique<LightTransitionTransformer>(millis(), length, this->current_values, target);
|
this->transformer_ = this->output_->create_default_transition();
|
||||||
this->remote_values = this->transformer_->get_remote_values();
|
this->transformer_->setup(this->current_values, target, length);
|
||||||
|
this->remote_values = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightState::start_flash_(const LightColorValues &target, uint32_t length) {
|
void LightState::start_flash_(const LightColorValues &target, uint32_t length) {
|
||||||
LightColorValues end_colors = this->current_values;
|
LightColorValues end_colors = this->remote_values;
|
||||||
// If starting a flash if one is already happening, set end values to end values of current flash
|
// If starting a flash if one is already happening, set end values to end values of current flash
|
||||||
// Hacky but works
|
// Hacky but works
|
||||||
if (this->transformer_ != nullptr)
|
if (this->transformer_ != nullptr)
|
||||||
end_colors = this->transformer_->get_end_values();
|
end_colors = this->transformer_->get_target_values();
|
||||||
this->transformer_ = make_unique<LightFlashTransformer>(millis(), length, end_colors, target);
|
|
||||||
this->remote_values = this->transformer_->get_remote_values();
|
this->transformer_ = make_unique<LightFlashTransformer>(*this);
|
||||||
|
this->transformer_->setup(end_colors, target, length);
|
||||||
|
this->remote_values = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
void LightState::set_immediately_(const LightColorValues &target, bool set_remote_values) {
|
||||||
|
@ -240,10 +244,6 @@ void LightState::set_immediately_(const LightColorValues &target, bool set_remot
|
||||||
this->next_write_ = true;
|
this->next_write_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightState::set_transformer_(std::unique_ptr<LightTransformer> transformer) {
|
|
||||||
this->transformer_ = std::move(transformer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LightState::save_remote_values_() {
|
void LightState::save_remote_values_() {
|
||||||
LightStateRTCState saved;
|
LightStateRTCState saved;
|
||||||
saved.color_mode = this->remote_values.get_color_mode();
|
saved.color_mode = this->remote_values.get_color_mode();
|
||||||
|
|
|
@ -157,9 +157,6 @@ class LightState : public Nameable, public Component {
|
||||||
/// Internal method to set the color values to target immediately (with no transition).
|
/// Internal method to set the color values to target immediately (with no transition).
|
||||||
void set_immediately_(const LightColorValues &target, bool set_remote_values);
|
void set_immediately_(const LightColorValues &target, bool set_remote_values);
|
||||||
|
|
||||||
/// Internal method to start a transformer.
|
|
||||||
void set_transformer_(std::unique_ptr<LightTransformer> transformer);
|
|
||||||
|
|
||||||
/// Internal method to save the current remote_values to the preferences
|
/// Internal method to save the current remote_values to the preferences
|
||||||
void save_remote_values_();
|
void save_remote_values_();
|
||||||
|
|
||||||
|
|
|
@ -1,42 +1,42 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "light_color_values.h"
|
#include "light_color_values.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace light {
|
namespace light {
|
||||||
|
|
||||||
/// Base-class for all light color transformers, such as transitions or flashes.
|
/// Base class for all light color transformers, such as transitions or flashes.
|
||||||
class LightTransformer {
|
class LightTransformer {
|
||||||
public:
|
public:
|
||||||
LightTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
|
void setup(const LightColorValues &start_values, const LightColorValues &target_values, uint32_t length) {
|
||||||
const LightColorValues &target_values)
|
this->start_time_ = millis();
|
||||||
: start_time_(start_time), length_(length), start_values_(start_values), target_values_(target_values) {}
|
this->length_ = length;
|
||||||
|
this->start_values_ = start_values;
|
||||||
|
this->target_values_ = target_values;
|
||||||
|
this->start();
|
||||||
|
}
|
||||||
|
|
||||||
LightTransformer() = delete;
|
/// Indicates whether this transformation is finished.
|
||||||
|
virtual bool is_finished() { return this->get_progress_() >= 1.0f; }
|
||||||
|
|
||||||
/// Whether this transformation is finished
|
/// This will be called before the transition is started.
|
||||||
virtual bool is_finished() { return this->get_progress() >= 1.0f; }
|
virtual void start() {}
|
||||||
|
|
||||||
/// This will be called to get the current values for output.
|
/// This will be called while the transformer is active to apply the transition to the light. Can either write to the
|
||||||
virtual LightColorValues get_values() = 0;
|
/// light directly, or return LightColorValues that will be applied.
|
||||||
|
virtual optional<LightColorValues> apply() = 0;
|
||||||
|
|
||||||
/// The values that should be reported to the front-end.
|
/// This will be called after transition is finished.
|
||||||
virtual LightColorValues get_remote_values() { return this->get_target_values_(); }
|
virtual void stop() {}
|
||||||
|
|
||||||
/// The values that should be set after this transformation is complete.
|
const LightColorValues &get_start_values() const { return this->start_values_; }
|
||||||
virtual LightColorValues get_end_values() { return this->get_target_values_(); }
|
|
||||||
|
|
||||||
virtual bool publish_at_end() = 0;
|
const LightColorValues &get_target_values() const { return this->target_values_; }
|
||||||
virtual bool is_transition() = 0;
|
|
||||||
|
|
||||||
float get_progress() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
const LightColorValues &get_start_values_() const { return this->start_values_; }
|
/// The progress of this transition, on a scale of 0 to 1.
|
||||||
|
float get_progress_() { return clamp((millis() - this->start_time_) / float(this->length_), 0.0f, 1.0f); }
|
||||||
const LightColorValues &get_target_values_() const { return this->target_values_; }
|
|
||||||
|
|
||||||
uint32_t start_time_;
|
uint32_t start_time_;
|
||||||
uint32_t length_;
|
uint32_t length_;
|
||||||
|
@ -44,69 +44,5 @@ class LightTransformer {
|
||||||
LightColorValues target_values_;
|
LightColorValues target_values_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class LightTransitionTransformer : public LightTransformer {
|
|
||||||
public:
|
|
||||||
LightTransitionTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
|
|
||||||
const LightColorValues &target_values)
|
|
||||||
: LightTransformer(start_time, length, start_values, target_values) {
|
|
||||||
// When turning light on from off state, use colors from new.
|
|
||||||
if (!this->start_values_.is_on() && this->target_values_.is_on()) {
|
|
||||||
this->start_values_ = LightColorValues(target_values);
|
|
||||||
this->start_values_.set_brightness(0.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
|
|
||||||
if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
|
||||||
this->changing_color_mode_ = true;
|
|
||||||
this->intermediate_values_ = LightColorValues(this->get_start_values_());
|
|
||||||
this->intermediate_values_.set_state(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LightColorValues get_values() override {
|
|
||||||
float p = this->get_progress();
|
|
||||||
|
|
||||||
// Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off.
|
|
||||||
if (this->changing_color_mode_ && p > 0.5f &&
|
|
||||||
this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
|
||||||
this->intermediate_values_ = LightColorValues(this->get_end_values());
|
|
||||||
this->intermediate_values_.set_state(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
|
|
||||||
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_;
|
|
||||||
if (this->changing_color_mode_)
|
|
||||||
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
|
|
||||||
|
|
||||||
float v = LightTransitionTransformer::smoothed_progress(p);
|
|
||||||
return LightColorValues::lerp(start, end, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool publish_at_end() override { return false; }
|
|
||||||
bool is_transition() override { return true; }
|
|
||||||
|
|
||||||
// This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like
|
|
||||||
// transition from 0 to 1 on x = [0, 1]
|
|
||||||
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
bool changing_color_mode_{false};
|
|
||||||
LightColorValues intermediate_values_{};
|
|
||||||
};
|
|
||||||
|
|
||||||
class LightFlashTransformer : public LightTransformer {
|
|
||||||
public:
|
|
||||||
LightFlashTransformer(uint32_t start_time, uint32_t length, const LightColorValues &start_values,
|
|
||||||
const LightColorValues &target_values)
|
|
||||||
: LightTransformer(start_time, length, start_values, target_values) {}
|
|
||||||
|
|
||||||
LightColorValues get_values() override { return this->get_target_values_(); }
|
|
||||||
|
|
||||||
LightColorValues get_end_values() override { return this->get_start_values_(); }
|
|
||||||
|
|
||||||
bool publish_at_end() override { return true; }
|
|
||||||
bool is_transition() override { return false; }
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace light
|
} // namespace light
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
75
esphome/components/light/transformers.h
Normal file
75
esphome/components/light/transformers.h
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "light_color_values.h"
|
||||||
|
#include "light_state.h"
|
||||||
|
#include "light_transformer.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace light {
|
||||||
|
|
||||||
|
class LightTransitionTransformer : public LightTransformer {
|
||||||
|
public:
|
||||||
|
void start() override {
|
||||||
|
// When turning light on from off state, use colors from target state.
|
||||||
|
if (!this->start_values_.is_on() && this->target_values_.is_on()) {
|
||||||
|
this->start_values_ = LightColorValues(this->target_values_);
|
||||||
|
this->start_values_.set_brightness(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
|
||||||
|
if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
||||||
|
this->changing_color_mode_ = true;
|
||||||
|
this->intermediate_values_ = this->start_values_;
|
||||||
|
this->intermediate_values_.set_state(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<LightColorValues> apply() override {
|
||||||
|
float p = this->get_progress_();
|
||||||
|
|
||||||
|
// Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off.
|
||||||
|
if (this->changing_color_mode_ && p > 0.5f &&
|
||||||
|
this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
||||||
|
this->intermediate_values_ = this->target_values_;
|
||||||
|
this->intermediate_values_.set_state(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
|
||||||
|
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_;
|
||||||
|
if (this->changing_color_mode_)
|
||||||
|
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
|
||||||
|
|
||||||
|
float v = LightTransitionTransformer::smoothed_progress(p);
|
||||||
|
return LightColorValues::lerp(start, end, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// This looks crazy, but it reduces to 6x^5 - 15x^4 + 10x^3 which is just a smooth sigmoid-like
|
||||||
|
// transition from 0 to 1 on x = [0, 1]
|
||||||
|
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
||||||
|
|
||||||
|
bool changing_color_mode_{false};
|
||||||
|
LightColorValues intermediate_values_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class LightFlashTransformer : public LightTransformer {
|
||||||
|
public:
|
||||||
|
LightFlashTransformer(LightState &state) : state_(state) {}
|
||||||
|
|
||||||
|
optional<LightColorValues> apply() override { return this->get_target_values(); }
|
||||||
|
|
||||||
|
// Restore the original values after the flash.
|
||||||
|
void stop() override {
|
||||||
|
this->state_.current_values = this->get_start_values();
|
||||||
|
this->state_.remote_values = this->get_start_values();
|
||||||
|
this->state_.publish_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LightState &state_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace light
|
||||||
|
} // namespace esphome
|
Loading…
Reference in a new issue