From d783293a213c07e7b3cf99b7c2a1d222602cc3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierre=20St=C3=A5hl?= Date: Mon, 26 Oct 2020 12:09:31 +0100 Subject: [PATCH] Add support for HSV encoded color lights (#90) * Add support for HSV encoded color light * Add debug logs for entity config * Unset color when going to white mode --- custom_components/localtuya/common.py | 11 ++- custom_components/localtuya/const.py | 2 + custom_components/localtuya/light.py | 68 ++++++++++++++----- .../localtuya/translations/en.json | 12 ++-- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 9c2d8d9..4f76843 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -219,6 +219,8 @@ class LocalTuyaEntity(Entity): """Subscribe localtuya events.""" await super().async_added_to_hass() + _LOGGER.debug("Adding %s with configuration: %s", self.entity_id, self._config) + def _update_handler(status): """Update entity state when status was updated.""" if status is not None: @@ -291,7 +293,14 @@ class LocalTuyaEntity(Entity): This method looks up which DP a certain config item uses based on user configuration and returns its value. """ - return self.dps(self._config.get(conf_item)) + dp_index = self._config.get(conf_item) + if dp_index is None: + _LOGGER.warning( + "Entity %s is requesting unset index for option %s", + self.entity_id, + conf_item, + ) + return self.dps(dp_index) def status_updated(self): """Device status was updated. diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index afd6f23..be5969a 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -11,6 +11,8 @@ CONF_DPS_STRINGS = "dps_strings" # light CONF_BRIGHTNESS_LOWER = "brightness_lower" CONF_BRIGHTNESS_UPPER = "brightness_upper" +CONF_COLOR = "color" +CONF_COLOR_MODE = "color_mode" # switch CONF_CURRENT = "current" diff --git a/custom_components/localtuya/light.py b/custom_components/localtuya/light.py index 4e29f30..67e3adf 100644 --- a/custom_components/localtuya/light.py +++ b/custom_components/localtuya/light.py @@ -1,5 +1,6 @@ """Platform to locally control Tuya-based light devices.""" import logging +import textwrap from functools import partial import voluptuous as vol @@ -10,12 +11,18 @@ from homeassistant.components.light import ( DOMAIN, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, + SUPPORT_COLOR, LightEntity, ) from homeassistant.const import CONF_BRIGHTNESS, CONF_COLOR_TEMP from .common import LocalTuyaEntity, async_setup_entry -from .const import CONF_BRIGHTNESS_LOWER, CONF_BRIGHTNESS_UPPER +from .const import ( + CONF_BRIGHTNESS_LOWER, + CONF_BRIGHTNESS_UPPER, + CONF_COLOR, + CONF_COLOR_MODE, +) _LOGGER = logging.getLogger(__name__) @@ -45,6 +52,8 @@ def flow_schema(dps): vol.Optional(CONF_BRIGHTNESS_UPPER, default=DEFAULT_UPPER_BRIGHTNESS): vol.All( vol.Coerce(int), vol.Range(min=0, max=10000) ), + vol.Optional(CONF_COLOR_MODE): vol.In(dps), + vol.Optional(CONF_COLOR): vol.In(dps), } @@ -69,6 +78,8 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): self._upper_brightness = self._config.get( CONF_BRIGHTNESS_UPPER, DEFAULT_UPPER_BRIGHTNESS ) + self._is_white_mode = True # Hopefully sane default + self._hs = None @property def is_on(self): @@ -78,7 +89,14 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): @property def brightness(self): """Return the brightness of the light.""" - return self._brightness + return map_range( + self._brightness, self._lower_brightness, self._upper_brightness, 0, 255 + ) + + @property + def hs_color(self): + """Return the hs color value.""" + return self._hs @property def color_temp(self): @@ -105,6 +123,8 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): supports |= SUPPORT_BRIGHTNESS if self.has_config(CONF_COLOR_TEMP): supports |= SUPPORT_COLOR_TEMP + if self.has_config(CONF_COLOR): + supports |= SUPPORT_COLOR | SUPPORT_BRIGHTNESS return supports async def async_turn_on(self, **kwargs): @@ -120,10 +140,22 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): self._lower_brightness, self._upper_brightness, ) - await self._device.set_dp(brightness, self._config.get(CONF_BRIGHTNESS)) + if self._is_white_mode: + await self._device.set_dp(brightness, self._config.get(CONF_BRIGHTNESS)) + else: + color = "{:04x}{:04x}{:04x}".format( + round(self._hs[0]), round(self._hs[1] * 10.0), brightness + ) + await self._device.set_dp(color, self._config.get(CONF_COLOR)) - if ATTR_HS_COLOR in kwargs: - raise ValueError(" TODO implement RGB from HS") + if ATTR_HS_COLOR in kwargs and (features & SUPPORT_COLOR): + hs = kwargs[ATTR_HS_COLOR] + color = "{:04x}{:04x}{:04x}".format( + round(hs[0]), round(hs[1] * 10.0), self._brightness + ) + await self._device.set_dp(color, self._config.get(CONF_COLOR)) + if self._is_white_mode: + await self._device.set_dp("colour", self._config.get(CONF_COLOR_MODE)) if ATTR_COLOR_TEMP in kwargs and (features & SUPPORT_COLOR_TEMP): color_temp = int( @@ -132,6 +164,8 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): * (int(kwargs[ATTR_COLOR_TEMP]) - MIN_MIRED) ) await self._device.set_dp(color_temp, self._config.get(CONF_COLOR_TEMP)) + if not self._is_white_mode: + await self._device.set_dp("white", self._config.get(CONF_COLOR_MODE)) async def async_turn_off(self, **kwargs): """Turn Tuya light off.""" @@ -142,17 +176,19 @@ class LocaltuyaLight(LocalTuyaEntity, LightEntity): self._state = self.dps(self._dp_id) supported = self.supported_features - if supported & SUPPORT_BRIGHTNESS: - brightness = self.dps_conf(CONF_BRIGHTNESS) - if brightness is not None: - brightness = map_range( - brightness, - self._lower_brightness, - self._upper_brightness, - 0, - 255, - ) - self._brightness = brightness + if supported & SUPPORT_COLOR: + self._is_white_mode = self.dps_conf(CONF_COLOR_MODE) == "white" + if self._is_white_mode: + self._hs = None + + if self._is_white_mode: + if supported & SUPPORT_BRIGHTNESS: + self._brightness = self.dps_conf(CONF_BRIGHTNESS) + else: + hsv_color = self.dps_conf(CONF_COLOR) + hue, sat, value = [int(value, 16) for value in textwrap.wrap(hsv_color, 4)] + self._hs = [hue, sat / 10.0] + self._brightness = value if supported & SUPPORT_COLOR_TEMP: self._color_temp = self.dps_conf(CONF_COLOR_TEMP) diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 536350f..2f98576 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -56,10 +56,12 @@ "scaling": "Scaling Factor", "state_on": "On Value", "state_off": "Off Value", - "brightness": "Brightness", + "brightness": "Brightness (only for white color)", "brightness_lower": "Brightness Lower Value", "brightness_upper": "Brightness Upper Value", - "color_temp": "Color Temperature" + "color_temp": "Color Temperature", + "color": "Color", + "color_mode": "Color Mode" } } } @@ -96,10 +98,12 @@ "scaling": "Scaling Factor", "state_on": "On Value", "state_off": "Off Value", - "brightness": "Brightness", + "brightness": "Brightness (only for white color)", "brightness_lower": "Brightness Lower Value", "brightness_upper": "Brightness Upper Value", - "color_temp": "Color Temperature" + "color_temp": "Color Temperature", + "color": "Color", + "color_mode": "Color Mode" } }, "yaml_import": {