mirror of
https://github.com/esphome/esphome.git
synced 2024-12-22 13:34:54 +01:00
Change color model to fix white channel issues (#1895)
This commit is contained in:
parent
fd4b7d4588
commit
f9797825ad
13 changed files with 200 additions and 83 deletions
|
@ -378,6 +378,7 @@ message LightStateResponse {
|
|||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
float color_brightness = 10;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
|
@ -396,6 +397,8 @@ message LightCommandRequest {
|
|||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_color_brightness = 20;
|
||||
float color_brightness = 21;
|
||||
bool has_rgb = 6;
|
||||
float red = 7;
|
||||
float green = 8;
|
||||
|
|
|
@ -308,6 +308,7 @@ bool APIConnection::send_light_state(light::LightState *light) {
|
|||
if (traits.get_supports_brightness())
|
||||
resp.brightness = values.get_brightness();
|
||||
if (traits.get_supports_rgb()) {
|
||||
resp.color_brightness = values.get_color_brightness();
|
||||
resp.red = values.get_red();
|
||||
resp.green = values.get_green();
|
||||
resp.blue = values.get_blue();
|
||||
|
@ -352,6 +353,8 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
|
|||
call.set_state(msg.state);
|
||||
if (msg.has_brightness)
|
||||
call.set_brightness(msg.brightness);
|
||||
if (msg.has_color_brightness)
|
||||
call.set_color_brightness(msg.color_brightness);
|
||||
if (msg.has_rgb) {
|
||||
call.set_red(msg.red);
|
||||
call.set_green(msg.green);
|
||||
|
|
|
@ -1263,6 +1263,10 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
|||
this->brightness = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->color_brightness = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->red = value.as_float();
|
||||
return true;
|
||||
|
@ -1291,6 +1295,7 @@ void LightStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_bool(2, this->state);
|
||||
buffer.encode_float(3, this->brightness);
|
||||
buffer.encode_float(10, this->color_brightness);
|
||||
buffer.encode_float(4, this->red);
|
||||
buffer.encode_float(5, this->green);
|
||||
buffer.encode_float(6, this->blue);
|
||||
|
@ -1315,6 +1320,11 @@ void LightStateResponse::dump_to(std::string &out) const {
|
|||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" color_brightness: ");
|
||||
sprintf(buffer, "%g", this->color_brightness);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" red: ");
|
||||
sprintf(buffer, "%g", this->red);
|
||||
out.append(buffer);
|
||||
|
@ -1359,6 +1369,10 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||
this->has_brightness = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 20: {
|
||||
this->has_color_brightness = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 6: {
|
||||
this->has_rgb = value.as_bool();
|
||||
return true;
|
||||
|
@ -1415,6 +1429,10 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
|||
this->brightness = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 21: {
|
||||
this->color_brightness = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->red = value.as_float();
|
||||
return true;
|
||||
|
@ -1445,6 +1463,8 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_bool(3, this->state);
|
||||
buffer.encode_bool(4, this->has_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_float(7, this->red);
|
||||
buffer.encode_float(8, this->green);
|
||||
|
@ -1485,6 +1505,15 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
|||
out.append(buffer);
|
||||
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(YESNO(this->has_rgb));
|
||||
out.append("\n");
|
||||
|
|
|
@ -371,6 +371,7 @@ class LightStateResponse : public ProtoMessage {
|
|||
uint32_t key{0};
|
||||
bool state{false};
|
||||
float brightness{0.0f};
|
||||
float color_brightness{0.0f};
|
||||
float red{0.0f};
|
||||
float green{0.0f};
|
||||
float blue{0.0f};
|
||||
|
@ -392,6 +393,8 @@ class LightCommandRequest : public ProtoMessage {
|
|||
bool state{false};
|
||||
bool has_brightness{false};
|
||||
float brightness{0.0f};
|
||||
bool has_color_brightness{false};
|
||||
float color_brightness{0.0f};
|
||||
bool has_rgb{false};
|
||||
float red{0.0f};
|
||||
float green{0.0f};
|
||||
|
|
|
@ -25,10 +25,10 @@ void AddressableLight::call_setup() {
|
|||
}
|
||||
|
||||
Color esp_color_from_light_color_values(LightColorValues val) {
|
||||
auto r = static_cast<uint8_t>(roundf(val.get_red() * 255.0f));
|
||||
auto g = static_cast<uint8_t>(roundf(val.get_green() * 255.0f));
|
||||
auto b = static_cast<uint8_t>(roundf(val.get_blue() * 255.0f));
|
||||
auto w = static_cast<uint8_t>(roundf(val.get_white() * val.get_state() * 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_color_brightness() * val.get_green() * 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() * 255.0f));
|
||||
return Color(r, g, b, w);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
|||
TEMPLATABLE_VALUE(uint32_t, transition_length)
|
||||
TEMPLATABLE_VALUE(uint32_t, flash_length)
|
||||
TEMPLATABLE_VALUE(float, brightness)
|
||||
TEMPLATABLE_VALUE(float, color_brightness)
|
||||
TEMPLATABLE_VALUE(float, red)
|
||||
TEMPLATABLE_VALUE(float, green)
|
||||
TEMPLATABLE_VALUE(float, blue)
|
||||
|
@ -42,6 +43,7 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
|||
auto call = this->parent_->make_call();
|
||||
call.set_state(this->state_.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_green(this->green_.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_to)
|
||||
TEMPLATABLE_VALUE(uint8_t, color_brightness)
|
||||
TEMPLATABLE_VALUE(uint8_t, red)
|
||||
TEMPLATABLE_VALUE(uint8_t, green)
|
||||
TEMPLATABLE_VALUE(uint8_t, blue)
|
||||
|
@ -148,13 +151,16 @@ template<typename... Ts> class AddressableSet : public Action<Ts...> {
|
|||
auto *out = (AddressableLight *) this->parent_->get_output();
|
||||
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;
|
||||
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);
|
||||
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())
|
||||
range.set_green(this->green_.value(x...));
|
||||
range.set_green(esp_scale8(this->green_.value(x...), color_brightness));
|
||||
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())
|
||||
range.set_white(this->white_.value(x...));
|
||||
out->schedule_show();
|
||||
|
|
|
@ -8,6 +8,7 @@ from esphome.const import (
|
|||
CONF_FLASH_LENGTH,
|
||||
CONF_EFFECT,
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_COLOR_BRIGHTNESS,
|
||||
CONF_RED,
|
||||
CONF_GREEN,
|
||||
CONF_BLUE,
|
||||
|
@ -63,6 +64,7 @@ LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema(
|
|||
),
|
||||
cv.Exclusive(CONF_EFFECT, "transformer"): cv.templatable(cv.string),
|
||||
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_GREEN): 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:
|
||||
template_ = await cg.templatable(config[CONF_BRIGHTNESS], args, float)
|
||||
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:
|
||||
template_ = await cg.templatable(config[CONF_RED], args, float)
|
||||
cg.add(var.set_red(template_))
|
||||
|
|
|
@ -12,6 +12,7 @@ from esphome.const import (
|
|||
CONF_STATE,
|
||||
CONF_DURATION,
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_COLOR_BRIGHTNESS,
|
||||
CONF_RED,
|
||||
CONF_GREEN,
|
||||
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_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_GREEN, 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(
|
||||
CONF_STATE,
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_COLOR_BRIGHTNESS,
|
||||
CONF_RED,
|
||||
CONF_GREEN,
|
||||
CONF_BLUE,
|
||||
|
@ -245,6 +248,7 @@ async def strobe_effect_to_code(config, effect_id):
|
|||
LightColorValues(
|
||||
color[CONF_STATE],
|
||||
color[CONF_BRIGHTNESS],
|
||||
color[CONF_COLOR_BRIGHTNESS],
|
||||
color[CONF_RED],
|
||||
color[CONF_GREEN],
|
||||
color[CONF_BLUE],
|
||||
|
|
|
@ -29,8 +29,7 @@ class ESPColorCorrection {
|
|||
return this->gamma_table_[res];
|
||||
}
|
||||
inline uint8_t color_correct_white(uint8_t white) const ALWAYS_INLINE {
|
||||
// do not scale white value with brightness
|
||||
uint8_t res = esp_scale8(white, this->max_brightness_.white);
|
||||
uint8_t res = esp_scale8(esp_scale8(white, this->max_brightness_.white), this->local_brightness_);
|
||||
return this->gamma_table_[res];
|
||||
}
|
||||
inline Color color_uncorrect(Color color) const ALWAYS_INLINE {
|
||||
|
@ -60,10 +59,10 @@ class ESPColorCorrection {
|
|||
return res;
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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")) {
|
||||
this->set_white(float(root["white_value"]) / 255.0f);
|
||||
}
|
||||
|
@ -182,6 +186,12 @@ LightColorValues LightCall::validate_() {
|
|||
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
|
||||
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||
if (!traits.get_supports_rgb()) {
|
||||
|
@ -204,34 +214,48 @@ LightColorValues LightCall::validate_() {
|
|||
this->color_temperature_.reset();
|
||||
}
|
||||
|
||||
// If white channel is specified, set RGB to white color (when interlock is enabled)
|
||||
if (this->white_.has_value()) {
|
||||
if (traits.get_supports_color_interlock()) {
|
||||
if (!this->red_.has_value() && !this->green_.has_value() && !this->blue_.has_value()) {
|
||||
this->red_ = optional<float>(1.0f);
|
||||
this->green_ = optional<float>(1.0f);
|
||||
this->blue_ = optional<float>(1.0f);
|
||||
}
|
||||
// make white values binary aka 0.0f or 1.0f... this allows brightness to do its job
|
||||
if (*this->white_ > 0.0f) {
|
||||
this->white_ = optional<float>(1.0f);
|
||||
} else {
|
||||
this->white_ = optional<float>(0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If only a color channel is specified, set white channel to 100% for white, otherwise 0% (when interlock is enabled)
|
||||
else if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||
if (traits.get_supports_color_interlock()) {
|
||||
if (*this->red_ == 1.0f && *this->green_ == 1.0f && *this->blue_ == 1.0f) {
|
||||
this->white_ = optional<float>(1.0f);
|
||||
} else {
|
||||
this->white_ = optional<float>(0.0f);
|
||||
}
|
||||
// Set color brightness to 100% if currently zero and a color is set. This is both for compatibility with older
|
||||
// clients that don't know about color brightness, and it's intuitive UX anyway: if I set a color, it should show up.
|
||||
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->color_brightness_ = optional<float>(1.0f);
|
||||
}
|
||||
|
||||
// Handle interaction between RGB and white for color interlock
|
||||
if (traits.get_supports_color_interlock()) {
|
||||
// Find out which channel (white or color) the user wanted to enable
|
||||
bool output_white = this->white_.has_value() && *this->white_ > 0.0f;
|
||||
bool output_color = (this->color_brightness_.has_value() && *this->color_brightness_ > 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) {
|
||||
output_white = true;
|
||||
output_color = false;
|
||||
|
||||
if (!this->white_.has_value())
|
||||
this->white_ = optional<float>(this->color_brightness_.value_or(1.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
|
||||
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->green_ = optional<float>(1.0f);
|
||||
this->blue_ = optional<float>(1.0f);
|
||||
|
@ -256,6 +280,7 @@ LightColorValues LightCall::validate_() {
|
|||
|
||||
// Range checks
|
||||
VALIDATE_RANGE(brightness, "Brightness")
|
||||
VALIDATE_RANGE(color_brightness, "Color brightness")
|
||||
VALIDATE_RANGE(red, "Red")
|
||||
VALIDATE_RANGE(green, "Green")
|
||||
VALIDATE_RANGE(blue, "Blue")
|
||||
|
@ -267,6 +292,8 @@ LightColorValues LightCall::validate_() {
|
|||
if (this->brightness_.has_value())
|
||||
v.set_brightness(*this->brightness_);
|
||||
|
||||
if (this->color_brightness_.has_value())
|
||||
v.set_color_brightness(*this->color_brightness_);
|
||||
if (this->red_.has_value())
|
||||
v.set_red(*this->red_);
|
||||
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) {
|
||||
this->set_state(values.is_on());
|
||||
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_green_if_supported(values.get_green());
|
||||
this->set_blue_if_supported(values.get_blue());
|
||||
|
@ -388,6 +416,11 @@ LightCall &LightCall::set_brightness_if_supported(float brightness) {
|
|||
this->set_brightness(brightness);
|
||||
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) {
|
||||
if (this->parent_->get_traits().get_supports_rgb())
|
||||
this->set_red(red);
|
||||
|
@ -445,6 +478,14 @@ LightCall &LightCall::set_brightness(float brightness) {
|
|||
this->brightness_ = brightness;
|
||||
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) {
|
||||
this->red_ = red;
|
||||
return *this;
|
||||
|
|
|
@ -44,6 +44,12 @@ class LightCall {
|
|||
LightCall &set_brightness(float brightness);
|
||||
/// Set the brightness property if the light supports 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.
|
||||
*
|
||||
* 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> flash_length_;
|
||||
optional<float> brightness_;
|
||||
optional<float> color_brightness_;
|
||||
optional<float> red_;
|
||||
optional<float> green_;
|
||||
optional<float> blue_;
|
||||
|
|
|
@ -13,17 +13,24 @@ namespace light {
|
|||
|
||||
/** 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).
|
||||
* Not all values have to be populated though, for example a simple monochromatic light only needs
|
||||
* to access the state and brightness attributes.
|
||||
* All values in this class (except color temperature) are represented using floats in the range
|
||||
* from 0.0 (off) to 1.0 (on). Please note that all values are automatically clamped to this range.
|
||||
*
|
||||
* 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.
|
||||
* brightness - The brightness of the light.
|
||||
* 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)
|
||||
* For lights with a color interlock (RGB lights and white light cannot be on at the same time), a
|
||||
* valid state has always either color_brightness or white (or both) set to zero.
|
||||
*/
|
||||
class LightColorValues {
|
||||
public:
|
||||
|
@ -31,16 +38,18 @@ class LightColorValues {
|
|||
LightColorValues()
|
||||
: state_(0.0f),
|
||||
brightness_(1.0f),
|
||||
color_brightness_(1.0f),
|
||||
red_(1.0f),
|
||||
green_(1.0f),
|
||||
blue_(1.0f),
|
||||
white_(1.0f),
|
||||
color_temperature_{1.0f} {}
|
||||
|
||||
LightColorValues(float state, float brightness, float red, float green, float blue, float white,
|
||||
float color_temperature = 1.0f) {
|
||||
LightColorValues(float state, float brightness, float color_brightness, float red, float green, float blue,
|
||||
float white, float color_temperature = 1.0f) {
|
||||
this->set_state(state);
|
||||
this->set_brightness(brightness);
|
||||
this->set_color_brightness(color_brightness);
|
||||
this->set_red(red);
|
||||
this->set_green(green);
|
||||
this->set_blue(blue);
|
||||
|
@ -48,38 +57,46 @@ class LightColorValues {
|
|||
this->set_color_temperature(color_temperature);
|
||||
}
|
||||
|
||||
LightColorValues(bool state, float brightness, float red, float green, float blue, float white,
|
||||
float color_temperature = 1.0f)
|
||||
: LightColorValues(state ? 1.0f : 0.0f, brightness, red, green, blue, white, color_temperature) {}
|
||||
LightColorValues(bool state, float brightness, float color_brightness, float red, float green, float blue,
|
||||
float white, float color_temperature = 1.0f)
|
||||
: 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.
|
||||
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.
|
||||
static LightColorValues from_monochromatic(float brightness) {
|
||||
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
|
||||
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.
|
||||
static LightColorValues from_rgb(float r, float g, float b) {
|
||||
float brightness = std::max(r, std::max(g, b));
|
||||
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 {
|
||||
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.
|
||||
static LightColorValues from_rgbw(float r, float g, float b, float w) {
|
||||
float brightness = std::max(r, std::max(g, std::max(b, w)));
|
||||
if (brightness == 0.0f) {
|
||||
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
float color_brightness = std::max(r, std::max(g, b));
|
||||
float master_brightness = std::max(color_brightness, w);
|
||||
if (master_brightness == 0.0f) {
|
||||
return {0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
|
||||
} 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;
|
||||
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_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_green(esphome::lerp(completion, start.get_green(), end.get_green()));
|
||||
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["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);
|
||||
}
|
||||
if (traits.get_supports_color_temperature())
|
||||
root["color_temp"] = uint32_t(this->get_color_temperature());
|
||||
}
|
||||
|
@ -131,21 +151,15 @@ class LightColorValues {
|
|||
/** Normalize the color (RGB/W) component.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
void normalize_color(const LightTraits &traits) {
|
||||
if (traits.get_supports_rgb()) {
|
||||
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) {
|
||||
this->set_red(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_rgb_white_value()) {
|
||||
// 0% brightness for RGBW[W] means no RGB channel, but white channel on.
|
||||
// do nothing
|
||||
} else {
|
||||
// 0% brightness means off
|
||||
this->set_state(false);
|
||||
// reset brightness to 100%
|
||||
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.
|
||||
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_);
|
||||
float brightness = this->state_ * this->brightness_ * this->color_brightness_;
|
||||
if (color_interlock && this->white_ > 0.0f) {
|
||||
brightness = 0;
|
||||
}
|
||||
*red = gamma_correct(brightness * this->red_, 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.
|
||||
bool operator==(const LightColorValues &rhs) const {
|
||||
return state_ == rhs.state_ && brightness_ == rhs.brightness_ && red_ == rhs.red_ && green_ == rhs.green_ &&
|
||||
blue_ == rhs.blue_ && white_ == rhs.white_ && color_temperature_ == rhs.color_temperature_;
|
||||
return state_ == rhs.state_ && brightness_ == rhs.brightness_ && color_brightness_ == rhs.color_brightness_ &&
|
||||
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); }
|
||||
|
||||
|
@ -251,6 +261,11 @@ class LightColorValues {
|
|||
/// 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); }
|
||||
|
||||
/// 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
|
||||
float get_red() const { return this->red_; }
|
||||
/// Set the red property of these light color values. In range 0.0 to 1.0
|
||||
|
@ -281,6 +296,7 @@ class LightColorValues {
|
|||
protected:
|
||||
float state_; ///< ON / OFF, float for transition
|
||||
float brightness_;
|
||||
float color_brightness_;
|
||||
float red_;
|
||||
float green_;
|
||||
float blue_;
|
||||
|
|
|
@ -121,6 +121,7 @@ CONF_CODE = "code"
|
|||
CONF_COLD_WHITE = "cold_white"
|
||||
CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature"
|
||||
CONF_COLOR = "color"
|
||||
CONF_COLOR_BRIGHTNESS = "color_brightness"
|
||||
CONF_COLOR_CORRECT = "color_correct"
|
||||
CONF_COLOR_TEMPERATURE = "color_temperature"
|
||||
CONF_COLORS = "colors"
|
||||
|
|
Loading…
Reference in a new issue