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;
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;

View file

@ -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);

View file

@ -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");

View file

@ -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};

View file

@ -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);
}

View file

@ -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();

View file

@ -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_))

View file

@ -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],

View file

@ -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;
}

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")) {
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);
// 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);
}
// 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 {
// 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 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);
}
}
}
// 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;

View file

@ -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_;

View file

@ -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,17 +172,12 @@ 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);
}
}
}
/// Convert these light color values to a binary representation and write them to binary.
void as_binary(bool *binary) const { *binary = this->state_ == 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_;

View file

@ -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"