From bc2c7b2d486018787078e489432f3b1e565e2832 Mon Sep 17 00:00:00 2001 From: Simone Chemelli Date: Mon, 11 Jan 2021 16:47:49 +0100 Subject: [PATCH] Add Shelly RGB devices management (#43993) * Add support for RGB devices * White value handling * Fixed logic for some devices (ColorTemp, White, Kelvin limits) * Code cleanup * Moved func from utils to light * Fix for DUO * Added "Optional" to properties that need it * Code more understandable * Applied code review suggestions * Applied code review suggestions * Updated logic to always show all available options --- homeassistant/components/shelly/const.py | 5 + homeassistant/components/shelly/light.py | 134 +++++++++++++++--- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 124 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/shelly/const.py b/homeassistant/components/shelly/const.py index b63de2e5fe0..a5922d0b9c0 100644 --- a/homeassistant/components/shelly/const.py +++ b/homeassistant/components/shelly/const.py @@ -70,3 +70,8 @@ INPUTS_EVENTS_SUBTYPES = { "button2": 2, "button3": 3, } + +# Kelvin value for colorTemp +KELVIN_MAX_VALUE = 6500 +KELVIN_MIN_VALUE = 2700 +KELVIN_MIN_VALUE_SHBLB_1 = 3000 diff --git a/homeassistant/components/shelly/light.py b/homeassistant/components/shelly/light.py index b3a6869d67d..0c91ddc1088 100644 --- a/homeassistant/components/shelly/light.py +++ b/homeassistant/components/shelly/light.py @@ -1,27 +1,47 @@ """Light for Shelly.""" -from typing import Optional +from typing import Optional, Tuple from aioshelly import Block from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + ATTR_WHITE_VALUE, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR, SUPPORT_COLOR_TEMP, + SUPPORT_WHITE_VALUE, LightEntity, ) from homeassistant.core import callback from homeassistant.util.color import ( + color_hs_to_RGB, + color_RGB_to_hs, color_temperature_kelvin_to_mired, color_temperature_mired_to_kelvin, ) from . import ShellyDeviceWrapper -from .const import COAP, DATA_CONFIG_ENTRY, DOMAIN +from .const import ( + COAP, + DATA_CONFIG_ENTRY, + DOMAIN, + KELVIN_MAX_VALUE, + KELVIN_MIN_VALUE, + KELVIN_MIN_VALUE_SHBLB_1, +) from .entity import ShellyBlockEntity from .utils import async_remove_shelly_entity +def min_kelvin(model: str): + """Kelvin (min) for colorTemp.""" + if model in ["SHBLB-1"]: + return KELVIN_MIN_VALUE_SHBLB_1 + return KELVIN_MIN_VALUE + + async def async_setup_entry(hass, config_entry, async_add_entities): """Set up lights for device.""" wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][COAP] @@ -54,11 +74,17 @@ class ShellyLight(ShellyBlockEntity, LightEntity): """Initialize light.""" super().__init__(wrapper, block) self.control_result = None + self.mode_result = None self._supported_features = 0 - if hasattr(block, "brightness"): + + if hasattr(block, "brightness") or hasattr(block, "gain"): self._supported_features |= SUPPORT_BRIGHTNESS if hasattr(block, "colorTemp"): self._supported_features |= SUPPORT_COLOR_TEMP + if hasattr(block, "white"): + self._supported_features |= SUPPORT_WHITE_VALUE + if hasattr(block, "red") and hasattr(block, "green") and hasattr(block, "blue"): + self._supported_features |= SUPPORT_COLOR @property def supported_features(self) -> int: @@ -73,18 +99,70 @@ class ShellyLight(ShellyBlockEntity, LightEntity): return self.block.output + @property + def mode(self) -> Optional[str]: + """Return the color mode of the light.""" + if self.mode_result: + return self.mode_result["mode"] + + if hasattr(self.block, "mode"): + return self.block.mode + + if ( + hasattr(self.block, "red") + and hasattr(self.block, "green") + and hasattr(self.block, "blue") + ): + return "color" + + return "white" + @property def brightness(self) -> Optional[int]: """Brightness of light.""" - if self.control_result: - brightness = self.control_result["brightness"] + if self.mode == "color": + if self.control_result: + brightness = self.control_result["gain"] + else: + brightness = self.block.gain else: - brightness = self.block.brightness + if self.control_result: + brightness = self.control_result["brightness"] + else: + brightness = self.block.brightness return int(brightness / 100 * 255) + @property + def white_value(self) -> Optional[int]: + """White value of light.""" + if self.control_result: + white = self.control_result["white"] + else: + white = self.block.white + return int(white) + + @property + def hs_color(self) -> Optional[Tuple[float, float]]: + """Return the hue and saturation color value of light.""" + if self.mode == "white": + return color_RGB_to_hs(255, 255, 255) + + if self.control_result: + red = self.control_result["red"] + green = self.control_result["green"] + blue = self.control_result["blue"] + else: + red = self.block.red + green = self.block.green + blue = self.block.blue + return color_RGB_to_hs(red, green, blue) + @property def color_temp(self) -> Optional[float]: """Return the CT color value in mireds.""" + if self.mode == "color": + return None + if self.control_result: color_temp = self.control_result["temp"] else: @@ -93,33 +171,52 @@ class ShellyLight(ShellyBlockEntity, LightEntity): # If you set DUO to max mireds in Shelly app, 2700K, # It reports 0 temp if color_temp == 0: - return self.max_mireds + return min_kelvin(self.wrapper.model) return int(color_temperature_kelvin_to_mired(color_temp)) @property - def min_mireds(self) -> float: + def min_mireds(self) -> Optional[float]: """Return the coldest color_temp that this light supports.""" - return color_temperature_kelvin_to_mired(6500) + return color_temperature_kelvin_to_mired(KELVIN_MAX_VALUE) @property - def max_mireds(self) -> float: + def max_mireds(self) -> Optional[float]: """Return the warmest color_temp that this light supports.""" - return color_temperature_kelvin_to_mired(2700) + return color_temperature_kelvin_to_mired(min_kelvin(self.wrapper.model)) async def async_turn_on(self, **kwargs) -> None: """Turn on light.""" params = {"turn": "on"} if ATTR_BRIGHTNESS in kwargs: - tmp_brightness = kwargs[ATTR_BRIGHTNESS] - params["brightness"] = int(tmp_brightness / 255 * 100) + tmp_brightness = int(kwargs[ATTR_BRIGHTNESS] / 255 * 100) + if hasattr(self.block, "gain"): + params["gain"] = tmp_brightness + if hasattr(self.block, "brightness"): + params["brightness"] = tmp_brightness if ATTR_COLOR_TEMP in kwargs: color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP]) - if color_temp > 6500: - color_temp = 6500 - elif color_temp < 2700: - color_temp = 2700 + color_temp = min( + KELVIN_MAX_VALUE, max(min_kelvin(self.wrapper.model), color_temp) + ) + # Color temperature change - used only in white mode, switch device mode to white + if self.mode == "color": + self.mode_result = await self.wrapper.device.switch_light_mode("white") + params["red"] = params["green"] = params["blue"] = 255 params["temp"] = int(color_temp) + elif ATTR_HS_COLOR in kwargs: + red, green, blue = color_hs_to_RGB(*kwargs[ATTR_HS_COLOR]) + # Color channels change - used only in color mode, switch device mode to color + if self.mode == "white": + self.mode_result = await self.wrapper.device.switch_light_mode("color") + params["red"] = red + params["green"] = green + params["blue"] = blue + elif ATTR_WHITE_VALUE in kwargs: + # White channel change - used only in color mode, switch device mode device to color + if self.mode == "white": + self.mode_result = await self.wrapper.device.switch_light_mode("color") + params["white"] = int(kwargs[ATTR_WHITE_VALUE]) self.control_result = await self.block.set_state(**params) self.async_write_ha_state() @@ -130,6 +227,7 @@ class ShellyLight(ShellyBlockEntity, LightEntity): @callback def _update_callback(self): - """When device updates, clear control result that overrides state.""" + """When device updates, clear control & mode result that overrides state.""" self.control_result = None + self.mode_result = None super()._update_callback() diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 71ee230d83d..923bcdced34 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==0.5.1"], + "requirements": ["aioshelly==0.5.3"], "zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }], "codeowners": ["@balloob", "@bieniu", "@thecode", "@chemelli74"] } diff --git a/requirements_all.txt b/requirements_all.txt index 16941bda7ce..82034f49020 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -218,7 +218,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.5.1 +aioshelly==0.5.3 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index b3908f59372..12419afea5f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aiopylgtv==0.3.3 aiorecollect==1.0.1 # homeassistant.components.shelly -aioshelly==0.5.1 +aioshelly==0.5.3 # homeassistant.components.switcher_kis aioswitcher==1.2.1