mirror of
https://github.com/esphome/esphome.git
synced 2024-12-01 19:24:14 +01:00
Support HSV-based color support on tuya light (#2400)
* fix: stop tuya light state getting reset * fix typo * Support for HSV color in Tuya * Clamp formatting
This commit is contained in:
parent
af04f565cf
commit
c39ac9edfe
5 changed files with 107 additions and 7 deletions
|
@ -22,6 +22,7 @@ CONF_COLOR_TEMPERATURE_DATAPOINT = "color_temperature_datapoint"
|
||||||
CONF_COLOR_TEMPERATURE_INVERT = "color_temperature_invert"
|
CONF_COLOR_TEMPERATURE_INVERT = "color_temperature_invert"
|
||||||
CONF_COLOR_TEMPERATURE_MAX_VALUE = "color_temperature_max_value"
|
CONF_COLOR_TEMPERATURE_MAX_VALUE = "color_temperature_max_value"
|
||||||
CONF_RGB_DATAPOINT = "rgb_datapoint"
|
CONF_RGB_DATAPOINT = "rgb_datapoint"
|
||||||
|
CONF_HSV_DATAPOINT = "hsv_datapoint"
|
||||||
|
|
||||||
TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component)
|
TuyaLight = tuya_ns.class_("TuyaLight", light.LightOutput, cg.Component)
|
||||||
|
|
||||||
|
@ -33,7 +34,8 @@ CONFIG_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t,
|
cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t,
|
||||||
cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t,
|
cv.Optional(CONF_MIN_VALUE_DATAPOINT): cv.uint8_t,
|
||||||
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
|
cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t,
|
||||||
cv.Optional(CONF_RGB_DATAPOINT): cv.uint8_t,
|
cv.Exclusive(CONF_RGB_DATAPOINT, "color"): cv.uint8_t,
|
||||||
|
cv.Exclusive(CONF_HSV_DATAPOINT, "color"): cv.uint8_t,
|
||||||
cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean,
|
cv.Optional(CONF_COLOR_INTERLOCK, default=False): cv.boolean,
|
||||||
cv.Inclusive(
|
cv.Inclusive(
|
||||||
CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature"
|
CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature"
|
||||||
|
@ -57,7 +59,10 @@ CONFIG_SCHEMA = cv.All(
|
||||||
}
|
}
|
||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.has_at_least_one_key(
|
cv.has_at_least_one_key(
|
||||||
CONF_DIMMER_DATAPOINT, CONF_SWITCH_DATAPOINT, CONF_RGB_DATAPOINT
|
CONF_DIMMER_DATAPOINT,
|
||||||
|
CONF_SWITCH_DATAPOINT,
|
||||||
|
CONF_RGB_DATAPOINT,
|
||||||
|
CONF_HSV_DATAPOINT,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -75,6 +80,8 @@ async def to_code(config):
|
||||||
cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT]))
|
cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT]))
|
||||||
if CONF_RGB_DATAPOINT in config:
|
if CONF_RGB_DATAPOINT in config:
|
||||||
cg.add(var.set_rgb_id(config[CONF_RGB_DATAPOINT]))
|
cg.add(var.set_rgb_id(config[CONF_RGB_DATAPOINT]))
|
||||||
|
elif CONF_HSV_DATAPOINT in config:
|
||||||
|
cg.add(var.set_hsv_id(config[CONF_HSV_DATAPOINT]))
|
||||||
if CONF_COLOR_TEMPERATURE_DATAPOINT in config:
|
if CONF_COLOR_TEMPERATURE_DATAPOINT in config:
|
||||||
cg.add(var.set_color_temperature_id(config[CONF_COLOR_TEMPERATURE_DATAPOINT]))
|
cg.add(var.set_color_temperature_id(config[CONF_COLOR_TEMPERATURE_DATAPOINT]))
|
||||||
cg.add(var.set_color_temperature_invert(config[CONF_COLOR_TEMPERATURE_INVERT]))
|
cg.add(var.set_color_temperature_invert(config[CONF_COLOR_TEMPERATURE_INVERT]))
|
||||||
|
|
|
@ -46,6 +46,19 @@ void TuyaLight::setup() {
|
||||||
call.perform();
|
call.perform();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (hsv_id_.has_value()) {
|
||||||
|
this->parent_->register_listener(*this->hsv_id_, [this](const TuyaDatapoint &datapoint) {
|
||||||
|
auto hue = parse_hex(datapoint.value_string, 0, 4);
|
||||||
|
auto saturation = parse_hex(datapoint.value_string, 4, 4);
|
||||||
|
auto value = parse_hex(datapoint.value_string, 8, 4);
|
||||||
|
if (hue.has_value() && saturation.has_value() && value.has_value()) {
|
||||||
|
float red, green, blue;
|
||||||
|
hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue);
|
||||||
|
auto call = this->state_->make_call();
|
||||||
|
call.set_rgb(red, green, blue);
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (min_value_datapoint_id_.has_value()) {
|
if (min_value_datapoint_id_.has_value()) {
|
||||||
parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_);
|
parent_->set_integer_datapoint_value(*this->min_value_datapoint_id_, this->min_value_);
|
||||||
|
@ -60,12 +73,14 @@ void TuyaLight::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_);
|
ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_);
|
||||||
if (this->rgb_id_.has_value())
|
if (this->rgb_id_.has_value())
|
||||||
ESP_LOGCONFIG(TAG, " RGB has datapoint ID %u", *this->rgb_id_);
|
ESP_LOGCONFIG(TAG, " RGB has datapoint ID %u", *this->rgb_id_);
|
||||||
|
else if (this->hsv_id_.has_value())
|
||||||
|
ESP_LOGCONFIG(TAG, " HSV has datapoint ID %u", *this->hsv_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
light::LightTraits TuyaLight::get_traits() {
|
light::LightTraits TuyaLight::get_traits() {
|
||||||
auto traits = light::LightTraits();
|
auto traits = light::LightTraits();
|
||||||
if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) {
|
if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) {
|
||||||
if (this->rgb_id_.has_value()) {
|
if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) {
|
||||||
if (this->color_interlock_)
|
if (this->color_interlock_)
|
||||||
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE});
|
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE});
|
||||||
else
|
else
|
||||||
|
@ -75,7 +90,7 @@ light::LightTraits TuyaLight::get_traits() {
|
||||||
traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE});
|
traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE});
|
||||||
traits.set_min_mireds(this->cold_white_temperature_);
|
traits.set_min_mireds(this->cold_white_temperature_);
|
||||||
traits.set_max_mireds(this->warm_white_temperature_);
|
traits.set_max_mireds(this->warm_white_temperature_);
|
||||||
} else if (this->rgb_id_.has_value()) {
|
} else if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) {
|
||||||
if (this->dimmer_id_.has_value()) {
|
if (this->dimmer_id_.has_value()) {
|
||||||
if (this->color_interlock_)
|
if (this->color_interlock_)
|
||||||
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE});
|
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE});
|
||||||
|
@ -97,7 +112,7 @@ void TuyaLight::write_state(light::LightState *state) {
|
||||||
float red = 0.0f, green = 0.0f, blue = 0.0f;
|
float red = 0.0f, green = 0.0f, blue = 0.0f;
|
||||||
float color_temperature = 0.0f, brightness = 0.0f;
|
float color_temperature = 0.0f, brightness = 0.0f;
|
||||||
|
|
||||||
if (this->rgb_id_.has_value()) {
|
if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) {
|
||||||
if (this->color_temperature_id_.has_value()) {
|
if (this->color_temperature_id_.has_value()) {
|
||||||
state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &brightness);
|
state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &brightness);
|
||||||
} else if (this->dimmer_id_.has_value()) {
|
} else if (this->dimmer_id_.has_value()) {
|
||||||
|
@ -137,8 +152,16 @@ void TuyaLight::write_state(light::LightState *state) {
|
||||||
if (this->rgb_id_.has_value()) {
|
if (this->rgb_id_.has_value()) {
|
||||||
char buffer[7];
|
char buffer[7];
|
||||||
sprintf(buffer, "%02X%02X%02X", int(red * 255), int(green * 255), int(blue * 255));
|
sprintf(buffer, "%02X%02X%02X", int(red * 255), int(green * 255), int(blue * 255));
|
||||||
std::string value = buffer;
|
std::string rgb_value = buffer;
|
||||||
this->parent_->set_string_datapoint_value(*this->rgb_id_, value);
|
this->parent_->set_string_datapoint_value(*this->rgb_id_, rgb_value);
|
||||||
|
} else if (this->hsv_id_.has_value()) {
|
||||||
|
int hue;
|
||||||
|
float saturation, value;
|
||||||
|
rgb_to_hsv(red, green, blue, hue, saturation, value);
|
||||||
|
char buffer[13];
|
||||||
|
sprintf(buffer, "%04X%04X%04X", hue, int(saturation * 1000), int(value * 1000));
|
||||||
|
std::string hsv_value = buffer;
|
||||||
|
this->parent_->set_string_datapoint_value(*this->hsv_id_, hsv_value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ class TuyaLight : public Component, public light::LightOutput {
|
||||||
}
|
}
|
||||||
void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; }
|
void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; }
|
||||||
void set_rgb_id(uint8_t rgb_id) { this->rgb_id_ = rgb_id; }
|
void set_rgb_id(uint8_t rgb_id) { this->rgb_id_ = rgb_id; }
|
||||||
|
void set_hsv_id(uint8_t hsv_id) { this->hsv_id_ = hsv_id; }
|
||||||
void set_color_temperature_id(uint8_t color_temperature_id) { this->color_temperature_id_ = color_temperature_id; }
|
void set_color_temperature_id(uint8_t color_temperature_id) { this->color_temperature_id_ = color_temperature_id; }
|
||||||
void set_color_temperature_invert(bool color_temperature_invert) {
|
void set_color_temperature_invert(bool color_temperature_invert) {
|
||||||
this->color_temperature_invert_ = color_temperature_invert;
|
this->color_temperature_invert_ = color_temperature_invert;
|
||||||
|
@ -48,6 +49,7 @@ class TuyaLight : public Component, public light::LightOutput {
|
||||||
optional<uint8_t> min_value_datapoint_id_{};
|
optional<uint8_t> min_value_datapoint_id_{};
|
||||||
optional<uint8_t> switch_id_{};
|
optional<uint8_t> switch_id_{};
|
||||||
optional<uint8_t> rgb_id_{};
|
optional<uint8_t> rgb_id_{};
|
||||||
|
optional<uint8_t> hsv_id_{};
|
||||||
optional<uint8_t> color_temperature_id_{};
|
optional<uint8_t> color_temperature_id_{};
|
||||||
uint32_t min_value_ = 0;
|
uint32_t min_value_ = 0;
|
||||||
uint32_t max_value_ = 255;
|
uint32_t max_value_ = 255;
|
||||||
|
|
|
@ -394,6 +394,69 @@ std::string hexencode(const uint8_t *data, uint32_t len) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) {
|
||||||
|
float max_color_value = std::max(std::max(red, green), blue);
|
||||||
|
float min_color_value = std::min(std::min(red, green), blue);
|
||||||
|
float delta = max_color_value - min_color_value;
|
||||||
|
|
||||||
|
if (delta == 0)
|
||||||
|
hue = 0;
|
||||||
|
else if (max_color_value == red)
|
||||||
|
hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360));
|
||||||
|
else if (max_color_value == green)
|
||||||
|
hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360));
|
||||||
|
else if (max_color_value == blue)
|
||||||
|
hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360));
|
||||||
|
|
||||||
|
if (max_color_value == 0)
|
||||||
|
saturation = 0;
|
||||||
|
else
|
||||||
|
saturation = delta / max_color_value;
|
||||||
|
|
||||||
|
value = max_color_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) {
|
||||||
|
float chroma = value * saturation;
|
||||||
|
float hue_prime = fmod(hue / 60.0, 6);
|
||||||
|
float intermediate = chroma * (1 - fabs(fmod(hue_prime, 2) - 1));
|
||||||
|
float delta = value - chroma;
|
||||||
|
|
||||||
|
if (0 <= hue_prime && hue_prime < 1) {
|
||||||
|
red = chroma;
|
||||||
|
green = intermediate;
|
||||||
|
blue = 0;
|
||||||
|
} else if (1 <= hue_prime && hue_prime < 2) {
|
||||||
|
red = intermediate;
|
||||||
|
green = chroma;
|
||||||
|
blue = 0;
|
||||||
|
} else if (2 <= hue_prime && hue_prime < 3) {
|
||||||
|
red = 0;
|
||||||
|
green = chroma;
|
||||||
|
blue = intermediate;
|
||||||
|
} else if (3 <= hue_prime && hue_prime < 4) {
|
||||||
|
red = 0;
|
||||||
|
green = intermediate;
|
||||||
|
blue = chroma;
|
||||||
|
} else if (4 <= hue_prime && hue_prime < 5) {
|
||||||
|
red = intermediate;
|
||||||
|
green = 0;
|
||||||
|
blue = chroma;
|
||||||
|
} else if (5 <= hue_prime && hue_prime < 6) {
|
||||||
|
red = chroma;
|
||||||
|
green = 0;
|
||||||
|
blue = intermediate;
|
||||||
|
} else {
|
||||||
|
red = 0;
|
||||||
|
green = 0;
|
||||||
|
blue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
red += delta;
|
||||||
|
green += delta;
|
||||||
|
blue += delta;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP8266
|
#ifdef USE_ESP8266
|
||||||
IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
|
IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
|
||||||
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); }
|
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); }
|
||||||
|
|
|
@ -149,6 +149,11 @@ std::array<uint8_t, 2> decode_uint16(uint16_t value);
|
||||||
/// Encode a 32-bit unsigned integer given four bytes in MSB -> LSB order
|
/// Encode a 32-bit unsigned integer given four bytes in MSB -> LSB order
|
||||||
uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb);
|
uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb);
|
||||||
|
|
||||||
|
/// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1)
|
||||||
|
void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value);
|
||||||
|
/// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1)
|
||||||
|
void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue);
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* An interrupt helper class.
|
* An interrupt helper class.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in a new issue