|
#pragma once
|
|
|
|
#include "common.h"
|
|
#include "gpio_outputs.h"
|
|
#include "color_instant_handler.h"
|
|
|
|
namespace esphome {
|
|
namespace yeelight {
|
|
namespace bs2 {
|
|
|
|
/**
|
|
* This is an interface definition that is used to extend the LightState
|
|
* class with functionality to inspect LightTransformer data from
|
|
* within other classes.
|
|
*
|
|
* This interface is required for the ColorTransitionHandler, so it can
|
|
* check whether or not a light color transition is in progress.
|
|
*/
|
|
class LightStateTransformerInspector {
|
|
public:
|
|
virtual bool is_active() = 0;
|
|
virtual bool is_transition() = 0;
|
|
virtual light::LightColorValues get_end_values() = 0;
|
|
virtual float get_progress() = 0;
|
|
};
|
|
|
|
/**
|
|
* This class is used to handle specific light color transition requirements
|
|
* for the device.
|
|
*
|
|
* When using the default ESPHome logic, transitioning is done by
|
|
* transitioning all light properties linearly from the original values to
|
|
* the new values, and letting the light output object translate these
|
|
* properties into light outputs on every step of the way. While this does
|
|
* work, it does not work nicely.
|
|
*
|
|
* For example, when transitioning from warm to cold white light, the color
|
|
* temperature would be transitioned from the old value to the new value.
|
|
* While doing so, the transition hits the middle white light setting, which
|
|
* shows up as a bright flash in the middle of the transition. The original
|
|
* firmware however, shows a smooth transition from warm to cold white
|
|
* light, without any flash.
|
|
*
|
|
* This class handles transitions by not varying the light properties over
|
|
* time, but by transitioning the LEDC duty cycle output levels over time.
|
|
* This matches the behavior of the original firmware.
|
|
*/
|
|
class ColorTransitionHandler : public GPIOOutputs {
|
|
public:
|
|
ColorTransitionHandler(LightStateTransformerInspector *inspector) : transformer_(inspector) {}
|
|
|
|
protected:
|
|
bool set_light_color_values(light::LightColorValues values) {
|
|
if (!light_state_has_active_transition_()) {
|
|
// Remember the last active light color values. When a transition
|
|
// is detected, we'll use these as the starting point. It is not
|
|
// possible to use the current values at that point, because the
|
|
// transition is already in progress by the time the transition
|
|
// is detected.
|
|
start_light_values_ = values;
|
|
|
|
active_ = false;
|
|
return false;
|
|
}
|
|
|
|
// When a fresh transition is started, then compute the GPIO outputs
|
|
// to use for both the start and end point. This transition handler
|
|
// will then transition linearly between these two.
|
|
if (is_fresh_transition_()) {
|
|
start_->set_light_color_values(start_light_values_);
|
|
end_light_values_ = transformer_->get_end_values();
|
|
end_->set_light_color_values(end_light_values_);
|
|
active_ = true;
|
|
}
|
|
// When a transition is modified, then use the current GPIO outputs
|
|
// as the new starting point.
|
|
else if (is_modified_transition_()) {
|
|
this->copy_to(start_);
|
|
end_light_values_ = transformer_->get_end_values();
|
|
end_->set_light_color_values(end_light_values_);
|
|
}
|
|
|
|
// Determine required GPIO outputs for current transition progress.
|
|
progress_ = transformer_->get_progress();
|
|
auto smoothed = light::LightTransitionTransformer::smoothed_progress(progress_);
|
|
red = esphome::lerp(smoothed, start_->red, end_->red);
|
|
green = esphome::lerp(smoothed, start_->green, end_->green);
|
|
blue = esphome::lerp(smoothed, start_->blue, end_->blue);
|
|
white = esphome::lerp(smoothed, start_->white, end_->white);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool active_ = false;
|
|
float progress_ = 0.0f;
|
|
LightStateTransformerInspector *transformer_;
|
|
light::LightColorValues start_light_values_;
|
|
light::LightColorValues end_light_values_;
|
|
GPIOOutputs *start_ = new ColorInstantHandler();
|
|
GPIOOutputs *end_ = new ColorInstantHandler();
|
|
|
|
/**
|
|
* Checks if the LightState currently has an active LightTransformer.
|
|
*/
|
|
bool light_state_has_active_transition_() {
|
|
if (!transformer_->is_active())
|
|
return false;
|
|
if (!transformer_->is_transition())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks if a fresh transitioning is started.
|
|
* A transitioning is fresh when no existing transition is active.
|
|
*/
|
|
bool is_fresh_transition_() {
|
|
return active_ == false;
|
|
}
|
|
|
|
/**
|
|
* Checks if a new end state is set, while an existing transition
|
|
* is active. This might be detected in two ways:
|
|
* - the end color has been updated
|
|
* - the progress has been reverted
|
|
*/
|
|
bool is_modified_transition_() {
|
|
auto new_end_light_values = transformer_->get_end_values();
|
|
auto new_progress = transformer_->get_progress();
|
|
return (
|
|
new_end_light_values != end_light_values_ ||
|
|
new_progress < progress_
|
|
);
|
|
}
|
|
};
|
|
|
|
} // namespace yeelight_bs2
|
|
} // namespace yeelight
|
|
} // namespace bs2
|