Change color model to fix white channel issues (#1895)

This commit is contained in:
Oxan van Leeuwen 2021-07-08 11:37:47 +02:00 committed by GitHub
parent fd4b7d4588
commit f9797825ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 200 additions and 83 deletions

View file

@ -378,6 +378,7 @@ message LightStateResponse {
fixed32 key = 1; fixed32 key = 1;
bool state = 2; bool state = 2;
float brightness = 3; float brightness = 3;
float color_brightness = 10;
float red = 4; float red = 4;
float green = 5; float green = 5;
float blue = 6; float blue = 6;
@ -396,6 +397,8 @@ message LightCommandRequest {
bool state = 3; bool state = 3;
bool has_brightness = 4; bool has_brightness = 4;
float brightness = 5; float brightness = 5;
bool has_color_brightness = 20;
float color_brightness = 21;
bool has_rgb = 6; bool has_rgb = 6;
float red = 7; float red = 7;
float green = 8; float green = 8;

View file

@ -308,6 +308,7 @@ bool APIConnection::send_light_state(light::LightState *light) {
if (traits.get_supports_brightness()) if (traits.get_supports_brightness())
resp.brightness = values.get_brightness(); resp.brightness = values.get_brightness();
if (traits.get_supports_rgb()) { if (traits.get_supports_rgb()) {
resp.color_brightness = values.get_color_brightness();
resp.red = values.get_red(); resp.red = values.get_red();
resp.green = values.get_green(); resp.green = values.get_green();
resp.blue = values.get_blue(); resp.blue = values.get_blue();
@ -352,6 +353,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
call.set_state(msg.state); call.set_state(msg.state);
if (msg.has_brightness) if (msg.has_brightness)
call.set_brightness(msg.brightness); call.set_brightness(msg.brightness);
if (msg.has_color_brightness)
call.set_color_brightness(msg.color_brightness);
if (msg.has_rgb) { if (msg.has_rgb) {
call.set_red(msg.red); call.set_red(msg.red);
call.set_green(msg.green); call.set_green(msg.green);

View file

@ -1263,6 +1263,10 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->brightness = value.as_float(); this->brightness = value.as_float();
return true; return true;
} }
case 10: {
this->color_brightness = value.as_float();
return true;
}
case 4: { case 4: {
this->red = value.as_float(); this->red = value.as_float();
return true; return true;
@ -1291,6 +1295,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key); buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->state); buffer.encode_bool(2, this->state);
buffer.encode_float(3, this->brightness); buffer.encode_float(3, this->brightness);
buffer.encode_float(10, this->color_brightness);
buffer.encode_float(4, this->red); buffer.encode_float(4, this->red);
buffer.encode_float(5, this->green); buffer.encode_float(5, this->green);
buffer.encode_float(6, this->blue); buffer.encode_float(6, this->blue);
@ -1315,6 +1320,11 @@ void LightStateResponse::dump_to(std::string &out) const {
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" color_brightness: ");
sprintf(buffer, "%g", this->color_brightness);
out.append(buffer);
out.append("\n");
out.append(" red: "); out.append(" red: ");
sprintf(buffer, "%g", this->red); sprintf(buffer, "%g", this->red);
out.append(buffer); out.append(buffer);
@ -1359,6 +1369,10 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_brightness = value.as_bool(); this->has_brightness = value.as_bool();
return true; return true;
} }
case 20: {
this->has_color_brightness = value.as_bool();
return true;
}
case 6: { case 6: {
this->has_rgb = value.as_bool(); this->has_rgb = value.as_bool();
return true; return true;
@ -1415,6 +1429,10 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
this->brightness = value.as_float(); this->brightness = value.as_float();
return true; return true;
} }
case 21: {
this->color_brightness = value.as_float();
return true;
}
case 7: { case 7: {
this->red = value.as_float(); this->red = value.as_float();
return true; return true;
@ -1445,6 +1463,8 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(3, this->state); buffer.encode_bool(3, this->state);
buffer.encode_bool(4, this->has_brightness); buffer.encode_bool(4, this->has_brightness);
buffer.encode_float(5, this->brightness); buffer.encode_float(5, this->brightness);
buffer.encode_bool(20, this->has_color_brightness);
buffer.encode_float(21, this->color_brightness);
buffer.encode_bool(6, this->has_rgb); buffer.encode_bool(6, this->has_rgb);
buffer.encode_float(7, this->red); buffer.encode_float(7, this->red);
buffer.encode_float(8, this->green); buffer.encode_float(8, this->green);
@ -1485,6 +1505,15 @@ void LightCommandRequest::dump_to(std::string &out) const {
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
out.append(" has_color_brightness: ");
out.append(YESNO(this->has_color_brightness));
out.append("\n");
out.append(" color_brightness: ");
sprintf(buffer, "%g", this->color_brightness);
out.append(buffer);
out.append("\n");
out.append(" has_rgb: "); out.append(" has_rgb: ");
out.append(YESNO(this->has_rgb)); out.append(YESNO(this->has_rgb));
out.append("\n"); out.append("\n");

View file

@ -371,6 +371,7 @@ class LightStateResponse : public ProtoMessage {
uint32_t key{0}; uint32_t key{0};
bool state{false}; bool state{false};
float brightness{0.0f}; float brightness{0.0f};
float color_brightness{0.0f};
float red{0.0f}; float red{0.0f};
float green{0.0f}; float green{0.0f};
float blue{0.0f}; float blue{0.0f};
@ -392,6 +393,8 @@ class LightCommandRequest : public ProtoMessage {
bool state{false}; bool state{false};
bool has_brightness{false}; bool has_brightness{false};
float brightness{0.0f}; float brightness{0.0f};
bool has_color_brightness{false};
float color_brightness{0.0f};
bool has_rgb{false}; bool has_rgb{false};
float red{0.0f}; float red{0.0f};
float green{0.0f}; float green{0.0f};

View file

@ -25,10 +25,10 @@ void AddressableLight::call_setup() {
} }
Color esp_color_from_light_color_values(LightColorValues val) { Color esp_color_from_light_color_values(LightColorValues val) {
auto r = static_cast<uint8_t>(roundf(val.get_red() * 255.0f)); auto r = static_cast<uint8_t>(roundf(val.get_color_brightness() * val.get_red() * 255.0f));
auto g = static_cast<uint8_t>(roundf(val.get_green() * 255.0f)); auto g = static_cast<uint8_t>(roundf(val.get_color_brightness() * val.get_green() * 255.0f));
auto b = static_cast<uint8_t>(roundf(val.get_blue() * 255.0f)); auto b = static_cast<uint8_t>(roundf(val.get_color_brightness() * val.get_blue() * 255.0f));
auto w = static_cast<uint8_t>(roundf(val.get_white() * val.get_state() * 255.0f)); auto w = static_cast<uint8_t>(roundf(val.get_white() * 255.0f));
return Color(r, g, b, w); return Color(r, g, b, w);
} }

View file

@ -31,6 +31,7 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
TEMPLATABLE_VALUE(uint32_t, transition_length) TEMPLATABLE_VALUE(uint32_t, transition_length)
TEMPLATABLE_VALUE(uint32_t, flash_length) TEMPLATABLE_VALUE(uint32_t, flash_length)
TEMPLATABLE_VALUE(float, brightness) TEMPLATABLE_VALUE(float, brightness)
TEMPLATABLE_VALUE(float, color_brightness)
TEMPLATABLE_VALUE(float, red) TEMPLATABLE_VALUE(float, red)
TEMPLATABLE_VALUE(float, green) TEMPLATABLE_VALUE(float, green)
TEMPLATABLE_VALUE(float, blue) TEMPLATABLE_VALUE(float, blue)
@ -42,6 +43,7 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
auto call = this->parent_->make_call(); auto call = this->parent_->make_call();
call.set_state(this->state_.optional_value(x...)); call.set_state(this->state_.optional_value(x...));
call.set_brightness(this->brightness_.optional_value(x...)); call.set_brightness(this->brightness_.optional_value(x...));
call.set_color_brightness(this->color_brightness_.optional_value(x...));
call.set_red(this->red_.optional_value(x...)); call.set_red(this->red_.optional_value(x...));
call.set_green(this->green_.optional_value(x...)); call.set_green(this->green_.optional_value(x...));
call.set_blue(this->blue_.optional_value(x...)); call.set_blue(this->blue_.optional_value(x...));
@ -139,6 +141,7 @@ template<typename... Ts> class AddressableSet : public Action<Ts...> {
TEMPLATABLE_VALUE(int32_t, range_from) TEMPLATABLE_VALUE(int32_t, range_from)
TEMPLATABLE_VALUE(int32_t, range_to) TEMPLATABLE_VALUE(int32_t, range_to)
TEMPLATABLE_VALUE(uint8_t, color_brightness)
TEMPLATABLE_VALUE(uint8_t, red) TEMPLATABLE_VALUE(uint8_t, red)
TEMPLATABLE_VALUE(uint8_t, green) TEMPLATABLE_VALUE(uint8_t, green)
TEMPLATABLE_VALUE(uint8_t, blue) TEMPLATABLE_VALUE(uint8_t, blue)
@ -148,13 +151,16 @@ template<typename... Ts> class AddressableSet : public Action<Ts...> {
auto *out = (AddressableLight *) this->parent_->get_output(); auto *out = (AddressableLight *) this->parent_->get_output();
int32_t range_from = this->range_from_.value_or(x..., 0); int32_t range_from = this->range_from_.value_or(x..., 0);
int32_t range_to = this->range_to_.value_or(x..., out->size() - 1) + 1; int32_t range_to = this->range_to_.value_or(x..., out->size() - 1) + 1;
uint8_t remote_color_brightness =
static_cast<uint8_t>(roundf(this->parent_->remote_values.get_color_brightness() * 255.0f));
uint8_t color_brightness = this->color_brightness_.value_or(x..., remote_color_brightness);
auto range = out->range(range_from, range_to); auto range = out->range(range_from, range_to);
if (this->red_.has_value()) if (this->red_.has_value())
range.set_red(this->red_.value(x...)); range.set_red(esp_scale8(this->red_.value(x...), color_brightness));
if (this->green_.has_value()) if (this->green_.has_value())
range.set_green(this->green_.value(x...)); range.set_green(esp_scale8(this->green_.value(x...), color_brightness));
if (this->blue_.has_value()) if (this->blue_.has_value())
range.set_blue(this->blue_.value(x...)); range.set_blue(esp_scale8(this->blue_.value(x...), color_brightness));
if (this->white_.has_value()) if (this->white_.has_value())
range.set_white(this->white_.value(x...)); range.set_white(this->white_.value(x...));
out->schedule_show(); out->schedule_show();

View file

@ -8,6 +8,7 @@ from esphome.const import (
CONF_FLASH_LENGTH, CONF_FLASH_LENGTH,
CONF_EFFECT, CONF_EFFECT,
CONF_BRIGHTNESS, CONF_BRIGHTNESS,
CONF_COLOR_BRIGHTNESS,
CONF_RED, CONF_RED,
CONF_GREEN, CONF_GREEN,
CONF_BLUE, CONF_BLUE,
@ -63,6 +64,7 @@ LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema(
), ),
cv.Exclusive(CONF_EFFECT, "transformer"): cv.templatable(cv.string), cv.Exclusive(CONF_EFFECT, "transformer"): cv.templatable(cv.string),
cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage), cv.Optional(CONF_BRIGHTNESS): cv.templatable(cv.percentage),
cv.Optional(CONF_COLOR_BRIGHTNESS): cv.templatable(cv.percentage),
cv.Optional(CONF_RED): cv.templatable(cv.percentage), cv.Optional(CONF_RED): cv.templatable(cv.percentage),
cv.Optional(CONF_GREEN): cv.templatable(cv.percentage), cv.Optional(CONF_GREEN): cv.templatable(cv.percentage),
cv.Optional(CONF_BLUE): cv.templatable(cv.percentage), cv.Optional(CONF_BLUE): cv.templatable(cv.percentage),
@ -114,6 +116,9 @@ async def light_control_to_code(config, action_id, template_arg, args):
if CONF_BRIGHTNESS in config: if CONF_BRIGHTNESS in config:
template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, float) template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, float)
cg.add(var.set_brightness(template_)) cg.add(var.set_brightness(template_))
if CONF_COLOR_BRIGHTNESS in config:
template_ = await cg.templatable(config[CONF_COLOR_BRIGHTNESS], args, float)
cg.add(var.set_color_brightness(template_))
if CONF_RED in config: if CONF_RED in config:
template_ = await cg.templatable(config[CONF_RED], args, float) template_ = await cg.templatable(config[CONF_RED], args, float)
cg.add(var.set_red(template_)) cg.add(var.set_red(template_))

View file

@ -12,6 +12,7 @@ from esphome.const import (
CONF_STATE, CONF_STATE,
CONF_DURATION, CONF_DURATION,
CONF_BRIGHTNESS, CONF_BRIGHTNESS,
CONF_COLOR_BRIGHTNESS,
CONF_RED, CONF_RED,
CONF_GREEN, CONF_GREEN,
CONF_BLUE, CONF_BLUE,
@ -211,6 +212,7 @@ async def random_effect_to_code(config, effect_id):
{ {
cv.Optional(CONF_STATE, default=True): cv.boolean, cv.Optional(CONF_STATE, default=True): cv.boolean,
cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage, cv.Optional(CONF_BRIGHTNESS, default=1.0): cv.percentage,
cv.Optional(CONF_COLOR_BRIGHTNESS, default=1.0): cv.percentage,
cv.Optional(CONF_RED, default=1.0): cv.percentage, cv.Optional(CONF_RED, default=1.0): cv.percentage,
cv.Optional(CONF_GREEN, default=1.0): cv.percentage, cv.Optional(CONF_GREEN, default=1.0): cv.percentage,
cv.Optional(CONF_BLUE, default=1.0): cv.percentage, cv.Optional(CONF_BLUE, default=1.0): cv.percentage,
@ -223,6 +225,7 @@ async def random_effect_to_code(config, effect_id):
cv.has_at_least_one_key( cv.has_at_least_one_key(
CONF_STATE, CONF_STATE,
CONF_BRIGHTNESS, CONF_BRIGHTNESS,
CONF_COLOR_BRIGHTNESS,
CONF_RED, CONF_RED,
CONF_GREEN, CONF_GREEN,
CONF_BLUE, CONF_BLUE,
@ -245,6 +248,7 @@ async def strobe_effect_to_code(config, effect_id):
LightColorValues( LightColorValues(
color[CONF_STATE], color[CONF_STATE],
color[CONF_BRIGHTNESS], color[CONF_BRIGHTNESS],
color[CONF_COLOR_BRIGHTNESS],
color[CONF_RED], color[CONF_RED],
color[CONF_GREEN], color[CONF_GREEN],
color[CONF_BLUE], color[CONF_BLUE],

View file

@ -29,8 +29,7 @@ class ESPColorCorrection {
return this->gamma_table_[res]; return this->gamma_table_[res];
} }
inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE { inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE {
// do not scale white value with brightness uint8_t res = esp_scale8(esp_scale8(white, this->max_brightness_.white), this->local_brightness_);
uint8_t res = esp_scale8(white, this->max_brightness_.white);
return this->gamma_table_[res]; return this->gamma_table_[res];
} }
inline Color color_uncorrect(Color color) const ALWAYS_INLINE { inline Color color_uncorrect(Color color) const ALWAYS_INLINE {
@ -60,10 +59,10 @@ class ESPColorCorrection {
return res; return res;
} }
inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE { inline uint8_t color_uncorrect_white(uint8_t white) const ALWAYS_INLINE {
if (this->max_brightness_.white == 0) if (this->max_brightness_.white == 0 || this->local_brightness_ == 0)
return 0; return 0;
uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL; uint16_t uncorrected = this->gamma_reverse_table_[white] * 255UL;
uint8_t res = uncorrected / this->max_brightness_.white; uint8_t res = ((uncorrected / this->max_brightness_.white) * 255UL) / this->local_brightness_;
return res; return res;
} }

View file

@ -43,6 +43,10 @@ LightCall &LightCall::parse_color_json(JsonObject &root) {
} }
} }
if (root.containsKey("color_brightness")) {
this->set_color_brightness(float(root["color_brightness"]) / 255.0f);
}
if (root.containsKey("white_value")) { if (root.containsKey("white_value")) {
this->set_white(float(root["white_value"]) / 255.0f); this->set_white(float(root["white_value"]) / 255.0f);
} }
@ -182,6 +186,12 @@ LightColorValues LightCall::validate_() {
this->transition_length_.reset(); this->transition_length_.reset();
} }
// Color brightness exists check
if (this->color_brightness_.has_value() && !traits.get_supports_rgb()) {
ESP_LOGW(TAG, "'%s' - This light does not support setting RGB brightness!", name);
this->color_brightness_.reset();
}
// RGB exists check // RGB exists check
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (!traits.get_supports_rgb()) { if (!traits.get_supports_rgb()) {
@ -204,34 +214,48 @@ LightColorValues LightCall::validate_() {
this->color_temperature_.reset(); this->color_temperature_.reset();
} }
// If white channel is specified, set RGB to white color (when interlock is enabled) // Set color brightness to 100% if currently zero and a color is set. This is both for compatibility with older
if (this->white_.has_value()) { // clients that don't know about color brightness, and it's intuitive UX anyway: if I set a color, it should show up.
if (traits.get_supports_color_interlock()) { if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) { if (!this->color_brightness_.has_value() && this->parent_->remote_values.get_color_brightness() == 0.0f)
this->red_ = optional<float>(1.0f); this->color_brightness_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f); }
this->blue_ = optional<float>(1.0f);
} // Handle interaction between RGB and white for color interlock
// 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) { // Find out which channel (white or color) the user wanted to enable
this->white_ = optional<float>(1.0f); bool output_white = this->white_.has_value() && *this->white_ > 0.0f;
} else { bool output_color = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) ||
this->white_ = optional<float>(0.0f); this->red_.has_value() || this->green_.has_value() || this->blue_.has_value();
}
} // Interpret setting the color to white as setting the white channel.
} if (output_color && *this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) {
// If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled) output_white = true;
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) { output_color = false;
if (traits.get_supports_color_interlock()) {
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) { if (!this->white_.has_value())
this->white_ = optional<float>(1.0f); this->white_ = optional<float>(this->color_brightness_.value_or(1.0f));
} else { }
this->white_ = optional<float>(0.0f);
} // Ensure either the white value or the color brightness is always zero.
if (output_white && output_color) {
ESP_LOGW(TAG, "'%s' - Cannot enable color and white channel simultaneously with interlock!", name);
// For compatibility with historic behaviour, prefer white channel in this case.
this->color_brightness_ = optional<float>(0.0f);
} else if (output_white) {
this->color_brightness_ = optional<float>(0.0f);
} else if (output_color) {
this->white_ = optional<float>(0.0f);
} }
} }
// If only a color temperature is specified, change to white light // If only a color temperature is specified, change to white light
else if (this->color_temperature_.has_value()) { if (this->color_temperature_.has_value() && !this->white_.has_value() && !this->red_.has_value() &&
!this->green_.has_value() && !this->blue_.has_value()) {
// Disable color LEDs explicitly if not already set
if (traits.get_supports_rgb() && !this->color_brightness_.has_value())
this->color_brightness_ = optional<float>(0.0f);
this->red_ = optional<float>(1.0f); this->red_ = optional<float>(1.0f);
this->green_ = optional<float>(1.0f); this->green_ = optional<float>(1.0f);
this->blue_ = optional<float>(1.0f); this->blue_ = optional<float>(1.0f);
@ -256,6 +280,7 @@ LightColorValues LightCall::validate_() {
// Range checks // Range checks
VALIDATE_RANGE(brightness, "Brightness") VALIDATE_RANGE(brightness, "Brightness")
VALIDATE_RANGE(color_brightness, "Color brightness")
VALIDATE_RANGE(red, "Red") VALIDATE_RANGE(red, "Red")
VALIDATE_RANGE(green, "Green") VALIDATE_RANGE(green, "Green")
VALIDATE_RANGE(blue, "Blue") VALIDATE_RANGE(blue, "Blue")
@ -267,6 +292,8 @@ LightColorValues LightCall::validate_() {
if (this->brightness_.has_value()) if (this->brightness_.has_value())
v.set_brightness(*this->brightness_); v.set_brightness(*this->brightness_);
if (this->color_brightness_.has_value())
v.set_color_brightness(*this->color_brightness_);
if (this->red_.has_value()) if (this->red_.has_value())
v.set_red(*this->red_); v.set_red(*this->red_);
if (this->green_.has_value()) if (this->green_.has_value())
@ -371,6 +398,7 @@ LightCall &LightCall::set_effect(const std::string &effect) {
LightCall &LightCall::from_light_color_values(const LightColorValues &values) { LightCall &LightCall::from_light_color_values(const LightColorValues &values) {
this->set_state(values.is_on()); this->set_state(values.is_on());
this->set_brightness_if_supported(values.get_brightness()); this->set_brightness_if_supported(values.get_brightness());
this->set_color_brightness_if_supported(values.get_color_brightness());
this->set_red_if_supported(values.get_red()); this->set_red_if_supported(values.get_red());
this->set_green_if_supported(values.get_green()); this->set_green_if_supported(values.get_green());
this->set_blue_if_supported(values.get_blue()); this->set_blue_if_supported(values.get_blue());
@ -388,6 +416,11 @@ LightCall &LightCall::set_brightness_if_supported(float brightness) {
this->set_brightness(brightness); this->set_brightness(brightness);
return *this; return *this;
} }
LightCall &LightCall::set_color_brightness_if_supported(float brightness) {
if (this->parent_->get_traits().get_supports_rgb_white_value())
this->set_brightness(brightness);
return *this;
}
LightCall &LightCall::set_red_if_supported(float red) { LightCall &LightCall::set_red_if_supported(float red) {
if (this->parent_->get_traits().get_supports_rgb()) if (this->parent_->get_traits().get_supports_rgb())
this->set_red(red); this->set_red(red);
@ -445,6 +478,14 @@ LightCall &LightCall::set_brightness(float brightness) {
this->brightness_ = brightness; this->brightness_ = brightness;
return *this; return *this;
} }
LightCall &LightCall::set_color_brightness(optional<float> brightness) {
this->color_brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_color_brightness(float brightness) {
this->color_brightness_ = brightness;
return *this;
}
LightCall &LightCall::set_red(optional<float> red) { LightCall &LightCall::set_red(optional<float> red) {
this->red_ = red; this->red_ = red;
return *this; return *this;

View file

@ -44,6 +44,12 @@ class LightCall {
LightCall &set_brightness(float brightness); LightCall &set_brightness(float brightness);
/// Set the brightness property if the light supports brightness. /// Set the brightness property if the light supports brightness.
LightCall &set_brightness_if_supported(float brightness); LightCall &set_brightness_if_supported(float brightness);
/// Set the color brightness of the light from 0.0 (no color) to 1.0 (fully on)
LightCall &set_color_brightness(optional<float> brightness);
/// Set the color brightness of the light from 0.0 (no color) to 1.0 (fully on)
LightCall &set_color_brightness(float brightness);
/// Set the color brightness property if the light supports RGBW.
LightCall &set_color_brightness_if_supported(float brightness);
/** Set the red RGB value of the light from 0.0 to 1.0. /** Set the red RGB value of the light from 0.0 to 1.0.
* *
* Note that this only controls the color of the light, not its brightness. * Note that this only controls the color of the light, not its brightness.
@ -146,6 +152,7 @@ class LightCall {
optional<uint32_t> transition_length_; optional<uint32_t> transition_length_;
optional<uint32_t> flash_length_; optional<uint32_t> flash_length_;
optional<float> brightness_; optional<float> brightness_;
optional<float> color_brightness_;
optional<float> red_; optional<float> red_;
optional<float> green_; optional<float> green_;
optional<float> blue_; optional<float> blue_;

View file

@ -13,17 +13,24 @@ namespace light {
/** This class represents the color state for a light object. /** This class represents the color state for a light object.
* *
* All values in this class are represented using floats in the range from 0.0 (off) to 1.0 (on). * All values in this class (except color temperature) are represented using floats in the range
* Not all values have to be populated though, for example a simple monochromatic light only needs * from 0.0 (off) to 1.0 (on). Please note that all values are automatically clamped to this range.
* to access the state and brightness attributes.
* *
* Please note all float values are automatically clamped. * This class has the following properties:
* - state: Whether the light should be on/off. Represented as a float for transitions. Used for
* all lights.
* - brightness: The master brightness of the light, applied to all channels. Used for all lights
* with brightness control.
* - color_brightness: The brightness of the color channels of the light. Used for RGB, RGBW and
* RGBWW lights.
* - red, green, blue: The RGB values of the current color. They are normalized, so at least one of
* them is always 1.0.
* - white: The brightness of the white channel of the light. Used for RGBW and RGBWW lights.
* - color_temperature: The color temperature of the white channel in mireds. Used for RGBWW and
* CWWW lights.
* *
* state - Whether the light should be on/off. Represented as a float for transitions. * For lights with a color interlock (RGB lights and white light cannot be on at the same time), a
* brightness - The brightness of the light. * valid state has always either color_brightness or white (or both) set to zero.
* red, green, blue - RGB values.
* white - The white value for RGBW lights.
* color_temperature - Temperature of the white value, range from 0.0 (cold) to 1.0 (warm)
*/ */
class LightColorValues { class LightColorValues {
public: public:
@ -31,16 +38,18 @@ class LightColorValues {
LightColorValues() LightColorValues()
: state_(0.0f), : state_(0.0f),
brightness_(1.0f), brightness_(1.0f),
color_brightness_(1.0f),
red_(1.0f), red_(1.0f),
green_(1.0f), green_(1.0f),
blue_(1.0f), blue_(1.0f),
white_(1.0f), white_(1.0f),
color_temperature_{1.0f} {} color_temperature_{1.0f} {}
LightColorValues(float state, float brightness, float red, float green, float blue, float white, LightColorValues(float state, float brightness, float color_brightness, float red, float green, float blue,
float color_temperature = 1.0f) { float white, float color_temperature = 1.0f) {
this->set_state(state); this->set_state(state);
this->set_brightness(brightness); this->set_brightness(brightness);
this->set_color_brightness(color_brightness);
this->set_red(red); this->set_red(red);
this->set_green(green); this->set_green(green);
this->set_blue(blue); this->set_blue(blue);
@ -48,38 +57,46 @@ class LightColorValues {
this->set_color_temperature(color_temperature); this->set_color_temperature(color_temperature);
} }
LightColorValues(bool state, float brightness, float red, float green, float blue, float white, LightColorValues(bool state, float brightness, float color_brightness, float red, float green, float blue,
float color_temperature = 1.0f) float white, float color_temperature = 1.0f)
: LightColorValues(state ? 1.0f : 0.0f, brightness, red, green, blue, white, color_temperature) {} : LightColorValues(state ? 1.0f : 0.0f, brightness, color_brightness, red, green, blue, white,
color_temperature) {}
/// Create light color values from a binary true/false state. /// Create light color values from a binary true/false state.
static LightColorValues from_binary(bool state) { return {state, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; } static LightColorValues from_binary(bool state) { return {state, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; }
/// Create light color values from a monochromatic brightness state. /// Create light color values from a monochromatic brightness state.
static LightColorValues from_monochromatic(float brightness) { static LightColorValues from_monochromatic(float brightness) {
if (brightness == 0.0f) if (brightness == 0.0f)
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
else else
return {1.0f, brightness, 1.0f, 1.0f, 1.0f, 1.0f}; return {1.0f, brightness, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
} }
/// Create light color values from an RGB state. /// Create light color values from an RGB state.
static LightColorValues from_rgb(float r, float g, float b) { static LightColorValues from_rgb(float r, float g, float b) {
float brightness = std::max(r, std::max(g, b)); float brightness = std::max(r, std::max(g, b));
if (brightness == 0.0f) { if (brightness == 0.0f) {
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
} else { } else {
return {1.0f, brightness, r / brightness, g / brightness, b / brightness, 1.0f}; return {1.0f, brightness, 1.0f, r / brightness, g / brightness, b / brightness, 1.0f};
} }
} }
/// Create light color values from an RGBW state. /// Create light color values from an RGBW state.
static LightColorValues from_rgbw(float r, float g, float b, float w) { static LightColorValues from_rgbw(float r, float g, float b, float w) {
float brightness = std::max(r, std::max(g, std::max(b, w))); float color_brightness = std::max(r, std::max(g, b));
if (brightness == 0.0f) { float master_brightness = std::max(color_brightness, w);
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; if (master_brightness == 0.0f) {
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
} else { } else {
return {1.0f, brightness, r / brightness, g / brightness, b / brightness, w / brightness}; return {1.0f,
master_brightness,
color_brightness / master_brightness,
r / color_brightness,
g / color_brightness,
b / color_brightness,
w / master_brightness};
} }
} }
@ -97,6 +114,7 @@ class LightColorValues {
LightColorValues v; LightColorValues v;
v.set_state(esphome::lerp(completion, start.get_state(), end.get_state())); v.set_state(esphome::lerp(completion, start.get_state(), end.get_state()));
v.set_brightness(esphome::lerp(completion, start.get_brightness(), end.get_brightness())); v.set_brightness(esphome::lerp(completion, start.get_brightness(), end.get_brightness()));
v.set_color_brightness(esphome::lerp(completion, start.get_color_brightness(), end.get_color_brightness()));
v.set_red(esphome::lerp(completion, start.get_red(), end.get_red())); v.set_red(esphome::lerp(completion, start.get_red(), end.get_red()));
v.set_green(esphome::lerp(completion, start.get_green(), end.get_green())); v.set_green(esphome::lerp(completion, start.get_green(), end.get_green()));
v.set_blue(esphome::lerp(completion, start.get_blue(), end.get_blue())); v.set_blue(esphome::lerp(completion, start.get_blue(), end.get_blue()));
@ -121,8 +139,10 @@ class LightColorValues {
color["g"] = uint8_t(this->get_green() * 255); color["g"] = uint8_t(this->get_green() * 255);
color["b"] = uint8_t(this->get_blue() * 255); color["b"] = uint8_t(this->get_blue() * 255);
} }
if (traits.get_supports_rgb_white_value()) if (traits.get_supports_rgb_white_value()) {
root["color_brightness"] = uint8_t(this->get_color_brightness() * 255);
root["white_value"] = uint8_t(this->get_white() * 255); root["white_value"] = uint8_t(this->get_white() * 255);
}
if (traits.get_supports_color_temperature()) if (traits.get_supports_color_temperature())
root["color_temp"] = uint32_t(this->get_color_temperature()); root["color_temp"] = uint32_t(this->get_color_temperature());
} }
@ -131,21 +151,15 @@ class LightColorValues {
/** Normalize the color (RGB/W) component. /** Normalize the color (RGB/W) component.
* *
* Divides all color attributes by the maximum attribute, so effectively set at least one attribute to 1. * Divides all color attributes by the maximum attribute, so effectively set at least one attribute to 1.
* For example: r=0.3, g=0.5, b=0.4 => r=0.6, g=1.0, b=0.8 * For example: r=0.3, g=0.5, b=0.4 => r=0.6, g=1.0, b=0.8.
*
* Note that this does NOT retain the brightness information from the color attributes.
* *
* @param traits Used for determining which attributes to consider. * @param traits Used for determining which attributes to consider.
*/ */
void normalize_color(const LightTraits &traits) { void normalize_color(const LightTraits &traits) {
if (traits.get_supports_rgb()) { if (traits.get_supports_rgb()) {
float max_value = fmaxf(this->get_red(), fmaxf(this->get_green(), this->get_blue())); float max_value = fmaxf(this->get_red(), fmaxf(this->get_green(), this->get_blue()));
if (traits.get_supports_rgb_white_value()) {
max_value = fmaxf(max_value, this->get_white());
if (max_value == 0.0f) {
this->set_white(1.0f);
} else {
this->set_white(this->get_white() / max_value);
}
}
if (max_value == 0.0f) { if (max_value == 0.0f) {
this->set_red(1.0f); this->set_red(1.0f);
this->set_green(1.0f); this->set_green(1.0f);
@ -158,15 +172,10 @@ class LightColorValues {
} }
if (traits.get_supports_brightness() && this->get_brightness() == 0.0f) { if (traits.get_supports_brightness() && this->get_brightness() == 0.0f) {
if (traits.get_supports_rgb_white_value()) { // 0% brightness means off
// 0% brightness for RGBW[W] means no RGB channel, but white channel on. this->set_state(false);
// do nothing // reset brightness to 100%
} else { this->set_brightness(1.0f);
// 0% brightness means off
this->set_state(false);
// reset brightness to 100%
this->set_brightness(1.0f);
}
} }
} }
@ -180,9 +189,9 @@ class LightColorValues {
/// Convert these light color values to an RGB representation and write them to red, green, blue. /// 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, bool color_interlock = false) const { void as_rgb(float *red, float *green, float *blue, float gamma = 0, bool color_interlock = false) const {
float brightness = this->state_ * this->brightness_; float brightness = this->state_ * this->brightness_ * this->color_brightness_;
if (color_interlock) { if (color_interlock && this->white_ > 0.0f) {
brightness = brightness * (1.0f - this->white_); brightness = 0;
} }
*red = gamma_correct(brightness * this->red_, gamma); *red = gamma_correct(brightness * this->red_, gamma);
*green = gamma_correct(brightness * this->green_, gamma); *green = gamma_correct(brightness * this->green_, gamma);
@ -232,8 +241,9 @@ class LightColorValues {
/// Compare this LightColorValues to rhs, return true if and only if all attributes match. /// Compare this LightColorValues to rhs, return true if and only if all attributes match.
bool operator==(const LightColorValues &rhs) const { bool operator==(const LightColorValues &rhs) const {
return state_ == rhs.state_ && brightness_ == rhs.brightness_ && red_ == rhs.red_ && green_ == rhs.green_ && return state_ == rhs.state_ && brightness_ == rhs.brightness_ && color_brightness_ == rhs.color_brightness_ &&
blue_ == rhs.blue_ && white_ == rhs.white_ && color_temperature_ == rhs.color_temperature_; red_ == rhs.red_ && green_ == rhs.green_ && blue_ == rhs.blue_ && white_ == rhs.white_ &&
color_temperature_ == rhs.color_temperature_;
} }
bool operator!=(const LightColorValues &rhs) const { return !(rhs == *this); } bool operator!=(const LightColorValues &rhs) const { return !(rhs == *this); }
@ -251,6 +261,11 @@ class LightColorValues {
/// Set the brightness property of these light color values. In range 0.0 to 1.0 /// Set the brightness property of these light color values. In range 0.0 to 1.0
void set_brightness(float brightness) { this->brightness_ = clamp(brightness, 0.0f, 1.0f); } void set_brightness(float brightness) { this->brightness_ = clamp(brightness, 0.0f, 1.0f); }
/// Get the color brightness property of these light color values. In range 0.0 to 1.0
float get_color_brightness() const { return this->color_brightness_; }
/// Set the color brightness property of these light color values. In range 0.0 to 1.0
void set_color_brightness(float brightness) { this->color_brightness_ = clamp(brightness, 0.0f, 1.0f); }
/// Get the red property of these light color values. In range 0.0 to 1.0 /// Get the red property of these light color values. In range 0.0 to 1.0
float get_red() const { return this->red_; } float get_red() const { return this->red_; }
/// Set the red property of these light color values. In range 0.0 to 1.0 /// Set the red property of these light color values. In range 0.0 to 1.0
@ -281,6 +296,7 @@ class LightColorValues {
protected: protected:
float state_; ///< ON / OFF, float for transition float state_; ///< ON / OFF, float for transition
float brightness_; float brightness_;
float color_brightness_;
float red_; float red_;
float green_; float green_;
float blue_; float blue_;

View file

@ -121,6 +121,7 @@ CONF_CODE = "code"
CONF_COLD_WHITE = "cold_white" CONF_COLD_WHITE = "cold_white"
CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature" CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature"
CONF_COLOR = "color" CONF_COLOR = "color"
CONF_COLOR_BRIGHTNESS = "color_brightness"
CONF_COLOR_CORRECT = "color_correct" CONF_COLOR_CORRECT = "color_correct"
CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLOR_TEMPERATURE = "color_temperature"
CONF_COLORS = "colors" CONF_COLORS = "colors"