diff --git a/esphome/components/light/light_color_values.h b/esphome/components/light/light_color_values.h index 90f5570b90..cdd05ae7b7 100644 --- a/esphome/components/light/light_color_values.h +++ b/esphome/components/light/light_color_values.h @@ -179,22 +179,28 @@ class LightColorValues { } /// Convert these light color values to an RGB representation and write them to red, green, blue. - void as_rgb(float *red, float *green, float *blue, float gamma = 0) const { - *red = gamma_correct(this->state_ * this->brightness_ * this->red_, gamma); - *green = gamma_correct(this->state_ * this->brightness_ * this->green_, gamma); - *blue = gamma_correct(this->state_ * this->brightness_ * this->blue_, gamma); + void as_rgb(float *red, float *green, float *blue, float gamma = 0, bool color_interlock = false) const { + float brightness = this->state_ * this->brightness_; + if (color_interlock) { + brightness = brightness * (1.0f - this->white_); + } + *red = gamma_correct(brightness * this->red_, gamma); + *green = gamma_correct(brightness * this->green_, gamma); + *blue = gamma_correct(brightness * this->blue_, gamma); } /// Convert these light color values to an RGBW representation and write them to red, green, blue, white. - void as_rgbw(float *red, float *green, float *blue, float *white, float gamma = 0) const { - this->as_rgb(red, green, blue, gamma); + void as_rgbw(float *red, float *green, float *blue, float *white, float gamma = 0, + bool color_interlock = false) const { + this->as_rgb(red, green, blue, gamma, color_interlock); *white = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma); } /// Convert these light color values to an RGBWW representation with the given parameters. void as_rgbww(float color_temperature_cw, float color_temperature_ww, float *red, float *green, float *blue, - float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false) const { - this->as_rgb(red, green, blue, gamma); + float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false, + bool color_interlock = false) const { + this->as_rgb(red, green, blue, gamma, color_interlock); const float color_temp = clamp(this->color_temperature_, color_temperature_cw, color_temperature_ww); const float ww_fraction = (color_temp - color_temperature_cw) / (color_temperature_ww - color_temperature_cw); const float cw_fraction = 1.0f - ww_fraction; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 9aa32f6904..d34bc88f53 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -400,26 +400,51 @@ LightColorValues LightCall::validate_() { this->green_ = optional(1.0f); this->blue_ = optional(1.0f); } + // make white values binary aka 0.0f or 1.0f...this allows brightness to do its job + if (traits.get_supports_color_interlock()) { + if (*this->white_ > 0.0f) { + this->white_ = optional(1.0f); + } else { + this->white_ = optional(0.0f); + } + } } - // White to 0% if (exclusively) setting any RGB value + // White to 0% if (exclusively) setting any RGB value that isn't 255,255,255 else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { - if (!this->white_.has_value()) { + if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f && traits.get_supports_rgb_white_value() && + traits.get_supports_color_interlock()) { + this->white_ = optional(1.0f); + } else if (!this->white_.has_value() || !traits.get_supports_rgb_white_value()) { this->white_ = optional(0.0f); } } // if changing Kelvin alone, change to white light else if (this->color_temperature_.has_value()) { - if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { - this->red_ = optional(1.0f); - this->green_ = optional(1.0f); - this->blue_ = optional(1.0f); + if (!traits.get_supports_color_interlock()) { + if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { + this->red_ = optional(1.0f); + this->green_ = optional(1.0f); + this->blue_ = optional(1.0f); + } } // if setting Kelvin from color (i.e. switching to white light), set White to 100% auto cv = this->parent_->remote_values; bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f; bool now_white = *this->red_ == 1.0f && *this->blue_ == 1.0f && *this->green_ == 1.0f; - if (!this->white_.has_value() && was_color && now_white) { - this->white_ = optional(1.0f); + if (traits.get_supports_color_interlock()) { + if (cv.get_white() < 1.0f) { + this->white_ = optional(1.0f); + } + + if (was_color && !this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { + this->red_ = optional(1.0f); + this->green_ = optional(1.0f); + this->blue_ = optional(1.0f); + } + } else { + if (!this->white_.has_value() && was_color && now_white) { + this->white_ = optional(1.0f); + } } } @@ -704,17 +729,20 @@ void LightState::current_values_as_binary(bool *binary) { this->current_values.a void LightState::current_values_as_brightness(float *brightness) { this->current_values.as_brightness(brightness, this->gamma_correct_); } -void LightState::current_values_as_rgb(float *red, float *green, float *blue) { - this->current_values.as_rgb(red, green, blue, this->gamma_correct_); +void LightState::current_values_as_rgb(float *red, float *green, float *blue, bool color_interlock) { + auto traits = this->get_traits(); + this->current_values.as_rgb(red, green, blue, this->gamma_correct_, traits.get_supports_color_interlock()); } -void LightState::current_values_as_rgbw(float *red, float *green, float *blue, float *white) { - this->current_values.as_rgbw(red, green, blue, white, this->gamma_correct_); +void LightState::current_values_as_rgbw(float *red, float *green, float *blue, float *white, bool color_interlock) { + auto traits = this->get_traits(); + this->current_values.as_rgbw(red, green, blue, white, this->gamma_correct_, traits.get_supports_color_interlock()); } void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, - bool constant_brightness) { + bool constant_brightness, bool color_interlock) { auto traits = this->get_traits(); this->current_values.as_rgbww(traits.get_min_mireds(), traits.get_max_mireds(), red, green, blue, cold_white, - warm_white, this->gamma_correct_, constant_brightness); + warm_white, this->gamma_correct_, constant_brightness, + traits.get_supports_color_interlock()); } void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) { auto traits = this->get_traits(); diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index f399cc2be4..e48cf9f864 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -266,12 +266,12 @@ class LightState : public Nameable, public Component { void current_values_as_brightness(float *brightness); - void current_values_as_rgb(float *red, float *green, float *blue); + void current_values_as_rgb(float *red, float *green, float *blue, bool color_interlock = false); - void current_values_as_rgbw(float *red, float *green, float *blue, float *white); + void current_values_as_rgbw(float *red, float *green, float *blue, float *white, bool color_interlock = false); void current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white, - bool constant_brightness = false); + bool constant_brightness = false, bool color_interlock = false); void current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness = false); diff --git a/esphome/components/light/light_traits.h b/esphome/components/light/light_traits.h index 2052b55c8e..ed9c0d44ea 100644 --- a/esphome/components/light/light_traits.h +++ b/esphome/components/light/light_traits.h @@ -20,6 +20,10 @@ class LightTraits { void set_supports_color_temperature(bool supports_color_temperature) { this->supports_color_temperature_ = supports_color_temperature; } + bool get_supports_color_interlock() const { return this->supports_color_interlock_; } + void set_supports_color_interlock(bool supports_color_interlock) { + this->supports_color_interlock_ = supports_color_interlock; + } float get_min_mireds() const { return this->min_mireds_; } void set_min_mireds(float min_mireds) { this->min_mireds_ = min_mireds; } float get_max_mireds() const { return this->max_mireds_; } @@ -32,6 +36,7 @@ class LightTraits { bool supports_color_temperature_{false}; float min_mireds_{0}; float max_mireds_{0}; + bool supports_color_interlock_{false}; }; } // namespace light diff --git a/esphome/components/rgb/rgb_light_output.h b/esphome/components/rgb/rgb_light_output.h index e612c80f73..1a3bf9f614 100644 --- a/esphome/components/rgb/rgb_light_output.h +++ b/esphome/components/rgb/rgb_light_output.h @@ -12,6 +12,7 @@ class RGBLightOutput : public light::LightOutput { void set_red(output::FloatOutput *red) { red_ = red; } void set_green(output::FloatOutput *green) { green_ = green; } void set_blue(output::FloatOutput *blue) { blue_ = blue; } + light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supports_brightness(true); @@ -20,7 +21,7 @@ class RGBLightOutput : public light::LightOutput { } void write_state(light::LightState *state) override { float red, green, blue; - state->current_values_as_rgb(&red, &green, &blue); + state->current_values_as_rgb(&red, &green, &blue, false); this->red_->set_level(red); this->green_->set_level(green); this->blue_->set_level(blue); diff --git a/esphome/components/rgbw/light.py b/esphome/components/rgbw/light.py index 75d6082e5a..ca31a8229d 100644 --- a/esphome/components/rgbw/light.py +++ b/esphome/components/rgbw/light.py @@ -5,6 +5,7 @@ from esphome.const import CONF_BLUE, CONF_GREEN, CONF_RED, CONF_OUTPUT_ID, CONF_ rgbw_ns = cg.esphome_ns.namespace('rgbw') RGBWLightOutput = rgbw_ns.class_('RGBWLightOutput', light.LightOutput) +CONF_COLOR_INTERLOCK = 'color_interlock' CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBWLightOutput), @@ -12,6 +13,7 @@ CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ cv.Required(CONF_GREEN): cv.use_id(output.FloatOutput), cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput), cv.Required(CONF_WHITE): cv.use_id(output.FloatOutput), + cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, }) @@ -27,3 +29,4 @@ def to_code(config): cg.add(var.set_blue(blue)) white = yield cg.get_variable(config[CONF_WHITE]) cg.add(var.set_white(white)) + cg.add(var.set_color_interlock(config[CONF_COLOR_INTERLOCK])) diff --git a/esphome/components/rgbw/rgbw_light_output.h b/esphome/components/rgbw/rgbw_light_output.h index b58c7f9d54..90a650851b 100644 --- a/esphome/components/rgbw/rgbw_light_output.h +++ b/esphome/components/rgbw/rgbw_light_output.h @@ -13,16 +13,18 @@ class RGBWLightOutput : public light::LightOutput { void set_green(output::FloatOutput *green) { green_ = green; } void set_blue(output::FloatOutput *blue) { blue_ = blue; } void set_white(output::FloatOutput *white) { white_ = white; } + void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supports_brightness(true); + traits.set_supports_color_interlock(this->color_interlock_); traits.set_supports_rgb(true); traits.set_supports_rgb_white_value(true); return traits; } void write_state(light::LightState *state) override { float red, green, blue, white; - state->current_values_as_rgbw(&red, &green, &blue, &white); + state->current_values_as_rgbw(&red, &green, &blue, &white, this->color_interlock_); this->red_->set_level(red); this->green_->set_level(green); this->blue_->set_level(blue); @@ -34,6 +36,7 @@ class RGBWLightOutput : public light::LightOutput { output::FloatOutput *green_; output::FloatOutput *blue_; output::FloatOutput *white_; + bool color_interlock_{false}; }; } // namespace rgbw diff --git a/esphome/components/rgbww/light.py b/esphome/components/rgbww/light.py index 78f4bee630..1513a684ea 100644 --- a/esphome/components/rgbww/light.py +++ b/esphome/components/rgbww/light.py @@ -9,6 +9,7 @@ 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 = light.RGB_LIGHT_SCHEMA.extend({ cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(RGBWWLightOutput), @@ -20,6 +21,7 @@ CONFIG_SCHEMA = light.RGB_LIGHT_SCHEMA.extend({ cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature, cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature, cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean, + cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean, }) @@ -42,3 +44,4 @@ def to_code(config): cg.add(var.set_warm_white(wwhite)) cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])) cg.add(var.set_constant_brightness(config[CONF_CONSTANT_BRIGHTNESS])) + cg.add(var.set_color_interlock(config[CONF_COLOR_INTERLOCK])) diff --git a/esphome/components/rgbww/rgbww_light_output.h b/esphome/components/rgbww/rgbww_light_output.h index a975331a37..152766970e 100644 --- a/esphome/components/rgbww/rgbww_light_output.h +++ b/esphome/components/rgbww/rgbww_light_output.h @@ -17,19 +17,22 @@ class RGBWWLightOutput : 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_constant_brightness(bool constant_brightness) { constant_brightness_ = constant_brightness; } + void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; } light::LightTraits get_traits() override { auto traits = light::LightTraits(); traits.set_supports_brightness(true); traits.set_supports_rgb(true); traits.set_supports_rgb_white_value(true); traits.set_supports_color_temperature(true); + traits.set_supports_color_interlock(this->color_interlock_); 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, cwhite, wwhite; - state->current_values_as_rgbww(&red, &green, &blue, &cwhite, &wwhite, this->constant_brightness_); + state->current_values_as_rgbww(&red, &green, &blue, &cwhite, &wwhite, this->constant_brightness_, + this->color_interlock_); this->red_->set_level(red); this->green_->set_level(green); this->blue_->set_level(blue); @@ -46,6 +49,7 @@ class RGBWWLightOutput : public light::LightOutput { float cold_white_temperature_; float warm_white_temperature_; bool constant_brightness_; + bool color_interlock_{false}; }; } // namespace rgbww diff --git a/tests/test1.yaml b/tests/test1.yaml index ec09a00208..83da6da9e3 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1114,6 +1114,7 @@ light: green: pca_4 blue: pca_5 white: pca_6 + color_interlock: true - platform: rgbww name: "Living Room Lights 2" red: pca_3 @@ -1123,6 +1124,7 @@ light: warm_white: 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