diff --git a/components/xiaomi_bslamp2/light/gpio_outputs.h b/components/xiaomi_bslamp2/light/color_handler.h similarity index 55% rename from components/xiaomi_bslamp2/light/gpio_outputs.h rename to components/xiaomi_bslamp2/light/color_handler.h index 555dea7..7d8cdea 100644 --- a/components/xiaomi_bslamp2/light/gpio_outputs.h +++ b/components/xiaomi_bslamp2/light/color_handler.h @@ -1,6 +1,6 @@ #pragma once -#include "light_modes.h" +#include "../light_hal.h" namespace esphome { namespace xiaomi { @@ -11,14 +11,8 @@ namespace bslamp2 { * LightColorValues into the required GPIO PWM duty cycle levels to represent * the requested color on the physical device. */ -class GPIOOutputs { +class ColorHandler : public GPIOOutputValues { public: - float red = 0.0f; - float green = 0.0f; - float blue = 0.0f; - float white = 0.0f; - std::string light_mode = LIGHT_MODE_OFF; - /** * Sets the red, green, blue, white fields to the PWM duty cycles * that are required to represent the requested light color for @@ -27,19 +21,6 @@ class GPIOOutputs { * Returns true when the input can be handled, false otherwise. */ virtual bool set_light_color_values(light::LightColorValues v) = 0; - - /** - * Copies the current output values to another GPIOOutputs object. - */ - void copy_to(GPIOOutputs *other) { - other->red = red; - other->green = green; - other->blue = blue; - other->white = white; - other->light_mode = light_mode; - } - - void log(const char *prefix) { ESP_LOGD(TAG, "%s: RGB=[%f,%f,%f], white=%f", prefix, red, green, blue, white); } }; } // namespace bslamp2 diff --git a/components/xiaomi_bslamp2/light/color_instant_handler.h b/components/xiaomi_bslamp2/light/color_handler_chain.h similarity index 70% rename from components/xiaomi_bslamp2/light/color_instant_handler.h rename to components/xiaomi_bslamp2/light/color_handler_chain.h index b2e90d1..a0ac4ed 100644 --- a/components/xiaomi_bslamp2/light/color_instant_handler.h +++ b/components/xiaomi_bslamp2/light/color_handler_chain.h @@ -4,11 +4,11 @@ #include #include "../common.h" -#include "color_night_light.h" -#include "color_off.h" -#include "color_rgb_light.h" -#include "color_white_light.h" -#include "gpio_outputs.h" +#include "color_handler.h" +#include "color_handler_off.h" +#include "color_handler_rgb.h" +#include "color_handler_color_temperature.h" +#include "color_handler_night_light.h" namespace esphome { namespace xiaomi { @@ -26,7 +26,7 @@ namespace bslamp2 { * - white light: based on color temperature + brightness * - RGB light: based on RGB values + brightness */ -class ColorInstantHandler : public GPIOOutputs { +class ColorHandlerChain : public ColorHandler { public: bool set_light_color_values(light::LightColorValues v) { // The actual implementation of the various light modes is in @@ -42,7 +42,7 @@ class ColorInstantHandler : public GPIOOutputs { else if (rgb_light_->set_light_color_values(v)) rgb_light_->copy_to(this); else { - ESP_LOGE(TAG, "Light color error: (None of the GPIOOutputs classes handles the requested light state; defaulting to 'off'"); + ESP_LOGE(TAG, "Light color error: (None of the ColorHandler classes handles the requested light state; defaulting to 'off'"); off_light_->copy_to(this); } @@ -50,10 +50,10 @@ class ColorInstantHandler : public GPIOOutputs { } protected: - GPIOOutputs *off_light_ = new ColorOff(); - GPIOOutputs *rgb_light_ = new ColorRGBLight(); - GPIOOutputs *white_light_ = new ColorWhiteLight(); - GPIOOutputs *night_light_ = new ColorNightLight(); + ColorHandler *off_light_ = new ColorHandlerOff(); + ColorHandler *rgb_light_ = new ColorHandlerRGB(); + ColorHandler *white_light_ = new ColorHandlerColorTemperature(); + ColorHandler *night_light_ = new ColorHandlerNightLight(); }; } // namespace bslamp2 diff --git a/components/xiaomi_bslamp2/light/color_white_light.h b/components/xiaomi_bslamp2/light/color_handler_color_temperature.h similarity index 96% rename from components/xiaomi_bslamp2/light/color_white_light.h rename to components/xiaomi_bslamp2/light/color_handler_color_temperature.h index 365339c..7852afe 100644 --- a/components/xiaomi_bslamp2/light/color_white_light.h +++ b/components/xiaomi_bslamp2/light/color_handler_color_temperature.h @@ -4,8 +4,8 @@ #include #include "../common.h" -#include "light_modes.h" -#include "gpio_outputs.h" +#include "../light_hal.h" +#include "color_handler.h" namespace esphome { namespace xiaomi { @@ -75,7 +75,7 @@ static const RGBWLevelsTable rgbw_levels_100_ {{ * This class can handle the GPIO outputs for the white light mode, * based on color temperature + brightness. */ -class ColorWhiteLight : public GPIOOutputs { +class ColorHandlerColorTemperature : public ColorHandler { public: bool set_light_color_values(light::LightColorValues v) { light_mode = LIGHT_MODE_WHITE; @@ -86,7 +86,7 @@ class ColorWhiteLight : public GPIOOutputs { } #else if (v.get_white() == 0.0f) { - return false; + return false } #endif diff --git a/components/xiaomi_bslamp2/light/color_night_light.h b/components/xiaomi_bslamp2/light/color_handler_night_light.h similarity index 89% rename from components/xiaomi_bslamp2/light/color_night_light.h rename to components/xiaomi_bslamp2/light/color_handler_night_light.h index 4af8f3d..47c7632 100644 --- a/components/xiaomi_bslamp2/light/color_night_light.h +++ b/components/xiaomi_bslamp2/light/color_handler_night_light.h @@ -1,8 +1,8 @@ #pragma once #include "../common.h" -#include "gpio_outputs.h" -#include "light_modes.h" +#include "../light_hal.h" +#include "color_handler.h" namespace esphome { namespace xiaomi { @@ -22,7 +22,7 @@ namespace bslamp2 { * light mode toggle, then this still could be implemented through the * device's yaml configuration. */ -class ColorNightLight : public GPIOOutputs { +class ColorHandlerNightLight : public ColorHandler { public: bool set_light_color_values(light::LightColorValues v) { light_mode = LIGHT_MODE_NIGHT; @@ -36,7 +36,11 @@ class ColorNightLight : public GPIOOutputs { // This night light mode is activated when white light is selected. // Based on measurements using the original device firmware, so it // matches the night light of the original firmware. - if (v.get_white() > 0) { +#ifdef HAS_COLOR_MODES + if (v.get_color_mode() == light::ColorMode::COLOR_TEMPERATURE) { +#else + if (v.get_white() > 0.0f) { +#endif red = 0.968f; green = 0.968f; blue = 0.972f; diff --git a/components/xiaomi_bslamp2/light/color_off.h b/components/xiaomi_bslamp2/light/color_handler_off.h similarity index 84% rename from components/xiaomi_bslamp2/light/color_off.h rename to components/xiaomi_bslamp2/light/color_handler_off.h index cd97b5d..527df28 100644 --- a/components/xiaomi_bslamp2/light/color_off.h +++ b/components/xiaomi_bslamp2/light/color_handler_off.h @@ -1,8 +1,8 @@ #pragma once #include "../common.h" -#include "gpio_outputs.h" -#include "light_modes.h" +#include "../light_hal.h" +#include "color_handler.h" namespace esphome { namespace xiaomi { @@ -11,7 +11,7 @@ namespace bslamp2 { /** * This class can handle the GPIO outputs in case the light of turned off. */ -class ColorOff : public GPIOOutputs { +class ColorHandlerOff : public ColorHandler { public: bool set_light_color_values(light::LightColorValues v) { light_mode = LIGHT_MODE_OFF; diff --git a/components/xiaomi_bslamp2/light/color_rgb_light.h b/components/xiaomi_bslamp2/light/color_handler_rgb.h similarity index 99% rename from components/xiaomi_bslamp2/light/color_rgb_light.h rename to components/xiaomi_bslamp2/light/color_handler_rgb.h index 757d048..04e0ac6 100644 --- a/components/xiaomi_bslamp2/light/color_rgb_light.h +++ b/components/xiaomi_bslamp2/light/color_handler_rgb.h @@ -4,8 +4,8 @@ #include #include "../common.h" -#include "gpio_outputs.h" -#include "light_modes.h" +#include "../light_hal.h" +#include "color_handler.h" namespace esphome { namespace xiaomi { @@ -247,7 +247,7 @@ static const RGBCircle rgb_circle_ {{ * This class can handle the GPIO outputs for the RGB light mode, * based on RGB color values + brightness. */ -class ColorRGBLight : public GPIOOutputs { +class ColorHandlerRGB : public ColorHandler { public: bool set_light_color_values(light::LightColorValues v) { light_mode = LIGHT_MODE_RGB; diff --git a/components/xiaomi_bslamp2/light/color_transition_handler.h.bak b/components/xiaomi_bslamp2/light/color_transition_handler.h.bak deleted file mode 100644 index ebf940d..0000000 --- a/components/xiaomi_bslamp2/light/color_transition_handler.h.bak +++ /dev/null @@ -1,136 +0,0 @@ -#pragma once - -#include "../common.h" -#include "color_instant_handler.h" -#include "interfaces.h" -#include "gpio_outputs.h" - -namespace esphome { -namespace xiaomi { -namespace bslamp2 { - -/** - * 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) {} - - light::LightColorValues get_end_values() { return end_light_values_; } - - 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_); - } - - light_mode = end_->light_mode; - progress_ = transformer_->get_progress(); - - // Determine required GPIO outputs for current transition progress. - - // In night light mode, do not use actual transitions. Transitioning - // between colors at the very low LED output levels of the night light, - // results in light drops, which are plain ugly to watch. - if (light_mode == "night") { - red = end_->red; - green = end_->green; - blue = end_->blue; - white = end_->white; - } - // In other light modes, apply smooth transitioning. - else { - 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; - } - - protected: - 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 bslamp2 -} // namespace xiaomi -} // namespace esphome diff --git a/components/xiaomi_bslamp2/light/light_modes.h b/components/xiaomi_bslamp2/light/light_modes.h deleted file mode 100644 index 2111d95..0000000 --- a/components/xiaomi_bslamp2/light/light_modes.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -namespace esphome { -namespace xiaomi { -namespace bslamp2 { - -static const std::string LIGHT_MODE_UNKNOWN{"unknown"}; -static const std::string LIGHT_MODE_OFF{"off"}; -static const std::string LIGHT_MODE_RGB{"rgb"}; -static const std::string LIGHT_MODE_WHITE{"white"}; -static const std::string LIGHT_MODE_NIGHT{"night"}; - -} // namespace bslamp2 -} // namespace xiaomi -} // namespace esphome diff --git a/components/xiaomi_bslamp2/light/light_output.h b/components/xiaomi_bslamp2/light/light_output.h index 99fe7e4..edfb54d 100644 --- a/components/xiaomi_bslamp2/light/light_output.h +++ b/components/xiaomi_bslamp2/light/light_output.h @@ -2,8 +2,9 @@ #include "../common.h" #include "../light_hal.h" -#include "color_instant_handler.h" +#include "color_handler_chain.h" #include "light_transformer.h" +#include "esphome/core/component.h" #include "esphome/components/ledc/ledc_output.h" namespace esphome { @@ -60,21 +61,9 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput { void write_state(light::LightState *state) { auto values = state->current_values; - // The color must either be set instantly, or the color is - // transitioning to an end color. The transition handler will do its - // own inspection to see if a transition is currently active or not. - // Based on the outcome, use either the instant or transition handler. - //GPIOOutputs *delegate; - //if (transition_handler_->set_light_color_values(values)) { - // delegate = transition_handler_; - // light_mode_callback_.call(delegate->light_mode); - // state_callback_.call(transition_handler_->get_end_values()); - //} else { - instant_handler_->set_light_color_values(values); - //delegate = instant_handler_; - light_mode_callback_.call(instant_handler_->light_mode); + color_handler_chain->set_light_color_values(values); + light_mode_callback_.call(color_handler_chain->light_mode); state_callback_.call(values); - //} // Note: one might think that it is more logical to turn on the LED // circuitry master switch after setting the individual channels, @@ -86,8 +75,8 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput { // Apply the current GPIO output levels from the selected handler. light_->set_rgbw( - instant_handler_->red, instant_handler_->green, instant_handler_->blue, - instant_handler_->white); + color_handler_chain->red, color_handler_chain->green, color_handler_chain->blue, + color_handler_chain->white); if (values.get_state() == 0) light_->turn_off(); @@ -95,7 +84,7 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput { protected: LightHAL *light_; - ColorInstantHandler *instant_handler_ = new ColorInstantHandler(); + ColorHandler *color_handler_chain = new ColorHandlerChain(); CallbackManager light_mode_callback_{}; CallbackManager state_callback_{}; }; diff --git a/components/xiaomi_bslamp2/light/light_transformer.h b/components/xiaomi_bslamp2/light/light_transformer.h index 8b0abac..c70d631 100644 --- a/components/xiaomi_bslamp2/light/light_transformer.h +++ b/components/xiaomi_bslamp2/light/light_transformer.h @@ -2,8 +2,7 @@ #include "../common.h" #include "../light_hal.h" -#include "color_instant_handler.h" -#include "gpio_outputs.h" +#include "color_handler_chain.h" #include "esphome/components/light/light_transformer.h" #include "esphome/components/light/light_color_values.h" @@ -18,50 +17,33 @@ class XiaomiBslamp2LightTransitionTransformer : public light::LightTransitionTra public: explicit XiaomiBslamp2LightTransitionTransformer(LightHAL *light) : light_(light) { } - bool is_completed() override { - return is_completed_ || get_progress_() >= 1.0f; + bool is_finished() override { + return force_finish_ || get_progress_() >= 1.0f; } void start() override { - // When a fresh transition is started, then compute the GPIO outputs - // to use for both the start and end point. This light transition transformer - // will then transition linearly between these two. - start_->set_light_color_values(start_values_); - target_->set_light_color_values(target_values_); - - // When a transition is modified, then use the current GPIO outputs - // as the new starting point. - // ... TODO - // - ESP_LOGE("DEBUG", "Start this thing"); // TODO - } - - void finish() override { - ESP_LOGE("DEBUG", "Finish this thing"); // TODO - } - - void abort() override { - ESP_LOGE("DEBUG", "Abort this thing"); // TODO + // Compute the GPIO outputs to use for the end point. + // This light transition transformer will then transition linearly between + // the current GPIO outputs and the target ones. + light_->copy_to(start_); + end_->set_light_color_values(target_values_); } optional apply() override { - if (target_->light_mode == "night") { - ESP_LOGE("DEBUG", "Set night light directly"); // DEBUG - light_->set_rgbw(target_->red, target_->green, target_->blue, target_->white); - is_completed_ = true; + if (end_->light_mode == "night") { + light_->set_state(end_); + force_finish_ = true; } else { auto smoothed = light::LightTransitionTransformer::smoothed_progress(get_progress_()); light_->set_rgbw( - esphome::lerp(smoothed, start_->red, target_->red), - esphome::lerp(smoothed, start_->green, target_->green), - esphome::lerp(smoothed, start_->blue, target_->blue), - esphome::lerp(smoothed, start_->white, target_->white)); + esphome::lerp(smoothed, start_->red, end_->red), + esphome::lerp(smoothed, start_->green, end_->green), + esphome::lerp(smoothed, start_->blue, end_->blue), + esphome::lerp(smoothed, start_->white, end_->white)); } - if (is_completed()) { - ESP_LOGE("DEBUG", "We're done, publish target values"); // TODO - is_completed_ = true; + if (is_finished()) { return target_values_; } else { return {}; @@ -70,9 +52,9 @@ class XiaomiBslamp2LightTransitionTransformer : public light::LightTransitionTra protected: LightHAL *light_; - bool is_completed_ = false; - GPIOOutputs *start_ = new ColorInstantHandler(); - GPIOOutputs *target_ = new ColorInstantHandler(); + bool force_finish_ = false; + GPIOOutputValues *start_ = new GPIOOutputValues(); + ColorHandler *end_ = new ColorHandlerChain(); }; } // namespace bslamp2 diff --git a/components/xiaomi_bslamp2/light_hal.h b/components/xiaomi_bslamp2/light_hal.h index 9711b9b..96803d4 100644 --- a/components/xiaomi_bslamp2/light_hal.h +++ b/components/xiaomi_bslamp2/light_hal.h @@ -8,39 +8,80 @@ namespace esphome { namespace xiaomi { namespace bslamp2 { -class LightHAL : Component { +static const std::string LIGHT_MODE_UNKNOWN{"unknown"}; +static const std::string LIGHT_MODE_OFF{"off"}; +static const std::string LIGHT_MODE_RGB{"rgb"}; +static const std::string LIGHT_MODE_WHITE{"white"}; +static const std::string LIGHT_MODE_NIGHT{"night"}; + +class GPIOOutputValues { + public: + float red = 0.0f; + float green = 0.0f; + float blue = 0.0f; + float white = 0.0f; + std::string light_mode = LIGHT_MODE_OFF; + + /** + * Copies the current output values to another GPIOOutputValues object. + */ + void copy_to(GPIOOutputValues *other) { + other->red = red; + other->green = green; + other->blue = blue; + other->white = white; + other->light_mode = light_mode; + } + + void log(const char *prefix) { ESP_LOGD(TAG, "%s: RGB=[%f,%f,%f], white=%f", prefix, red, green, blue, white); } +}; + +class LightHAL : Component, public GPIOOutputValues { public: - void set_red_pin(ledc::LEDCOutput *pin) { red_ = pin; } - void set_green_pin(ledc::LEDCOutput *pin) { green_ = pin; } - void set_blue_pin(ledc::LEDCOutput *pin) { blue_ = pin; } - void set_white_pin(ledc::LEDCOutput *pin) { white_ = pin; } - void set_master1_pin(gpio::GPIOBinaryOutput *pin) { master1_ = pin; } - void set_master2_pin(gpio::GPIOBinaryOutput *pin) { master2_ = pin; } + void set_red_pin(ledc::LEDCOutput *pin) { red_pin_ = pin; } + void set_green_pin(ledc::LEDCOutput *pin) { green_pin_ = pin; } + void set_blue_pin(ledc::LEDCOutput *pin) { blue_pin_ = pin; } + void set_white_pin(ledc::LEDCOutput *pin) { white_pin_ = pin; } + void set_master1_pin(gpio::GPIOBinaryOutput *pin) { master1_pin_ = pin; } + void set_master2_pin(gpio::GPIOBinaryOutput *pin) { master2_pin_ = pin; } void turn_on() { - master1_->turn_on(); - master2_->turn_on(); + master1_pin_->turn_on(); + master2_pin_->turn_on(); } void turn_off() { - master1_->turn_off(); - master2_->turn_off(); + master1_pin_->turn_off(); + master2_pin_->turn_off(); + } + + void set_state(GPIOOutputValues *new_state) { + new_state->copy_to(this); + red_pin_->set_level(this->red); + green_pin_->set_level(this->green); + blue_pin_->set_level(this->blue); + white_pin_->set_level(this->white); } void set_rgbw(float r, float g, float b, float w) { - red_->set_level(r); - green_->set_level(g); - blue_->set_level(b); - white_->set_level(w); + red_pin_->set_level(r); + green_pin_->set_level(g); + blue_pin_->set_level(b); + white_pin_->set_level(w); + + this->red = r; + this->green = g; + this->blue = b; + this->white = w; } protected: - ledc::LEDCOutput *red_; - ledc::LEDCOutput *green_; - ledc::LEDCOutput *blue_; - ledc::LEDCOutput *white_; - gpio::GPIOBinaryOutput *master1_; - gpio::GPIOBinaryOutput *master2_; + ledc::LEDCOutput *red_pin_; + ledc::LEDCOutput *green_pin_; + ledc::LEDCOutput *blue_pin_; + ledc::LEDCOutput *white_pin_; + gpio::GPIOBinaryOutput *master1_pin_; + gpio::GPIOBinaryOutput *master2_pin_; }; } // namespace bslamp2