|
@ -36,6 +36,65 @@ namespace bs2 { |
|
|
virtual float get_transformer_progress() = 0; |
|
|
virtual float get_transformer_progress() = 0; |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/// This class translates LightColorValues into GPIO duty cycles |
|
|
|
|
|
/// for representing a requested light color. |
|
|
|
|
|
class ColorTranslator : public DutyCycles { |
|
|
|
|
|
public: |
|
|
|
|
|
void set_light_color_values(light::LightColorValues values) { |
|
|
|
|
|
// The light is turned off. |
|
|
|
|
|
if (values.get_state() == 0.0f || values.get_brightness() == 0.0f) { |
|
|
|
|
|
red = 0.0f; |
|
|
|
|
|
green = 0.0f; |
|
|
|
|
|
blue = 0.0f; |
|
|
|
|
|
white = 0.0f; |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// At the lowest brightness setting, switch to night light mode. |
|
|
|
|
|
// In the Yeelight integration in Home Assistant, this feature is |
|
|
|
|
|
// exposed trough a separate switch. I have found that the switch |
|
|
|
|
|
// is both confusing and made me run into issues when automating |
|
|
|
|
|
// the lights. |
|
|
|
|
|
// I don't simply check for a brightness at or below 0.01 (1%), |
|
|
|
|
|
// because the lowest brightness setting from Home Assistant |
|
|
|
|
|
// turns up as 0.011765 in here (which is 3/255). |
|
|
|
|
|
if (values.get_brightness() < 0.012f) { |
|
|
|
|
|
// TODO make the color implementations return a DutyCycles object? |
|
|
|
|
|
// TODO Use polymorphic color classes? |
|
|
|
|
|
red = night_light_.red; |
|
|
|
|
|
green = night_light_.green; |
|
|
|
|
|
blue = night_light_.blue; |
|
|
|
|
|
white = night_light_.white; |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// White light mode: temperature + brightness. |
|
|
|
|
|
if (values.get_white() > 0.0f) { |
|
|
|
|
|
auto temperature = values.get_color_temperature(); |
|
|
|
|
|
white_light_.set_color(temperature, values.get_brightness()); |
|
|
|
|
|
red = white_light_.red; |
|
|
|
|
|
green = white_light_.green; |
|
|
|
|
|
blue = white_light_.blue; |
|
|
|
|
|
white = white_light_.white; |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// RGB color mode: red, green, blue + brightness. |
|
|
|
|
|
rgb_light_.set_color( |
|
|
|
|
|
values.get_red(), values.get_green(), values.get_blue(), |
|
|
|
|
|
values.get_brightness()); |
|
|
|
|
|
red = rgb_light_.red; |
|
|
|
|
|
green = rgb_light_.green; |
|
|
|
|
|
blue = rgb_light_.blue; |
|
|
|
|
|
white = rgb_light_.white; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected: |
|
|
|
|
|
ColorWhiteLight white_light_; |
|
|
|
|
|
ColorRGBLight rgb_light_; |
|
|
|
|
|
ColorNightLight night_light_; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
/// This class is used to handle color transition requirements. |
|
|
/// This class is used to handle color transition requirements. |
|
|
/// |
|
|
/// |
|
|
/// When using the default ESPHome logic, transitioning is done by |
|
|
/// When using the default ESPHome logic, transitioning is done by |
|
@ -44,9 +103,16 @@ namespace bs2 { |
|
|
/// translate these properties into light outputs on every step of the |
|
|
/// translate these properties into light outputs on every step of the |
|
|
/// way. While this does work, it does not work nicely. |
|
|
/// way. While this does work, it does not work nicely. |
|
|
/// |
|
|
/// |
|
|
/// This class will take care of transitioning LEDC light outputs |
|
|
|
|
|
/// instead, which is what the original firmware in the device does as |
|
|
|
|
|
/// well. This makes transitions a lot cleaner. |
|
|
|
|
|
|
|
|
/// 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 TransitionHandler { |
|
|
class TransitionHandler { |
|
|
public: |
|
|
public: |
|
|
TransitionHandler(LightStateDataExposer *exposer) : exposer_(exposer) {} |
|
|
TransitionHandler(LightStateDataExposer *exposer) : exposer_(exposer) {} |
|
@ -97,22 +163,11 @@ namespace bs2 { |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/// An implementation of the LightOutput interface for the Yeelight |
|
|
/// An implementation of the LightOutput interface for the Yeelight |
|
|
/// Bedside Lamp 2. |
|
|
|
|
|
|
|
|
/// Bedside Lamp 2. The function of this class is to translate a |
|
|
|
|
|
/// required light state into actual physicial GPIO output signals |
|
|
|
|
|
/// to drive the device's LED circuitry. |
|
|
class YeelightBS2LightOutput : public Component, public light::LightOutput { |
|
|
class YeelightBS2LightOutput : public Component, public light::LightOutput { |
|
|
public: |
|
|
public: |
|
|
light::LightTraits get_traits() override |
|
|
|
|
|
{ |
|
|
|
|
|
auto traits = light::LightTraits(); |
|
|
|
|
|
traits.set_supports_rgb(true); |
|
|
|
|
|
traits.set_supports_color_temperature(true); |
|
|
|
|
|
traits.set_supports_brightness(true); |
|
|
|
|
|
traits.set_supports_rgb_white_value(false); |
|
|
|
|
|
traits.set_supports_color_interlock(true); |
|
|
|
|
|
traits.set_min_mireds(MIRED_MIN); |
|
|
|
|
|
traits.set_max_mireds(MIRED_MAX); |
|
|
|
|
|
return traits; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Set the LEDC output for the red LED circuitry channel. |
|
|
/// Set the LEDC output for the red LED circuitry channel. |
|
|
void set_red_output(ledc::LEDCOutput *red) { |
|
|
void set_red_output(ledc::LEDCOutput *red) { |
|
|
red_ = red; |
|
|
red_ = red; |
|
@ -145,18 +200,36 @@ namespace bs2 { |
|
|
master2_ = master2; |
|
|
master2_ = master2; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void write_state(light::LightState *state) |
|
|
|
|
|
|
|
|
/// Returns a LightTraits object, which is used to explain to the |
|
|
|
|
|
/// outside world (e.g. Home Assistant) what features are supported |
|
|
|
|
|
/// by this device. |
|
|
|
|
|
light::LightTraits get_traits() override |
|
|
{ |
|
|
{ |
|
|
if (transition_->handle()) { |
|
|
|
|
|
ESP_LOGD(TAG, "HANDLE transition!"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
auto traits = light::LightTraits(); |
|
|
|
|
|
traits.set_supports_rgb(true); |
|
|
|
|
|
traits.set_supports_color_temperature(true); |
|
|
|
|
|
traits.set_supports_brightness(true); |
|
|
|
|
|
traits.set_supports_rgb_white_value(false); |
|
|
|
|
|
traits.set_supports_color_interlock(true); |
|
|
|
|
|
traits.set_min_mireds(MIRED_MIN); |
|
|
|
|
|
traits.set_max_mireds(MIRED_MAX); |
|
|
|
|
|
return traits; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Tranlates a requested light state into physicial GPIO outputs. |
|
|
|
|
|
void write_state(light::LightState *state) |
|
|
|
|
|
{ |
|
|
auto values = state->current_values; |
|
|
auto values = state->current_values; |
|
|
|
|
|
|
|
|
// Power down the light when its state is 'off'. |
|
|
// Power down the light when its state is 'off'. |
|
|
if (values.get_state() == 0) |
|
|
if (values.get_state() == 0) |
|
|
{ |
|
|
{ |
|
|
turn_off_(); |
|
|
|
|
|
|
|
|
red_->set_level(1.0f); |
|
|
|
|
|
green_->set_level(1.0f); |
|
|
|
|
|
blue_->set_level(1.0f); |
|
|
|
|
|
white_->set_level(0.0f); |
|
|
|
|
|
master2_->turn_off(); |
|
|
|
|
|
master1_->turn_off(); |
|
|
#ifdef TRANSITION_TO_OFF_BUGFIX |
|
|
#ifdef TRANSITION_TO_OFF_BUGFIX |
|
|
previous_state_ = -1; |
|
|
previous_state_ = -1; |
|
|
previous_brightness_ = 0; |
|
|
previous_brightness_ = 0; |
|
@ -164,10 +237,13 @@ namespace bs2 { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
auto brightness = values.get_brightness(); |
|
|
|
|
|
|
|
|
if (transition_->handle()) { |
|
|
|
|
|
ESP_LOGD(TAG, "HANDLE transition!"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
#ifdef TRANSITION_TO_OFF_BUGFIX |
|
|
#ifdef TRANSITION_TO_OFF_BUGFIX |
|
|
// Remember the brightness that is used when the light is fully ON. |
|
|
// Remember the brightness that is used when the light is fully ON. |
|
|
|
|
|
auto brightness = values.get_brightness(); |
|
|
if (values.get_state() == 1) { |
|
|
if (values.get_state() == 1) { |
|
|
previous_brightness_ = brightness; |
|
|
previous_brightness_ = brightness; |
|
|
} |
|
|
} |
|
@ -183,36 +259,13 @@ namespace bs2 { |
|
|
previous_state_ = values.get_state(); |
|
|
previous_state_ = values.get_state(); |
|
|
#endif |
|
|
#endif |
|
|
|
|
|
|
|
|
// At the lowest brightness setting, switch to night light mode. |
|
|
|
|
|
// In the Yeelight integration in Home Assistant, this feature is |
|
|
|
|
|
// exposed trough a separate switch. I have found that the switch |
|
|
|
|
|
// is both confusing and made me run into issues when automating |
|
|
|
|
|
// the lights. |
|
|
|
|
|
// I don't simply check for a brightness at or below 0.01 (1%), |
|
|
|
|
|
// because the lowest brightness setting from Home Assistant |
|
|
|
|
|
// turns up as 0.011765 in here (which is 3/255). |
|
|
|
|
|
if (brightness < 0.012f && values.get_state() == 1) { |
|
|
|
|
|
turn_on_in_night_light_mode_(); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Leave it to the default tooling to figure out the basics. |
|
|
|
|
|
// Because of color interlocking, there are two possible outcomes: |
|
|
|
|
|
// - red, green, blue zero -> white light color temperature mode |
|
|
|
|
|
// - cwhite, wwhite zero -> RGB mode |
|
|
|
|
|
float red, green, blue, cwhite, wwhite; |
|
|
|
|
|
state->current_values_as_rgbww( |
|
|
|
|
|
&red, &green, &blue, &cwhite, &wwhite, true, false); |
|
|
|
|
|
|
|
|
|
|
|
if (cwhite > 0 || wwhite > 0) { |
|
|
|
|
|
turn_on_in_white_mode_( |
|
|
|
|
|
values.get_color_temperature(), brightness); |
|
|
|
|
|
} |
|
|
|
|
|
else { |
|
|
|
|
|
turn_on_in_rgb_mode_( |
|
|
|
|
|
values.get_red(), values.get_green(), values.get_blue(), |
|
|
|
|
|
brightness, values.get_state()); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
duty_cycles_.set_light_color_values(values); |
|
|
|
|
|
master2_->turn_on(); |
|
|
|
|
|
master1_->turn_on(); |
|
|
|
|
|
red_->set_level(duty_cycles_.red); |
|
|
|
|
|
green_->set_level(duty_cycles_.green); |
|
|
|
|
|
blue_->set_level(duty_cycles_.blue); |
|
|
|
|
|
white_->set_level(duty_cycles_.white); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
protected: |
|
|
protected: |
|
@ -222,10 +275,8 @@ namespace bs2 { |
|
|
ledc::LEDCOutput *white_; |
|
|
ledc::LEDCOutput *white_; |
|
|
esphome::gpio::GPIOBinaryOutput *master1_; |
|
|
esphome::gpio::GPIOBinaryOutput *master1_; |
|
|
esphome::gpio::GPIOBinaryOutput *master2_; |
|
|
esphome::gpio::GPIOBinaryOutput *master2_; |
|
|
ColorWhiteLight white_light_; |
|
|
|
|
|
ColorRGBLight rgb_light_; |
|
|
|
|
|
ColorNightLight night_light_; |
|
|
|
|
|
TransitionHandler *transition_; |
|
|
TransitionHandler *transition_; |
|
|
|
|
|
ColorTranslator duty_cycles_; // TODO move to own class DefaultHandler |
|
|
#ifdef TRANSITION_TO_OFF_BUGFIX |
|
|
#ifdef TRANSITION_TO_OFF_BUGFIX |
|
|
float previous_state_ = 1; |
|
|
float previous_state_ = 1; |
|
|
float previous_brightness_ = -1; |
|
|
float previous_brightness_ = -1; |
|
@ -238,69 +289,6 @@ namespace bs2 { |
|
|
void set_light_state_data_exposer(LightStateDataExposer *exposer) { |
|
|
void set_light_state_data_exposer(LightStateDataExposer *exposer) { |
|
|
transition_ = new TransitionHandler(exposer); |
|
|
transition_ = new TransitionHandler(exposer); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void turn_off_() |
|
|
|
|
|
{ |
|
|
|
|
|
red_->set_level(1); |
|
|
|
|
|
green_->set_level(1); |
|
|
|
|
|
blue_->set_level(1); |
|
|
|
|
|
white_->set_level(0); |
|
|
|
|
|
master2_->turn_off(); |
|
|
|
|
|
master1_->turn_off(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void turn_on_in_night_light_mode_() |
|
|
|
|
|
{ |
|
|
|
|
|
ESP_LOGD(TAG, "Activate Night light feature"); |
|
|
|
|
|
|
|
|
|
|
|
night_light_.set_color(1, 1, 1, 0.01, 1); |
|
|
|
|
|
|
|
|
|
|
|
ESP_LOGD(TAG, "New LED state : RGBW %f, %f, %f, %f", night_light_.red, night_light_.green, night_light_.blue, night_light_.white); |
|
|
|
|
|
|
|
|
|
|
|
// Drive the LEDs. |
|
|
|
|
|
master2_->turn_on(); |
|
|
|
|
|
master1_->turn_on(); |
|
|
|
|
|
red_->set_level(night_light_.red); |
|
|
|
|
|
green_->set_level(night_light_.green); |
|
|
|
|
|
blue_->set_level(night_light_.blue); |
|
|
|
|
|
white_->set_level(night_light_.white); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void turn_on_in_rgb_mode_(float red, float green, float blue, float brightness, float state) |
|
|
|
|
|
{ |
|
|
|
|
|
ESP_LOGD(TAG, "Activate RGB %f, %f, %f, BRIGHTNESS %f", red, green, blue, brightness); |
|
|
|
|
|
|
|
|
|
|
|
rgb_light_.set_color(red, green, blue, brightness, state); |
|
|
|
|
|
|
|
|
|
|
|
ESP_LOGD(TAG, "New LED state : RGBW %f, %f, %f, off", rgb_light_.red, rgb_light_.green, rgb_light_.blue); |
|
|
|
|
|
|
|
|
|
|
|
// Drive the LEDs. |
|
|
|
|
|
master2_->turn_on(); |
|
|
|
|
|
master1_->turn_on(); |
|
|
|
|
|
red_->set_level(rgb_light_.red); |
|
|
|
|
|
green_->set_level(rgb_light_.green); |
|
|
|
|
|
blue_->set_level(rgb_light_.blue); |
|
|
|
|
|
white_->turn_off(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void turn_on_in_white_mode_(float temperature, float brightness) |
|
|
|
|
|
{ |
|
|
|
|
|
ESP_LOGD(TAG, "Activate TEMPERATURE %f, BRIGHTNESS %f", |
|
|
|
|
|
temperature, brightness); |
|
|
|
|
|
|
|
|
|
|
|
white_light_.set_color(temperature, brightness); |
|
|
|
|
|
|
|
|
|
|
|
ESP_LOGD(TAG, "New LED state : RGBW %f, %f, %f, %f", |
|
|
|
|
|
white_light_.red, white_light_.green, white_light_.blue, |
|
|
|
|
|
white_light_.white); |
|
|
|
|
|
|
|
|
|
|
|
master2_->turn_on(); |
|
|
|
|
|
master1_->turn_on(); |
|
|
|
|
|
red_->set_level(white_light_.red); |
|
|
|
|
|
green_->set_level(white_light_.green); |
|
|
|
|
|
blue_->set_level(white_light_.blue); |
|
|
|
|
|
white_->set_level(white_light_.white); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
class YeelightBS2LightState : public light::LightState, public LightStateDataExposer |
|
|
class YeelightBS2LightState : public light::LightState, public LightStateDataExposer |
|
|