diff --git a/binary_sensor/__init__.py b/binary_sensor/__init__.py index bd3ba3f..2f25eb5 100644 --- a/binary_sensor/__init__.py +++ b/binary_sensor/__init__.py @@ -22,11 +22,12 @@ def validate_part(value): value = cv.string(value) return cv.enum(PARTS, upper=True, space='_')(value) -YeelightBS2Button = bs2_ns.class_("YeelightBS2Button", binary_sensor.BinarySensor, cg.Component) +YeelightBS2TouchBinarySensor = bs2_ns.class_( + "YeelightBS2TouchBinarySensor", binary_sensor.BinarySensor, cg.Component) CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(YeelightBS2Button), + cv.GenerateID(): cv.declare_id(YeelightBS2TouchBinarySensor), cv.GenerateID(CONF_FRONT_PANEL_HAL_ID): cv.use_id(FrontPanelHAL), cv.Optional(CONF_PART, default="ANY"): validate_part, } diff --git a/binary_sensor/binary_sensor.h b/binary_sensor/touch_binary_sensor.h similarity index 87% rename from binary_sensor/binary_sensor.h rename to binary_sensor/touch_binary_sensor.h index 61c2715..7602ec1 100644 --- a/binary_sensor/binary_sensor.h +++ b/binary_sensor/touch_binary_sensor.h @@ -9,10 +9,10 @@ namespace yeelight { namespace bs2 { /** - * This class implements a binary sensor for the buttons on the - * Yeelight Bedside Lamp 2. + * This class implements a binary sensor for the touch buttons + * on the Yeelight Bedside Lamp 2. */ -class YeelightBS2Button : public binary_sensor::BinarySensor, public Component { +class YeelightBS2TouchBinarySensor : public binary_sensor::BinarySensor, public Component { public: void set_parent(FrontPanelHAL *front_panel) { front_panel_ = front_panel; diff --git a/doc/example.yaml b/doc/example.yaml index 6bed889..731df7c 100644 --- a/doc/example.yaml +++ b/doc/example.yaml @@ -14,6 +14,7 @@ substitutions: id_power_button: ${name}_power_button id_color_button: ${name}_color_button id_slider_level: ${name}_slider_level + id_light_mode: ${name}_light_mode # -------------------------------------------------------------------------- # Use your own preferences for these components. @@ -100,6 +101,14 @@ light: call.set_brightness(random_float()); call.perform(); + +# This text sensor propagates the currently active light mode. +# The possible light modes are: "off", "rgb", "white" and "night". +text_sensor: + - platform: yeelight_bs2 + name: ${name} Light Mode + id: ${id_light_mode} + # This output controls the front panel illumination + level indication. # Value 0.0 turns off the illumination. # Other values (up to 1.0) turn on the illumination and set the level diff --git a/light/__init__.py b/light/__init__.py index 8e65eee..9bf1ab7 100644 --- a/light/__init__.py +++ b/light/__init__.py @@ -4,7 +4,7 @@ from esphome.components import light from esphome import automation from esphome.const import ( CONF_RED, CONF_GREEN, CONF_BLUE, CONF_WHITE, - CONF_OUTPUT_ID, CONF_TRIGGER_ID + CONF_OUTPUT_ID, CONF_TRIGGER_ID, CONF_ID ) from .. import bs2_ns, CODEOWNERS, CONF_LIGHT_HAL_ID, LightHAL @@ -17,6 +17,7 @@ CONF_ON_BRIGHTNESS = "on_brightness" YeelightBS2LightState = bs2_ns.class_("YeelightBS2LightState", light.LightState) YeelightBS2LightOutput = bs2_ns.class_("YeelightBS2LightOutput", light.LightOutput) BrightnessTrigger = bs2_ns.class_("BrightnessTrigger", automation.Trigger.template()) +LightModeTrigger = bs2_ns.class_("LightModeTrigger", automation.Trigger.template()) CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend( { diff --git a/light/automation.h b/light/automation.h index 594e448..aca3c02 100644 --- a/light/automation.h +++ b/light/automation.h @@ -12,22 +12,40 @@ namespace bs2 { class BrightnessTrigger : public Trigger { public: explicit BrightnessTrigger(YeelightBS2LightOutput *parent) { - parent->add_on_state_callback([this](light::LightColorValues values) { - auto new_brightness = values.get_brightness(); - if (values.get_state() == 0) { - new_brightness = 0.0f; + parent->add_on_state_callback( + [this](light::LightColorValues values, std::string light_mode) { + auto new_brightness = values.get_brightness(); + if (values.get_state() == 0) { + new_brightness = 0.0f; + } + new_brightness = roundf(new_brightness * 100.0f) / 100.0f; + if (last_brightness_ != new_brightness) { + trigger(new_brightness); + last_brightness_ = new_brightness; + } } - new_brightness = roundf(new_brightness * 100.0f) / 100.0f; - if (last_brightness_ != new_brightness) { - trigger(new_brightness); - last_brightness_ = new_brightness; - } - }); + ); } protected: float last_brightness_ = -1.0f; }; +class LightModeTrigger : public Trigger { +public: + explicit LightModeTrigger(YeelightBS2LightOutput *parent) { + parent->add_on_state_callback( + [this](light::LightColorValues values, std::string light_mode) { + if (last_light_mode_ != light_mode) { + trigger(light_mode); + last_light_mode_ = light_mode; + } + } + ); + } +protected: + std::string last_light_mode_ = LIGHT_MODE_UNKNOWN; +}; + } // namespace yeelight_bs2 } // namespace yeelight } // namespace bs2 diff --git a/light/color_instant_handler.h b/light/color_instant_handler.h index 29587e3..d9b22c5 100644 --- a/light/color_instant_handler.h +++ b/light/color_instant_handler.h @@ -50,6 +50,7 @@ public: return true; } +protected: GPIOOutputs *off_light_ = new ColorOff(); GPIOOutputs *rgb_light_ = new ColorRGBLight(); GPIOOutputs *white_light_ = new ColorWhiteLight(); diff --git a/light/color_night_light.h b/light/color_night_light.h index fdce2ba..9fc9443 100644 --- a/light/color_night_light.h +++ b/light/color_night_light.h @@ -20,6 +20,8 @@ namespace bs2 { class ColorNightLight : public GPIOOutputs { public: bool set_light_color_values(light::LightColorValues v) { + light_mode = LIGHT_MODE_NIGHT; + // Note: I do not check for a brightness at or below 0.01 (1%) here, // because the lowest brightness setting from Home Assistant turns // up as 0.011765 in here (which is 3/255 and not 1/100). diff --git a/light/color_off.h b/light/color_off.h index 36973cc..7f57935 100644 --- a/light/color_off.h +++ b/light/color_off.h @@ -16,6 +16,8 @@ namespace bs2 { class ColorOff : public GPIOOutputs { public: bool set_light_color_values(light::LightColorValues v) { + light_mode = LIGHT_MODE_OFF; + if (v.get_state() != 0.0f && v.get_brightness() != 0.0f) { return false; } diff --git a/light/color_rgb_light.h b/light/color_rgb_light.h index fffadaa..5648654 100644 --- a/light/color_rgb_light.h +++ b/light/color_rgb_light.h @@ -247,6 +247,8 @@ static const RGBCircle rgb_circle_ {{ class ColorRGBLight : public GPIOOutputs { public: bool set_light_color_values(light::LightColorValues v) { + light_mode = LIGHT_MODE_RGB; + if (v.get_white() > 0.0f) { return false; } diff --git a/light/color_transition_handler.h b/light/color_transition_handler.h index 8cc72ae..da89367 100644 --- a/light/color_transition_handler.h +++ b/light/color_transition_handler.h @@ -83,6 +83,8 @@ public: end_->set_light_color_values(end_light_values_); } + light_mode = end_->light_mode; + // Determine required GPIO outputs for current transition progress. progress_ = transformer_->get_progress(); auto smoothed = light::LightTransitionTransformer::smoothed_progress(progress_); diff --git a/light/color_white_light.h b/light/color_white_light.h index a761834..41761fd 100644 --- a/light/color_white_light.h +++ b/light/color_white_light.h @@ -75,6 +75,8 @@ static const RGBWLevelsTable rgbw_levels_100_ {{ class ColorWhiteLight : public GPIOOutputs { public: bool set_light_color_values(light::LightColorValues v) { + light_mode = LIGHT_MODE_WHITE; + if (v.get_white() == 0.0f) { return false; } diff --git a/light/gpio_outputs.h b/light/gpio_outputs.h index 342fb7c..0ff18de 100644 --- a/light/gpio_outputs.h +++ b/light/gpio_outputs.h @@ -4,6 +4,13 @@ namespace esphome { namespace yeelight { namespace bs2 { +// Light modes that can be reported by implementations of GPIOOutputs. +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" }; + /** * This abstract class is used for implementing classes that translate * LightColorValues into the required GPIO PWM duty cycle levels to represent @@ -15,6 +22,7 @@ public: 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 @@ -33,6 +41,7 @@ public: other->green = green; other->blue = blue; other->white = white; + other->light_mode = light_mode; } void log(const char *prefix) { diff --git a/light/light_output.h b/light/light_output.h index 638921e..06617e4 100644 --- a/light/light_output.h +++ b/light/light_output.h @@ -39,7 +39,7 @@ public: return traits; } - void add_on_state_callback(std::function &&callback) { + void add_on_state_callback(std::function &&callback) { state_callback_.add(std::move(callback)); } @@ -57,13 +57,17 @@ public: GPIOOutputs *delegate; if (transition_handler_->set_light_color_values(values)) { delegate = transition_handler_; - state_callback_.call(transition_handler_->get_end_values()); + state_callback_.call( + transition_handler_->get_end_values(), + delegate->light_mode); } else { instant_handler_->set_light_color_values(values); delegate = instant_handler_; - state_callback_.call(values); + state_callback_.call(values, delegate->light_mode); } + light_mode_ = delegate->light_mode; + // 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 @@ -84,11 +88,16 @@ public: light_->turn_off(); } + std::string get_light_mode() { + return light_mode_; + } + protected: LightHAL *light_; ColorTransitionHandler *transition_handler_; ColorInstantHandler *instant_handler_ = new ColorInstantHandler(); - CallbackManager state_callback_{}; + CallbackManager state_callback_{}; + std::string light_mode_; friend class YeelightBS2LightState; diff --git a/sensor/sensor.h b/sensor/slider_sensor.h similarity index 100% rename from sensor/sensor.h rename to sensor/slider_sensor.h diff --git a/text_sensor/__init__.py b/text_sensor/__init__.py new file mode 100644 index 0000000..f0f3b06 --- /dev/null +++ b/text_sensor/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import CONF_ID, CONF_OUTPUT_ID +from .. import bs2_ns, CODEOWNERS +from ..light import YeelightBS2LightOutput + +DEPENDENCIES = ["yeelight_bs2"] + +CONF_LIGHT_ID = "light_id" + +YeelightBS2LightModeTextSensor = bs2_ns.class_( + "YeelightBS2LightModeTextSensor", text_sensor.TextSensor, cg.Component +) + +CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(YeelightBS2LightModeTextSensor), + cv.GenerateID(CONF_OUTPUT_ID): cv.use_id(YeelightBS2LightOutput), + } +).extend(cv.COMPONENT_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield text_sensor.register_text_sensor(var, config) + + parent_var = yield cg.get_variable(config[CONF_OUTPUT_ID]) + cg.add(var.set_parent(parent_var)) diff --git a/text_sensor/light_mode_text_sensor.h b/text_sensor/light_mode_text_sensor.h new file mode 100644 index 0000000..91d743e --- /dev/null +++ b/text_sensor/light_mode_text_sensor.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include "../common.h" +#include "../front_panel_hal.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace yeelight { +namespace bs2 { + +/** + * A text sensor, used for propagating the active light mode on the + * Yeelight Bedside Lamp 2. + * + * The possible light modes are "off", "rgb", "white" and "night". + */ +class YeelightBS2LightModeTextSensor : public text_sensor::TextSensor, public Component { +public: + void set_parent(YeelightBS2LightOutput *light) { light_ = light; } + + void setup() { + light_->add_on_state_callback( + [this](light::LightColorValues values, std::string light_mode) { + if (last_light_mode_ != light_mode) { + publish_state(light_mode); + last_light_mode_ = light_mode; + } + } + ); + } + +protected: + YeelightBS2LightOutput *light_; + std::string last_light_mode_ = LIGHT_MODE_UNKNOWN; +}; + +} // namespace bs2 +} // namespace yeelight +} // namespace esphome