mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 00:48:19 +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_MAX_VALUE = "color_temperature_max_value"
|
||||
CONF_RGB_DATAPOINT = "rgb_datapoint"
|
||||
CONF_HSV_DATAPOINT = "hsv_datapoint"
|
||||
|
||||
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_MIN_VALUE_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.Inclusive(
|
||||
CONF_COLOR_TEMPERATURE_DATAPOINT, "color_temperature"
|
||||
|
@ -57,7 +59,10 @@ CONFIG_SCHEMA = cv.All(
|
|||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
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]))
|
||||
if CONF_RGB_DATAPOINT in config:
|
||||
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:
|
||||
cg.add(var.set_color_temperature_id(config[CONF_COLOR_TEMPERATURE_DATAPOINT]))
|
||||
cg.add(var.set_color_temperature_invert(config[CONF_COLOR_TEMPERATURE_INVERT]))
|
||||
|
|
|
@ -46,6 +46,19 @@ void TuyaLight::setup() {
|
|||
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()) {
|
||||
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_);
|
||||
if (this->rgb_id_.has_value())
|
||||
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() {
|
||||
auto traits = light::LightTraits();
|
||||
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_)
|
||||
traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE});
|
||||
else
|
||||
|
@ -75,7 +90,7 @@ light::LightTraits TuyaLight::get_traits() {
|
|||
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->rgb_id_.has_value()) {
|
||||
} else if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) {
|
||||
if (this->dimmer_id_.has_value()) {
|
||||
if (this->color_interlock_)
|
||||
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 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()) {
|
||||
state->current_values_as_rgbct(&red, &green, &blue, &color_temperature, &brightness);
|
||||
} else if (this->dimmer_id_.has_value()) {
|
||||
|
@ -137,8 +152,16 @@ void TuyaLight::write_state(light::LightState *state) {
|
|||
if (this->rgb_id_.has_value()) {
|
||||
char buffer[7];
|
||||
sprintf(buffer, "%02X%02X%02X", int(red * 255), int(green * 255), int(blue * 255));
|
||||
std::string value = buffer;
|
||||
this->parent_->set_string_datapoint_value(*this->rgb_id_, value);
|
||||
std::string rgb_value = buffer;
|
||||
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_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_invert(bool 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> switch_id_{};
|
||||
optional<uint8_t> rgb_id_{};
|
||||
optional<uint8_t> hsv_id_{};
|
||||
optional<uint8_t> color_temperature_id_{};
|
||||
uint32_t min_value_ = 0;
|
||||
uint32_t max_value_ = 255;
|
||||
|
|
|
@ -394,6 +394,69 @@ std::string hexencode(const uint8_t *data, uint32_t len) {
|
|||
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
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
|
||||
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
|
||||
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.
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue