From 945c41aa3b92beb20c09555196c1d3ee43622a03 Mon Sep 17 00:00:00 2001 From: Mara Sophie Grosch Date: Sun, 26 May 2024 00:11:14 +0200 Subject: [PATCH] WIP: "off" color temperature for (rgb)ct lights Some (rgb)ct lights require a certain color temperature to be set to actually turn off (the white component, in case of rgbct lights), this allows configuring that. --- .../color_temperature/ct_light_output.h | 3 +++ esphome/components/color_temperature/light.py | 19 ++++++++++++++++ esphome/components/light/light_color_values.h | 22 +++++++++++++------ esphome/components/light/light_state.cpp | 8 +++---- esphome/components/light/light_traits.h | 3 +++ esphome/components/rgbct/light.py | 19 ++++++++++++++++ esphome/components/rgbct/rgbct_light_output.h | 3 +++ esphome/const.py | 1 + 8 files changed, 67 insertions(+), 11 deletions(-) diff --git a/esphome/components/color_temperature/ct_light_output.h b/esphome/components/color_temperature/ct_light_output.h index 4ff86c8b80..a7578164dc 100644 --- a/esphome/components/color_temperature/ct_light_output.h +++ b/esphome/components/color_temperature/ct_light_output.h @@ -13,11 +13,13 @@ class CTLightOutput : public light::LightOutput { void set_brightness(output::FloatOutput *brightness) { brightness_ = brightness; } void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; } void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; } + void set_off_color_temperature(float off_color_temperature) { off_color_temperature_ = off_color_temperature; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); + traits.set_off_mireds(this->off_color_temperature_); return traits; } void write_state(light::LightState *state) override { @@ -32,6 +34,7 @@ class CTLightOutput : public light::LightOutput { output::FloatOutput *brightness_; float cold_white_temperature_; float warm_white_temperature_; + float off_color_temperature_{0}; }; } // namespace color_temperature diff --git a/esphome/components/color_temperature/light.py b/esphome/components/color_temperature/light.py index 3e7a0e73ae..1bfc12b80e 100644 --- a/esphome/components/color_temperature/light.py +++ b/esphome/components/color_temperature/light.py @@ -7,6 +7,9 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE, + CONF_OFF_COLOR_TEMPERATURE, + CONF_COLD_WHITE, + CONF_WARM_WHITE, ) CODEOWNERS = ["@jesserockz"] @@ -22,6 +25,12 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_BRIGHTNESS): cv.use_id(output.FloatOutput), cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_OFF_COLOR_TEMPERATURE): cv.enum( + { + CONF_COLD_WHITE: None, + CONF_WARM_WHITE: None, + } + ), } ), light.validate_color_temperature_channels, @@ -40,3 +49,13 @@ async def to_code(config): cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])) cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) + + if CONF_OFF_COLOR_TEMPERATURE in config: + if config[CONF_OFF_COLOR_TEMPERATURE] == CONF_COLD_WHITE: + cg.add( + var.set_off_color_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]) + ) + elif config[CONF_OFF_COLOR_TEMPERATURE] == CONF_WARM_WHITE: + cg.add( + var.set_off_color_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]) + ) diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index bad180ce6d..d2bad22195 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -165,10 +165,11 @@ class LightColorValues { } /// Convert these light color values to an RGB+CT+BR representation with the given parameters. - void as_rgbct(float color_temperature_cw, float color_temperature_ww, float *red, float *green, float *blue, - float *color_temperature, float *white_brightness, float gamma = 0) const { + void as_rgbct(float color_temperature_cw, float color_temperature_ww, float color_temperature_off, float *red, + float *green, float *blue, float *color_temperature, float *white_brightness, float gamma = 0) const { this->as_rgb(red, green, blue, gamma); - this->as_ct(color_temperature_cw, color_temperature_ww, color_temperature, white_brightness, gamma); + this->as_ct(color_temperature_cw, color_temperature_ww, color_temperature_off, color_temperature, white_brightness, + gamma); } /// Convert these light color values to an CWWW representation with the given parameters. @@ -196,16 +197,23 @@ class LightColorValues { } /// Convert these light color values to a CT+BR representation with the given parameters. - void as_ct(float color_temperature_cw, float color_temperature_ww, float *color_temperature, float *white_brightness, - float gamma = 0) const { + void as_ct(float color_temperature_cw, float color_temperature_ww, float color_temperature_off, + float *color_temperature, float *white_brightness, float gamma = 0) const { const float white_level = this->color_mode_ & ColorCapability::RGB ? this->white_ : 1; + float output_color_temperature = color_temperature_off; + if (this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) { - *color_temperature = - (this->color_temperature_ - color_temperature_cw) / (color_temperature_ww - color_temperature_cw); *white_brightness = gamma_correct(this->state_ * this->brightness_ * white_level, gamma); + + if (*white_brightness != 0.0f) { + output_color_temperature = this->color_temperature_; + } } else { // Probably won't get here but put this here anyway. *white_brightness = 0; } + + *color_temperature = + (output_color_temperature - color_temperature_cw) / (color_temperature_ww - color_temperature_cw); } /// Compare this LightColorValues to rhs, return true if and only if all attributes match. diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index fe6538e65e..9e67bdf944 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -203,8 +203,8 @@ void LightState::current_values_as_rgbww(float *red, float *green, float *blue, void LightState::current_values_as_rgbct(float *red, float *green, float *blue, float *color_temperature, float *white_brightness) { auto traits = this->get_traits(); - this->current_values.as_rgbct(traits.get_min_mireds(), traits.get_max_mireds(), red, green, blue, color_temperature, - white_brightness, this->gamma_correct_); + this->current_values.as_rgbct(traits.get_min_mireds(), traits.get_max_mireds(), traits.get_off_mireds(), red, green, + blue, color_temperature, white_brightness, this->gamma_correct_); } void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) { auto traits = this->get_traits(); @@ -212,8 +212,8 @@ void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bo } void LightState::current_values_as_ct(float *color_temperature, float *white_brightness) { auto traits = this->get_traits(); - this->current_values.as_ct(traits.get_min_mireds(), traits.get_max_mireds(), color_temperature, white_brightness, - this->gamma_correct_); + this->current_values.as_ct(traits.get_min_mireds(), traits.get_max_mireds(), traits.get_off_mireds(), + color_temperature, white_brightness, this->gamma_correct_); } bool LightState::is_transformer_active() { return this->is_transformer_active_; } diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index 7c99d721f0..4988322310 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -50,11 +50,14 @@ class LightTraits { void set_min_mireds(float min_mireds) { this->min_mireds_ = min_mireds; } float get_max_mireds() const { return this->max_mireds_; } void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; } + float get_off_mireds() const { return this->off_mireds_; } + void set_off_mireds(float off_mireds) { this->off_mireds_ = off_mireds; } protected: std::set supported_color_modes_{}; float min_mireds_{0}; float max_mireds_{0}; + float off_mireds_{0}; }; } // namespace light diff --git a/esphome/components/rgbct/light.py b/esphome/components/rgbct/light.py index 0565057316..75f6f68c2a 100644 --- a/esphome/components/rgbct/light.py +++ b/esphome/components/rgbct/light.py @@ -10,6 +10,9 @@ from esphome.const import ( CONF_OUTPUT_ID, CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE, + CONF_OFF_COLOR_TEMPERATURE, + CONF_COLD_WHITE, + CONF_WARM_WHITE, ) CODEOWNERS = ["@jesserockz"] @@ -30,6 +33,12 @@ CONFIG_SCHEMA = cv.All( cv.Required(CONF_WHITE_BRIGHTNESS): cv.use_id(output.FloatOutput), cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, + cv.Optional(CONF_OFF_COLOR_TEMPERATURE): cv.enum( + { + CONF_COLD_WHITE: None, + CONF_WARM_WHITE: None, + } + ), cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, } ), @@ -56,4 +65,14 @@ async def to_code(config): cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])) cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) + if CONF_OFF_COLOR_TEMPERATURE in config: + if config[CONF_OFF_COLOR_TEMPERATURE] == CONF_COLD_WHITE: + cg.add( + var.set_off_color_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]) + ) + elif config[CONF_OFF_COLOR_TEMPERATURE] == CONF_WARM_WHITE: + cg.add( + var.set_off_color_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]) + ) + cg.add(var.set_color_interlock(config[CONF_COLOR_INTERLOCK])) diff --git a/esphome/components/rgbct/rgbct_light_output.h b/esphome/components/rgbct/rgbct_light_output.h index 9257d67cd1..8a57f0a08e 100644 --- a/esphome/components/rgbct/rgbct_light_output.h +++ b/esphome/components/rgbct/rgbct_light_output.h @@ -19,6 +19,7 @@ class RGBCTLightOutput : public light::LightOutput { void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; } void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; } + void set_off_color_temperature(float off_color_temperature) { off_color_temperature_ = off_color_temperature; } void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } light::LightTraits get_traits() override { @@ -29,6 +30,7 @@ class RGBCTLightOutput : public light::LightOutput { traits.set_supported_color_modes({light::ColorMode::RGB_COLOR_TEMPERATURE, light::ColorMode::COLOR_TEMPERATURE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); + traits.set_off_mireds(this->off_color_temperature_); return traits; } void write_state(light::LightState *state) override { @@ -51,6 +53,7 @@ class RGBCTLightOutput : public light::LightOutput { output::FloatOutput *white_brightness_; float cold_white_temperature_; float warm_white_temperature_; + float off_color_temperature_{0}; bool color_interlock_{true}; }; diff --git a/esphome/const.py b/esphome/const.py index 616b686052..f0fb65d1e0 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -516,6 +516,7 @@ CONF_NUM_SCANS = "num_scans" CONF_NUMBER = "number" CONF_NUMBER_DATAPOINT = "number_datapoint" CONF_OE_PIN = "oe_pin" +CONF_OFF_COLOR_TEMPERATURE = "off_color_temperature" CONF_OFF_MODE = "off_mode" CONF_OFF_SPEED_CYCLE = "off_speed_cycle" CONF_OFFSET = "offset"