Browse Source

Compatibility fix: custom light transformer implementation (#50)

* New light transformer implementation
* Fixed missing ; in the code, thanks Jos!
* Removed backward compatibility code for v1.20.x (because there were too many other changes that broke the build, and for which creating a work-around was not feasible).
* Fixed duplicate api section in the example.yaml (thanks @badbroechten\!) and updated the example to not use an underscore in the hostname.
* Prepare for release
release/2021.8.0
Maurice Makaay 3 years ago
committed by GitHub
parent
commit
9427c0561d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 207 additions and 330 deletions
  1. +8
    -8
      CHANGELOG.md
  2. +1
    -1
      README.md
  3. +0
    -2
      components/xiaomi_bslamp2/light/__init__.py
  4. +2
    -21
      components/xiaomi_bslamp2/light/color_handler.h
  5. +17
    -18
      components/xiaomi_bslamp2/light/color_handler_chain.h
  6. +3
    -9
      components/xiaomi_bslamp2/light/color_handler_color_temperature.h
  7. +4
    -4
      components/xiaomi_bslamp2/light/color_handler_night_light.h
  8. +3
    -3
      components/xiaomi_bslamp2/light/color_handler_off.h
  9. +3
    -9
      components/xiaomi_bslamp2/light/color_handler_rgb.h
  10. +0
    -136
      components/xiaomi_bslamp2/light/color_transition_handler.h
  11. +0
    -16
      components/xiaomi_bslamp2/light/interfaces.h
  12. +0
    -15
      components/xiaomi_bslamp2/light/light_modes.h
  13. +13
    -40
      components/xiaomi_bslamp2/light/light_output.h
  14. +3
    -13
      components/xiaomi_bslamp2/light/light_state.h
  15. +62
    -0
      components/xiaomi_bslamp2/light/light_transformer.h
  16. +69
    -22
      components/xiaomi_bslamp2/light_hal.h
  17. +1
    -1
      doc/installation.md
  18. +8
    -0
      doc/testplan.md
  19. +10
    -12
      example.yaml

+ 8
- 8
CHANGELOG.md View File

@ -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/), 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). 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 ### Added
- Preset identifiers (`group` and `preset`) for the `preset.activate` action are now - 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 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 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 ### 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
(<year>.<month>.<patch>), because the ESPHome project adopted this versioning
scheme too.
## [1.1.0] ## [1.1.0]


+ 1
- 1
README.md View File

@ -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: 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 `<CONFIG_DIR>/<NODE_NAME>.yaml`. * Copy [`example.yaml`](example.yaml) to `<CONFIG_DIR>/<NODE_NAME>.yaml`.
* Modify the configuration to your needs (see the [configuration guide](doc/configuration.md)). * 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 * Compile the `firmware.bin` file and download it to the device to which you have connected your


+ 0
- 2
components/xiaomi_bslamp2/light/__init__.py View File

@ -225,8 +225,6 @@ def light_output_to_code(config):
yield light.register_light(light_output_var, config) yield light.register_light(light_output_var, config)
light_hal_var = yield cg.get_variable(config[CONF_LIGHT_HAL_ID]) light_hal_var = yield cg.get_variable(config[CONF_LIGHT_HAL_ID])
cg.add(light_output_var.set_parent(light_hal_var)) 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 @coroutine
def on_brightness_to_code(config): def on_brightness_to_code(config):


components/xiaomi_bslamp2/light/gpio_outputs.h → components/xiaomi_bslamp2/light/color_handler.h View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "light_modes.h"
#include "../light_hal.h"
namespace esphome { namespace esphome {
namespace xiaomi { namespace xiaomi {
@ -11,14 +11,8 @@ namespace bslamp2 {
* LightColorValues into the required GPIO PWM duty cycle levels to represent * LightColorValues into the required GPIO PWM duty cycle levels to represent
* the requested color on the physical device. * the requested color on the physical device.
*/ */
class GPIOOutputs {
class ColorHandler : public GPIOOutputValues {
public: 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 * Sets the red, green, blue, white fields to the PWM duty cycles
* that are required to represent the requested light color for * 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. * Returns true when the input can be handled, false otherwise.
*/ */
virtual bool set_light_color_values(light::LightColorValues v) = 0; 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 } // namespace bslamp2

components/xiaomi_bslamp2/light/color_instant_handler.h → components/xiaomi_bslamp2/light/color_handler_chain.h View File

@ -4,20 +4,19 @@
#include <stdexcept> #include <stdexcept>
#include "../common.h" #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 esphome {
namespace xiaomi { namespace xiaomi {
namespace bslamp2 { 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: * The code handles all known light modes for the device:
* *
@ -26,13 +25,13 @@ namespace bslamp2 {
* - white light: based on color temperature + brightness * - white light: based on color temperature + brightness
* - RGB light: based on RGB values + brightness * - RGB light: based on RGB values + brightness
*/ */
class ColorInstantHandler : public GPIOOutputs {
class ColorHandlerChain : public ColorHandler {
public: public:
bool set_light_color_values(light::LightColorValues v) { 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)) if (off_light_->set_light_color_values(v))
off_light_->copy_to(this); off_light_->copy_to(this);
else if (night_light_->set_light_color_values(v)) 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)) else if (rgb_light_->set_light_color_values(v))
rgb_light_->copy_to(this); rgb_light_->copy_to(this);
else { 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); off_light_->copy_to(this);
} }
@ -50,10 +49,10 @@ class ColorInstantHandler : public GPIOOutputs {
} }
protected: 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 } // namespace bslamp2

components/xiaomi_bslamp2/light/color_white_light.h → components/xiaomi_bslamp2/light/color_handler_color_temperature.h View File

@ -4,8 +4,8 @@
#include <stdexcept> #include <stdexcept>
#include "../common.h" #include "../common.h"
#include "light_modes.h"
#include "gpio_outputs.h"
#include "../light_hal.h"
#include "color_handler.h"
namespace esphome { namespace esphome {
namespace xiaomi { namespace xiaomi {
@ -75,20 +75,14 @@ static const RGBWLevelsTable rgbw_levels_100_ {{
* This class can handle the GPIO outputs for the white light mode, * This class can handle the GPIO outputs for the white light mode,
* based on color temperature + brightness. * based on color temperature + brightness.
*/ */
class ColorWhiteLight : public GPIOOutputs {
class ColorHandlerColorTemperature : public ColorHandler {
public: public:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
light_mode = LIGHT_MODE_WHITE; light_mode = LIGHT_MODE_WHITE;
#ifdef HAS_COLOR_MODES
if (v.get_color_mode() != light::ColorMode::COLOR_TEMPERATURE) { if (v.get_color_mode() != light::ColorMode::COLOR_TEMPERATURE) {
return false; return false;
} }
#else
if (v.get_white() == 0.0f) {
return false;
}
#endif
auto temperature = clamp_temperature_(v.get_color_temperature()); auto temperature = clamp_temperature_(v.get_color_temperature());
auto brightness = clamp_brightness_(v.get_brightness()); auto brightness = clamp_brightness_(v.get_brightness());

components/xiaomi_bslamp2/light/color_night_light.h → components/xiaomi_bslamp2/light/color_handler_night_light.h View File

@ -1,8 +1,8 @@
#pragma once #pragma once
#include "../common.h" #include "../common.h"
#include "gpio_outputs.h"
#include "light_modes.h"
#include "../light_hal.h"
#include "color_handler.h"
namespace esphome { namespace esphome {
namespace xiaomi { namespace xiaomi {
@ -22,7 +22,7 @@ namespace bslamp2 {
* light mode toggle, then this still could be implemented through the * light mode toggle, then this still could be implemented through the
* device's yaml configuration. * device's yaml configuration.
*/ */
class ColorNightLight : public GPIOOutputs {
class ColorHandlerNightLight : public ColorHandler {
public: public:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
light_mode = LIGHT_MODE_NIGHT; light_mode = LIGHT_MODE_NIGHT;
@ -36,7 +36,7 @@ class ColorNightLight : public GPIOOutputs {
// This night light mode is activated when white light is selected. // This night light mode is activated when white light is selected.
// Based on measurements using the original device firmware, so it // Based on measurements using the original device firmware, so it
// matches the night light of the original firmware. // 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; red = 0.968f;
green = 0.968f; green = 0.968f;
blue = 0.972f; blue = 0.972f;

components/xiaomi_bslamp2/light/color_off.h → components/xiaomi_bslamp2/light/color_handler_off.h View File

@ -1,8 +1,8 @@
#pragma once #pragma once
#include "../common.h" #include "../common.h"
#include "gpio_outputs.h"
#include "light_modes.h"
#include "../light_hal.h"
#include "color_handler.h"
namespace esphome { namespace esphome {
namespace xiaomi { namespace xiaomi {
@ -11,7 +11,7 @@ namespace bslamp2 {
/** /**
* This class can handle the GPIO outputs in case the light of turned off. * This class can handle the GPIO outputs in case the light of turned off.
*/ */
class ColorOff : public GPIOOutputs {
class ColorHandlerOff : public ColorHandler {
public: public:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
light_mode = LIGHT_MODE_OFF; light_mode = LIGHT_MODE_OFF;

components/xiaomi_bslamp2/light/color_rgb_light.h → components/xiaomi_bslamp2/light/color_handler_rgb.h View File

@ -4,8 +4,8 @@
#include <cmath> #include <cmath>
#include "../common.h" #include "../common.h"
#include "gpio_outputs.h"
#include "light_modes.h"
#include "../light_hal.h"
#include "color_handler.h"
namespace esphome { namespace esphome {
namespace xiaomi { namespace xiaomi {
@ -247,20 +247,14 @@ static const RGBCircle rgb_circle_ {{
* This class can handle the GPIO outputs for the RGB light mode, * This class can handle the GPIO outputs for the RGB light mode,
* based on RGB color values + brightness. * based on RGB color values + brightness.
*/ */
class ColorRGBLight : public GPIOOutputs {
class ColorHandlerRGB : public ColorHandler {
public: public:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
light_mode = LIGHT_MODE_RGB; light_mode = LIGHT_MODE_RGB;
#ifdef HAS_COLOR_MODES
if (v.get_color_mode() != light::ColorMode::RGB) { if (v.get_color_mode() != light::ColorMode::RGB) {
return false; 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 // 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 // and 7, determining in what ring of the RGB circle the requested

+ 0
- 136
components/xiaomi_bslamp2/light/color_transition_handler.h View File

@ -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

+ 0
- 16
components/xiaomi_bslamp2/light/interfaces.h View File

@ -4,22 +4,6 @@ namespace esphome {
namespace xiaomi { namespace xiaomi {
namespace bslamp2 { 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 * This is an interface definition that is used to extend the LightState
* class with functionality for disco actions (immediate light updates, * class with functionality for disco actions (immediate light updates,


+ 0
- 15
components/xiaomi_bslamp2/light/light_modes.h View File

@ -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

+ 13
- 40
components/xiaomi_bslamp2/light/light_output.h View File

@ -2,8 +2,9 @@
#include "../common.h" #include "../common.h"
#include "../light_hal.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" #include "esphome/components/ledc/ledc_output.h"
namespace esphome { namespace esphome {
@ -28,20 +29,16 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput {
*/ */
light::LightTraits get_traits() override { light::LightTraits get_traits() override {
auto traits = light::LightTraits(); auto traits = light::LightTraits();
#ifdef HAS_COLOR_MODES
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); 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_min_mireds(MIRED_MIN);
traits.set_max_mireds(MIRED_MAX); traits.set_max_mireds(MIRED_MAX);
return traits; return traits;
} }
std::unique_ptr<light::LightTransformer> create_default_transition() override {
return make_unique<XiaomiBslamp2LightTransitionTransformer>(light_);
}
void add_on_light_mode_callback(std::function<void(std::string)> &&callback) { void add_on_light_mode_callback(std::function<void(std::string)> &&callback) {
light_mode_callback_.add(std::move(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) { void write_state(light::LightState *state) {
auto values = state->current_values; 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 // Note: one might think that it is more logical to turn on the LED
// circuitry master switch after setting the individual channels, // circuitry master switch after setting the individual channels,
@ -80,8 +65,8 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput {
if (values.get_state() != 0) if (values.get_state() != 0)
light_->turn_on(); 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) if (values.get_state() == 0)
light_->turn_off(); light_->turn_off();
@ -89,21 +74,9 @@ class XiaomiBslamp2LightOutput : public Component, public light::LightOutput {
protected: protected:
LightHAL *light_; LightHAL *light_;
ColorTransitionHandler *transition_handler_;
ColorInstantHandler *instant_handler_ = new ColorInstantHandler();
ColorHandler *color_handler_chain = new ColorHandlerChain();
CallbackManager<void(std::string)> light_mode_callback_{}; CallbackManager<void(std::string)> light_mode_callback_{};
CallbackManager<void(light::LightColorValues)> state_callback_{}; CallbackManager<void(light::LightColorValues)> 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 } // namespace bslamp2


+ 3
- 13
components/xiaomi_bslamp2/light/light_state.h View File

@ -24,22 +24,12 @@ struct MyLightStateRTCState {
/** /**
* A custom LightState class for the Xiaomi Bedside Lamp 2. * 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. * 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: 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() { void disco_stop() {
MyLightStateRTCState recovered{}; MyLightStateRTCState recovered{};


+ 62
- 0
components/xiaomi_bslamp2/light/light_transformer.h View File

@ -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<light::LightColorValues> 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

+ 69
- 22
components/xiaomi_bslamp2/light_hal.h View File

@ -8,39 +8,86 @@ namespace esphome {
namespace xiaomi { namespace xiaomi {
namespace bslamp2 { 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: 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() { 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) { 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: 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 } // namespace bslamp2


+ 1
- 1
doc/installation.md View File

@ -2,7 +2,7 @@
# Installation guide # 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, 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 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). documentation on the [ESPHome website](https://esphome.io).


+ 8
- 0
doc/testplan.md View File

@ -65,3 +65,11 @@ been introduced.
## Step 3: Release the new version ## Step 3: Release the new version
Only after performing these tests successfully, the new version can be released. 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

+ 10
- 12
example.yaml View File

@ -3,17 +3,18 @@
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
substitutions: substitutions:
name: bedside_lamp
name: bedside-lamp
friendly_name: Bedside Lamp friendly_name: Bedside Lamp
transition_length: 500ms 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. # Use your own preferences for these components.
@ -39,9 +40,6 @@ api:
# flicker. # flicker.
reboot_timeout: 0s reboot_timeout: 0s
api:
password: !secret api_password
# If you want to use light presets (see below) from Home Assistant, # If you want to use light presets (see below) from Home Assistant,
# then you can expose the required functionality as a service here. # 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. # 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. # for the lamp in Home Assistant.
text_sensor: text_sensor:
- platform: xiaomi_bslamp2 - platform: xiaomi_bslamp2
name: ${name} Light Mode
name: ${friendly_name} Light Mode
id: ${id_light_mode} id: ${id_light_mode}
# This float output controls the front panel illumination + level indicator. # This float output controls the front panel illumination + level indicator.


Loading…
Cancel
Save