From 6f4876e523f3f1df7c2d845ca2c8515e0c9c1203 Mon Sep 17 00:00:00 2001 From: petretiandrea Date: Fri, 3 Nov 2023 18:05:58 +0100 Subject: [PATCH] fix: light scene improvement (#599) --- .devcontainer/configuration.yaml | 193 ++++++++++++++++++++++++++++-- custom_components/tapo/helpers.py | 44 +++++++ custom_components/tapo/light.py | 179 ++++++++++++++------------- 3 files changed, 322 insertions(+), 94 deletions(-) diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml index d2313e90..462a7a25 100644 --- a/.devcontainer/configuration.yaml +++ b/.devcontainer/configuration.yaml @@ -12,34 +12,201 @@ debugpy: wait: false scene: - - id: "1677657730214" - name: L530 + - id: "1682456037783" + name: 3 Office pinks entities: - light.lampadina_smart: + light.striscia_luminosa_smart: min_color_temp_kelvin: 2500 - max_color_temp_kelvin: 6535 + max_color_temp_kelvin: 6500 min_mireds: 153 max_mireds: 400 + effect_list: + - bubblingcauldron + - aurora + - candycane + - christmas + - flicker + - christmaslight + - hanukkah + - hauntedmansion + - icicle + - lightning + - ocean + - rainbow + - raindrop + - spring + - sunrise + - sunset + - valentines supported_color_modes: - brightness - color_temp - hs - onoff + friendly_name: Office kallax + supported_features: 4 color_mode: hs brightness: 255 hs_color: - - 120 - - 85 + - 295 + - 99 rgb_color: - - 38 + - 233 + - 2 - 255 - - 38 xy_color: - - 0.182 - - 0.721 - friendly_name: Luce Camera Andrea - supported_features: 0 + - 0.357 + - 0.142 state: "on" metadata: - light.lampadina_smart: + light.striscia_luminosa_smart: entity_only: true + icon: mdi:desktop-classic + - id: "1682456224579" + name: 2 Office White + entities: + light.striscia_luminosa_smart: + min_color_temp_kelvin: 2500 + max_color_temp_kelvin: 6500 + min_mireds: 153 + max_mireds: 400 + effect_list: + - bubblingcauldron + - aurora + - candycane + - christmas + - flicker + - christmaslight + - hanukkah + - hauntedmansion + - icicle + - lightning + - ocean + - rainbow + - raindrop + - spring + - sunrise + - sunset + - valentines + supported_color_modes: + - brightness + - color_temp + - hs + - onoff + color_mode: color_temp + brightness: 255 + color_temp_kelvin: 6535 + color_temp: 153 + hs_color: + - 54.768 + - 1.6 + rgb_color: + - 255 + - 254 + - 250 + xy_color: + - 0.326 + - 0.333 + friendly_name: Office kallax + supported_features: 4 + state: "on" + metadata: + light.striscia_luminosa_smart: + entity_only: true + icon: mdi:desktop-classic + - id: "1697651778268" + name: 1 Office Streamin + entities: + light.striscia_luminosa_smart: + min_color_temp_kelvin: 2500 + max_color_temp_kelvin: 6500 + min_mireds: 153 + max_mireds: 400 + effect_list: + - bubblingcauldron + - aurora + - candycane + - christmas + - flicker + - christmaslight + - hanukkah + - hauntedmansion + - icicle + - lightning + - ocean + - rainbow + - raindrop + - spring + - sunrise + - sunset + - valentines + supported_color_modes: + - brightness + - color_temp + - hs + - onoff + friendly_name: Office kallax + supported_features: 4 + color_mode: color_temp + brightness: 255 + color_temp_kelvin: 2500 + color_temp: 400 + hs_color: + - 28.874 + - 72.522 + rgb_color: + - 255 + - 159 + - 70 + xy_color: + - 0.546 + - 0.389 + effect: Rainbow + state: "on" + icon: mdi:desktop-classic + metadata: + light.striscia_luminosa_smart: + entity_only: true + - id: "1687783375727" + name: Studio Light Warm 100% + entities: + light.striscia_luminosa_smart: + effect_list: + - bubblingcauldron + - aurora + - candycane + - christmas + - flicker + - christmaslight + - hanukkah + - hauntedmansion + - icicle + - lightning + - ocean + - rainbow + - raindrop + - spring + - sunrise + - sunset + - valentines + supported_color_modes: + - brightness + - hs + - onoff + color_mode: hs + brightness: 128 + hs_color: + - 30 + - 94 + rgb_color: + - 255 + - 135 + - 15 + xy_color: + - 0.6 + - 0.381 + friendly_name: Studio Right Strip + supported_features: 4 + state: "on" + icon: mdi:globe-light + metadata: {} diff --git a/custom_components/tapo/helpers.py b/custom_components/tapo/helpers.py index bc317db3..c9f60a5a 100644 --- a/custom_components/tapo/helpers.py +++ b/custom_components/tapo/helpers.py @@ -3,6 +3,12 @@ from typing import TypeVar from homeassistant.components.network.models import Adapter +from homeassistant.util.color import ( + color_temperature_kelvin_to_mired as kelvin_to_mired, +) +from homeassistant.util.color import ( + color_temperature_mired_to_kelvin as mired_to_kelvin, +) from plugp100.common.functional.tri import Try T = TypeVar("T") @@ -20,6 +26,44 @@ def get_short_model(model: str) -> str: return model.lower().split(maxsplit=1)[0] +def hass_to_tapo_brightness(brightness: float | None) -> float | None: + if brightness is not None: + return round((brightness / 255) * 100) + return brightness + + +def tapo_to_hass_brightness(brightness: float | None) -> float | None: + if brightness is not None: + return round((brightness * 255) / 100) + return brightness + + +# Mireds and Kelving are min, max tuple +def hass_to_tapo_color_temperature( + color_temp: int | None, mireds: (int, int), kelvin: (int, int) +) -> int | None: + if color_temp is not None: + constraint_color_temp = clamp(color_temp, mireds[0], mireds[1]) + return clamp( + mired_to_kelvin(constraint_color_temp), + min_value=kelvin[0], + max_value=kelvin[1], + ) + return color_temp + + +def tapo_to_hass_color_temperature( + color_temp: int | None, mireds: (int, int) +) -> int | None: + if color_temp is not None and color_temp > 0: + return clamp( + kelvin_to_mired(color_temp), + min_value=mireds[0], + max_value=mireds[1], + ) + return None + + async def find_adapter_for( adapters: list[Adapter], ip: Optional[str] ) -> Optional[Adapter]: diff --git a/custom_components/tapo/light.py b/custom_components/tapo/light.py index 96895865..b2357e2e 100755 --- a/custom_components/tapo/light.py +++ b/custom_components/tapo/light.py @@ -11,8 +11,11 @@ from custom_components.tapo.coordinators import LightTapoCoordinator from custom_components.tapo.coordinators import TapoCoordinator from custom_components.tapo.entity import BaseTapoEntity -from custom_components.tapo.helpers import clamp from custom_components.tapo.helpers import get_short_model +from custom_components.tapo.helpers import hass_to_tapo_brightness +from custom_components.tapo.helpers import hass_to_tapo_color_temperature +from custom_components.tapo.helpers import tapo_to_hass_brightness +from custom_components.tapo.helpers import tapo_to_hass_color_temperature from custom_components.tapo.setup_helpers import setup_from_platform_config from homeassistant.components.light import ATTR_BRIGHTNESS from homeassistant.components.light import ATTR_COLOR_TEMP @@ -27,9 +30,6 @@ from homeassistant.util.color import ( color_temperature_kelvin_to_mired as kelvin_to_mired, ) -from homeassistant.util.color import ( - color_temperature_mired_to_kelvin as mired_to_kelvin, -) from plugp100.api.light_effect_preset import LightEffectPreset _LOGGER = logging.getLogger(__name__) @@ -92,12 +92,12 @@ def is_on(self): @property def brightness(self): - if self._effects and self.coordinator.light_state.lighting_effect is not None: - return round( - (self.coordinator.light_state.lighting_effect.brightness * 255) / 100 - ) - else: - return round((self.coordinator.light_state.brightness * 255) / 100) + current_brightness = ( + self.coordinator.light_state.lighting_effect.brightness + if self._has_light_effect_enabled() + else self.coordinator.light_state.brightness + ) + return tapo_to_hass_brightness(current_brightness) @property def hs_color(self): @@ -112,102 +112,119 @@ def hs_color(self): @property def color_temp(self): - color_temp = self.coordinator.light_state.color_temp - if color_temp is not None and color_temp > 0: - return clamp( - kelvin_to_mired(color_temp), - min_value=self.min_mireds, - max_value=self.max_mireds, - ) - else: - return None + return tapo_to_hass_color_temperature( + self.coordinator.light_state.color_temp, (self.min_mireds, self.max_mireds) + ) @property def effect(self) -> Optional[str]: - if ( - self._effects - and self.coordinator.light_state.lighting_effect is not None - and self.coordinator.light_state.lighting_effect.enable - ): + if self._has_light_effect_enabled(): return self.coordinator.light_state.lighting_effect.name.lower() else: return None + def _has_light_effect_enabled(self) -> bool: + is_enabled = ( + self._effects + and self.coordinator.light_state.lighting_effect is not None + and self.coordinator.light_state.lighting_effect.enable + ) + print(f"Effect enabledd {is_enabled}") + return is_enabled + async def async_turn_on(self, **kwargs): + print(kwargs) brightness = kwargs.get(ATTR_BRIGHTNESS) color = kwargs.get(ATTR_HS_COLOR) color_temp = kwargs.get(ATTR_COLOR_TEMP) effect = kwargs.get(ATTR_EFFECT) + tapo_brightness = hass_to_tapo_brightness(brightness) + tapo_color_temp = hass_to_tapo_color_temperature( + color_temp, + (self.min_mireds, self.max_mireds), + (self.min_color_temp_kelvin, self.max_color_temp_kelvin), + ) - _LOGGER.info("Setting brightness: %s", str(brightness)) - _LOGGER.info("Setting color: %s", str(color)) - _LOGGER.info("Setting color_temp: %s", str(color_temp)) + _LOGGER.info( + "Setting brightness: %s, tapo %s", str(brightness), str(tapo_brightness) + ) + _LOGGER.info("Setting color: %s, tapo %s", str(color), str(color)) + _LOGGER.info( + "Setting color_temp: %s, tapo %s", str(color_temp), str(tapo_color_temp) + ) _LOGGER.info("Setting effect: %s", str(effect)) - if ( - brightness is not None - or color is not None - or color_temp is not None - or effect is not None - ): - if self.is_on is False: - (await self.coordinator.device.on()).get_or_raise() - if color is not None and ColorMode.HS in self.supported_color_modes: - hue = int(color[0]) - saturation = int(color[1]) - await self._change_color(hue, saturation) - elif ( - color_temp is not None - and ColorMode.COLOR_TEMP in self.supported_color_modes - ): - color_temp = int(color_temp) - await self._change_color_temp(color_temp) - if brightness is not None: - await self._change_brightness(brightness) - if effect is not None: - ( - await self.coordinator.device.set_light_effect( - self._effects[effect].to_effect() - ) - ).get_or_raise() - else: - (await self.coordinator.device.on()).get_or_raise() + await self._set_state( + on=True, + color_temp=tapo_color_temp, + hue_saturation=color, + brightness=tapo_brightness, + effect=effect, + current_effect=self.effect, + ) await self.coordinator.async_request_refresh() async def async_turn_off(self, **kwargs): - (await self.coordinator.device.off()).get_or_raise() + await self._set_state(on=False) await self.coordinator.async_request_refresh() - async def _change_brightness(self, new_brightness): - brightness_to_set = round((new_brightness / 255) * 100) - _LOGGER.debug("Change brightness to: %s", str(brightness_to_set)) - if self.effect is not None: + async def _set_state( + self, + on: bool, + color_temp=None, + hue_saturation=None, + brightness=None, + effect: str = None, + current_effect: str = None, + ): + if not on: + return (await self.coordinator.device.off()).get_or_raise() + + (await self.coordinator.device.on()).get_or_raise() + if effect is not None: ( - await self.coordinator.device.set_light_effect_brightness( - self._effects[self.effect].to_effect(), brightness_to_set + await self.coordinator.device.set_light_effect( + self._effects[effect.lower()].to_effect() ) ).get_or_raise() - else: + elif hue_saturation is not None and ColorMode.HS in self.supported_color_modes: + hue = int(hue_saturation[0]) + saturation = int(hue_saturation[1]) ( - await self.coordinator.device.set_brightness(brightness_to_set) + await self.coordinator.device.set_hue_saturation(hue, saturation) + ).get_or_raise() + elif ( + color_temp is not None + and ColorMode.COLOR_TEMP in self.supported_color_modes + ): + color_temp = int(color_temp) + ( + await self.coordinator.device.set_color_temperature(color_temp) ).get_or_raise() - async def _change_color_temp(self, color_temp): - _LOGGER.debug("Change color temp to: %s", str(color_temp)) - constraint_color_temp = clamp(color_temp, self.min_mireds, self.max_mireds) - kelvin_color_temp = clamp( - mired_to_kelvin(constraint_color_temp), - min_value=self.min_color_temp_kelvin, - max_value=self.max_color_temp_kelvin, - ) - - ( - await self.coordinator.device.set_color_temperature(kelvin_color_temp) - ).get_or_raise() + # handle all brightness user use cases + # 1. brightness set with effect (scene) + # 2. brightness set with colors (scene) + # 3. brightness set with slider, so change effect or color based on last state + if brightness is not None: + if effect is not None: + await self._change_brightness(brightness, apply_to_effect=effect) + elif color_temp is not None or hue_saturation is not None: + await self._change_brightness(brightness, apply_to_effect=None) + else: + await self._change_brightness( + brightness, apply_to_effect=current_effect + ) - async def _change_color(self, hue, saturation): - _LOGGER.debug("Change colors to: (%s, %s)", str(hue), str(saturation)) - ( - await self.coordinator.device.set_hue_saturation(hue, saturation) - ).get_or_raise() + async def _change_brightness(self, new_brightness, apply_to_effect: str = None): + if apply_to_effect: + ( + await self.coordinator.device.set_light_effect_brightness( + self._effects[apply_to_effect.lower()].to_effect(), new_brightness + ) + ).get_or_raise() + else: + ( + await self.coordinator.device.set_brightness(new_brightness) + ).get_or_raise()