diff --git a/color_rgb_light.h b/color_rgb_light.h index 4214a8f..afe8a44 100644 --- a/color_rgb_light.h +++ b/color_rgb_light.h @@ -251,7 +251,7 @@ public: float blue = 0; float white = 0; - void set_color(float red, float green, float blue, float brightness, float state) { + void set_color(float red, float green, float blue, float brightness) { // Determine the ring level for the color. This is a value between // 0 and 7, determining in what ring of the RGB circle the requested // color resides. diff --git a/color_white_light.h b/color_white_light.h index ee4c3f3..46d85c8 100644 --- a/color_white_light.h +++ b/color_white_light.h @@ -84,9 +84,9 @@ public: protected: float clamp_temperature_(float temperature) { - if (temperature < MIRED_MAX) + if (temperature > MIRED_MAX) temperature = MIRED_MAX; - else if (temperature > MIRED_MIN) + else if (temperature < MIRED_MIN) temperature = MIRED_MIN; return temperature; } diff --git a/light.h b/light.h index 21b1777..605b664 100644 --- a/light.h +++ b/light.h @@ -36,6 +36,65 @@ namespace bs2 { 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. /// /// 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 /// 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 { public: TransitionHandler(LightStateDataExposer *exposer) : exposer_(exposer) {} @@ -97,22 +163,11 @@ namespace bs2 { }; /// 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 { 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. void set_red_output(ledc::LEDCOutput *red) { red_ = red; @@ -145,18 +200,36 @@ namespace bs2 { 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; // Power down the light when its state is 'off'. 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 previous_state_ = -1; previous_brightness_ = 0; @@ -164,10 +237,13 @@ namespace bs2 { return; } - auto brightness = values.get_brightness(); + if (transition_->handle()) { + ESP_LOGD(TAG, "HANDLE transition!"); + } #ifdef TRANSITION_TO_OFF_BUGFIX // Remember the brightness that is used when the light is fully ON. + auto brightness = values.get_brightness(); if (values.get_state() == 1) { previous_brightness_ = brightness; } @@ -183,36 +259,13 @@ namespace bs2 { previous_state_ = values.get_state(); #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: @@ -222,10 +275,8 @@ namespace bs2 { ledc::LEDCOutput *white_; esphome::gpio::GPIOBinaryOutput *master1_; esphome::gpio::GPIOBinaryOutput *master2_; - ColorWhiteLight white_light_; - ColorRGBLight rgb_light_; - ColorNightLight night_light_; TransitionHandler *transition_; + ColorTranslator duty_cycles_; // TODO move to own class DefaultHandler #ifdef TRANSITION_TO_OFF_BUGFIX float previous_state_ = 1; float previous_brightness_ = -1; @@ -238,69 +289,6 @@ namespace bs2 { void set_light_state_data_exposer(LightStateDataExposer *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