diff --git a/CHANGELOG.md b/CHANGELOG.md index 54ff06d..75ce405 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,22 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.2.0-RC1] +## [2021.8.0] -**Note**: This release requires ESPHome v1.21.0 and Home Assistant 2021.8.0 or newer. -The code will compile with ESPHome v1.20.0, but the lamp will not be controllable -through the Home Assistant GUI when using Home Assistant 2021.8.0 or newer. -Only turn on/off and brightness will be available, not the RGB and Color Temperature -tabs.. +**Note**: This release requires ESPHome 2021.8.0 and Home Assistant 2021.8.0 or newer. ### Added - Preset identifiers (`group` and `preset`) for the `preset.activate` action are now validated at compile time. This prevents us from building a firmware with incorrect preset identifiers. Before this change, using an invalid preset name would only - result in a warning message in the device log. + result in a warning message in the device log, which is only moderately useful. ### Changed -- The code has been made compatible with the new color mode support in Home Assistant. +- The code has been made compatible with the new color mode support in Home Assistant + and ESPHome. +- My project will follow the Home Assistant / ESPHome versioning scheme from now on + (..), because the ESPHome project adopted this versioning + scheme too. ## [1.1.0] diff --git a/README.md b/README.md index 028dbce..dbcfbc8 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ aspect of the lamp and to integrate the lamp in your Home Assistant setup. For those who have experience with flashing ESPHome onto devices: -* Make sure you are using ESPHome v1.21.0 or newer. +* Make sure you are using ESPHome 2021.8.0 or newer. * Copy [`example.yaml`](example.yaml) to `/.yaml`. * Modify the configuration to your needs (see the [configuration guide](doc/configuration.md)). * Compile the `firmware.bin` file and download it to the device to which you have connected your diff --git a/components/xiaomi_bslamp2/light/__init__.py b/components/xiaomi_bslamp2/light/__init__.py index 43927c0..d57e6d6 100644 --- a/components/xiaomi_bslamp2/light/__init__.py +++ b/components/xiaomi_bslamp2/light/__init__.py @@ -225,8 +225,6 @@ def light_output_to_code(config): yield light.register_light(light_output_var, config) light_hal_var = yield cg.get_variable(config[CONF_LIGHT_HAL_ID]) cg.add(light_output_var.set_parent(light_hal_var)) - if hasattr(light, "types") and hasattr(light.types, "COLOR_MODES"): - cg.add_define('HAS_COLOR_MODES') @coroutine def on_brightness_to_code(config): 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 56% rename from components/xiaomi_bslamp2/light/color_instant_handler.h rename to components/xiaomi_bslamp2/light/color_handler_chain.h index b2e90d1..243e28a 100644 --- a/components/xiaomi_bslamp2/light/color_instant_handler.h +++ b/components/xiaomi_bslamp2/light/color_handler_chain.h @@ -4,20 +4,19 @@ #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 { namespace bslamp2 { /** - * This class translates LightColorValues into GPIO duty cycles that - * can be used for representing a requested light color on the - * physical device. + * This class translates LightColorValues into GPIO duty cycles that can be + * used for representing a requested light color on the physical device. * * The code handles all known light modes for the device: * @@ -26,13 +25,13 @@ 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 - // separated targeted classes. These classes are called here - // in a chain of command-like pattern, to let the first one - // that can handle the light settings do the honours. + // The actual implementation of the various light modes is in separate + // targeted classes. These classes are called here in a chain of + // command-like pattern, to let the first one that can handle the light + // settings do the honours. if (off_light_->set_light_color_values(v)) off_light_->copy_to(this); else if (night_light_->set_light_color_values(v)) @@ -42,7 +41,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 +49,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 95% rename from components/xiaomi_bslamp2/light/color_white_light.h rename to components/xiaomi_bslamp2/light/color_handler_color_temperature.h index 365339c..f27b206 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,20 +75,14 @@ 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; -#ifdef HAS_COLOR_MODES if (v.get_color_mode() != light::ColorMode::COLOR_TEMPERATURE) { return false; } -#else - if (v.get_white() == 0.0f) { - return false; - } -#endif auto temperature = clamp_temperature_(v.get_color_temperature()); auto brightness = clamp_brightness_(v.get_brightness()); diff --git a/components/xiaomi_bslamp2/light/color_night_light.h b/components/xiaomi_bslamp2/light/color_handler_night_light.h similarity index 92% rename from components/xiaomi_bslamp2/light/color_night_light.h rename to components/xiaomi_bslamp2/light/color_handler_night_light.h index 4af8f3d..026f13f 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,7 @@ 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) { + if (v.get_color_mode() == light::ColorMode::COLOR_TEMPERATURE) { 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..7d2896c 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,20 +247,14 @@ 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; -#ifdef HAS_COLOR_MODES if (v.get_color_mode() != light::ColorMode::RGB) { return false; } -#else - if (v.get_white() > 0.0f) { - return false; - } -#endif // 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 diff --git a/components/xiaomi_bslamp2/light/color_transition_handler.h b/components/xiaomi_bslamp2/light/color_transition_handler.h deleted file mode 100644 index ebf940d..0000000 --- a/components/xiaomi_bslamp2/light/color_transition_handler.h +++ /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/interfaces.h b/components/xiaomi_bslamp2/light/interfaces.h index f9e47cb..1c69a41 100644 --- a/components/xiaomi_bslamp2/light/interfaces.h +++ b/components/xiaomi_bslamp2/light/interfaces.h @@ -4,22 +4,6 @@ namespace esphome { namespace xiaomi { namespace bslamp2 { -/** - * 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 is an interface definition that is used to extend the LightState * class with functionality for disco actions (immediate light updates, 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 4a399c8..7d14e79 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_transition_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 { @@ -28,20 +29,16 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput { */ light::LightTraits get_traits() override { auto traits = light::LightTraits(); -#ifdef HAS_COLOR_MODES traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); -#else - 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); -#endif traits.set_min_mireds(MIRED_MIN); traits.set_max_mireds(MIRED_MAX); return traits; } + std::unique_ptr create_default_transition() override { + return make_unique(light_); + } + void add_on_light_mode_callback(std::function &&callback) { light_mode_callback_.add(std::move(callback)); } @@ -56,21 +53,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(delegate->light_mode); - state_callback_.call(values); - } + 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, @@ -80,8 +65,8 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput { if (values.get_state() != 0) light_->turn_on(); - // Apply the current GPIO output levels from the selected handler. - light_->set_rgbw(delegate->red, delegate->green, delegate->blue, delegate->white); + // Apply the GPIO output levels as defined by the color handler. + light_->set_state(color_handler_chain); if (values.get_state() == 0) light_->turn_off(); @@ -89,21 +74,9 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput { protected: LightHAL *light_; - ColorTransitionHandler *transition_handler_; - ColorInstantHandler *instant_handler_ = new ColorInstantHandler(); + ColorHandler *color_handler_chain = new ColorHandlerChain(); CallbackManager light_mode_callback_{}; CallbackManager state_callback_{}; - - friend class XiaomiBslamp2LightState; - - /** - * Called by the XiaomiBslamp2LightState class, to set the object that can be - * used to access the protected LightTransformer data from the LightState - * object. - */ - void set_transformer_inspector(LightStateTransformerInspector *exposer) { - transition_handler_ = new ColorTransitionHandler(exposer); - } }; } // namespace bslamp2 diff --git a/components/xiaomi_bslamp2/light/light_state.h b/components/xiaomi_bslamp2/light/light_state.h index 5ff05b4..1f18cd3 100644 --- a/components/xiaomi_bslamp2/light/light_state.h +++ b/components/xiaomi_bslamp2/light/light_state.h @@ -24,22 +24,12 @@ struct MyLightStateRTCState { /** * A custom LightState class for the Xiaomi Bedside Lamp 2. * - * This class is used by the ColorTransitionHandler class to inspect if - * an ongoing light color transition is active in the LightState object. - * - * It is also used by the DiscoAction to apply immediate light output + * It is used by the DiscoAction to apply immediate light output * updates, without saving or publishing the new state. */ -class XiaomiBslamp2LightState : public light::LightState, public LightStateTransformerInspector, public LightStateDiscoSupport { +class XiaomiBslamp2LightState : public light::LightState, public LightStateDiscoSupport { public: - XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) { - output->set_transformer_inspector(this); - } - - bool is_active() { return transformer_ != nullptr; } - bool is_transition() { return transformer_->is_transition(); } - light::LightColorValues get_end_values() { return transformer_->get_end_values(); } - float get_progress() { return transformer_->get_progress(); } + XiaomiBslamp2LightState(const std::string &name, XiaomiBslamp2LightOutput *output) : light::LightState(name, output) { } void disco_stop() { MyLightStateRTCState recovered{}; diff --git a/components/xiaomi_bslamp2/light/light_transformer.h b/components/xiaomi_bslamp2/light/light_transformer.h new file mode 100644 index 0000000..c70d631 --- /dev/null +++ b/components/xiaomi_bslamp2/light/light_transformer.h @@ -0,0 +1,62 @@ +#pragma once + +#include "../common.h" +#include "../light_hal.h" +#include "color_handler_chain.h" +#include "esphome/components/light/light_transformer.h" +#include "esphome/components/light/light_color_values.h" + +namespace esphome { +namespace xiaomi { +namespace bslamp2 { + +/** + * A LightTransitionTransformer class for the Xiaomi Mijia Bedside Lamp 2. + */ +class XiaomiBslamp2LightTransitionTransformer : public light::LightTransitionTransformer { + public: + explicit XiaomiBslamp2LightTransitionTransformer(LightHAL *light) : light_(light) { } + + bool is_finished() override { + return force_finish_ || get_progress_() >= 1.0f; + } + + void start() override { + // 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 (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, 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_finished()) { + return target_values_; + } else { + return {}; + } + } + + protected: + LightHAL *light_; + bool force_finish_ = false; + GPIOOutputValues *start_ = new GPIOOutputValues(); + ColorHandler *end_ = new ColorHandlerChain(); +}; + +} // namespace bslamp2 +} // namespace xiaomi +} // namespace esphome diff --git a/components/xiaomi_bslamp2/light_hal.h b/components/xiaomi_bslamp2/light_hal.h index 9711b9b..dfeff8c 100644 --- a/components/xiaomi_bslamp2/light_hal.h +++ b/components/xiaomi_bslamp2/light_hal.h @@ -8,39 +8,86 @@ 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: - 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; } + float red = 0.0f; + float green = 0.0f; + float blue = 0.0f; + float white = 0.0f; + std::string light_mode = LIGHT_MODE_OFF; - void turn_on() { - master1_->turn_on(); - master2_->turn_on(); + /** + * 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_ = 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; } + + /** + * Turn on the master switch for the LEDs. + */ + void turn_on() { + master1_pin_->turn_on(); + master2_pin_->turn_on(); + } + + /** + * Turn off the master switch for the LEDs. + */ 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 diff --git a/doc/installation.md b/doc/installation.md index e04ff72..4732382 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -2,7 +2,7 @@ # Installation guide -The code must be compiled into a firmware using ESPHome v1.19.0 or later. Therefore, a prerequisite +The code must be compiled into a firmware using ESPHome 2021.8.0 or later. Therefore, a prerequisite is that you have ESPHome up and running in some form (command line, docker container, web dashboard, possibly from within Home Assistant as an add-on). For information on this, please refer to the documentation on the [ESPHome website](https://esphome.io). diff --git a/doc/testplan.md b/doc/testplan.md index b500829..8de87f5 100644 --- a/doc/testplan.md +++ b/doc/testplan.md @@ -65,3 +65,11 @@ been introduced. ## Step 3: Release the new version Only after performing these tests successfully, the new version can be released. + +Before release, check if the minimum version requirement for ESPHome is documented +correctly in the following files: + + * doc/installation.md + * README.md + * CHANGELOG.md + diff --git a/example.yaml b/example.yaml index d5769ac..11d934a 100644 --- a/example.yaml +++ b/example.yaml @@ -3,17 +3,18 @@ # -------------------------------------------------------------------------- substitutions: - name: bedside_lamp + name: bedside-lamp friendly_name: Bedside Lamp transition_length: 500ms - # Derive component identifiers, based on the name. - id_light: ${name} - id_light_mode: ${name}_light_mode - id_power_button: ${name}_power_button - id_color_button: ${name}_color_button - id_slider_level: ${name}_slider_level - id_front_panel_illumination: ${name}_front_panel_illumination + # Component identifiers. + prefix: bedside_lamp + id_light: ${prefix} + id_light_mode: ${prefix}_light_mode + id_power_button: ${prefix}_power_button + id_color_button: ${prefix}_color_button + id_slider_level: ${prefix}_slider_level + id_front_panel_illumination: ${prefix}_front_panel_illumination # -------------------------------------------------------------------------- # Use your own preferences for these components. @@ -39,9 +40,6 @@ api: # flicker. reboot_timeout: 0s -api: - password: !secret api_password - # If you want to use light presets (see below) from Home Assistant, # then you can expose the required functionality as a service here. # This is an example of how you could expose the activation of a preset. @@ -190,7 +188,7 @@ light: # for the lamp in Home Assistant. text_sensor: - platform: xiaomi_bslamp2 - name: ${name} Light Mode + name: ${friendly_name} Light Mode id: ${id_light_mode} # This float output controls the front panel illumination + level indicator.