mirror of
https://github.com/esphome/esphome.git
synced 2025-10-09 21:33:48 +01:00
Color mode implementation (#2012)
This commit is contained in:
@@ -108,7 +108,9 @@ ADDRESSABLE_LIGHT_SCHEMA = RGB_LIGHT_SCHEMA.extend(
|
||||
|
||||
def validate_color_temperature_channels(value):
|
||||
if (
|
||||
value[CONF_COLD_WHITE_COLOR_TEMPERATURE]
|
||||
CONF_COLD_WHITE_COLOR_TEMPERATURE in value
|
||||
and CONF_WARM_WHITE_COLOR_TEMPERATURE in value
|
||||
and value[CONF_COLD_WHITE_COLOR_TEMPERATURE]
|
||||
>= value[CONF_WARM_WHITE_COLOR_TEMPERATURE]
|
||||
):
|
||||
raise cv.Invalid(
|
||||
|
@@ -27,6 +27,7 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit LightControlAction(LightState *parent) : parent_(parent) {}
|
||||
|
||||
TEMPLATABLE_VALUE(ColorMode, color_mode)
|
||||
TEMPLATABLE_VALUE(bool, state)
|
||||
TEMPLATABLE_VALUE(uint32_t, transition_length)
|
||||
TEMPLATABLE_VALUE(uint32_t, flash_length)
|
||||
@@ -37,10 +38,13 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
||||
TEMPLATABLE_VALUE(float, blue)
|
||||
TEMPLATABLE_VALUE(float, white)
|
||||
TEMPLATABLE_VALUE(float, color_temperature)
|
||||
TEMPLATABLE_VALUE(float, cold_white)
|
||||
TEMPLATABLE_VALUE(float, warm_white)
|
||||
TEMPLATABLE_VALUE(std::string, effect)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->parent_->make_call();
|
||||
call.set_color_mode(this->color_mode_.optional_value(x...));
|
||||
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...));
|
||||
@@ -49,6 +53,8 @@ template<typename... Ts> class LightControlAction : public Action<Ts...> {
|
||||
call.set_blue(this->blue_.optional_value(x...));
|
||||
call.set_white(this->white_.optional_value(x...));
|
||||
call.set_color_temperature(this->color_temperature_.optional_value(x...));
|
||||
call.set_cold_white(this->cold_white_.optional_value(x...));
|
||||
call.set_warm_white(this->warm_white_.optional_value(x...));
|
||||
call.set_effect(this->effect_.optional_value(x...));
|
||||
call.set_flash_length(this->flash_length_.optional_value(x...));
|
||||
call.set_transition_length(this->transition_length_.optional_value(x...));
|
||||
|
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_COLOR_MODE,
|
||||
CONF_TRANSITION_LENGTH,
|
||||
CONF_STATE,
|
||||
CONF_FLASH_LENGTH,
|
||||
@@ -14,10 +15,14 @@ from esphome.const import (
|
||||
CONF_BLUE,
|
||||
CONF_WHITE,
|
||||
CONF_COLOR_TEMPERATURE,
|
||||
CONF_COLD_WHITE,
|
||||
CONF_WARM_WHITE,
|
||||
CONF_RANGE_FROM,
|
||||
CONF_RANGE_TO,
|
||||
)
|
||||
from .types import (
|
||||
ColorMode,
|
||||
COLOR_MODES,
|
||||
DimRelativeAction,
|
||||
ToggleAction,
|
||||
LightState,
|
||||
@@ -55,6 +60,7 @@ async def light_toggle_to_code(config, action_id, template_arg, args):
|
||||
LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LightState),
|
||||
cv.Optional(CONF_COLOR_MODE): cv.enum(COLOR_MODES, upper=True, space="_"),
|
||||
cv.Optional(CONF_STATE): cv.templatable(cv.boolean),
|
||||
cv.Exclusive(CONF_TRANSITION_LENGTH, "transformer"): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
@@ -70,6 +76,8 @@ LIGHT_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_BLUE): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_WHITE): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_COLOR_TEMPERATURE): cv.templatable(cv.color_temperature),
|
||||
cv.Optional(CONF_COLD_WHITE): cv.templatable(cv.percentage),
|
||||
cv.Optional(CONF_WARM_WHITE): cv.templatable(cv.percentage),
|
||||
}
|
||||
)
|
||||
LIGHT_TURN_OFF_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
@@ -102,6 +110,9 @@ LIGHT_TURN_ON_ACTION_SCHEMA = automation.maybe_simple_id(
|
||||
async def light_control_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
if CONF_COLOR_MODE in config:
|
||||
template_ = await cg.templatable(config[CONF_COLOR_MODE], args, ColorMode)
|
||||
cg.add(var.set_color_mode(template_))
|
||||
if CONF_STATE in config:
|
||||
template_ = await cg.templatable(config[CONF_STATE], args, bool)
|
||||
cg.add(var.set_state(template_))
|
||||
@@ -134,6 +145,12 @@ async def light_control_to_code(config, action_id, template_arg, args):
|
||||
if CONF_COLOR_TEMPERATURE in config:
|
||||
template_ = await cg.templatable(config[CONF_COLOR_TEMPERATURE], args, float)
|
||||
cg.add(var.set_color_temperature(template_))
|
||||
if CONF_COLD_WHITE in config:
|
||||
template_ = await cg.templatable(config[CONF_COLD_WHITE], args, float)
|
||||
cg.add(var.set_cold_white(template_))
|
||||
if CONF_WARM_WHITE in config:
|
||||
template_ = await cg.templatable(config[CONF_WARM_WHITE], args, float)
|
||||
cg.add(var.set_warm_white(template_))
|
||||
if CONF_EFFECT in config:
|
||||
template_ = await cg.templatable(config[CONF_EFFECT], args, cg.std_string)
|
||||
cg.add(var.set_effect(template_))
|
||||
|
@@ -57,16 +57,31 @@ class RandomLightEffect : public LightEffect {
|
||||
if (now - this->last_color_change_ < this->update_interval_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto color_mode = this->state_->remote_values.get_color_mode();
|
||||
auto call = this->state_->turn_on();
|
||||
if (this->state_->get_traits().get_supports_rgb()) {
|
||||
call.set_red_if_supported(random_float());
|
||||
call.set_green_if_supported(random_float());
|
||||
call.set_blue_if_supported(random_float());
|
||||
call.set_white_if_supported(random_float());
|
||||
} else {
|
||||
call.set_brightness_if_supported(random_float());
|
||||
bool changed = false;
|
||||
if (color_mode & ColorCapability::RGB) {
|
||||
call.set_red(random_float());
|
||||
call.set_green(random_float());
|
||||
call.set_blue(random_float());
|
||||
changed = true;
|
||||
}
|
||||
if (color_mode & ColorCapability::COLOR_TEMPERATURE) {
|
||||
float min = this->state_->get_traits().get_min_mireds();
|
||||
float max = this->state_->get_traits().get_max_mireds();
|
||||
call.set_color_temperature(min + random_float() * (max - min));
|
||||
changed = true;
|
||||
}
|
||||
if (color_mode & ColorCapability::COLD_WARM_WHITE) {
|
||||
call.set_cold_white(random_float());
|
||||
call.set_warm_white(random_float());
|
||||
changed = true;
|
||||
}
|
||||
if (!changed) {
|
||||
// only randomize brightness if there's no colored option available
|
||||
call.set_brightness(random_float());
|
||||
}
|
||||
call.set_color_temperature_if_supported(random_float());
|
||||
call.set_transition_length_if_supported(this->transition_length_);
|
||||
call.set_publish(true);
|
||||
call.set_save(false);
|
||||
@@ -142,7 +157,6 @@ class StrobeLightEffect : public LightEffect {
|
||||
if (!color.is_on()) {
|
||||
// Don't turn the light off, otherwise the light effect will be stopped
|
||||
call.set_brightness_if_supported(0.0f);
|
||||
call.set_white_if_supported(0.0f);
|
||||
call.set_state(true);
|
||||
}
|
||||
call.set_publish(false);
|
||||
@@ -177,13 +191,16 @@ class FlickerLightEffect : public LightEffect {
|
||||
out.set_green(remote.get_green() * beta + current.get_green() * alpha + (random_cubic_float() * this->intensity_));
|
||||
out.set_blue(remote.get_blue() * beta + current.get_blue() * alpha + (random_cubic_float() * this->intensity_));
|
||||
out.set_white(remote.get_white() * beta + current.get_white() * alpha + (random_cubic_float() * this->intensity_));
|
||||
out.set_cold_white(remote.get_cold_white() * beta + current.get_cold_white() * alpha +
|
||||
(random_cubic_float() * this->intensity_));
|
||||
out.set_warm_white(remote.get_warm_white() * beta + current.get_warm_white() * alpha +
|
||||
(random_cubic_float() * this->intensity_));
|
||||
|
||||
auto traits = this->state_->get_traits();
|
||||
auto call = this->state_->make_call();
|
||||
call.set_publish(false);
|
||||
call.set_save(false);
|
||||
if (traits.get_supports_brightness())
|
||||
call.set_transition_length(0);
|
||||
call.set_transition_length_if_supported(0);
|
||||
call.from_light_color_values(out);
|
||||
call.set_state(true);
|
||||
call.perform();
|
||||
|
107
esphome/components/light/color_mode.h
Normal file
107
esphome/components/light/color_mode.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
/// Color capabilities are the various outputs that a light has and that can be independently controlled by the user.
|
||||
enum class ColorCapability : uint8_t {
|
||||
/// Light can be turned on/off.
|
||||
ON_OFF = 1 << 0,
|
||||
/// Master brightness of the light can be controlled.
|
||||
BRIGHTNESS = 1 << 1,
|
||||
/// Brightness of white channel can be controlled separately from other channels.
|
||||
WHITE = 1 << 2,
|
||||
/// Color temperature can be controlled.
|
||||
COLOR_TEMPERATURE = 1 << 3,
|
||||
/// Brightness of cold and warm white output can be controlled.
|
||||
COLD_WARM_WHITE = 1 << 4,
|
||||
/// Color can be controlled using RGB format (includes a brightness control for the color).
|
||||
RGB = 1 << 5
|
||||
};
|
||||
|
||||
/// Helper class to allow bitwise operations on ColorCapability
|
||||
class ColorCapabilityHelper {
|
||||
public:
|
||||
constexpr ColorCapabilityHelper(ColorCapability val) : val_(val) {}
|
||||
constexpr operator ColorCapability() const { return val_; }
|
||||
constexpr operator uint8_t() const { return static_cast<uint8_t>(val_); }
|
||||
constexpr operator bool() const { return static_cast<uint8_t>(val_) != 0; }
|
||||
|
||||
protected:
|
||||
ColorCapability val_;
|
||||
};
|
||||
constexpr ColorCapabilityHelper operator&(ColorCapability lhs, ColorCapability rhs) {
|
||||
return static_cast<ColorCapability>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
|
||||
}
|
||||
constexpr ColorCapabilityHelper operator&(ColorCapabilityHelper lhs, ColorCapability rhs) {
|
||||
return static_cast<ColorCapability>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
|
||||
}
|
||||
constexpr ColorCapabilityHelper operator|(ColorCapability lhs, ColorCapability rhs) {
|
||||
return static_cast<ColorCapability>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
||||
}
|
||||
constexpr ColorCapabilityHelper operator|(ColorCapabilityHelper lhs, ColorCapability rhs) {
|
||||
return static_cast<ColorCapability>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
||||
}
|
||||
|
||||
/// Color modes are a combination of color capabilities that can be used at the same time.
|
||||
enum class ColorMode : uint8_t {
|
||||
/// No color mode configured (cannot be a supported mode, only active when light is off).
|
||||
UNKNOWN = 0,
|
||||
/// Only on/off control.
|
||||
ON_OFF = (uint8_t) ColorCapability::ON_OFF,
|
||||
/// Dimmable light.
|
||||
BRIGHTNESS = (uint8_t) ColorCapability::BRIGHTNESS,
|
||||
/// White output only (use only if the light also has another color mode such as RGB).
|
||||
WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::WHITE),
|
||||
/// Controllable color temperature output.
|
||||
COLOR_TEMPERATURE =
|
||||
(uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::COLOR_TEMPERATURE),
|
||||
/// Cold and warm white output with individually controllable brightness.
|
||||
COLD_WARM_WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::COLD_WARM_WHITE),
|
||||
/// RGB color output.
|
||||
RGB = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB),
|
||||
/// RGB color output and a separate white output.
|
||||
RGB_WHITE =
|
||||
(uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB | ColorCapability::WHITE),
|
||||
/// RGB color output and a separate white output with controllable color temperature.
|
||||
RGB_COLOR_TEMPERATURE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB |
|
||||
ColorCapability::WHITE | ColorCapability::COLOR_TEMPERATURE),
|
||||
/// RGB color output, and separate cold and warm white outputs.
|
||||
RGB_COLD_WARM_WHITE = (uint8_t)(ColorCapability::ON_OFF | ColorCapability::BRIGHTNESS | ColorCapability::RGB |
|
||||
ColorCapability::COLD_WARM_WHITE),
|
||||
};
|
||||
|
||||
/// Helper class to allow bitwise operations on ColorMode with ColorCapability
|
||||
class ColorModeHelper {
|
||||
public:
|
||||
constexpr ColorModeHelper(ColorMode val) : val_(val) {}
|
||||
constexpr operator ColorMode() const { return val_; }
|
||||
constexpr operator uint8_t() const { return static_cast<uint8_t>(val_); }
|
||||
constexpr operator bool() const { return static_cast<uint8_t>(val_) != 0; }
|
||||
|
||||
protected:
|
||||
ColorMode val_;
|
||||
};
|
||||
constexpr ColorModeHelper operator&(ColorMode lhs, ColorMode rhs) {
|
||||
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
|
||||
}
|
||||
constexpr ColorModeHelper operator&(ColorMode lhs, ColorCapability rhs) {
|
||||
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
|
||||
}
|
||||
constexpr ColorModeHelper operator&(ColorModeHelper lhs, ColorMode rhs) {
|
||||
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
|
||||
}
|
||||
constexpr ColorModeHelper operator|(ColorMode lhs, ColorMode rhs) {
|
||||
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
||||
}
|
||||
constexpr ColorModeHelper operator|(ColorMode lhs, ColorCapability rhs) {
|
||||
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
||||
}
|
||||
constexpr ColorModeHelper operator|(ColorModeHelper lhs, ColorMode rhs) {
|
||||
return static_cast<ColorMode>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
||||
}
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
@@ -12,11 +12,15 @@ from esphome.const import (
|
||||
CONF_STATE,
|
||||
CONF_DURATION,
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_COLOR_MODE,
|
||||
CONF_COLOR_BRIGHTNESS,
|
||||
CONF_RED,
|
||||
CONF_GREEN,
|
||||
CONF_BLUE,
|
||||
CONF_WHITE,
|
||||
CONF_COLOR_TEMPERATURE,
|
||||
CONF_COLD_WHITE,
|
||||
CONF_WARM_WHITE,
|
||||
CONF_ALPHA,
|
||||
CONF_INTENSITY,
|
||||
CONF_SPEED,
|
||||
@@ -27,6 +31,8 @@ from esphome.const import (
|
||||
)
|
||||
from esphome.util import Registry
|
||||
from .types import (
|
||||
ColorMode,
|
||||
COLOR_MODES,
|
||||
LambdaLightEffect,
|
||||
PulseLightEffect,
|
||||
RandomLightEffect,
|
||||
@@ -212,11 +218,17 @@ 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_MODE): cv.enum(
|
||||
COLOR_MODES, upper=True, space="_"
|
||||
),
|
||||
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,
|
||||
cv.Optional(CONF_WHITE, default=1.0): cv.percentage,
|
||||
cv.Optional(CONF_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Optional(CONF_COLD_WHITE, default=1.0): cv.percentage,
|
||||
cv.Optional(CONF_WARM_WHITE, default=1.0): cv.percentage,
|
||||
cv.Required(
|
||||
CONF_DURATION
|
||||
): cv.positive_time_period_milliseconds,
|
||||
@@ -225,11 +237,15 @@ async def random_effect_to_code(config, effect_id):
|
||||
cv.has_at_least_one_key(
|
||||
CONF_STATE,
|
||||
CONF_BRIGHTNESS,
|
||||
CONF_COLOR_MODE,
|
||||
CONF_COLOR_BRIGHTNESS,
|
||||
CONF_RED,
|
||||
CONF_GREEN,
|
||||
CONF_BLUE,
|
||||
CONF_WHITE,
|
||||
CONF_COLOR_TEMPERATURE,
|
||||
CONF_COLD_WHITE,
|
||||
CONF_WARM_WHITE,
|
||||
),
|
||||
),
|
||||
cv.Length(min=2),
|
||||
@@ -246,6 +262,7 @@ async def strobe_effect_to_code(config, effect_id):
|
||||
(
|
||||
"color",
|
||||
LightColorValues(
|
||||
color.get(CONF_COLOR_MODE, ColorMode.UNKNOWN),
|
||||
color[CONF_STATE],
|
||||
color[CONF_BRIGHTNESS],
|
||||
color[CONF_COLOR_BRIGHTNESS],
|
||||
@@ -253,6 +270,9 @@ async def strobe_effect_to_code(config, effect_id):
|
||||
color[CONF_GREEN],
|
||||
color[CONF_BLUE],
|
||||
color[CONF_WHITE],
|
||||
color.get(CONF_COLOR_TEMPERATURE, 0.0),
|
||||
color[CONF_COLD_WHITE],
|
||||
color[CONF_WARM_WHITE],
|
||||
),
|
||||
),
|
||||
("duration", color[CONF_DURATION]),
|
||||
|
@@ -7,95 +7,42 @@ namespace light {
|
||||
|
||||
static const char *const TAG = "light";
|
||||
|
||||
#ifdef USE_JSON
|
||||
LightCall &LightCall::parse_color_json(JsonObject &root) {
|
||||
if (root.containsKey("state")) {
|
||||
auto val = parse_on_off(root["state"]);
|
||||
switch (val) {
|
||||
case PARSE_ON:
|
||||
this->set_state(true);
|
||||
break;
|
||||
case PARSE_OFF:
|
||||
this->set_state(false);
|
||||
break;
|
||||
case PARSE_TOGGLE:
|
||||
this->set_state(!this->parent_->remote_values.is_on());
|
||||
break;
|
||||
case PARSE_NONE:
|
||||
break;
|
||||
}
|
||||
static const char *color_mode_to_human(ColorMode color_mode) {
|
||||
switch (color_mode) {
|
||||
case ColorMode::UNKNOWN:
|
||||
return "Unknown";
|
||||
case ColorMode::WHITE:
|
||||
return "White";
|
||||
case ColorMode::COLOR_TEMPERATURE:
|
||||
return "Color temperature";
|
||||
case ColorMode::COLD_WARM_WHITE:
|
||||
return "Cold/warm white";
|
||||
case ColorMode::RGB:
|
||||
return "RGB";
|
||||
case ColorMode::RGB_WHITE:
|
||||
return "RGBW";
|
||||
case ColorMode::RGB_COLD_WARM_WHITE:
|
||||
return "RGB + cold/warm white";
|
||||
case ColorMode::RGB_COLOR_TEMPERATURE:
|
||||
return "RGB + color temperature";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
if (root.containsKey("brightness")) {
|
||||
this->set_brightness(float(root["brightness"]) / 255.0f);
|
||||
}
|
||||
|
||||
if (root.containsKey("color")) {
|
||||
JsonObject &color = root["color"];
|
||||
// HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness.
|
||||
float max_rgb = 0.0f;
|
||||
if (color.containsKey("r")) {
|
||||
float r = float(color["r"]) / 255.0f;
|
||||
max_rgb = fmaxf(max_rgb, r);
|
||||
this->set_red(r);
|
||||
}
|
||||
if (color.containsKey("g")) {
|
||||
float g = float(color["g"]) / 255.0f;
|
||||
max_rgb = fmaxf(max_rgb, g);
|
||||
this->set_green(g);
|
||||
}
|
||||
if (color.containsKey("b")) {
|
||||
float b = float(color["b"]) / 255.0f;
|
||||
max_rgb = fmaxf(max_rgb, b);
|
||||
this->set_blue(b);
|
||||
}
|
||||
if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) {
|
||||
this->set_color_brightness(max_rgb);
|
||||
}
|
||||
}
|
||||
|
||||
if (root.containsKey("white_value")) {
|
||||
this->set_white(float(root["white_value"]) / 255.0f);
|
||||
}
|
||||
|
||||
if (root.containsKey("color_temp")) {
|
||||
this->set_color_temperature(float(root["color_temp"]));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::parse_json(JsonObject &root) {
|
||||
this->parse_color_json(root);
|
||||
|
||||
if (root.containsKey("flash")) {
|
||||
auto length = uint32_t(float(root["flash"]) * 1000);
|
||||
this->set_flash_length(length);
|
||||
}
|
||||
|
||||
if (root.containsKey("transition")) {
|
||||
auto length = uint32_t(float(root["transition"]) * 1000);
|
||||
this->set_transition_length(length);
|
||||
}
|
||||
|
||||
if (root.containsKey("effect")) {
|
||||
const char *effect = root["effect"];
|
||||
this->set_effect(effect);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
void LightCall::perform() {
|
||||
// use remote values for fallback
|
||||
const char *name = this->parent_->get_name().c_str();
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, "'%s' Setting:", name);
|
||||
}
|
||||
|
||||
LightColorValues v = this->validate_();
|
||||
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, "'%s' Setting:", name);
|
||||
|
||||
// Only print color mode when it's being changed
|
||||
ColorMode current_color_mode = this->parent_->remote_values.get_color_mode();
|
||||
if (this->color_mode_.value_or(current_color_mode) != current_color_mode) {
|
||||
ESP_LOGD(TAG, " Color mode: %s", color_mode_to_human(v.get_color_mode()));
|
||||
}
|
||||
|
||||
// Only print state when it's being changed
|
||||
bool current_state = this->parent_->remote_values.is_on();
|
||||
if (this->state_.value_or(current_state) != current_state) {
|
||||
@@ -106,30 +53,35 @@ void LightCall::perform() {
|
||||
ESP_LOGD(TAG, " Brightness: %.0f%%", v.get_brightness() * 100.0f);
|
||||
}
|
||||
|
||||
if (this->color_temperature_.has_value()) {
|
||||
ESP_LOGD(TAG, " Color Temperature: %.1f mireds", v.get_color_temperature());
|
||||
}
|
||||
|
||||
if (this->red_.has_value() || this->green_.has_value() || this->blue_.has_value()) {
|
||||
ESP_LOGD(TAG, " Red=%.0f%%, Green=%.0f%%, Blue=%.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
||||
ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
||||
v.get_blue() * 100.0f);
|
||||
}
|
||||
|
||||
if (this->white_.has_value()) {
|
||||
ESP_LOGD(TAG, " White Value: %.0f%%", v.get_white() * 100.0f);
|
||||
ESP_LOGD(TAG, " White: %.0f%%", v.get_white() * 100.0f);
|
||||
}
|
||||
if (this->color_temperature_.has_value()) {
|
||||
ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature());
|
||||
}
|
||||
|
||||
if (this->cold_white_.has_value() || this->warm_white_.has_value()) {
|
||||
ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f,
|
||||
v.get_warm_white() * 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->has_flash_()) {
|
||||
// FLASH
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, " Flash Length: %.1fs", *this->flash_length_ / 1e3f);
|
||||
ESP_LOGD(TAG, " Flash length: %.1fs", *this->flash_length_ / 1e3f);
|
||||
}
|
||||
|
||||
this->parent_->start_flash_(v, *this->flash_length_);
|
||||
} else if (this->has_transition_()) {
|
||||
// TRANSITION
|
||||
if (this->publish_) {
|
||||
ESP_LOGD(TAG, " Transition Length: %.1fs", *this->transition_length_ / 1e3f);
|
||||
ESP_LOGD(TAG, " Transition length: %.1fs", *this->transition_length_ / 1e3f);
|
||||
}
|
||||
|
||||
// Special case: Transition and effect can be set when turning off
|
||||
@@ -177,32 +129,48 @@ void LightCall::perform() {
|
||||
}
|
||||
|
||||
LightColorValues LightCall::validate_() {
|
||||
// use remote values for fallback
|
||||
auto *name = this->parent_->get_name().c_str();
|
||||
auto traits = this->parent_->get_traits();
|
||||
|
||||
// Color mode check
|
||||
if (this->color_mode_.has_value() && !traits.supports_color_mode(this->color_mode_.value())) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support color mode %s!", name,
|
||||
color_mode_to_human(this->color_mode_.value()));
|
||||
this->color_mode_.reset();
|
||||
}
|
||||
|
||||
// Ensure there is always a color mode set
|
||||
if (!this->color_mode_.has_value()) {
|
||||
this->color_mode_ = this->compute_color_mode_();
|
||||
}
|
||||
auto color_mode = *this->color_mode_;
|
||||
|
||||
// Transform calls that use non-native parameters for the current mode.
|
||||
this->transform_parameters_();
|
||||
|
||||
// Brightness exists check
|
||||
if (this->brightness_.has_value() && !traits.get_supports_brightness()) {
|
||||
if (this->brightness_.has_value() && !(color_mode & ColorCapability::BRIGHTNESS)) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support setting brightness!", name);
|
||||
this->brightness_.reset();
|
||||
}
|
||||
|
||||
// Transition length possible check
|
||||
if (this->transition_length_.has_value() && *this->transition_length_ != 0 && !traits.get_supports_brightness()) {
|
||||
if (this->transition_length_.has_value() && *this->transition_length_ != 0 &&
|
||||
!(color_mode & ColorCapability::BRIGHTNESS)) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support transitions!", name);
|
||||
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);
|
||||
if (this->color_brightness_.has_value() && !(color_mode & ColorCapability::RGB)) {
|
||||
ESP_LOGW(TAG, "'%s' - This color mode 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()) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support setting RGB color!", name);
|
||||
if (!(color_mode & ColorCapability::RGB)) {
|
||||
ESP_LOGW(TAG, "'%s' - This color mode does not support setting RGB color!", name);
|
||||
this->red_.reset();
|
||||
this->green_.reset();
|
||||
this->blue_.reset();
|
||||
@@ -210,68 +178,25 @@ LightColorValues LightCall::validate_() {
|
||||
}
|
||||
|
||||
// White value exists check
|
||||
if (this->white_.has_value() && !traits.get_supports_rgb_white_value()) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support setting white value!", name);
|
||||
if (this->white_.has_value() &&
|
||||
!(color_mode & ColorCapability::WHITE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||
ESP_LOGW(TAG, "'%s' - This color mode does not support setting white value!", name);
|
||||
this->white_.reset();
|
||||
}
|
||||
|
||||
// Color temperature exists check
|
||||
if (this->color_temperature_.has_value() && !traits.get_supports_color_temperature()) {
|
||||
ESP_LOGW(TAG, "'%s' - This light does not support setting color temperature!", name);
|
||||
if (this->color_temperature_.has_value() &&
|
||||
!(color_mode & ColorCapability::COLOR_TEMPERATURE || color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||
ESP_LOGW(TAG, "'%s' - This color mode does not support setting color temperature!", name);
|
||||
this->color_temperature_.reset();
|
||||
}
|
||||
|
||||
// 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
|
||||
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);
|
||||
|
||||
// if setting color temperature from color (i.e. switching to white light), set White to 100%
|
||||
auto cv = this->parent_->remote_values;
|
||||
bool was_color = cv.get_red() != 1.0f || cv.get_blue() != 1.0f || cv.get_green() != 1.0f;
|
||||
if (traits.get_supports_color_interlock() || was_color) {
|
||||
this->white_ = optional<float>(1.0f);
|
||||
// Cold/warm white value exists check
|
||||
if (this->cold_white_.has_value() || this->warm_white_.has_value()) {
|
||||
if (!(color_mode & ColorCapability::COLD_WARM_WHITE)) {
|
||||
ESP_LOGW(TAG, "'%s' - This color mode does not support setting cold/warm white value!", name);
|
||||
this->cold_white_.reset();
|
||||
this->warm_white_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,13 +217,22 @@ LightColorValues LightCall::validate_() {
|
||||
VALIDATE_RANGE(green, "Green")
|
||||
VALIDATE_RANGE(blue, "Blue")
|
||||
VALIDATE_RANGE(white, "White")
|
||||
VALIDATE_RANGE(cold_white, "Cold white")
|
||||
VALIDATE_RANGE(warm_white, "Warm white")
|
||||
|
||||
// Set color brightness to 100% if currently zero and a color is set.
|
||||
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);
|
||||
}
|
||||
|
||||
auto v = this->parent_->remote_values;
|
||||
if (this->color_mode_.has_value())
|
||||
v.set_color_mode(*this->color_mode_);
|
||||
if (this->state_.has_value())
|
||||
v.set_state(*this->state_);
|
||||
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())
|
||||
@@ -309,9 +243,12 @@ LightColorValues LightCall::validate_() {
|
||||
v.set_blue(*this->blue_);
|
||||
if (this->white_.has_value())
|
||||
v.set_white(*this->white_);
|
||||
|
||||
if (this->color_temperature_.has_value())
|
||||
v.set_color_temperature(*this->color_temperature_);
|
||||
if (this->cold_white_.has_value())
|
||||
v.set_cold_white(*this->cold_white_);
|
||||
if (this->warm_white_.has_value())
|
||||
v.set_warm_white(*this->warm_white_);
|
||||
|
||||
v.normalize_color(traits);
|
||||
|
||||
@@ -322,7 +259,7 @@ LightColorValues LightCall::validate_() {
|
||||
}
|
||||
|
||||
// validate transition length/flash length/effect not used at the same time
|
||||
bool supports_transition = traits.get_supports_brightness();
|
||||
bool supports_transition = color_mode & ColorCapability::BRIGHTNESS;
|
||||
|
||||
// If effect is already active, remove effect start
|
||||
if (this->has_effect_() && *this->effect_ == this->parent_->active_effect_index_) {
|
||||
@@ -331,7 +268,7 @@ LightColorValues LightCall::validate_() {
|
||||
|
||||
// validate effect index
|
||||
if (this->has_effect_() && *this->effect_ > this->parent_->effects_.size()) {
|
||||
ESP_LOGW(TAG, "'%s' Invalid effect index %u", name, *this->effect_);
|
||||
ESP_LOGW(TAG, "'%s' - Invalid effect index %u!", name, *this->effect_);
|
||||
this->effect_.reset();
|
||||
}
|
||||
|
||||
@@ -381,6 +318,127 @@ LightColorValues LightCall::validate_() {
|
||||
|
||||
return v;
|
||||
}
|
||||
void LightCall::transform_parameters_() {
|
||||
auto traits = this->parent_->get_traits();
|
||||
|
||||
// Allow CWWW modes to be set with a white value and/or color temperature. This is used by HA,
|
||||
// which doesn't support CWWW modes (yet?), and for compatibility with the pre-colormode model,
|
||||
// as CWWW and RGBWW lights used to represent their values as white + color temperature.
|
||||
if (((this->white_.has_value() && *this->white_ > 0.0f) || this->color_temperature_.has_value()) && //
|
||||
(*this->color_mode_ & ColorCapability::COLD_WARM_WHITE) && //
|
||||
!(*this->color_mode_ & ColorCapability::WHITE) && //
|
||||
!(*this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
|
||||
traits.get_min_mireds() > 0.0f && traits.get_max_mireds() > 0.0f) {
|
||||
ESP_LOGD(TAG, "'%s' - Setting cold/warm white channels using white/color temperature values.",
|
||||
this->parent_->get_name().c_str());
|
||||
auto current_values = this->parent_->remote_values;
|
||||
if (this->color_temperature_.has_value()) {
|
||||
const float white =
|
||||
this->white_.value_or(fmaxf(current_values.get_cold_white(), current_values.get_warm_white()));
|
||||
const float color_temp = clamp(*this->color_temperature_, traits.get_min_mireds(), traits.get_max_mireds());
|
||||
const float ww_fraction =
|
||||
(color_temp - traits.get_min_mireds()) / (traits.get_max_mireds() - traits.get_min_mireds());
|
||||
const float cw_fraction = 1.0f - ww_fraction;
|
||||
const float max_cw_ww = std::max(ww_fraction, cw_fraction);
|
||||
this->cold_white_ = white * gamma_uncorrect(cw_fraction / max_cw_ww, this->parent_->get_gamma_correct());
|
||||
this->warm_white_ = white * gamma_uncorrect(ww_fraction / max_cw_ww, this->parent_->get_gamma_correct());
|
||||
} else {
|
||||
const float max_cw_ww = std::max(current_values.get_warm_white(), current_values.get_cold_white());
|
||||
this->cold_white_ = *this->white_ * current_values.get_cold_white() / max_cw_ww;
|
||||
this->warm_white_ = *this->white_ * current_values.get_warm_white() / max_cw_ww;
|
||||
}
|
||||
}
|
||||
}
|
||||
ColorMode LightCall::compute_color_mode_() {
|
||||
auto supported_modes = this->parent_->get_traits().get_supported_color_modes();
|
||||
int supported_count = supported_modes.size();
|
||||
|
||||
// Some lights don't support any color modes (e.g. monochromatic light), leave it at unknown.
|
||||
if (supported_count == 0)
|
||||
return ColorMode::UNKNOWN;
|
||||
|
||||
// In the common case of lights supporting only a single mode, use that one.
|
||||
if (supported_count == 1)
|
||||
return *supported_modes.begin();
|
||||
|
||||
// Don't change if the light is being turned off.
|
||||
ColorMode current_mode = this->parent_->remote_values.get_color_mode();
|
||||
if (this->state_.has_value() && !*this->state_)
|
||||
return current_mode;
|
||||
|
||||
// If no color mode is specified, we try to guess the color mode. This is needed for backward compatibility to
|
||||
// pre-colormode clients and automations, but also for the MQTT API, where HA doesn't let us know which color mode
|
||||
// was used for some reason.
|
||||
std::set<ColorMode> suitable_modes = this->get_suitable_color_modes_();
|
||||
|
||||
// Don't change if the current mode is suitable.
|
||||
if (suitable_modes.count(current_mode) > 0) {
|
||||
ESP_LOGI(TAG, "'%s' - Keeping current color mode %s for call without color mode.",
|
||||
this->parent_->get_name().c_str(), color_mode_to_human(current_mode));
|
||||
return current_mode;
|
||||
}
|
||||
|
||||
// Use the preferred suitable mode.
|
||||
for (auto mode : suitable_modes) {
|
||||
if (supported_modes.count(mode) == 0)
|
||||
continue;
|
||||
|
||||
ESP_LOGI(TAG, "'%s' - Using color mode %s for call without color mode.", this->parent_->get_name().c_str(),
|
||||
color_mode_to_human(mode));
|
||||
return mode;
|
||||
}
|
||||
|
||||
// There's no supported mode for this call, so warn, use the current more or a mode at random and let validation strip
|
||||
// out whatever we don't support.
|
||||
auto color_mode = current_mode != ColorMode::UNKNOWN ? current_mode : *supported_modes.begin();
|
||||
ESP_LOGW(TAG, "'%s' - No color mode suitable for this call supported, defaulting to %s!",
|
||||
this->parent_->get_name().c_str(), color_mode_to_human(color_mode));
|
||||
return color_mode;
|
||||
}
|
||||
std::set<ColorMode> LightCall::get_suitable_color_modes_() {
|
||||
bool has_white = this->white_.has_value() && *this->white_ > 0.0f;
|
||||
bool has_ct = this->color_temperature_.has_value();
|
||||
bool has_cwww = (this->cold_white_.has_value() && *this->cold_white_ > 0.0f) ||
|
||||
(this->warm_white_.has_value() && *this->warm_white_ > 0.0f);
|
||||
bool has_rgb = (this->color_brightness_.has_value() && *this->color_brightness_ > 0.0f) ||
|
||||
(this->red_.has_value() || this->green_.has_value() || this->blue_.has_value());
|
||||
|
||||
#define KEY(white, ct, cwww, rgb) ((white) << 0 | (ct) << 1 | (cwww) << 2 | (rgb) << 3)
|
||||
#define ENTRY(white, ct, cwww, rgb, ...) \
|
||||
std::make_tuple<uint8_t, std::set<ColorMode>>(KEY(white, ct, cwww, rgb), __VA_ARGS__)
|
||||
|
||||
// Flag order: white, color temperature, cwww, rgb
|
||||
std::array<std::tuple<uint8_t, std::set<ColorMode>>, 10> lookup_table{
|
||||
ENTRY(true, false, false, false,
|
||||
{ColorMode::WHITE, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, true, false, false,
|
||||
{ColorMode::COLOR_TEMPERATURE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE,
|
||||
ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(true, true, false, false,
|
||||
{ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, false, true, false, {ColorMode::COLD_WARM_WHITE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, false, false, false,
|
||||
{ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE, ColorMode::RGB,
|
||||
ColorMode::WHITE, ColorMode::COLOR_TEMPERATURE, ColorMode::COLD_WARM_WHITE}),
|
||||
ENTRY(true, false, false, true,
|
||||
{ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, true, false, true, {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(true, true, false, true, {ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, false, true, true, {ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
ENTRY(false, false, false, true,
|
||||
{ColorMode::RGB, ColorMode::RGB_WHITE, ColorMode::RGB_COLOR_TEMPERATURE, ColorMode::RGB_COLD_WARM_WHITE}),
|
||||
};
|
||||
|
||||
auto key = KEY(has_white, has_ct, has_cwww, has_rgb);
|
||||
for (auto &item : lookup_table)
|
||||
if (std::get<0>(item) == key)
|
||||
return std::get<1>(item);
|
||||
|
||||
// This happens if there are conflicting flags given.
|
||||
return {};
|
||||
}
|
||||
|
||||
LightCall &LightCall::set_effect(const std::string &effect) {
|
||||
if (strcasecmp(effect.c_str(), "none") == 0) {
|
||||
this->set_effect(0);
|
||||
@@ -406,53 +464,74 @@ 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_color_mode_if_supported(values.get_color_mode());
|
||||
this->set_red_if_supported(values.get_red());
|
||||
this->set_green_if_supported(values.get_green());
|
||||
this->set_blue_if_supported(values.get_blue());
|
||||
this->set_white_if_supported(values.get_white());
|
||||
this->set_color_temperature_if_supported(values.get_color_temperature());
|
||||
this->set_cold_white_if_supported(values.get_cold_white());
|
||||
this->set_warm_white_if_supported(values.get_warm_white());
|
||||
return *this;
|
||||
}
|
||||
ColorMode LightCall::get_active_color_mode_() {
|
||||
return this->color_mode_.value_or(this->parent_->remote_values.get_color_mode());
|
||||
}
|
||||
LightCall &LightCall::set_transition_length_if_supported(uint32_t transition_length) {
|
||||
if (this->parent_->get_traits().get_supports_brightness())
|
||||
if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS)
|
||||
this->set_transition_length(transition_length);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_brightness_if_supported(float brightness) {
|
||||
if (this->parent_->get_traits().get_supports_brightness())
|
||||
if (this->get_active_color_mode_() & ColorCapability::BRIGHTNESS)
|
||||
this->set_brightness(brightness);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_mode_if_supported(ColorMode color_mode) {
|
||||
if (this->parent_->get_traits().supports_color_mode(color_mode))
|
||||
this->color_mode_ = color_mode;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_brightness_if_supported(float brightness) {
|
||||
if (this->parent_->get_traits().get_supports_rgb_white_value())
|
||||
if (this->get_active_color_mode_() & ColorCapability::RGB)
|
||||
this->set_color_brightness(brightness);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_red_if_supported(float red) {
|
||||
if (this->parent_->get_traits().get_supports_rgb())
|
||||
if (this->get_active_color_mode_() & ColorCapability::RGB)
|
||||
this->set_red(red);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_green_if_supported(float green) {
|
||||
if (this->parent_->get_traits().get_supports_rgb())
|
||||
if (this->get_active_color_mode_() & ColorCapability::RGB)
|
||||
this->set_green(green);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_blue_if_supported(float blue) {
|
||||
if (this->parent_->get_traits().get_supports_rgb())
|
||||
if (this->get_active_color_mode_() & ColorCapability::RGB)
|
||||
this->set_blue(blue);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_white_if_supported(float white) {
|
||||
if (this->parent_->get_traits().get_supports_rgb_white_value())
|
||||
if (this->get_active_color_mode_() & ColorCapability::WHITE)
|
||||
this->set_white(white);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_temperature_if_supported(float color_temperature) {
|
||||
if (this->parent_->get_traits().get_supports_color_temperature())
|
||||
if (this->get_active_color_mode_() & ColorCapability::COLOR_TEMPERATURE)
|
||||
this->set_color_temperature(color_temperature);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_cold_white_if_supported(float cold_white) {
|
||||
if (this->get_active_color_mode_() & ColorCapability::COLD_WARM_WHITE)
|
||||
this->set_cold_white(cold_white);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_warm_white_if_supported(float warm_white) {
|
||||
if (this->get_active_color_mode_() & ColorCapability::COLD_WARM_WHITE)
|
||||
this->set_warm_white(warm_white);
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_state(optional<bool> state) {
|
||||
this->state_ = state;
|
||||
return *this;
|
||||
@@ -485,6 +564,14 @@ LightCall &LightCall::set_brightness(float brightness) {
|
||||
this->brightness_ = brightness;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_mode(optional<ColorMode> color_mode) {
|
||||
this->color_mode_ = color_mode;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_mode(ColorMode color_mode) {
|
||||
this->color_mode_ = color_mode;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_color_brightness(optional<float> brightness) {
|
||||
this->color_brightness_ = brightness;
|
||||
return *this;
|
||||
@@ -533,6 +620,22 @@ LightCall &LightCall::set_color_temperature(float color_temperature) {
|
||||
this->color_temperature_ = color_temperature;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_cold_white(optional<float> cold_white) {
|
||||
this->cold_white_ = cold_white;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_cold_white(float cold_white) {
|
||||
this->cold_white_ = cold_white;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_warm_white(optional<float> warm_white) {
|
||||
this->warm_white_ = warm_white;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_warm_white(float warm_white) {
|
||||
this->warm_white_ = warm_white;
|
||||
return *this;
|
||||
}
|
||||
LightCall &LightCall::set_effect(optional<std::string> effect) {
|
||||
if (effect.has_value())
|
||||
this->set_effect(*effect);
|
||||
|
@@ -44,6 +44,14 @@ 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 mode of the light.
|
||||
LightCall &set_color_mode(optional<ColorMode> color_mode);
|
||||
/// Set the color mode of the light.
|
||||
LightCall &set_color_mode(ColorMode color_mode);
|
||||
/// Set the color mode of the light, if this mode is supported.
|
||||
LightCall &set_color_mode_if_supported(ColorMode color_mode);
|
||||
|
||||
/// 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)
|
||||
@@ -98,6 +106,18 @@ class LightCall {
|
||||
LightCall &set_color_temperature(float color_temperature);
|
||||
/// Set the color_temperature property if the light supports color temperature.
|
||||
LightCall &set_color_temperature_if_supported(float color_temperature);
|
||||
/// Set the cold white value of the light from 0.0 to 1.0.
|
||||
LightCall &set_cold_white(optional<float> cold_white);
|
||||
/// Set the cold white value of the light from 0.0 to 1.0.
|
||||
LightCall &set_cold_white(float cold_white);
|
||||
/// Set the cold white property if the light supports cold white output.
|
||||
LightCall &set_cold_white_if_supported(float cold_white);
|
||||
/// Set the warm white value of the light from 0.0 to 1.0.
|
||||
LightCall &set_warm_white(optional<float> warm_white);
|
||||
/// Set the warm white value of the light from 0.0 to 1.0.
|
||||
LightCall &set_warm_white(float warm_white);
|
||||
/// Set the warm white property if the light supports cold white output.
|
||||
LightCall &set_warm_white_if_supported(float warm_white);
|
||||
/// Set the effect of the light by its name.
|
||||
LightCall &set_effect(optional<std::string> effect);
|
||||
/// Set the effect of the light by its name.
|
||||
@@ -131,18 +151,24 @@ class LightCall {
|
||||
* @return The light call for chaining setters.
|
||||
*/
|
||||
LightCall &set_rgbw(float red, float green, float blue, float white);
|
||||
#ifdef USE_JSON
|
||||
LightCall &parse_color_json(JsonObject &root);
|
||||
LightCall &parse_json(JsonObject &root);
|
||||
#endif
|
||||
LightCall &from_light_color_values(const LightColorValues &values);
|
||||
|
||||
void perform();
|
||||
|
||||
protected:
|
||||
/// Get the currently targeted, or active if none set, color mode.
|
||||
ColorMode get_active_color_mode_();
|
||||
|
||||
/// Validate all properties and return the target light color values.
|
||||
LightColorValues validate_();
|
||||
|
||||
//// Compute the color mode that should be used for this call.
|
||||
ColorMode compute_color_mode_();
|
||||
/// Get potential color modes for this light call.
|
||||
std::set<ColorMode> get_suitable_color_modes_();
|
||||
/// Some color modes also can be set using non-native parameters, transform those calls.
|
||||
void transform_parameters_();
|
||||
|
||||
bool has_transition_() { return this->transition_length_.has_value(); }
|
||||
bool has_flash_() { return this->flash_length_.has_value(); }
|
||||
bool has_effect_() { return this->effect_.has_value(); }
|
||||
@@ -151,6 +177,7 @@ class LightCall {
|
||||
optional<bool> state_;
|
||||
optional<uint32_t> transition_length_;
|
||||
optional<uint32_t> flash_length_;
|
||||
optional<ColorMode> color_mode_;
|
||||
optional<float> brightness_;
|
||||
optional<float> color_brightness_;
|
||||
optional<float> red_;
|
||||
@@ -158,6 +185,8 @@ class LightCall {
|
||||
optional<float> blue_;
|
||||
optional<float> white_;
|
||||
optional<float> color_temperature_;
|
||||
optional<float> cold_white_;
|
||||
optional<float> warm_white_;
|
||||
optional<uint32_t> effect_;
|
||||
bool publish_{true};
|
||||
bool save_{true};
|
||||
|
@@ -15,40 +15,56 @@ inline static uint8_t to_uint8_scale(float x) { return static_cast<uint8_t>(roun
|
||||
|
||||
/** This class represents the color state for a light object.
|
||||
*
|
||||
* 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.
|
||||
* The representation of the color state is dependent on the active color mode. A color mode consists of multiple
|
||||
* color capabilities, and each color capability has its own representation in this class. The fields available are as
|
||||
* follows:
|
||||
*
|
||||
* 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.
|
||||
* Always:
|
||||
* - color_mode: The currently active color mode.
|
||||
*
|
||||
* 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.
|
||||
* For ON_OFF capability:
|
||||
* - state: Whether the light should be on/off. Represented as a float for transitions.
|
||||
*
|
||||
* For BRIGHTNESS capability:
|
||||
* - brightness: The master brightness of the light, should be applied to all channels.
|
||||
*
|
||||
* For RGB capability:
|
||||
* - color_brightness: The brightness of the color channels of the light.
|
||||
* - red, green, blue: The RGB values of the current color. They are normalized, so at least one of them is always 1.0.
|
||||
*
|
||||
* For WHITE capability:
|
||||
* - white: The brightness of the white channel of the light.
|
||||
*
|
||||
* For COLOR_TEMPERATURE capability:
|
||||
* - color_temperature: The color temperature of the white channel in mireds. Note that it is not clamped to the valid
|
||||
* range as set in the traits, so the output needs to do this.
|
||||
*
|
||||
* For COLD_WARM_WHITE capability:
|
||||
* - cold_white, warm_white: The brightness of the cald and warm white channels of the light.
|
||||
*
|
||||
* All values (except color temperature) are represented using floats in the range 0.0 (off) to 1.0 (on), and are
|
||||
* automatically clamped to this range. Properties not used in the current color mode can still have (invalid) values
|
||||
* and must not be accessed by the light output.
|
||||
*/
|
||||
class LightColorValues {
|
||||
public:
|
||||
/// Construct the LightColorValues with all attributes enabled, but state set to 0.0
|
||||
/// Construct the LightColorValues with all attributes enabled, but state set to off.
|
||||
LightColorValues()
|
||||
: state_(0.0f),
|
||||
: color_mode_(ColorMode::UNKNOWN),
|
||||
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} {}
|
||||
color_temperature_{0.0f},
|
||||
cold_white_{1.0f},
|
||||
warm_white_{1.0f} {}
|
||||
|
||||
LightColorValues(float state, float brightness, float color_brightness, float red, float green, float blue,
|
||||
float white, float color_temperature = 1.0f) {
|
||||
LightColorValues(ColorMode color_mode, float state, float brightness, float color_brightness, float red, float green,
|
||||
float blue, float white, float color_temperature, float cold_white, float warm_white) {
|
||||
this->set_color_mode(color_mode);
|
||||
this->set_state(state);
|
||||
this->set_brightness(brightness);
|
||||
this->set_color_brightness(color_brightness);
|
||||
@@ -57,49 +73,8 @@ class LightColorValues {
|
||||
this->set_blue(blue);
|
||||
this->set_white(white);
|
||||
this->set_color_temperature(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, 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, 1.0f};
|
||||
else
|
||||
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, 1.0f};
|
||||
} else {
|
||||
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 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,
|
||||
master_brightness,
|
||||
color_brightness / master_brightness,
|
||||
r / color_brightness,
|
||||
g / color_brightness,
|
||||
b / color_brightness,
|
||||
w / master_brightness};
|
||||
}
|
||||
this->set_cold_white(cold_white);
|
||||
this->set_warm_white(warm_white);
|
||||
}
|
||||
|
||||
/** Linearly interpolate between the values in start to the values in end.
|
||||
@@ -114,6 +89,7 @@ class LightColorValues {
|
||||
*/
|
||||
static LightColorValues lerp(const LightColorValues &start, const LightColorValues &end, float completion) {
|
||||
LightColorValues v;
|
||||
v.set_color_mode(end.color_mode_);
|
||||
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()));
|
||||
@@ -122,33 +98,11 @@ class LightColorValues {
|
||||
v.set_blue(esphome::lerp(completion, start.get_blue(), end.get_blue()));
|
||||
v.set_white(esphome::lerp(completion, start.get_white(), end.get_white()));
|
||||
v.set_color_temperature(esphome::lerp(completion, start.get_color_temperature(), end.get_color_temperature()));
|
||||
v.set_cold_white(esphome::lerp(completion, start.get_cold_white(), end.get_cold_white()));
|
||||
v.set_warm_white(esphome::lerp(completion, start.get_warm_white(), end.get_warm_white()));
|
||||
return v;
|
||||
}
|
||||
|
||||
#ifdef USE_JSON
|
||||
/** Dump this color into a JsonObject. Only dumps values if the corresponding traits are marked supported by traits.
|
||||
*
|
||||
* @param root The json root object.
|
||||
* @param traits The traits object used for determining whether to include certain attributes.
|
||||
*/
|
||||
void dump_json(JsonObject &root, const LightTraits &traits) const {
|
||||
root["state"] = (this->get_state() != 0.0f) ? "ON" : "OFF";
|
||||
if (traits.get_supports_brightness())
|
||||
root["brightness"] = uint8_t(this->get_brightness() * 255);
|
||||
if (traits.get_supports_rgb()) {
|
||||
JsonObject &color = root.createNestedObject("color");
|
||||
color["r"] = uint8_t(this->get_color_brightness() * this->get_red() * 255);
|
||||
color["g"] = uint8_t(this->get_color_brightness() * this->get_green() * 255);
|
||||
color["b"] = uint8_t(this->get_color_brightness() * this->get_blue() * 255);
|
||||
}
|
||||
if (traits.get_supports_rgb_white_value()) {
|
||||
root["white_value"] = uint8_t(this->get_white() * 255);
|
||||
}
|
||||
if (traits.get_supports_color_temperature())
|
||||
root["color_temp"] = uint32_t(this->get_color_temperature());
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Normalize the color (RGB/W) component.
|
||||
*
|
||||
* Divides all color attributes by the maximum attribute, so effectively set at least one attribute to 1.
|
||||
@@ -159,7 +113,7 @@ class LightColorValues {
|
||||
* @param traits Used for determining which attributes to consider.
|
||||
*/
|
||||
void normalize_color(const LightTraits &traits) {
|
||||
if (traits.get_supports_rgb()) {
|
||||
if (this->color_mode_ & ColorCapability::RGB) {
|
||||
float max_value = fmaxf(this->get_red(), fmaxf(this->get_green(), this->get_blue()));
|
||||
if (max_value == 0.0f) {
|
||||
this->set_red(1.0f);
|
||||
@@ -172,7 +126,7 @@ class LightColorValues {
|
||||
}
|
||||
}
|
||||
|
||||
if (traits.get_supports_brightness() && this->get_brightness() == 0.0f) {
|
||||
if (this->color_mode_ & ColorCapability::BRIGHTNESS && this->get_brightness() == 0.0f) {
|
||||
// 0% brightness means off
|
||||
this->set_state(false);
|
||||
// reset brightness to 100%
|
||||
@@ -180,6 +134,9 @@ class LightColorValues {
|
||||
}
|
||||
}
|
||||
|
||||
// Note that method signature of as_* methods is kept as-is for compatibility reasons, so not all parameters
|
||||
// are always used or necessary. Methods will be deprecated later.
|
||||
|
||||
/// 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; }
|
||||
|
||||
@@ -190,64 +147,68 @@ 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_ * this->color_brightness_;
|
||||
if (color_interlock && this->white_ > 0.0f) {
|
||||
brightness = 0;
|
||||
if (this->color_mode_ & ColorCapability::RGB) {
|
||||
float brightness = this->state_ * this->brightness_ * this->color_brightness_;
|
||||
*red = gamma_correct(brightness * this->red_, gamma);
|
||||
*green = gamma_correct(brightness * this->green_, gamma);
|
||||
*blue = gamma_correct(brightness * this->blue_, gamma);
|
||||
} else {
|
||||
*red = *green = *blue = 0;
|
||||
}
|
||||
*red = gamma_correct(brightness * this->red_, gamma);
|
||||
*green = gamma_correct(brightness * this->green_, gamma);
|
||||
*blue = gamma_correct(brightness * this->blue_, gamma);
|
||||
}
|
||||
|
||||
/// Convert these light color values to an RGBW representation and write them to red, green, blue, white.
|
||||
void as_rgbw(float *red, float *green, float *blue, float *white, float gamma = 0,
|
||||
bool color_interlock = false) const {
|
||||
this->as_rgb(red, green, blue, gamma, color_interlock);
|
||||
*white = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma);
|
||||
this->as_rgb(red, green, blue, gamma);
|
||||
if (this->color_mode_ & ColorCapability::WHITE) {
|
||||
*white = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma);
|
||||
} else {
|
||||
*white = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert these light color values to an RGBWW representation with the given parameters.
|
||||
void as_rgbww(float color_temperature_cw, float color_temperature_ww, float *red, float *green, float *blue,
|
||||
float *cold_white, float *warm_white, float gamma = 0, bool constant_brightness = false,
|
||||
bool color_interlock = false) const {
|
||||
this->as_rgb(red, green, blue, gamma, color_interlock);
|
||||
const float color_temp = clamp(this->color_temperature_, color_temperature_cw, color_temperature_ww);
|
||||
const float ww_fraction = (color_temp - color_temperature_cw) / (color_temperature_ww - color_temperature_cw);
|
||||
const float cw_fraction = 1.0f - ww_fraction;
|
||||
const float white_level = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma);
|
||||
*cold_white = white_level * cw_fraction;
|
||||
*warm_white = white_level * ww_fraction;
|
||||
if (!constant_brightness) {
|
||||
const float max_cw_ww = std::max(ww_fraction, cw_fraction);
|
||||
*cold_white /= max_cw_ww;
|
||||
*warm_white /= max_cw_ww;
|
||||
}
|
||||
this->as_rgb(red, green, blue, gamma);
|
||||
this->as_cwww(0, 0, cold_white, warm_white, gamma, constant_brightness);
|
||||
}
|
||||
|
||||
/// Convert these light color values to an CWWW representation with the given parameters.
|
||||
void as_cwww(float color_temperature_cw, float color_temperature_ww, float *cold_white, float *warm_white,
|
||||
float gamma = 0, bool constant_brightness = false) const {
|
||||
const float color_temp = clamp(this->color_temperature_, color_temperature_cw, color_temperature_ww);
|
||||
const float ww_fraction = (color_temp - color_temperature_cw) / (color_temperature_ww - color_temperature_cw);
|
||||
const float cw_fraction = 1.0f - ww_fraction;
|
||||
const float white_level = gamma_correct(this->state_ * this->brightness_ * this->white_, gamma);
|
||||
*cold_white = white_level * cw_fraction;
|
||||
*warm_white = white_level * ww_fraction;
|
||||
if (!constant_brightness) {
|
||||
const float max_cw_ww = std::max(ww_fraction, cw_fraction);
|
||||
*cold_white /= max_cw_ww;
|
||||
*warm_white /= max_cw_ww;
|
||||
if (this->color_mode_ & ColorMode::COLD_WARM_WHITE) {
|
||||
const float cw_level = gamma_correct(this->cold_white_, gamma);
|
||||
const float ww_level = gamma_correct(this->warm_white_, gamma);
|
||||
const float white_level = gamma_correct(this->state_ * this->brightness_, gamma);
|
||||
*cold_white = white_level * cw_level;
|
||||
*warm_white = white_level * ww_level;
|
||||
if (constant_brightness && (cw_level > 0 || ww_level > 0)) {
|
||||
const float sum = cw_level + ww_level;
|
||||
*cold_white /= sum;
|
||||
*warm_white /= sum;
|
||||
}
|
||||
} else {
|
||||
*cold_white = *warm_white = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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_ && color_brightness_ == rhs.color_brightness_ &&
|
||||
red_ == rhs.red_ && green_ == rhs.green_ && blue_ == rhs.blue_ && white_ == rhs.white_ &&
|
||||
color_temperature_ == rhs.color_temperature_;
|
||||
return color_mode_ == rhs.color_mode_ && 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_ &&
|
||||
cold_white_ == rhs.cold_white_ && warm_white_ == rhs.warm_white_;
|
||||
}
|
||||
bool operator!=(const LightColorValues &rhs) const { return !(rhs == *this); }
|
||||
|
||||
/// Get the color mode of these light color values.
|
||||
ColorMode get_color_mode() const { return this->color_mode_; }
|
||||
/// Set the color mode of these light color values.
|
||||
void set_color_mode(ColorMode color_mode) { this->color_mode_ = color_mode; }
|
||||
|
||||
/// Get the state of these light color values. In range from 0.0 (off) to 1.0 (on)
|
||||
float get_state() const { return this->state_; }
|
||||
/// Get the binary true/false state of these light color values.
|
||||
@@ -290,11 +251,20 @@ class LightColorValues {
|
||||
/// Get the color temperature property of these light color values in mired.
|
||||
float get_color_temperature() const { return this->color_temperature_; }
|
||||
/// Set the color temperature property of these light color values in mired.
|
||||
void set_color_temperature(float color_temperature) {
|
||||
this->color_temperature_ = std::max(0.000001f, color_temperature);
|
||||
}
|
||||
void set_color_temperature(float color_temperature) { this->color_temperature_ = color_temperature; }
|
||||
|
||||
/// Get the cold white property of these light color values. In range 0.0 to 1.0.
|
||||
float get_cold_white() const { return this->cold_white_; }
|
||||
/// Set the cold white property of these light color values. In range 0.0 to 1.0.
|
||||
void set_cold_white(float cold_white) { this->cold_white_ = clamp(cold_white, 0.0f, 1.0f); }
|
||||
|
||||
/// Get the warm white property of these light color values. In range 0.0 to 1.0.
|
||||
float get_warm_white() const { return this->warm_white_; }
|
||||
/// Set the warm white property of these light color values. In range 0.0 to 1.0.
|
||||
void set_warm_white(float warm_white) { this->warm_white_ = clamp(warm_white, 0.0f, 1.0f); }
|
||||
|
||||
protected:
|
||||
ColorMode color_mode_;
|
||||
float state_; ///< ON / OFF, float for transition
|
||||
float brightness_;
|
||||
float color_brightness_;
|
||||
@@ -303,6 +273,8 @@ class LightColorValues {
|
||||
float blue_;
|
||||
float white_;
|
||||
float color_temperature_; ///< Color Temperature in Mired
|
||||
float cold_white_;
|
||||
float warm_white_;
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
|
165
esphome/components/light/light_json_schema.cpp
Normal file
165
esphome/components/light/light_json_schema.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
#include "light_json_schema.h"
|
||||
#include "light_output.h"
|
||||
|
||||
#ifdef USE_JSON
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
// See https://www.home-assistant.io/integrations/light.mqtt/#json-schema for documentation on the schema
|
||||
|
||||
void LightJSONSchema::dump_json(LightState &state, JsonObject &root) {
|
||||
if (state.supports_effects())
|
||||
root["effect"] = state.get_effect_name();
|
||||
|
||||
auto values = state.remote_values;
|
||||
auto traits = state.get_output()->get_traits();
|
||||
|
||||
switch (values.get_color_mode()) {
|
||||
case ColorMode::UNKNOWN: // don't need to set color mode if we don't know it
|
||||
break;
|
||||
case ColorMode::ON_OFF:
|
||||
root["color_mode"] = "onoff";
|
||||
break;
|
||||
case ColorMode::BRIGHTNESS:
|
||||
root["color_mode"] = "brightness";
|
||||
break;
|
||||
case ColorMode::WHITE: // not supported by HA in MQTT
|
||||
root["color_mode"] = "white";
|
||||
break;
|
||||
case ColorMode::COLOR_TEMPERATURE:
|
||||
root["color_mode"] = "color_temp";
|
||||
break;
|
||||
case ColorMode::COLD_WARM_WHITE: // not supported by HA
|
||||
root["color_mode"] = "cwww";
|
||||
break;
|
||||
case ColorMode::RGB:
|
||||
root["color_mode"] = "rgb";
|
||||
break;
|
||||
case ColorMode::RGB_WHITE:
|
||||
root["color_mode"] = "rgbw";
|
||||
break;
|
||||
case ColorMode::RGB_COLOR_TEMPERATURE: // not supported by HA
|
||||
root["color_mode"] = "rgbct";
|
||||
break;
|
||||
case ColorMode::RGB_COLD_WARM_WHITE:
|
||||
root["color_mode"] = "rgbww";
|
||||
break;
|
||||
}
|
||||
|
||||
if (values.get_color_mode() & ColorCapability::ON_OFF)
|
||||
root["state"] = (values.get_state() != 0.0f) ? "ON" : "OFF";
|
||||
if (values.get_color_mode() & ColorCapability::BRIGHTNESS)
|
||||
root["brightness"] = uint8_t(values.get_brightness() * 255);
|
||||
|
||||
JsonObject &color = root.createNestedObject("color");
|
||||
if (values.get_color_mode() & ColorCapability::RGB) {
|
||||
color["r"] = uint8_t(values.get_color_brightness() * values.get_red() * 255);
|
||||
color["g"] = uint8_t(values.get_color_brightness() * values.get_green() * 255);
|
||||
color["b"] = uint8_t(values.get_color_brightness() * values.get_blue() * 255);
|
||||
}
|
||||
if (values.get_color_mode() & ColorCapability::WHITE) {
|
||||
color["w"] = uint8_t(values.get_white() * 255);
|
||||
root["white_value"] = uint8_t(values.get_white() * 255); // legacy API
|
||||
}
|
||||
if (values.get_color_mode() & ColorCapability::COLOR_TEMPERATURE) {
|
||||
// this one isn't under the color subkey for some reason
|
||||
root["color_temp"] = uint32_t(values.get_color_temperature());
|
||||
}
|
||||
if (values.get_color_mode() & ColorCapability::COLD_WARM_WHITE) {
|
||||
color["c"] = uint8_t(values.get_cold_white() * 255);
|
||||
color["w"] = uint8_t(values.get_warm_white() * 255);
|
||||
}
|
||||
}
|
||||
|
||||
void LightJSONSchema::parse_color_json(LightState &state, LightCall &call, JsonObject &root) {
|
||||
if (root.containsKey("state")) {
|
||||
auto val = parse_on_off(root["state"]);
|
||||
switch (val) {
|
||||
case PARSE_ON:
|
||||
call.set_state(true);
|
||||
break;
|
||||
case PARSE_OFF:
|
||||
call.set_state(false);
|
||||
break;
|
||||
case PARSE_TOGGLE:
|
||||
call.set_state(!state.remote_values.is_on());
|
||||
break;
|
||||
case PARSE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (root.containsKey("brightness")) {
|
||||
call.set_brightness(float(root["brightness"]) / 255.0f);
|
||||
}
|
||||
|
||||
if (root.containsKey("color")) {
|
||||
JsonObject &color = root["color"];
|
||||
// HA also encodes brightness information in the r, g, b values, so extract that and set it as color brightness.
|
||||
float max_rgb = 0.0f;
|
||||
if (color.containsKey("r")) {
|
||||
float r = float(color["r"]) / 255.0f;
|
||||
max_rgb = fmaxf(max_rgb, r);
|
||||
call.set_red(r);
|
||||
}
|
||||
if (color.containsKey("g")) {
|
||||
float g = float(color["g"]) / 255.0f;
|
||||
max_rgb = fmaxf(max_rgb, g);
|
||||
call.set_green(g);
|
||||
}
|
||||
if (color.containsKey("b")) {
|
||||
float b = float(color["b"]) / 255.0f;
|
||||
max_rgb = fmaxf(max_rgb, b);
|
||||
call.set_blue(b);
|
||||
}
|
||||
if (color.containsKey("r") || color.containsKey("g") || color.containsKey("b")) {
|
||||
call.set_color_brightness(max_rgb);
|
||||
}
|
||||
|
||||
if (color.containsKey("c")) {
|
||||
call.set_cold_white(float(color["c"]) / 255.0f);
|
||||
}
|
||||
if (color.containsKey("w")) {
|
||||
// the HA scheme is ambigious here, the same key is used for white channel in RGBW and warm
|
||||
// white channel in RGBWW.
|
||||
if (color.containsKey("c")) {
|
||||
call.set_warm_white(float(color["w"]) / 255.0f);
|
||||
} else {
|
||||
call.set_white(float(color["w"]) / 255.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (root.containsKey("white_value")) { // legacy API
|
||||
call.set_white(float(root["white_value"]) / 255.0f);
|
||||
}
|
||||
|
||||
if (root.containsKey("color_temp")) {
|
||||
call.set_color_temperature(float(root["color_temp"]));
|
||||
}
|
||||
}
|
||||
|
||||
void LightJSONSchema::parse_json(LightState &state, LightCall &call, JsonObject &root) {
|
||||
LightJSONSchema::parse_color_json(state, call, root);
|
||||
|
||||
if (root.containsKey("flash")) {
|
||||
auto length = uint32_t(float(root["flash"]) * 1000);
|
||||
call.set_flash_length(length);
|
||||
}
|
||||
|
||||
if (root.containsKey("transition")) {
|
||||
auto length = uint32_t(float(root["transition"]) * 1000);
|
||||
call.set_transition_length(length);
|
||||
}
|
||||
|
||||
if (root.containsKey("effect")) {
|
||||
const char *effect = root["effect"];
|
||||
call.set_effect(effect);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
25
esphome/components/light/light_json_schema.h
Normal file
25
esphome/components/light/light_json_schema.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "light_call.h"
|
||||
#include "light_state.h"
|
||||
|
||||
#ifdef USE_JSON
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
class LightJSONSchema {
|
||||
public:
|
||||
/// Dump the state of a light as JSON.
|
||||
static void dump_json(LightState &state, JsonObject &root);
|
||||
/// Parse the JSON state of a light to a LightCall.
|
||||
static void parse_json(LightState &state, LightCall &call, JsonObject &root);
|
||||
|
||||
protected:
|
||||
static void parse_color_json(LightState &state, LightCall &call, JsonObject &root);
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -16,6 +16,7 @@ LightCall LightState::toggle() { return this->make_call().set_state(!this->remot
|
||||
LightCall LightState::make_call() { return LightCall(this); }
|
||||
|
||||
struct LightStateRTCState {
|
||||
ColorMode color_mode{ColorMode::UNKNOWN};
|
||||
bool state{false};
|
||||
float brightness{1.0f};
|
||||
float color_brightness{1.0f};
|
||||
@@ -24,6 +25,8 @@ struct LightStateRTCState {
|
||||
float blue{1.0f};
|
||||
float white{1.0f};
|
||||
float color_temp{1.0f};
|
||||
float cold_white{1.0f};
|
||||
float warm_white{1.0f};
|
||||
uint32_t effect{0};
|
||||
};
|
||||
|
||||
@@ -64,6 +67,7 @@ void LightState::setup() {
|
||||
break;
|
||||
}
|
||||
|
||||
call.set_color_mode_if_supported(recovered.color_mode);
|
||||
call.set_state(recovered.state);
|
||||
call.set_brightness_if_supported(recovered.brightness);
|
||||
call.set_color_brightness_if_supported(recovered.color_brightness);
|
||||
@@ -72,6 +76,8 @@ void LightState::setup() {
|
||||
call.set_blue_if_supported(recovered.blue);
|
||||
call.set_white_if_supported(recovered.white);
|
||||
call.set_color_temperature_if_supported(recovered.color_temp);
|
||||
call.set_cold_white_if_supported(recovered.cold_white);
|
||||
call.set_warm_white_if_supported(recovered.warm_white);
|
||||
if (recovered.effect != 0) {
|
||||
call.set_effect(recovered.effect);
|
||||
} else {
|
||||
@@ -81,11 +87,11 @@ void LightState::setup() {
|
||||
}
|
||||
void LightState::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Light '%s'", this->get_name().c_str());
|
||||
if (this->get_traits().get_supports_brightness()) {
|
||||
if (this->get_traits().supports_color_capability(ColorCapability::BRIGHTNESS)) {
|
||||
ESP_LOGCONFIG(TAG, " Default Transition Length: %.1fs", this->default_transition_length_ / 1e3f);
|
||||
ESP_LOGCONFIG(TAG, " Gamma Correct: %.2f", this->gamma_correct_);
|
||||
}
|
||||
if (this->get_traits().get_supports_color_temperature()) {
|
||||
if (this->get_traits().supports_color_capability(ColorCapability::COLOR_TEMPERATURE)) {
|
||||
ESP_LOGCONFIG(TAG, " Min Mireds: %.1f", this->get_traits().get_min_mireds());
|
||||
ESP_LOGCONFIG(TAG, " Max Mireds: %.1f", this->get_traits().get_max_mireds());
|
||||
}
|
||||
@@ -141,14 +147,6 @@ void LightState::add_new_target_state_reached_callback(std::function<void()> &&s
|
||||
this->target_state_reached_callback_.add(std::move(send_callback));
|
||||
}
|
||||
|
||||
#ifdef USE_JSON
|
||||
void LightState::dump_json(JsonObject &root) {
|
||||
if (this->supports_effects())
|
||||
root["effect"] = this->get_effect_name();
|
||||
this->remote_values.dump_json(root, this->output_->get_traits());
|
||||
}
|
||||
#endif
|
||||
|
||||
void LightState::set_default_transition_length(uint32_t default_transition_length) {
|
||||
this->default_transition_length_ = default_transition_length;
|
||||
}
|
||||
@@ -169,18 +167,17 @@ void LightState::current_values_as_brightness(float *brightness) {
|
||||
}
|
||||
void LightState::current_values_as_rgb(float *red, float *green, float *blue, bool color_interlock) {
|
||||
auto traits = this->get_traits();
|
||||
this->current_values.as_rgb(red, green, blue, this->gamma_correct_, traits.get_supports_color_interlock());
|
||||
this->current_values.as_rgb(red, green, blue, this->gamma_correct_, false);
|
||||
}
|
||||
void LightState::current_values_as_rgbw(float *red, float *green, float *blue, float *white, bool color_interlock) {
|
||||
auto traits = this->get_traits();
|
||||
this->current_values.as_rgbw(red, green, blue, white, this->gamma_correct_, traits.get_supports_color_interlock());
|
||||
this->current_values.as_rgbw(red, green, blue, white, this->gamma_correct_, false);
|
||||
}
|
||||
void LightState::current_values_as_rgbww(float *red, float *green, float *blue, float *cold_white, float *warm_white,
|
||||
bool constant_brightness, bool color_interlock) {
|
||||
auto traits = this->get_traits();
|
||||
this->current_values.as_rgbww(traits.get_min_mireds(), traits.get_max_mireds(), red, green, blue, cold_white,
|
||||
warm_white, this->gamma_correct_, constant_brightness,
|
||||
traits.get_supports_color_interlock());
|
||||
warm_white, this->gamma_correct_, constant_brightness, false);
|
||||
}
|
||||
void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bool constant_brightness) {
|
||||
auto traits = this->get_traits();
|
||||
@@ -241,6 +238,7 @@ void LightState::set_transformer_(std::unique_ptr<LightTransformer> transformer)
|
||||
|
||||
void LightState::save_remote_values_() {
|
||||
LightStateRTCState saved;
|
||||
saved.color_mode = this->remote_values.get_color_mode();
|
||||
saved.state = this->remote_values.is_on();
|
||||
saved.brightness = this->remote_values.get_brightness();
|
||||
saved.color_brightness = this->remote_values.get_color_brightness();
|
||||
@@ -249,6 +247,8 @@ void LightState::save_remote_values_() {
|
||||
saved.blue = this->remote_values.get_blue();
|
||||
saved.white = this->remote_values.get_white();
|
||||
saved.color_temp = this->remote_values.get_color_temperature();
|
||||
saved.cold_white = this->remote_values.get_cold_white();
|
||||
saved.warm_white = this->remote_values.get_warm_white();
|
||||
saved.effect = this->active_effect_index_;
|
||||
this->rtc_.save(&saved);
|
||||
}
|
||||
|
@@ -93,11 +93,6 @@ class LightState : public Nameable, public Component {
|
||||
*/
|
||||
void add_new_target_state_reached_callback(std::function<void()> &&send_callback);
|
||||
|
||||
#ifdef USE_JSON
|
||||
/// Dump the state of this light as JSON.
|
||||
void dump_json(JsonObject &root);
|
||||
#endif
|
||||
|
||||
/// Set the default transition length, i.e. the transition length when no transition is provided.
|
||||
void set_default_transition_length(uint32_t default_transition_length);
|
||||
|
||||
|
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "color_mode.h"
|
||||
#include <set>
|
||||
|
||||
namespace esphome {
|
||||
namespace light {
|
||||
|
||||
@@ -8,35 +11,49 @@ class LightTraits {
|
||||
public:
|
||||
LightTraits() = default;
|
||||
|
||||
bool get_supports_brightness() const { return this->supports_brightness_; }
|
||||
void set_supports_brightness(bool supports_brightness) { this->supports_brightness_ = supports_brightness; }
|
||||
bool get_supports_rgb() const { return this->supports_rgb_; }
|
||||
void set_supports_rgb(bool supports_rgb) { this->supports_rgb_ = supports_rgb; }
|
||||
bool get_supports_rgb_white_value() const { return this->supports_rgb_white_value_; }
|
||||
void set_supports_rgb_white_value(bool supports_rgb_white_value) {
|
||||
this->supports_rgb_white_value_ = supports_rgb_white_value;
|
||||
const std::set<ColorMode> &get_supported_color_modes() const { return this->supported_color_modes_; }
|
||||
void set_supported_color_modes(std::set<ColorMode> supported_color_modes) {
|
||||
this->supported_color_modes_ = std::move(supported_color_modes);
|
||||
}
|
||||
bool get_supports_color_temperature() const { return this->supports_color_temperature_; }
|
||||
void set_supports_color_temperature(bool supports_color_temperature) {
|
||||
this->supports_color_temperature_ = supports_color_temperature;
|
||||
|
||||
bool supports_color_mode(ColorMode color_mode) const { return this->supported_color_modes_.count(color_mode); }
|
||||
bool supports_color_capability(ColorCapability color_capability) const {
|
||||
for (auto mode : this->supported_color_modes_) {
|
||||
if (mode & color_capability)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool get_supports_color_interlock() const { return this->supports_color_interlock_; }
|
||||
void set_supports_color_interlock(bool supports_color_interlock) {
|
||||
this->supports_color_interlock_ = supports_color_interlock;
|
||||
|
||||
ESPDEPRECATED("get_supports_brightness() is deprecated, use color modes instead.")
|
||||
bool get_supports_brightness() const { return this->supports_color_capability(ColorCapability::BRIGHTNESS); }
|
||||
ESPDEPRECATED("get_supports_rgb() is deprecated, use color modes instead.")
|
||||
bool get_supports_rgb() const { return this->supports_color_capability(ColorCapability::RGB); }
|
||||
ESPDEPRECATED("get_supports_rgb_white_value() is deprecated, use color modes instead.")
|
||||
bool get_supports_rgb_white_value() const {
|
||||
return this->supports_color_mode(ColorMode::RGB_WHITE) ||
|
||||
this->supports_color_mode(ColorMode::RGB_COLOR_TEMPERATURE);
|
||||
}
|
||||
ESPDEPRECATED("get_supports_color_temperature() is deprecated, use color modes instead.")
|
||||
bool get_supports_color_temperature() const {
|
||||
return this->supports_color_capability(ColorCapability::COLOR_TEMPERATURE);
|
||||
}
|
||||
ESPDEPRECATED("get_supports_color_interlock() is deprecated, use color modes instead.")
|
||||
bool get_supports_color_interlock() const {
|
||||
return this->supports_color_mode(ColorMode::RGB) &&
|
||||
(this->supports_color_mode(ColorMode::WHITE) || this->supports_color_mode(ColorMode::COLD_WARM_WHITE) ||
|
||||
this->supports_color_mode(ColorMode::COLOR_TEMPERATURE));
|
||||
}
|
||||
|
||||
float get_min_mireds() const { return this->min_mireds_; }
|
||||
void set_min_mireds(float min_mireds) { this->min_mireds_ = min_mireds; }
|
||||
float get_max_mireds() const { return this->max_mireds_; }
|
||||
void set_max_mireds(float max_mireds) { this->max_mireds_ = max_mireds; }
|
||||
|
||||
protected:
|
||||
bool supports_brightness_{false};
|
||||
bool supports_rgb_{false};
|
||||
bool supports_rgb_white_value_{false};
|
||||
bool supports_color_temperature_{false};
|
||||
std::set<ColorMode> supported_color_modes_{};
|
||||
float min_mireds_{0};
|
||||
float max_mireds_{0};
|
||||
bool supports_color_interlock_{false};
|
||||
};
|
||||
|
||||
} // namespace light
|
||||
|
@@ -51,24 +51,45 @@ class LightTransitionTransformer : public LightTransformer {
|
||||
: LightTransformer(start_time, length, start_values, target_values) {
|
||||
// When turning light on from off state, use colors from new.
|
||||
if (!this->start_values_.is_on() && this->target_values_.is_on()) {
|
||||
this->start_values_ = LightColorValues(target_values);
|
||||
this->start_values_.set_brightness(0.0f);
|
||||
this->start_values_.set_red(target_values.get_red());
|
||||
this->start_values_.set_green(target_values.get_green());
|
||||
this->start_values_.set_blue(target_values.get_blue());
|
||||
this->start_values_.set_white(target_values.get_white());
|
||||
this->start_values_.set_color_temperature(target_values.get_color_temperature());
|
||||
}
|
||||
|
||||
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
|
||||
if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
||||
this->changing_color_mode_ = true;
|
||||
this->intermediate_values_ = LightColorValues(this->get_start_values_());
|
||||
this->intermediate_values_.set_state(false);
|
||||
}
|
||||
}
|
||||
|
||||
LightColorValues get_values() override {
|
||||
float v = LightTransitionTransformer::smoothed_progress(this->get_progress());
|
||||
return LightColorValues::lerp(this->get_start_values_(), this->get_target_values_(), v);
|
||||
float p = this->get_progress();
|
||||
|
||||
// Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off.
|
||||
if (this->changing_color_mode_ && p > 0.5f &&
|
||||
this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
||||
this->intermediate_values_ = LightColorValues(this->get_end_values());
|
||||
this->intermediate_values_.set_state(false);
|
||||
}
|
||||
|
||||
LightColorValues &start = this->changing_color_mode_ && p > 0.5f ? this->intermediate_values_ : this->start_values_;
|
||||
LightColorValues &end = this->changing_color_mode_ && p < 0.5f ? this->intermediate_values_ : this->target_values_;
|
||||
if (this->changing_color_mode_)
|
||||
p = p < 0.5f ? p * 2 : (p - 0.5) * 2;
|
||||
|
||||
float v = LightTransitionTransformer::smoothed_progress(p);
|
||||
return LightColorValues::lerp(start, end, v);
|
||||
}
|
||||
|
||||
bool publish_at_end() override { return false; }
|
||||
bool is_transition() override { return true; }
|
||||
|
||||
static float smoothed_progress(float x) { return x * x * x * (x * (x * 6.0f - 15.0f) + 10.0f); }
|
||||
|
||||
protected:
|
||||
bool changing_color_mode_{false};
|
||||
LightColorValues intermediate_values_{};
|
||||
};
|
||||
|
||||
class LightFlashTransformer : public LightTransformer {
|
||||
|
@@ -13,6 +13,20 @@ AddressableLightRef = AddressableLight.operator("ref")
|
||||
Color = cg.esphome_ns.class_("Color")
|
||||
LightColorValues = light_ns.class_("LightColorValues")
|
||||
|
||||
# Color modes
|
||||
ColorMode = light_ns.enum("ColorMode", is_class=True)
|
||||
COLOR_MODES = {
|
||||
"ON_OFF": ColorMode.ON_OFF,
|
||||
"BRIGHTNESS": ColorMode.BRIGHTNESS,
|
||||
"WHITE": ColorMode.WHITE,
|
||||
"COLOR_TEMPERATURE": ColorMode.COLOR_TEMPERATURE,
|
||||
"COLD_WARM_WHITE": ColorMode.COLD_WARM_WHITE,
|
||||
"RGB": ColorMode.RGB,
|
||||
"RGB_WHITE": ColorMode.RGB_WHITE,
|
||||
"RGB_COLOR_TEMPERATURE": ColorMode.RGB_COLOR_TEMPERATURE,
|
||||
"RGB_COLD_WARM_WHITE": ColorMode.RGB_COLD_WARM_WHITE,
|
||||
}
|
||||
|
||||
# Actions
|
||||
ToggleAction = light_ns.class_("ToggleAction", automation.Action)
|
||||
LightControlAction = light_ns.class_("LightControlAction", automation.Action)
|
||||
|
Reference in New Issue
Block a user