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:
irtimaled 2021-09-28 13:19:17 -07:00 committed by GitHub
parent af04f565cf
commit c39ac9edfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 107 additions and 7 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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.
* *