Browse Source

Another round of code cleanup. Almost done, one more pass and I'm good to continue with the next task: the touch panel.

pull/3/head
Maurice Makaay 3 years ago
parent
commit
46e4b69c7c
9 changed files with 403 additions and 372 deletions
  1. +14
    -14
      color_instant_handler.h
  2. +14
    -13
      color_night_light.h
  3. +5
    -5
      color_off.h
  4. +26
    -31
      color_rgb_light.h
  5. +139
    -0
      color_transition_handler.h
  6. +22
    -22
      color_white_light.h
  7. +2
    -42
      common.h
  8. +46
    -0
      gpio_outputs.h
  9. +135
    -245
      light.h

color_translator.h → color_instant_handler.h View File

@ -4,6 +4,7 @@
#include <stdexcept> #include <stdexcept>
#include "common.h" #include "common.h"
#include "gpio_outputs.h"
#include "color_off.h" #include "color_off.h"
#include "color_night_light.h" #include "color_night_light.h"
#include "color_white_light.h" #include "color_white_light.h"
@ -13,20 +14,20 @@ namespace esphome {
namespace yeelight { namespace yeelight {
namespace bs2 { namespace bs2 {
/// This class translates LightColorValues into GPIO duty cycles
/// for representing a requested light color.
///
/// The code handles all known light modes for the device:
///
/// - off: the light is off
/// - night light: activated when brightness is at its lowest
/// - white light: based on color temperature + brightness
/// - RGB light: based on RGB values + brightness
class ColorTranslator : public GPIOOutputs {
public:
/**
* This class translates LightColorValues into GPIO duty cycles for
* representing a requested light color.
*
* The code handles all known light modes for the device:
*
* - off: the light is off
* - night light: based on RGB or white mode + lowest possible brightness
* - white light: based on color temperature + brightness
* - RGB light: based on RGB values + brightness
*/
class ColorInstantHandler : public GPIOOutputs {
protected:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
values = v;
GPIOOutputs *delegate; GPIOOutputs *delegate;
// The actual implementation of the various light modes is in // The actual implementation of the various light modes is in
@ -48,7 +49,6 @@ public:
return true; return true;
} }
protected:
GPIOOutputs *off_light_ = new ColorOff(); GPIOOutputs *off_light_ = new ColorOff();
GPIOOutputs *rgb_light_ = new ColorRGBLight(); GPIOOutputs *rgb_light_ = new ColorRGBLight();
GPIOOutputs *white_light_ = new ColorWhiteLight(); GPIOOutputs *white_light_ = new ColorWhiteLight();

+ 14
- 13
color_night_light.h View File

@ -1,31 +1,32 @@
#pragma once #pragma once
#include "common.h" #include "common.h"
#include "gpio_outputs.h"
namespace esphome { namespace esphome {
namespace yeelight { namespace yeelight {
namespace bs2 { namespace bs2 {
/**
* This class can handle the GPIO outputs for the night light mode.
*
* At the lowest brightness setting, the light will 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.
* Using the lowest brightness for triggering the night light feels a lot
* more natural.
*/
class ColorNightLight : public GPIOOutputs { class ColorNightLight : public GPIOOutputs {
public:
protected:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
// This class can handle the GPIO outputs for the night light mode.
//
// 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%),
// Note: I do not check for a brightness at or below 0.01 (1%) here,
// because the lowest brightness setting from Home Assistant turns // because the lowest brightness setting from Home Assistant turns
// up as 0.011765 in here (which is 3/255).
// up as 0.011765 in here (which is 3/255 and not 1/100).
if (v.get_brightness() >= 0.012f) { if (v.get_brightness() >= 0.012f) {
return false; return false;
} }
values = v;
// 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.


+ 5
- 5
color_off.h View File

@ -4,22 +4,22 @@
#include <stdexcept> #include <stdexcept>
#include "common.h" #include "common.h"
#include "gpio_outputs.h"
namespace esphome { namespace esphome {
namespace yeelight { namespace yeelight {
namespace bs2 { namespace bs2 {
/**
* This class can handle the GPIO outputs in case the light of turned off.
*/
class ColorOff : public GPIOOutputs { class ColorOff : public GPIOOutputs {
public:
protected:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
// This class can handle the light settings when the light is turned
// off or the brightness is set to zero.
if (v.get_state() != 0.0f && v.get_brightness() != 0.0f) { if (v.get_state() != 0.0f && v.get_brightness() != 0.0f) {
return false; return false;
} }
values = v;
red = 1.0f; red = 1.0f;
green = 1.0f; green = 1.0f;
blue = 1.0f; blue = 1.0f;


+ 26
- 31
color_rgb_light.h View File

@ -1,13 +1,10 @@
/**
* This code implements the RGB light mode (based on RGB + brightness)
* for the Yeelight Bedside Lamp 2.
*/
#pragma once #pragma once
#include <array> #include <array>
#include <cmath> #include <cmath>
#include "common.h" #include "common.h"
#include "gpio_outputs.h"
namespace esphome { namespace esphome {
namespace yeelight { namespace yeelight {
@ -29,28 +26,27 @@ using RGBRing = std::array<RGBPoint, 24>;
using RGBCircle = std::array<RGBRing, 7>; using RGBCircle = std::array<RGBRing, 7>;
/** /**
* The following table contains GPIO PWM duty cycles as used for driving
* the LEDs in the device in RGB mode.
* The following table contains GPIO PWM duty cycles as used for driving the
* LEDs in the device in RGB mode.
* *
* The base for this table are measurements against the original device * The base for this table are measurements against the original device
* firmware, using the RGB color circle as used in Home Assistant as the * firmware, using the RGB color circle as used in Home Assistant as the
* color space model. * color space model.
* *
* This circle has 7 colored rings around a white center point.
* The outer ring, with the highest saturation, is numbered as 0.
* The inner ring around the white center point is numbered as 6.
* The white center point itself is numbered as 7, although this one
* cannot really be called "a ring".
* This circle has 7 colored rings around a white center point. The outer
* ring, with the highest saturation, is numbered as 0. The inner ring
* around the white center point is numbered as 6. The white center point
* itself is numbered as 7, although this one cannot really be called "a
* ring".
* *
* For each ring, there are 24 color positions, starting at the
* color red (0°), going around the circle clockwise via
* green (120°) and blue (240°).
* For each ring, there are 24 color positions, starting at the color red
* (0°), going around the circle clockwise via green (120°) and blue (240°).
* *
* For each color position, two duty cycle measurements are registered: * For each color position, two duty cycle measurements are registered:
* - one defining the duty cycles at 1% brightness * - one defining the duty cycles at 1% brightness
* - one defining the duty cycles at 100% brightness
* Duty cycles for in-between brightnesses can be derived from these
* values by means of linear interpolation.
* - one defining the duty cycles at 100% brightness Duty cycles for
* in-between brightnesses can be derived from these values by means of
* linear interpolation.
*/ */
static const RGBCircle rgb_circle_ {{ static const RGBCircle rgb_circle_ {{
// Ring 0, min value RGB component value = 0 // Ring 0, min value RGB component value = 0
@ -244,19 +240,19 @@ 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 ColorRGBLight : public GPIOOutputs {
public:
protected:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
// This class can handle the GPIO outputs for RGB light, based
// on RGB color values + brightness.
if (v.get_white() > 0.0f) { if (v.get_white() > 0.0f) {
return false; return false;
} }
values = v;
// 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
// 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. // color resides.
auto rgb_min = min(min(v.get_red(), v.get_green()), v.get_blue()); auto rgb_min = min(min(v.get_red(), v.get_green()), v.get_blue());
auto level = 7.0f * rgb_min; auto level = 7.0f * rgb_min;
@ -282,6 +278,7 @@ public:
// Almost there! We now have the correct duty cycles for the // Almost there! We now have the correct duty cycles for the
// two rings that we were looking at. In this last step, the // two rings that we were looking at. In this last step, the
// two values are interpolated based on the ring level. // two values are interpolated based on the ring level.
// TODO use esphome::lerp ?
auto d = level - level_a; auto d = level - level_a;
red = rgb_a_.red + d * (rgb_b_.red - rgb_a_.red); red = rgb_a_.red + d * (rgb_b_.red - rgb_a_.red);
green = rgb_a_.green + d * (rgb_b_.green - rgb_a_.green); green = rgb_a_.green + d * (rgb_b_.green - rgb_a_.green);
@ -293,7 +290,6 @@ public:
return true; return true;
} }
protected:
RGBPoint rgbp_a_; RGBPoint rgbp_a_;
RGBPoint rgbp_b_; RGBPoint rgbp_b_;
RGB rgb_a_; RGB rgb_a_;
@ -311,8 +307,8 @@ protected:
return; return;
} }
// Other ring levels are more complex. Start by retrieving the
// duty cycle measurement data for the ring at hand.
// Other ring levels are more complex. Start by retrieving the duty
// cycle measurement data for the ring at hand.
auto ring = rgb_circle_[ring_level]; auto ring = rgb_circle_[ring_level];
// Because we only have a subset of all colors in the RGB ring // Because we only have a subset of all colors in the RGB ring
@ -367,10 +363,9 @@ protected:
} }
/** /**
* Apply brightness interpolation to the duty cycle measurements.
* We have the low (0.01) and high (1.00) brightness measurements
* in the data. Brightness can be applied by means of linear
* interpolation.
* Apply brightness interpolation to the duty cycle measurements. We
* have the low (0.01) and high (1.00) brightness measurements in the
* data. Brightness can be applied by means of linear interpolation.
*/ */
void apply_brightness_(RGBPoint *p, float brightness, RGB *rgb) { void apply_brightness_(RGBPoint *p, float brightness, RGB *rgb) {
auto d = brightness - 0.01f; auto d = brightness - 0.01f;


+ 139
- 0
color_transition_handler.h View File

@ -0,0 +1,139 @@
#pragma once
#include "common.h"
#include "gpio_outputs.h"
#include "color_instant_handler.h"
namespace esphome {
namespace yeelight {
namespace bs2 {
/**
* 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 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) {}
protected:
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_);
}
// Determine required GPIO outputs for current transition progress.
progress_ = transformer_->get_progress();
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;
}
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 yeelight_bs2
} // namespace yeelight
} // namespace bs2

+ 22
- 22
color_white_light.h View File

@ -1,18 +1,27 @@
/**
* This code implements the white light mode (based on temperature +
* brightness) for the Yeelight Bedside Lamp 2.
*/
#pragma once #pragma once
#include <array> #include <array>
#include <stdexcept> #include <stdexcept>
#include "common.h" #include "common.h"
#include "gpio_outputs.h"
namespace esphome { namespace esphome {
namespace yeelight { namespace yeelight {
namespace bs2 { namespace bs2 {
/**
* The minimum color temperature in mired. Same as supported by
* the original Yeelight firmware.
*/
static const int MIRED_MIN = 153;
/**
* The maximum color temperature in mired. Same as supported by
* the original Yeelight firmware.
*/
static const int MIRED_MAX = 588;
struct RGBWLevelsByTemperature { struct RGBWLevelsByTemperature {
float from_temperature; float from_temperature;
float red; float red;
@ -59,32 +68,31 @@ static const RGBWLevelsTable rgbw_levels_100_ {{
{ 153.0f, 1.000f, 0.000f, 0.187f, 0.335f } { 153.0f, 1.000f, 0.000f, 0.187f, 0.335f }
}}; }};
/**
* This class can handle the GPIO outputs for the white light mode,
* based on color temperature + brightness.
*/
class ColorWhiteLight : public GPIOOutputs { class ColorWhiteLight : public GPIOOutputs {
public:
protected:
bool set_light_color_values(light::LightColorValues v) { bool set_light_color_values(light::LightColorValues v) {
// This class can handle the light settings when white light is
// requested, based on color temperature + brightness.
if (v.get_white() == 0.0f) { if (v.get_white() == 0.0f) {
return false; return false;
} }
values = v;
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());
auto levels_1 = lookup_in_table_(rgbw_levels_1_, temperature); auto levels_1 = lookup_in_table_(rgbw_levels_1_, temperature);
auto levels_100 = lookup_in_table_(rgbw_levels_100_, temperature); auto levels_100 = lookup_in_table_(rgbw_levels_100_, temperature);
red = interpolate_(levels_1.red, levels_100.red, brightness);
green = interpolate_(levels_1.green, levels_100.green, brightness);
blue = interpolate_(levels_1.blue, levels_100.blue, brightness);
white = interpolate_(levels_1.white, levels_100.white, brightness);
red = esphome::lerp(brightness, levels_1.red, levels_100.red);
green = esphome::lerp(brightness, levels_1.green, levels_100.green);
blue = esphome::lerp(brightness, levels_1.blue, levels_100.blue);
white = esphome::lerp(brightness, levels_1.white, levels_100.white);
return true; return true;
} }
protected:
float clamp_temperature_(float temperature) float clamp_temperature_(float temperature)
{ {
if (temperature > MIRED_MAX) if (temperature > MIRED_MAX)
@ -110,14 +118,6 @@ protected:
return item; return item;
throw std::invalid_argument("received too low temperature"); throw std::invalid_argument("received too low temperature");
} }
// TODO Use esphome::lerp?
float interpolate_(float level_1, float level_100, float brightness)
{
auto coefficient = (level_100 - level_1) / 0.99f;
auto level = level_1 + (brightness - 0.01f) * coefficient;
return level;
}
}; };
} // namespace bs2 } // namespace bs2


+ 2
- 42
common.h View File

@ -4,49 +4,9 @@ namespace esphome {
namespace yeelight { namespace yeelight {
namespace bs2 { namespace bs2 {
/// A tag, used for logging.
static const char *TAG = "yeelight_bs2";
/** A tag, used for logging. */
static const char *TAG = "yeelight_bs2";
/// The minimum color temperature in mired. Same as supported by
/// the original Yeelight firmware.
static const int MIRED_MIN = 153;
/// The maximum color temperature in mired. Same as supported by
/// the original Yeelight firmware.
static const int MIRED_MAX = 588;
/// This abstract class is used for building classes that translate
/// LightColorValues into the required GPIO pin outputs to represent
/// the requested color on the device.
class GPIOOutputs {
public:
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
float white = 0.0f;
light::LightColorValues values;
/// Set the red, green, blue, white fields to the PWM duty cycles
/// that are required to represent the requested light color for
/// the provided LightColorValues input.
/// Returns true when the class can handle the input, false otherwise.
virtual bool set_light_color_values(light::LightColorValues v) = 0;
/// Copy the output values to another GPIOOutputs object.
void copy_to(GPIOOutputs *other) {
other->red = red;
other->green = green;
other->blue = blue;
other->white = white;
other->values = values;
}
void log(const char *prefix) {
ESP_LOGD(TAG, "%s: RGB=[%f,%f,%f], white=%f",
prefix, red, green, blue, white);
}
};
} // namespace bs2 } // namespace bs2
} // namespace yeelight } // namespace yeelight
} // namespace esphome } // namespace esphome

+ 46
- 0
gpio_outputs.h View File

@ -0,0 +1,46 @@
#pragma once
namespace esphome {
namespace yeelight {
namespace bs2 {
/**
* This abstract class is used for implementing classes that translate
* LightColorValues into the required GPIO PWM duty cycle levels to represent
* the requested color on the physical device.
*/
class GPIOOutputs {
public:
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
float white = 0.0f;
/**
* Sets the red, green, blue, white fields to the PWM duty cycles
* that are required to represent the requested light color for
* the provided LightColorValues input.
*
* 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;
}
void log(const char *prefix) {
ESP_LOGD(TAG, "%s: RGB=[%f,%f,%f], white=%f",
prefix, red, green, blue, white);
}
};
} // namespace bs2
} // namespace yeelight
} // namespace esphome

+ 135
- 245
light.h View File

@ -1,258 +1,148 @@
#pragma once #pragma once
#include "common.h"
#include "color_instant_handler.h"
#include "color_transition_handler.h"
namespace esphome { namespace esphome {
namespace yeelight { namespace yeelight {
namespace bs2 { namespace bs2 {
/// This is an interface definition that is used to extend the
/// YeelightBS2LightOutput class with methods to access properties
/// of an active LightTranformer from the TransitionHandler class.
///
/// The transformer is protected in the light output class, making
/// it impossible to access these properties directly from the
/// light output class.
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 class is used to handle color transition requirements.
///
/// 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 TransitionHandler : public GPIOOutputs {
public:
TransitionHandler(LightStateTransformerInspector *inspector) : transformer_(inspector) {}
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, 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_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_values);
end_->set_light_color_values(transformer_->get_end_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_->set_light_color_values(transformer_->get_end_values());
}
// Determine the required GPIO outputs for the current transition progress.
progress_ = transformer_->get_progress();
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_values;
GPIOOutputs *start_ = new ColorTranslator();
GPIOOutputs *end_ = new ColorTranslator();
/// Checks if the LightState object 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_values = transformer_->get_end_values();
auto new_progress = transformer_->get_progress();
return new_end_values != end_->values || new_progress < progress_;
}
};
/// An implementation of the LightOutput interface for the Yeelight
/// 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:
/// Set the LEDC output for the red LED circuitry channel.
void set_red_output(ledc::LEDCOutput *red) {
red_ = red;
}
/// Set the LEDC output for the green LED circuitry channel.
void set_green_output(ledc::LEDCOutput *green) {
green_ = green;
}
/// Set the LEDC output for the blue LED circuitry channel.
void set_blue_output(ledc::LEDCOutput *blue) {
blue_ = blue;
}
/// Set the LEDC output for the white LED circuitry channel.
void set_white_output(ledc::LEDCOutput *white) {
white_ = white;
}
/// Set the first GPIO binary output, used as internal master
/// switch for the LED light circuitry.
void set_master1_output(gpio::GPIOBinaryOutput *master1) {
master1_ = master1;
}
/// Set the second GPIO binary output, used as internal master
/// switch for the LED light circuitry.
void set_master2_output(gpio::GPIOBinaryOutput *master2) {
master2_ = master2;
}
/// 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
{
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;
}
/// Applies a requested light state to the physicial GPIO outputs.
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_;
} else {
instant_handler_->set_light_color_values(values);
delegate = instant_handler_;
}
// Note: one might think that it is more logical to turn on
// the LED circuitry master switch after setting the individual
// channels, but this is the order that was used by the original
// firmware. I tried to stay as close as possible to the original
// behavior, so that's why these GPIOs are turned on at this point.
if (values.get_state() != 0)
{
master2_->turn_on();
master1_->turn_on();
}
// Apply the current GPIO output levels from the selected handler.
red_->set_level(delegate->red);
green_->set_level(delegate->green);
blue_->set_level(delegate->blue);
white_->set_level(delegate->white);
if (values.get_state() == 0)
{
master2_->turn_off();
master1_->turn_off();
}
}
protected:
ledc::LEDCOutput *red_;
ledc::LEDCOutput *green_;
ledc::LEDCOutput *blue_;
ledc::LEDCOutput *white_;
esphome::gpio::GPIOBinaryOutput *master1_;
esphome::gpio::GPIOBinaryOutput *master2_;
GPIOOutputs *transition_handler_;
GPIOOutputs *instant_handler_ = new ColorTranslator();
friend class YeelightBS2LightState;
/// Called by the YeelightBS2LightState class, to set the object that
/// can be used to access protected data from the light state object.
void set_transformer_inspector(LightStateTransformerInspector *exposer) {
transition_handler_ = new TransitionHandler(exposer);
}
};
/// This custom LightState class is used to provide access to the
/// protected LightTranformer information in the LightState class.
class YeelightBS2LightState : public light::LightState, public LightStateTransformerInspector
/**
* A LightOutput class for the Yeelight 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. It forms the glue between the physical device and the
* logical light color input.
*/
class YeelightBS2LightOutput : public Component, public light::LightOutput {
public:
/** Sets the LEDC output for the red LED circuitry channel. */
void set_red_output(ledc::LEDCOutput *red) { red_ = red; }
/** Sets the LEDC output for the green LED circuitry channel. */
void set_green_output(ledc::LEDCOutput *green) { green_ = green; }
/** Sets the LEDC output for the blue LED circuitry channel. */
void set_blue_output(ledc::LEDCOutput *blue) { blue_ = blue; }
/** Sets the LEDC output for the white LED circuitry channel. */
void set_white_output(ledc::LEDCOutput *white) { white_ = white; }
/**
* Sets the first GPIO binary output, used as internal master switch for
* the LED light circuitry.
*/
void set_master1_output(gpio::GPIOBinaryOutput *master1) { master1_ = master1; }
/**
* Set the second GPIO binary output, used as internal master switch for
* the LED light circuitry.
*/
void set_master2_output(gpio::GPIOBinaryOutput *master2) { master2_ = master2; }
/**
* 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
{ {
public:
YeelightBS2LightState(const std::string &name, YeelightBS2LightOutput *output) : light::LightState(name, output) {
output->set_transformer_inspector(this);
}
bool is_active() {
return this->transformer_ != nullptr;
}
bool is_transition() {
return this->transformer_->is_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;
}
/**
* Applies a requested light state to the physicial GPIO outputs.
*/
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_;
} else {
instant_handler_->set_light_color_values(values);
delegate = instant_handler_;
}
// Note: one might think that it is more logical to turn on the LED
// circuitry master switch after setting the individual channels,
// but this is the order that was used by the original firmware. I
// tried to stay as close as possible to the original behavior, so
// that's why these GPIOs are turned on at this point.
if (values.get_state() != 0)
{
master2_->turn_on();
master1_->turn_on();
} }
light::LightColorValues get_end_values() {
return this->transformer_->get_end_values();
}
// Apply the current GPIO output levels from the selected handler.
red_->set_level(delegate->red);
green_->set_level(delegate->green);
blue_->set_level(delegate->blue);
white_->set_level(delegate->white);
float get_progress() {
return this->transformer_->get_progress();
}
};
if (values.get_state() == 0)
{
master2_->turn_off();
master1_->turn_off();
}
}
protected:
ledc::LEDCOutput *red_;
ledc::LEDCOutput *green_;
ledc::LEDCOutput *blue_;
ledc::LEDCOutput *white_;
esphome::gpio::GPIOBinaryOutput *master1_;
esphome::gpio::GPIOBinaryOutput *master2_;
GPIOOutputs *transition_handler_;
GPIOOutputs *instant_handler_ = new ColorInstantHandler();
friend class YeelightBS2LightState;
/**
* Called by the YeelightBS2LightState 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);
}
};
/**
* This custom LightState class is used to provide access to the protected
* LightTranformer information in the LightState class.
*
* This class is used by the ColorTransitionHandler class to inspect if
* an ongoing light color transition is active in a LightState object.
*/
class YeelightBS2LightState : public light::LightState, public LightStateTransformerInspector
{
public:
YeelightBS2LightState(const std::string &name, YeelightBS2LightOutput *output) : light::LightState(name, output) {
output->set_transformer_inspector(this);
}
bool is_active() { return this->transformer_ != nullptr; }
bool is_transition() { return this->transformer_->is_transition(); }
light::LightColorValues get_end_values() { return this->transformer_->get_end_values(); }
float get_progress() { return this->transformer_->get_progress(); }
};
} // namespace bs2 } // namespace bs2
} // namespace yeelight } // namespace yeelight


Loading…
Cancel
Save