diff --git a/CODEOWNERS b/CODEOWNERS index 1edea18157..714ef58538 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -29,6 +29,7 @@ esphome/components/canbus/* @danielschramm @mvturnho esphome/components/captive_portal/* @OttoWinter esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet +esphome/components/color_temperature/* @jesserockz esphome/components/coolix/* @glmnet esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun @@ -98,6 +99,7 @@ esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet esphome/components/restart/* @esphome/core esphome/components/rf_bridge/* @jesserockz +esphome/components/rgbct/* @jesserockz esphome/components/rtttl/* @glmnet esphome/components/script/* @esphome/core esphome/components/sdm_meter/* @jesserockz @polyfaces diff --git a/esphome/components/color_temperature/__init__.py b/esphome/components/color_temperature/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/color_temperature/ct_light_output.h b/esphome/components/color_temperature/ct_light_output.h new file mode 100644 index 0000000000..4ff86c8b80 --- /dev/null +++ b/esphome/components/color_temperature/ct_light_output.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/components/light/light_output.h" +#include "esphome/components/output/float_output.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace color_temperature { + +class CTLightOutput : public light::LightOutput { + public: + void set_color_temperature(output::FloatOutput *color_temperature) { color_temperature_ = color_temperature; } + 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; } + 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_); + return traits; + } + void write_state(light::LightState *state) override { + float color_temperature, brightness; + state->current_values_as_ct(&color_temperature, &brightness); + this->color_temperature_->set_level(color_temperature); + this->brightness_->set_level(brightness); + } + + protected: + output::FloatOutput *color_temperature_; + output::FloatOutput *brightness_; + float cold_white_temperature_; + float warm_white_temperature_; +}; + +} // namespace color_temperature +} // namespace esphome diff --git a/esphome/components/color_temperature/light.py b/esphome/components/color_temperature/light.py new file mode 100644 index 0000000000..3e7a0e73ae --- /dev/null +++ b/esphome/components/color_temperature/light.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light, output +from esphome.const import ( + CONF_BRIGHTNESS, + CONF_COLOR_TEMPERATURE, + CONF_OUTPUT_ID, + CONF_COLD_WHITE_COLOR_TEMPERATURE, + CONF_WARM_WHITE_COLOR_TEMPERATURE, +) + +CODEOWNERS = ["@jesserockz"] + +color_temperature_ns = cg.esphome_ns.namespace("color_temperature") +CTLightOutput = color_temperature_ns.class_("CTLightOutput", light.LightOutput) + +CONFIG_SCHEMA = cv.All( + light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CTLightOutput), + cv.Required(CONF_COLOR_TEMPERATURE): cv.use_id(output.FloatOutput), + 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, + } + ), + light.validate_color_temperature_channels, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await light.register_light(var, config) + + color_temperature = await cg.get_variable(config[CONF_COLOR_TEMPERATURE]) + cg.add(var.set_color_temperature(color_temperature)) + + brightness = await cg.get_variable(config[CONF_BRIGHTNESS]) + cg.add(var.set_brightness(brightness)) + + 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])) diff --git a/esphome/components/cwww/light.py b/esphome/components/cwww/light.py index 734f9aa1e7..fc204b2f3b 100644 --- a/esphome/components/cwww/light.py +++ b/esphome/components/cwww/light.py @@ -2,6 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import light, output from esphome.const import ( + CONF_CONSTANT_BRIGHTNESS, CONF_OUTPUT_ID, CONF_COLD_WHITE, CONF_WARM_WHITE, @@ -12,8 +13,6 @@ from esphome.const import ( cwww_ns = cg.esphome_ns.namespace("cwww") CWWWLightOutput = cwww_ns.class_("CWWWLightOutput", light.LightOutput) -CONF_CONSTANT_BRIGHTNESS = "constant_brightness" - CONFIG_SCHEMA = cv.All( light.RGB_LIGHT_SCHEMA.extend( { diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index c696ed8516..dd74c396c6 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -163,6 +163,13 @@ class LightColorValues { this->as_cwww(cold_white, warm_white, gamma, constant_brightness); } + /// 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 { + this->as_rgb(red, green, blue, gamma); + this->as_ct(color_temperature_cw, color_temperature_ww, color_temperature, white_brightness, gamma); + } + /// Convert these light color values to an CWWW representation with the given parameters. void as_cwww(float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false) const { if (this->color_mode_ & ColorCapability::COLD_WARM_WHITE) { @@ -187,6 +194,19 @@ 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 { + const float white_level = this->color_mode_ & ColorCapability::RGB ? this->white_ : 1; + 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); + } else { // Probably wont get here but put this here anyway. + *white_brightness = 0; + } + } + /// Compare this LightColorValues to rhs, return true if and only if all attributes match. bool operator==(const LightColorValues &rhs) const { return color_mode_ == rhs.color_mode_ && state_ == rhs.state_ && brightness_ == rhs.brightness_ && diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index d24b40e68e..3c4a10c88e 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -175,13 +175,23 @@ void LightState::current_values_as_rgbw(float *red, float *green, float *blue, f } void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, bool constant_brightness) { - auto traits = this->get_traits(); this->current_values.as_rgbww(red, green, blue, cold_white, warm_white, this->gamma_correct_, constant_brightness); } +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_); +} void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) { auto traits = this->get_traits(); this->current_values.as_cwww(cold_white, warm_white, this->gamma_correct_, constant_brightness); } +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_); +} void LightState::start_effect_(uint32_t effect_index) { this->stop_effect_(); diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index b0c60c625a..23527e8a47 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -128,8 +128,13 @@ class LightState : public Nameable, public Component { void current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, bool constant_brightness = false); + void current_values_as_rgbct(float *red, float *green, float *blue, float *color_temperature, + float *white_brightness); + void current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness = false); + void current_values_as_ct(float *color_temperature, float *white_brightness); + protected: friend LightOutput; friend LightCall; diff --git a/esphome/components/rgbct/__init__.py b/esphome/components/rgbct/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/rgbct/light.py b/esphome/components/rgbct/light.py new file mode 100644 index 0000000000..e525c207c7 --- /dev/null +++ b/esphome/components/rgbct/light.py @@ -0,0 +1,59 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import light, output +from esphome.const import ( + CONF_BLUE, + CONF_COLOR_TEMPERATURE, + CONF_GREEN, + CONF_RED, + CONF_OUTPUT_ID, + CONF_COLD_WHITE_COLOR_TEMPERATURE, + CONF_WARM_WHITE_COLOR_TEMPERATURE, +) + +CODEOWNERS = ["@jesserockz"] + +rgbct_ns = cg.esphome_ns.namespace("rgbct") +RGBCTLightOutput = rgbct_ns.class_("RGBCTLightOutput", light.LightOutput) + +CONF_COLOR_INTERLOCK = "color_interlock" +CONF_WHITE_BRIGHTNESS = "white_brightness" + +CONFIG_SCHEMA = cv.All( + light.RGB_LIGHT_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBCTLightOutput), + cv.Required(CONF_RED): cv.use_id(output.FloatOutput), + cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), + cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), + cv.Required(CONF_COLOR_TEMPERATURE): cv.use_id(output.FloatOutput), + 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_COLOR_INTERLOCK, default=False): cv.boolean, + } + ), + light.validate_color_temperature_channels, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await light.register_light(var, config) + + red = await cg.get_variable(config[CONF_RED]) + cg.add(var.set_red(red)) + green = await cg.get_variable(config[CONF_GREEN]) + cg.add(var.set_green(green)) + blue = await cg.get_variable(config[CONF_BLUE]) + cg.add(var.set_blue(blue)) + + color_temp = await cg.get_variable(config[CONF_COLOR_TEMPERATURE]) + cg.add(var.set_color_temperature(color_temp)) + white_brightness = await cg.get_variable(config[CONF_WHITE_BRIGHTNESS]) + cg.add(var.set_white_brightness(white_brightness)) + + 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])) + + 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 new file mode 100644 index 0000000000..9257d67cd1 --- /dev/null +++ b/esphome/components/rgbct/rgbct_light_output.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/components/light/color_mode.h" +#include "esphome/components/light/light_output.h" +#include "esphome/components/output/float_output.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace rgbct { + +class RGBCTLightOutput : public light::LightOutput { + public: + void set_red(output::FloatOutput *red) { red_ = red; } + void set_green(output::FloatOutput *green) { green_ = green; } + void set_blue(output::FloatOutput *blue) { blue_ = blue; } + + void set_color_temperature(output::FloatOutput *color_temperature) { color_temperature_ = color_temperature; } + void set_white_brightness(output::FloatOutput *white_brightness) { white_brightness_ = white_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_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } + + light::LightTraits get_traits() override { + auto traits = light::LightTraits(); + if (this->color_interlock_) + traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); + else + 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_); + return traits; + } + void write_state(light::LightState *state) override { + float red, green, blue, color_temperature, white_brightness; + + state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &white_brightness); + + this->red_->set_level(red); + this->green_->set_level(green); + this->blue_->set_level(blue); + this->color_temperature_->set_level(color_temperature); + this->white_brightness_->set_level(white_brightness); + } + + protected: + output::FloatOutput *red_; + output::FloatOutput *green_; + output::FloatOutput *blue_; + output::FloatOutput *color_temperature_; + output::FloatOutput *white_brightness_; + float cold_white_temperature_; + float warm_white_temperature_; + bool color_interlock_{true}; +}; + +} // namespace rgbct +} // namespace esphome diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index 37bc668215..c0ce85e267 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -3,6 +3,7 @@ import esphome.config_validation as cv from esphome.components import light, output from esphome.const import ( CONF_BLUE, + CONF_CONSTANT_BRIGHTNESS, CONF_GREEN, CONF_RED, CONF_OUTPUT_ID, @@ -15,7 +16,6 @@ from esphome.const import ( rgbww_ns = cg.esphome_ns.namespace("rgbww") RGBWWLightOutput = rgbww_ns.class_("RGBWWLightOutput", light.LightOutput) -CONF_CONSTANT_BRIGHTNESS = "constant_brightness" CONF_COLOR_INTERLOCK = "color_interlock" CONFIG_SCHEMA = cv.All( diff --git a/esphome/const.py b/esphome/const.py index a2f49fe3ca..9f3ce13b46 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -134,6 +134,7 @@ CONF_COMPONENTS = "components" CONF_CONDITION = "condition" CONF_CONDITION_ID = "condition_id" CONF_CONDUCTIVITY = "conductivity" +CONF_CONSTANT_BRIGHTNESS = "constant_brightness" CONF_CONTRAST = "contrast" CONF_COOL_ACTION = "cool_action" CONF_COOL_DEADBAND = "cool_deadband" diff --git a/tests/test1.yaml b/tests/test1.yaml index 2bc767c885..9b7139de3a 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1429,6 +1429,16 @@ light: cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds color_interlock: true + - platform: rgbct + name: 'Living Room Lights 2' + red: pca_3 + green: pca_4 + blue: pca_5 + color_temperature: pca_6 + white_brightness: pca_6 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds + color_interlock: true - platform: cwww name: 'Living Room Lights 2' cold_white: pca_6 @@ -1436,6 +1446,12 @@ light: cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds constant_brightness: true + - platform: color_temperature + name: 'Living Room Lights 2' + color_temperature: pca_6 + brightness: pca_6 + cold_white_color_temperature: 153 mireds + warm_white_color_temperature: 500 mireds - platform: fastled_clockless id: addr1 chipset: WS2811