mirror of
https://github.com/esphome/esphome.git
synced 2024-12-23 16:47:57 +01:00
Color mode implementation (#2012)
This commit is contained in:
parent
de382b704c
commit
5983ccc55c
@ -352,6 +352,18 @@ message FanCommandRequest {
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
enum ColorMode {
|
||||
COLOR_MODE_UNKNOWN = 0;
|
||||
COLOR_MODE_ON_OFF = 1;
|
||||
COLOR_MODE_BRIGHTNESS = 2;
|
||||
COLOR_MODE_WHITE = 7;
|
||||
COLOR_MODE_COLOR_TEMPERATURE = 11;
|
||||
COLOR_MODE_COLD_WARM_WHITE = 19;
|
||||
COLOR_MODE_RGB = 35;
|
||||
COLOR_MODE_RGB_WHITE = 39;
|
||||
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47;
|
||||
COLOR_MODE_RGB_COLD_WARM_WHITE = 51;
|
||||
}
|
||||
message ListEntitiesLightResponse {
|
||||
option (id) = 15;
|
||||
option (source) = SOURCE_SERVER;
|
||||
@ -362,10 +374,12 @@ message ListEntitiesLightResponse {
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
bool supports_brightness = 5;
|
||||
bool supports_rgb = 6;
|
||||
bool supports_white_value = 7;
|
||||
bool supports_color_temperature = 8;
|
||||
repeated ColorMode supported_color_modes = 12;
|
||||
// next four supports_* are for legacy clients, newer clients should use color modes
|
||||
bool legacy_supports_brightness = 5 [deprecated=true];
|
||||
bool legacy_supports_rgb = 6 [deprecated=true];
|
||||
bool legacy_supports_white_value = 7 [deprecated=true];
|
||||
bool legacy_supports_color_temperature = 8 [deprecated=true];
|
||||
float min_mireds = 9;
|
||||
float max_mireds = 10;
|
||||
repeated string effects = 11;
|
||||
@ -379,12 +393,15 @@ message LightStateResponse {
|
||||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
float brightness = 3;
|
||||
ColorMode color_mode = 11;
|
||||
float color_brightness = 10;
|
||||
float red = 4;
|
||||
float green = 5;
|
||||
float blue = 6;
|
||||
float white = 7;
|
||||
float color_temperature = 8;
|
||||
float cold_white = 12;
|
||||
float warm_white = 13;
|
||||
string effect = 9;
|
||||
}
|
||||
message LightCommandRequest {
|
||||
@ -398,6 +415,8 @@ message LightCommandRequest {
|
||||
bool state = 3;
|
||||
bool has_brightness = 4;
|
||||
float brightness = 5;
|
||||
bool has_color_mode = 22;
|
||||
ColorMode color_mode = 23;
|
||||
bool has_color_brightness = 20;
|
||||
float color_brightness = 21;
|
||||
bool has_rgb = 6;
|
||||
@ -408,6 +427,10 @@ message LightCommandRequest {
|
||||
float white = 11;
|
||||
bool has_color_temperature = 12;
|
||||
float color_temperature = 13;
|
||||
bool has_cold_white = 24;
|
||||
float cold_white = 25;
|
||||
bool has_warm_white = 26;
|
||||
float warm_white = 27;
|
||||
bool has_transition_length = 14;
|
||||
uint32 transition_length = 15;
|
||||
bool has_flash_length = 16;
|
||||
|
@ -301,22 +301,28 @@ bool APIConnection::send_light_state(light::LightState *light) {
|
||||
|
||||
auto traits = light->get_traits();
|
||||
auto values = light->remote_values;
|
||||
auto color_mode = values.get_color_mode();
|
||||
LightStateResponse resp{};
|
||||
|
||||
resp.key = light->get_object_id_hash();
|
||||
resp.state = values.is_on();
|
||||
if (traits.get_supports_brightness())
|
||||
resp.color_mode = static_cast<enums::ColorMode>(color_mode);
|
||||
if (color_mode & light::ColorCapability::BRIGHTNESS)
|
||||
resp.brightness = values.get_brightness();
|
||||
if (traits.get_supports_rgb()) {
|
||||
if (color_mode & light::ColorCapability::RGB) {
|
||||
resp.color_brightness = values.get_color_brightness();
|
||||
resp.red = values.get_red();
|
||||
resp.green = values.get_green();
|
||||
resp.blue = values.get_blue();
|
||||
}
|
||||
if (traits.get_supports_rgb_white_value())
|
||||
if (color_mode & light::ColorCapability::WHITE)
|
||||
resp.white = values.get_white();
|
||||
if (traits.get_supports_color_temperature())
|
||||
if (color_mode & light::ColorCapability::COLOR_TEMPERATURE)
|
||||
resp.color_temperature = values.get_color_temperature();
|
||||
if (color_mode & light::ColorCapability::COLD_WARM_WHITE) {
|
||||
resp.cold_white = values.get_cold_white();
|
||||
resp.warm_white = values.get_warm_white();
|
||||
}
|
||||
if (light->supports_effects())
|
||||
resp.effect = light->get_effect_name();
|
||||
return this->send_light_state_response(resp);
|
||||
@ -328,11 +334,18 @@ bool APIConnection::send_light_info(light::LightState *light) {
|
||||
msg.object_id = light->get_object_id();
|
||||
msg.name = light->get_name();
|
||||
msg.unique_id = get_default_unique_id("light", light);
|
||||
msg.supports_brightness = traits.get_supports_brightness();
|
||||
msg.supports_rgb = traits.get_supports_rgb();
|
||||
msg.supports_white_value = traits.get_supports_rgb_white_value();
|
||||
msg.supports_color_temperature = traits.get_supports_color_temperature();
|
||||
if (msg.supports_color_temperature) {
|
||||
for (auto mode : traits.get_supported_color_modes())
|
||||
msg.supported_color_modes.push_back(static_cast<enums::ColorMode>(mode));
|
||||
|
||||
msg.legacy_supports_brightness = traits.supports_color_capability(light::ColorCapability::BRIGHTNESS);
|
||||
msg.legacy_supports_rgb = traits.supports_color_capability(light::ColorCapability::RGB);
|
||||
msg.legacy_supports_white_value =
|
||||
msg.legacy_supports_rgb && (traits.supports_color_capability(light::ColorCapability::WHITE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE));
|
||||
msg.legacy_supports_color_temperature = traits.supports_color_capability(light::ColorCapability::COLOR_TEMPERATURE) ||
|
||||
traits.supports_color_capability(light::ColorCapability::COLD_WARM_WHITE);
|
||||
|
||||
if (msg.legacy_supports_color_temperature) {
|
||||
msg.min_mireds = traits.get_min_mireds();
|
||||
msg.max_mireds = traits.get_max_mireds();
|
||||
}
|
||||
@ -353,6 +366,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_mode)
|
||||
call.set_color_mode(static_cast<light::ColorMode>(msg.color_mode));
|
||||
if (msg.has_color_brightness)
|
||||
call.set_color_brightness(msg.color_brightness);
|
||||
if (msg.has_rgb) {
|
||||
@ -364,6 +379,10 @@ void APIConnection::light_command(const LightCommandRequest &msg) {
|
||||
call.set_white(msg.white);
|
||||
if (msg.has_color_temperature)
|
||||
call.set_color_temperature(msg.color_temperature);
|
||||
if (msg.has_cold_white)
|
||||
call.set_cold_white(msg.cold_white);
|
||||
if (msg.has_warm_white)
|
||||
call.set_warm_white(msg.warm_white);
|
||||
if (msg.has_transition_length)
|
||||
call.set_transition_length(msg.transition_length);
|
||||
if (msg.has_flash_length)
|
||||
@ -655,7 +674,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 5;
|
||||
resp.api_version_minor = 6;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
|
@ -62,6 +62,32 @@ template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirec
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::ColorMode>(enums::ColorMode value) {
|
||||
switch (value) {
|
||||
case enums::COLOR_MODE_UNKNOWN:
|
||||
return "COLOR_MODE_UNKNOWN";
|
||||
case enums::COLOR_MODE_ON_OFF:
|
||||
return "COLOR_MODE_ON_OFF";
|
||||
case enums::COLOR_MODE_BRIGHTNESS:
|
||||
return "COLOR_MODE_BRIGHTNESS";
|
||||
case enums::COLOR_MODE_WHITE:
|
||||
return "COLOR_MODE_WHITE";
|
||||
case enums::COLOR_MODE_COLOR_TEMPERATURE:
|
||||
return "COLOR_MODE_COLOR_TEMPERATURE";
|
||||
case enums::COLOR_MODE_COLD_WARM_WHITE:
|
||||
return "COLOR_MODE_COLD_WARM_WHITE";
|
||||
case enums::COLOR_MODE_RGB:
|
||||
return "COLOR_MODE_RGB";
|
||||
case enums::COLOR_MODE_RGB_WHITE:
|
||||
return "COLOR_MODE_RGB_WHITE";
|
||||
case enums::COLOR_MODE_RGB_COLOR_TEMPERATURE:
|
||||
return "COLOR_MODE_RGB_COLOR_TEMPERATURE";
|
||||
case enums::COLOR_MODE_RGB_COLD_WARM_WHITE:
|
||||
return "COLOR_MODE_RGB_COLD_WARM_WHITE";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::SensorStateClass>(enums::SensorStateClass value) {
|
||||
switch (value) {
|
||||
case enums::STATE_CLASS_NONE:
|
||||
@ -1117,20 +1143,24 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
||||
}
|
||||
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 12: {
|
||||
this->supported_color_modes.push_back(value.as_enum<enums::ColorMode>());
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->supports_brightness = value.as_bool();
|
||||
this->legacy_supports_brightness = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 6: {
|
||||
this->supports_rgb = value.as_bool();
|
||||
this->legacy_supports_rgb = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->supports_white_value = value.as_bool();
|
||||
this->legacy_supports_white_value = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->supports_color_temperature = value.as_bool();
|
||||
this->legacy_supports_color_temperature = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
@ -1182,10 +1212,13 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_bool(5, this->supports_brightness);
|
||||
buffer.encode_bool(6, this->supports_rgb);
|
||||
buffer.encode_bool(7, this->supports_white_value);
|
||||
buffer.encode_bool(8, this->supports_color_temperature);
|
||||
for (auto &it : this->supported_color_modes) {
|
||||
buffer.encode_enum<enums::ColorMode>(12, it, true);
|
||||
}
|
||||
buffer.encode_bool(5, this->legacy_supports_brightness);
|
||||
buffer.encode_bool(6, this->legacy_supports_rgb);
|
||||
buffer.encode_bool(7, this->legacy_supports_white_value);
|
||||
buffer.encode_bool(8, this->legacy_supports_color_temperature);
|
||||
buffer.encode_float(9, this->min_mireds);
|
||||
buffer.encode_float(10, this->max_mireds);
|
||||
for (auto &it : this->effects) {
|
||||
@ -1212,20 +1245,26 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
|
||||
out.append("'").append(this->unique_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_brightness: ");
|
||||
out.append(YESNO(this->supports_brightness));
|
||||
for (const auto &it : this->supported_color_modes) {
|
||||
out.append(" supported_color_modes: ");
|
||||
out.append(proto_enum_to_string<enums::ColorMode>(it));
|
||||
out.append("\n");
|
||||
}
|
||||
|
||||
out.append(" legacy_supports_brightness: ");
|
||||
out.append(YESNO(this->legacy_supports_brightness));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_rgb: ");
|
||||
out.append(YESNO(this->supports_rgb));
|
||||
out.append(" legacy_supports_rgb: ");
|
||||
out.append(YESNO(this->legacy_supports_rgb));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_white_value: ");
|
||||
out.append(YESNO(this->supports_white_value));
|
||||
out.append(" legacy_supports_white_value: ");
|
||||
out.append(YESNO(this->legacy_supports_white_value));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_color_temperature: ");
|
||||
out.append(YESNO(this->supports_color_temperature));
|
||||
out.append(" legacy_supports_color_temperature: ");
|
||||
out.append(YESNO(this->legacy_supports_color_temperature));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" min_mireds: ");
|
||||
@ -1251,6 +1290,10 @@ bool LightStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->state = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->color_mode = value.as_enum<enums::ColorMode>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1299,6 +1342,14 @@ bool LightStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
this->color_temperature = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->cold_white = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 13: {
|
||||
this->warm_white = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1307,12 +1358,15 @@ 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_enum<enums::ColorMode>(11, this->color_mode);
|
||||
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);
|
||||
buffer.encode_float(7, this->white);
|
||||
buffer.encode_float(8, this->color_temperature);
|
||||
buffer.encode_float(12, this->cold_white);
|
||||
buffer.encode_float(13, this->warm_white);
|
||||
buffer.encode_string(9, this->effect);
|
||||
}
|
||||
void LightStateResponse::dump_to(std::string &out) const {
|
||||
@ -1332,6 +1386,10 @@ void LightStateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" color_mode: ");
|
||||
out.append(proto_enum_to_string<enums::ColorMode>(this->color_mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" color_brightness: ");
|
||||
sprintf(buffer, "%g", this->color_brightness);
|
||||
out.append(buffer);
|
||||
@ -1362,6 +1420,16 @@ void LightStateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" cold_white: ");
|
||||
sprintf(buffer, "%g", this->cold_white);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" warm_white: ");
|
||||
sprintf(buffer, "%g", this->warm_white);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" effect: ");
|
||||
out.append("'").append(this->effect).append("'");
|
||||
out.append("\n");
|
||||
@ -1381,6 +1449,14 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->has_brightness = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 22: {
|
||||
this->has_color_mode = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 23: {
|
||||
this->color_mode = value.as_enum<enums::ColorMode>();
|
||||
return true;
|
||||
}
|
||||
case 20: {
|
||||
this->has_color_brightness = value.as_bool();
|
||||
return true;
|
||||
@ -1397,6 +1473,14 @@ bool LightCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
this->has_color_temperature = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 24: {
|
||||
this->has_cold_white = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 26: {
|
||||
this->has_warm_white = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 14: {
|
||||
this->has_transition_length = value.as_bool();
|
||||
return true;
|
||||
@ -1465,6 +1549,14 @@ bool LightCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
this->color_temperature = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 25: {
|
||||
this->cold_white = value.as_float();
|
||||
return true;
|
||||
}
|
||||
case 27: {
|
||||
this->warm_white = value.as_float();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -1475,6 +1567,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(22, this->has_color_mode);
|
||||
buffer.encode_enum<enums::ColorMode>(23, this->color_mode);
|
||||
buffer.encode_bool(20, this->has_color_brightness);
|
||||
buffer.encode_float(21, this->color_brightness);
|
||||
buffer.encode_bool(6, this->has_rgb);
|
||||
@ -1485,6 +1579,10 @@ void LightCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(11, this->white);
|
||||
buffer.encode_bool(12, this->has_color_temperature);
|
||||
buffer.encode_float(13, this->color_temperature);
|
||||
buffer.encode_bool(24, this->has_cold_white);
|
||||
buffer.encode_float(25, this->cold_white);
|
||||
buffer.encode_bool(26, this->has_warm_white);
|
||||
buffer.encode_float(27, this->warm_white);
|
||||
buffer.encode_bool(14, this->has_transition_length);
|
||||
buffer.encode_uint32(15, this->transition_length);
|
||||
buffer.encode_bool(16, this->has_flash_length);
|
||||
@ -1517,6 +1615,14 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_color_mode: ");
|
||||
out.append(YESNO(this->has_color_mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" color_mode: ");
|
||||
out.append(proto_enum_to_string<enums::ColorMode>(this->color_mode));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_color_brightness: ");
|
||||
out.append(YESNO(this->has_color_brightness));
|
||||
out.append("\n");
|
||||
@ -1563,6 +1669,24 @@ void LightCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_cold_white: ");
|
||||
out.append(YESNO(this->has_cold_white));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" cold_white: ");
|
||||
sprintf(buffer, "%g", this->cold_white);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_warm_white: ");
|
||||
out.append(YESNO(this->has_warm_white));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" warm_white: ");
|
||||
sprintf(buffer, "%g", this->warm_white);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_transition_length: ");
|
||||
out.append(YESNO(this->has_transition_length));
|
||||
out.append("\n");
|
||||
|
@ -32,6 +32,18 @@ enum FanDirection : uint32_t {
|
||||
FAN_DIRECTION_FORWARD = 0,
|
||||
FAN_DIRECTION_REVERSE = 1,
|
||||
};
|
||||
enum ColorMode : uint32_t {
|
||||
COLOR_MODE_UNKNOWN = 0,
|
||||
COLOR_MODE_ON_OFF = 1,
|
||||
COLOR_MODE_BRIGHTNESS = 2,
|
||||
COLOR_MODE_WHITE = 7,
|
||||
COLOR_MODE_COLOR_TEMPERATURE = 11,
|
||||
COLOR_MODE_COLD_WARM_WHITE = 19,
|
||||
COLOR_MODE_RGB = 35,
|
||||
COLOR_MODE_RGB_WHITE = 39,
|
||||
COLOR_MODE_RGB_COLOR_TEMPERATURE = 47,
|
||||
COLOR_MODE_RGB_COLD_WARM_WHITE = 51,
|
||||
};
|
||||
enum SensorStateClass : uint32_t {
|
||||
STATE_CLASS_NONE = 0,
|
||||
STATE_CLASS_MEASUREMENT = 1,
|
||||
@ -356,10 +368,11 @@ class ListEntitiesLightResponse : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
bool supports_brightness{false};
|
||||
bool supports_rgb{false};
|
||||
bool supports_white_value{false};
|
||||
bool supports_color_temperature{false};
|
||||
std::vector<enums::ColorMode> supported_color_modes{};
|
||||
bool legacy_supports_brightness{false};
|
||||
bool legacy_supports_rgb{false};
|
||||
bool legacy_supports_white_value{false};
|
||||
bool legacy_supports_color_temperature{false};
|
||||
float min_mireds{0.0f};
|
||||
float max_mireds{0.0f};
|
||||
std::vector<std::string> effects{};
|
||||
@ -376,12 +389,15 @@ class LightStateResponse : public ProtoMessage {
|
||||
uint32_t key{0};
|
||||
bool state{false};
|
||||
float brightness{0.0f};
|
||||
enums::ColorMode color_mode{};
|
||||
float color_brightness{0.0f};
|
||||
float red{0.0f};
|
||||
float green{0.0f};
|
||||
float blue{0.0f};
|
||||
float white{0.0f};
|
||||
float color_temperature{0.0f};
|
||||
float cold_white{0.0f};
|
||||
float warm_white{0.0f};
|
||||
std::string effect{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
@ -398,6 +414,8 @@ class LightCommandRequest : public ProtoMessage {
|
||||
bool state{false};
|
||||
bool has_brightness{false};
|
||||
float brightness{0.0f};
|
||||
bool has_color_mode{false};
|
||||
enums::ColorMode color_mode{};
|
||||
bool has_color_brightness{false};
|
||||
float color_brightness{0.0f};
|
||||
bool has_rgb{false};
|
||||
@ -408,6 +426,10 @@ class LightCommandRequest : public ProtoMessage {
|
||||
float white{0.0f};
|
||||
bool has_color_temperature{false};
|
||||
float color_temperature{0.0f};
|
||||
bool has_cold_white{false};
|
||||
float cold_white{0.0f};
|
||||
bool has_warm_white{false};
|
||||
float warm_white{0.0f};
|
||||
bool has_transition_length{false};
|
||||
uint32_t transition_length{0};
|
||||
bool has_flash_length{false};
|
||||
|
@ -12,7 +12,7 @@ class BinaryLightOutput : public light::LightOutput {
|
||||
void set_output(output::BinaryOutput *output) { output_ = output; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(false);
|
||||
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||
return traits;
|
||||
}
|
||||
void write_state(light::LightState *state) override {
|
||||
|
@ -16,10 +16,7 @@ class CWWWLightOutput : public light::LightOutput {
|
||||
void set_constant_brightness(bool constant_brightness) { constant_brightness_ = constant_brightness; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(false);
|
||||
traits.set_supports_rgb_white_value(false);
|
||||
traits.set_supports_color_temperature(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE});
|
||||
traits.set_min_mireds(this->cold_white_temperature_);
|
||||
traits.set_max_mireds(this->warm_white_temperature_);
|
||||
return traits;
|
||||
@ -34,8 +31,8 @@ class CWWWLightOutput : public light::LightOutput {
|
||||
protected:
|
||||
output::FloatOutput *cold_white_;
|
||||
output::FloatOutput *warm_white_;
|
||||
float cold_white_temperature_;
|
||||
float warm_white_temperature_;
|
||||
float cold_white_temperature_{0};
|
||||
float warm_white_temperature_{0};
|
||||
bool constant_brightness_;
|
||||
};
|
||||
|
||||
|
@ -20,11 +20,14 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(CWWWLightOutput),
|
||||
cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Optional(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Optional(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.has_none_or_all_keys(
|
||||
[CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE]
|
||||
),
|
||||
light.validate_color_temperature_channels,
|
||||
)
|
||||
|
||||
@ -32,11 +35,19 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
await light.register_light(var, config)
|
||||
|
||||
cwhite = await cg.get_variable(config[CONF_COLD_WHITE])
|
||||
cg.add(var.set_cold_white(cwhite))
|
||||
cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]))
|
||||
if CONF_COLD_WHITE_COLOR_TEMPERATURE in config:
|
||||
cg.add(
|
||||
var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])
|
||||
)
|
||||
|
||||
wwhite = await cg.get_variable(config[CONF_WARM_WHITE])
|
||||
cg.add(var.set_warm_white(wwhite))
|
||||
cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]))
|
||||
if CONF_WARM_WHITE_COLOR_TEMPERATURE in config:
|
||||
cg.add(
|
||||
var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])
|
||||
)
|
||||
|
||||
cg.add(var.set_constant_brightness(config[CONF_CONSTANT_BRIGHTNESS]))
|
||||
|
@ -28,45 +28,31 @@ class DemoLight : public light::LightOutput, public Component {
|
||||
void set_type(DemoLightType type) { type_ = type; }
|
||||
light::LightTraits get_traits() override {
|
||||
light::LightTraits traits{};
|
||||
// brightness
|
||||
// rgb
|
||||
// rgb_white_value
|
||||
// color_temperature, min_mireds, max_mireds
|
||||
// color_interlock
|
||||
switch (type_) {
|
||||
case DemoLightType::TYPE_1:
|
||||
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||
break;
|
||||
case DemoLightType::TYPE_2:
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
|
||||
break;
|
||||
case DemoLightType::TYPE_3:
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
break;
|
||||
case DemoLightType::TYPE_4:
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supports_rgb_white_value(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE});
|
||||
break;
|
||||
case DemoLightType::TYPE_5:
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supports_rgb_white_value(true);
|
||||
traits.set_supports_color_temperature(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB_COLOR_TEMPERATURE});
|
||||
traits.set_min_mireds(153);
|
||||
traits.set_max_mireds(500);
|
||||
break;
|
||||
case DemoLightType::TYPE_6:
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_color_temperature(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE});
|
||||
traits.set_min_mireds(153);
|
||||
traits.set_max_mireds(500);
|
||||
break;
|
||||
case DemoLightType::TYPE_7:
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supports_rgb_white_value(true);
|
||||
traits.set_supports_color_interlock(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE});
|
||||
break;
|
||||
}
|
||||
return traits;
|
||||
|
@ -208,8 +208,7 @@ class FastLEDLightOutput : public light::AddressableLight {
|
||||
// (In most use cases you won't need these)
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
return traits;
|
||||
}
|
||||
void setup() override;
|
||||
|
@ -18,10 +18,7 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput {
|
||||
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true); // Dimming
|
||||
traits.set_supports_rgb(false);
|
||||
traits.set_supports_rgb_white_value(true); // hbridge color
|
||||
traits.set_supports_color_temperature(false);
|
||||
traits.set_supported_color_modes({light::ColorMode::COLD_WARM_WHITE});
|
||||
return traits;
|
||||
}
|
||||
|
||||
@ -31,11 +28,11 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput {
|
||||
// This method runs around 60 times per second
|
||||
// We cannot do the PWM ourselves so we are reliant on the hardware PWM
|
||||
if (!this->forward_direction_) { // First LED Direction
|
||||
this->pinb_pin_->set_level(this->duty_off_);
|
||||
this->pina_pin_->set_level(this->pina_duty_);
|
||||
this->pinb_pin_->set_level(0);
|
||||
this->forward_direction_ = true;
|
||||
} else { // Second LED Direction
|
||||
this->pina_pin_->set_level(this->duty_off_);
|
||||
this->pina_pin_->set_level(0);
|
||||
this->pinb_pin_->set_level(this->pinb_duty_);
|
||||
this->forward_direction_ = false;
|
||||
}
|
||||
@ -44,23 +41,7 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput {
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void write_state(light::LightState *state) override {
|
||||
float bright;
|
||||
state->current_values_as_brightness(&bright);
|
||||
|
||||
state->set_gamma_correct(0);
|
||||
float red, green, blue, white;
|
||||
state->current_values_as_rgbw(&red, &green, &blue, &white);
|
||||
|
||||
if ((white / bright) > 0.55) {
|
||||
this->pina_duty_ = (bright * (1 - (white / bright)));
|
||||
this->pinb_duty_ = bright;
|
||||
} else if (white < 0.45) {
|
||||
this->pina_duty_ = bright;
|
||||
this->pinb_duty_ = white;
|
||||
} else {
|
||||
this->pina_duty_ = bright;
|
||||
this->pinb_duty_ = bright;
|
||||
}
|
||||
state->current_values_as_cwww(&this->pina_duty_, &this->pinb_duty_, false);
|
||||
}
|
||||
|
||||
protected:
|
||||
@ -68,7 +49,6 @@ class HBridgeLightOutput : public PollingComponent, public light::LightOutput {
|
||||
output::FloatOutput *pinb_pin_;
|
||||
float pina_duty_ = 0;
|
||||
float pinb_duty_ = 0;
|
||||
float duty_off_ = 0;
|
||||
bool forward_direction_ = false;
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
if (this->color_mode_ & ColorCapability::RGB) {
|
||||
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);
|
||||
*blue = gamma_correct(brightness * this->blue_, gamma);
|
||||
} else {
|
||||
*red = *green = *blue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -12,7 +12,7 @@ class MonochromaticLightOutput : public light::LightOutput {
|
||||
void set_output(output::FloatOutput *output) { output_ = output; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
|
||||
return traits;
|
||||
}
|
||||
void write_state(light::LightState *state) override {
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "mqtt_light.h"
|
||||
#include "esphome/components/light/light_json_schema.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
@ -14,7 +15,9 @@ std::string MQTTJSONLightComponent::component_type() const { return "light"; }
|
||||
|
||||
void MQTTJSONLightComponent::setup() {
|
||||
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject &root) {
|
||||
this->state_->make_call().parse_json(root).perform();
|
||||
LightCall call = this->state_->make_call();
|
||||
LightJSONSchema::parse_json(*this->state_, call, root);
|
||||
call.perform();
|
||||
});
|
||||
|
||||
auto f = std::bind(&MQTTJSONLightComponent::publish_state_, this);
|
||||
@ -24,21 +27,40 @@ void MQTTJSONLightComponent::setup() {
|
||||
MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : MQTTComponent(), state_(state) {}
|
||||
|
||||
bool MQTTJSONLightComponent::publish_state_() {
|
||||
return this->publish_json(this->get_state_topic_(), [this](JsonObject &root) { this->state_->dump_json(root); });
|
||||
return this->publish_json(this->get_state_topic_(),
|
||||
[this](JsonObject &root) { LightJSONSchema::dump_json(*this->state_, root); });
|
||||
}
|
||||
LightState *MQTTJSONLightComponent::get_state() const { return this->state_; }
|
||||
std::string MQTTJSONLightComponent::friendly_name() const { return this->state_->get_name(); }
|
||||
void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
|
||||
root["schema"] = "json";
|
||||
auto traits = this->state_->get_traits();
|
||||
if (traits.get_supports_brightness())
|
||||
|
||||
root["color_mode"] = true;
|
||||
JsonArray &color_modes = root.createNestedArray("supported_color_modes");
|
||||
if (traits.supports_color_mode(ColorMode::COLOR_TEMPERATURE))
|
||||
color_modes.add("color_temp");
|
||||
if (traits.supports_color_mode(ColorMode::RGB))
|
||||
color_modes.add("rgb");
|
||||
if (traits.supports_color_mode(ColorMode::RGB_WHITE))
|
||||
color_modes.add("rgbw");
|
||||
if (traits.supports_color_mode(ColorMode::RGB_COLD_WARM_WHITE))
|
||||
color_modes.add("rgbww");
|
||||
if (traits.supports_color_mode(ColorMode::BRIGHTNESS))
|
||||
color_modes.add("brightness");
|
||||
if (traits.supports_color_mode(ColorMode::ON_OFF))
|
||||
color_modes.add("onoff");
|
||||
|
||||
// legacy API
|
||||
if (traits.supports_color_capability(ColorCapability::BRIGHTNESS))
|
||||
root["brightness"] = true;
|
||||
if (traits.get_supports_rgb())
|
||||
if (traits.supports_color_capability(ColorCapability::RGB))
|
||||
root["rgb"] = true;
|
||||
if (traits.get_supports_color_temperature())
|
||||
if (traits.supports_color_capability(ColorCapability::COLOR_TEMPERATURE))
|
||||
root["color_temp"] = true;
|
||||
if (traits.get_supports_rgb_white_value())
|
||||
if (traits.supports_color_capability(ColorCapability::WHITE))
|
||||
root["white_value"] = true;
|
||||
|
||||
if (this->state_->supports_effects()) {
|
||||
root["effect"] = true;
|
||||
JsonArray &effect_list = root.createNestedArray("effect_list");
|
||||
|
@ -115,8 +115,7 @@ class NeoPixelRGBLightOutput : public NeoPixelBusLightOutputBase<T_METHOD, T_COL
|
||||
public:
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
return traits;
|
||||
}
|
||||
|
||||
@ -133,9 +132,7 @@ class NeoPixelRGBWLightOutput : public NeoPixelBusLightOutputBase<T_METHOD, T_CO
|
||||
public:
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supports_rgb_white_value(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE});
|
||||
return traits;
|
||||
}
|
||||
|
||||
|
@ -15,8 +15,7 @@ class RGBLightOutput : public light::LightOutput {
|
||||
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB});
|
||||
return traits;
|
||||
}
|
||||
void write_state(light::LightState *state) override {
|
||||
|
@ -16,10 +16,10 @@ class RGBWLightOutput : public light::LightOutput {
|
||||
void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_color_interlock(this->color_interlock_);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supports_rgb_white_value(true);
|
||||
if (this->color_interlock_)
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE});
|
||||
else
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB_WHITE});
|
||||
return traits;
|
||||
}
|
||||
void write_state(light::LightState *state) override {
|
||||
|
@ -27,12 +27,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_BLUE): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_COLD_WHITE): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_WARM_WHITE): cv.use_id(output.FloatOutput),
|
||||
cv.Required(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Required(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Optional(CONF_COLD_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Optional(CONF_WARM_WHITE_COLOR_TEMPERATURE): cv.color_temperature,
|
||||
cv.Optional(CONF_CONSTANT_BRIGHTNESS, default=False): cv.boolean,
|
||||
cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.has_none_or_all_keys(
|
||||
[CONF_COLD_WHITE_COLOR_TEMPERATURE, CONF_WARM_WHITE_COLOR_TEMPERATURE]
|
||||
),
|
||||
light.validate_color_temperature_channels,
|
||||
)
|
||||
|
||||
@ -50,10 +53,17 @@ async def to_code(config):
|
||||
|
||||
cwhite = await cg.get_variable(config[CONF_COLD_WHITE])
|
||||
cg.add(var.set_cold_white(cwhite))
|
||||
cg.add(var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE]))
|
||||
if CONF_COLD_WHITE_COLOR_TEMPERATURE in config:
|
||||
cg.add(
|
||||
var.set_cold_white_temperature(config[CONF_COLD_WHITE_COLOR_TEMPERATURE])
|
||||
)
|
||||
|
||||
wwhite = await cg.get_variable(config[CONF_WARM_WHITE])
|
||||
cg.add(var.set_warm_white(wwhite))
|
||||
cg.add(var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE]))
|
||||
if CONF_WARM_WHITE_COLOR_TEMPERATURE in config:
|
||||
cg.add(
|
||||
var.set_warm_white_temperature(config[CONF_WARM_WHITE_COLOR_TEMPERATURE])
|
||||
)
|
||||
|
||||
cg.add(var.set_constant_brightness(config[CONF_CONSTANT_BRIGHTNESS]))
|
||||
cg.add(var.set_color_interlock(config[CONF_COLOR_INTERLOCK]))
|
||||
|
@ -14,17 +14,16 @@ class RGBWWLightOutput : public light::LightOutput {
|
||||
void set_blue(output::FloatOutput *blue) { blue_ = blue; }
|
||||
void set_cold_white(output::FloatOutput *cold_white) { cold_white_ = cold_white; }
|
||||
void set_warm_white(output::FloatOutput *warm_white) { warm_white_ = warm_white; }
|
||||
void set_cold_white_temperature(int cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; }
|
||||
void set_warm_white_temperature(int warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; }
|
||||
void set_cold_white_temperature(float cold_white_temperature) { cold_white_temperature_ = cold_white_temperature; }
|
||||
void set_warm_white_temperature(float warm_white_temperature) { warm_white_temperature_ = warm_white_temperature; }
|
||||
void set_constant_brightness(bool constant_brightness) { constant_brightness_ = constant_brightness; }
|
||||
void set_color_interlock(bool color_interlock) { color_interlock_ = color_interlock; }
|
||||
light::LightTraits get_traits() override {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(true);
|
||||
traits.set_supports_rgb(true);
|
||||
traits.set_supports_rgb_white_value(true);
|
||||
traits.set_supports_color_temperature(true);
|
||||
traits.set_supports_color_interlock(this->color_interlock_);
|
||||
if (this->color_interlock_)
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLD_WARM_WHITE});
|
||||
else
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB_COLD_WARM_WHITE});
|
||||
traits.set_min_mireds(this->cold_white_temperature_);
|
||||
traits.set_max_mireds(this->warm_white_temperature_);
|
||||
return traits;
|
||||
@ -46,8 +45,8 @@ class RGBWWLightOutput : public light::LightOutput {
|
||||
output::FloatOutput *blue_;
|
||||
output::FloatOutput *cold_white_;
|
||||
output::FloatOutput *warm_white_;
|
||||
int cold_white_temperature_;
|
||||
int warm_white_temperature_;
|
||||
float cold_white_temperature_{0};
|
||||
float warm_white_temperature_{0};
|
||||
bool constant_brightness_;
|
||||
bool color_interlock_{false};
|
||||
};
|
||||
|
@ -45,11 +45,14 @@ void TuyaLight::dump_config() {
|
||||
|
||||
light::LightTraits TuyaLight::get_traits() {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supports_brightness(this->dimmer_id_.has_value());
|
||||
traits.set_supports_color_temperature(this->color_temperature_id_.has_value());
|
||||
if (this->color_temperature_id_.has_value()) {
|
||||
if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) {
|
||||
traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE});
|
||||
traits.set_min_mireds(this->cold_white_temperature_);
|
||||
traits.set_max_mireds(this->warm_white_temperature_);
|
||||
} else if (this->dimmer_id_.has_value()) {
|
||||
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
|
||||
} else {
|
||||
traits.set_supported_color_modes({light::ColorMode::ON_OFF});
|
||||
}
|
||||
return traits;
|
||||
}
|
||||
|
@ -8,6 +8,10 @@
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
#include "esphome/components/light/light_json_schema.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include <esphome/components/logger/logger.h>
|
||||
#endif
|
||||
@ -528,7 +532,7 @@ std::string WebServer::light_json(light::LightState *obj) {
|
||||
return json::build_json([obj](JsonObject &root) {
|
||||
root["id"] = "light-" + obj->get_object_id();
|
||||
root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
|
||||
obj->dump_json(root);
|
||||
light::LightJSONSchema::dump_json(*obj, root);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
@ -563,6 +563,23 @@ def has_at_most_one_key(*keys):
|
||||
return validate
|
||||
|
||||
|
||||
def has_none_or_all_keys(*keys):
|
||||
"""Validate that none or all of the given keys exist in the config."""
|
||||
|
||||
def validate(obj):
|
||||
if not isinstance(obj, dict):
|
||||
raise Invalid("expected dictionary")
|
||||
|
||||
number = sum(k in keys for k in obj)
|
||||
if number != 0 and number != len(keys):
|
||||
raise Invalid(
|
||||
"Must specify either none or all of {}.".format(", ".join(keys))
|
||||
)
|
||||
return obj
|
||||
|
||||
return validate
|
||||
|
||||
|
||||
TIME_PERIOD_ERROR = (
|
||||
"Time period {} should be format number + unit, for example 5ms, 5s, 5min, 5h"
|
||||
)
|
||||
|
@ -120,6 +120,7 @@ CONF_COLD_WHITE_COLOR_TEMPERATURE = "cold_white_color_temperature"
|
||||
CONF_COLOR = "color"
|
||||
CONF_COLOR_BRIGHTNESS = "color_brightness"
|
||||
CONF_COLOR_CORRECT = "color_correct"
|
||||
CONF_COLOR_MODE = "color_mode"
|
||||
CONF_COLOR_TEMPERATURE = "color_temperature"
|
||||
CONF_COLORS = "colors"
|
||||
CONF_COMMAND = "command"
|
||||
|
@ -79,6 +79,15 @@ float gamma_correct(float value, float gamma) {
|
||||
|
||||
return powf(value, gamma);
|
||||
}
|
||||
float gamma_uncorrect(float value, float gamma) {
|
||||
if (value <= 0.0f)
|
||||
return 0.0f;
|
||||
if (gamma <= 0.0f)
|
||||
return value;
|
||||
|
||||
return powf(value, 1 / gamma);
|
||||
}
|
||||
|
||||
std::string to_lowercase_underscore(std::string s) {
|
||||
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
|
||||
std::replace(s.begin(), s.end(), ' ', '_');
|
||||
|
@ -116,6 +116,8 @@ uint8_t fast_random_8();
|
||||
|
||||
/// Applies gamma correction with the provided gamma to value.
|
||||
float gamma_correct(float value, float gamma);
|
||||
/// Reverts gamma correction with the provided gamma to value.
|
||||
float gamma_uncorrect(float value, float gamma);
|
||||
|
||||
/// Create a string from a value and an accuracy in decimals.
|
||||
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals);
|
||||
|
Loading…
Reference in New Issue
Block a user