Use supported color modes in deCONZ integration (#51656)

* Initial commit everything is working, need to reevaluate tests

* Fix supported color modes and hs_color

* Attest color mode
This commit is contained in:
Robert Svensson 2021-06-10 08:51:58 +02:00 committed by GitHub
parent 9097f41219
commit b1022ce84e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 93 additions and 36 deletions

View File

@ -1,5 +1,8 @@
"""Support for deCONZ lights.""" """Support for deCONZ lights."""
from __future__ import annotations
from pydeconz.group import DeconzGroup as Group
from pydeconz.light import Light from pydeconz.light import Light
from homeassistant.components.light import ( from homeassistant.components.light import (
@ -9,13 +12,16 @@ from homeassistant.components.light import (
ATTR_FLASH, ATTR_FLASH,
ATTR_HS_COLOR, ATTR_HS_COLOR,
ATTR_TRANSITION, ATTR_TRANSITION,
ATTR_XY_COLOR,
COLOR_MODE_BRIGHTNESS,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS,
COLOR_MODE_ONOFF,
COLOR_MODE_XY,
DOMAIN, DOMAIN,
EFFECT_COLORLOOP, EFFECT_COLORLOOP,
FLASH_LONG, FLASH_LONG,
FLASH_SHORT, FLASH_SHORT,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT, SUPPORT_EFFECT,
SUPPORT_FLASH, SUPPORT_FLASH,
SUPPORT_TRANSITION, SUPPORT_TRANSITION,
@ -23,7 +29,6 @@ from homeassistant.components.light import (
) )
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.util.color as color_util
from .const import ( from .const import (
COVER_TYPES, COVER_TYPES,
@ -106,24 +111,50 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
"""Set up light.""" """Set up light."""
super().__init__(device, gateway) super().__init__(device, gateway)
self._attr_supported_color_modes = set()
self.update_features(self._device) self.update_features(self._device)
def update_features(self, device): def update_features(self, device: Light | Group) -> None:
"""Calculate supported features of device.""" """Calculate supported features of device."""
supported_color_modes = self._attr_supported_color_modes
if device.ct is not None:
supported_color_modes.add(COLOR_MODE_COLOR_TEMP)
if device.hue is not None and device.sat is not None:
supported_color_modes.add(COLOR_MODE_HS)
if device.xy is not None:
supported_color_modes.add(COLOR_MODE_XY)
if not supported_color_modes and device.brightness is not None:
supported_color_modes.add(COLOR_MODE_BRIGHTNESS)
if not supported_color_modes:
supported_color_modes.add(COLOR_MODE_ONOFF)
if device.brightness is not None: if device.brightness is not None:
self._attr_supported_features |= SUPPORT_BRIGHTNESS
self._attr_supported_features |= SUPPORT_FLASH self._attr_supported_features |= SUPPORT_FLASH
self._attr_supported_features |= SUPPORT_TRANSITION self._attr_supported_features |= SUPPORT_TRANSITION
if device.ct is not None:
self._attr_supported_features |= SUPPORT_COLOR_TEMP
if device.xy is not None or (device.hue is not None and device.sat is not None):
self._attr_supported_features |= SUPPORT_COLOR
if device.effect is not None: if device.effect is not None:
self._attr_supported_features |= SUPPORT_EFFECT self._attr_supported_features |= SUPPORT_EFFECT
@property
def color_mode(self) -> str:
"""Return the color mode of the light."""
if self._device.colormode == "ct":
color_mode = COLOR_MODE_COLOR_TEMP
elif self._device.colormode == "hs":
color_mode = COLOR_MODE_HS
elif self._device.colormode == "xy":
color_mode = COLOR_MODE_XY
elif self._device.brightness is not None:
color_mode = COLOR_MODE_BRIGHTNESS
else:
color_mode = COLOR_MODE_ONOFF
return color_mode
@property @property
def brightness(self): def brightness(self):
"""Return the brightness of this light between 0..255.""" """Return the brightness of this light between 0..255."""
@ -137,20 +168,17 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
@property @property
def color_temp(self): def color_temp(self):
"""Return the CT color value.""" """Return the CT color value."""
if self._device.colormode != "ct":
return None
return self._device.ct return self._device.ct
@property @property
def hs_color(self): def hs_color(self) -> tuple:
"""Return the hs color value.""" """Return the hs color value."""
if self._device.colormode in ("xy", "hs"): return (self._device.hue / 65535 * 360, self._device.sat / 255 * 100)
if self._device.xy:
return color_util.color_xy_to_hs(*self._device.xy) @property
if self._device.hue and self._device.sat: def xy_color(self) -> tuple | None:
return (self._device.hue / 65535 * 360, self._device.sat / 255 * 100) """Return the XY color value."""
return None return self._device.xy
@property @property
def is_on(self): def is_on(self):
@ -161,18 +189,18 @@ class DeconzBaseLight(DeconzDevice, LightEntity):
"""Turn on light.""" """Turn on light."""
data = {"on": True} data = {"on": True}
if ATTR_BRIGHTNESS in kwargs:
data["bri"] = kwargs[ATTR_BRIGHTNESS]
if ATTR_COLOR_TEMP in kwargs: if ATTR_COLOR_TEMP in kwargs:
data["ct"] = kwargs[ATTR_COLOR_TEMP] data["ct"] = kwargs[ATTR_COLOR_TEMP]
if ATTR_HS_COLOR in kwargs: if ATTR_HS_COLOR in kwargs:
if self._device.xy is not None: data["hue"] = int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535)
data["xy"] = color_util.color_hs_to_xy(*kwargs[ATTR_HS_COLOR]) data["sat"] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255)
else:
data["hue"] = int(kwargs[ATTR_HS_COLOR][0] / 360 * 65535)
data["sat"] = int(kwargs[ATTR_HS_COLOR][1] / 100 * 255)
if ATTR_BRIGHTNESS in kwargs: if ATTR_XY_COLOR in kwargs:
data["bri"] = kwargs[ATTR_BRIGHTNESS] data["xy"] = kwargs[ATTR_XY_COLOR]
if ATTR_TRANSITION in kwargs: if ATTR_TRANSITION in kwargs:
data["transitiontime"] = int(kwargs[ATTR_TRANSITION] * 10) data["transitiontime"] = int(kwargs[ATTR_TRANSITION] * 10)
@ -250,6 +278,21 @@ class DeconzGroup(DeconzBaseLight):
if light.ZHATYPE == Light.ZHATYPE: if light.ZHATYPE == Light.ZHATYPE:
self.update_features(light) self.update_features(light)
for exclusive_color_mode in [COLOR_MODE_ONOFF, COLOR_MODE_BRIGHTNESS]:
if (
exclusive_color_mode in self._attr_supported_color_modes
and len(self._attr_supported_color_modes) > 1
):
self._attr_supported_color_modes.remove(exclusive_color_mode)
@property
def hs_color(self) -> tuple | None:
"""Return the hs color value."""
try:
return super().hs_color
except TypeError:
return None
@property @property
def unique_id(self): def unique_id(self):
"""Return a unique identifier for this device.""" """Return a unique identifier for this device."""

View File

@ -7,13 +7,19 @@ import pytest
from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_COLOR_TEMP, ATTR_COLOR_TEMP,
ATTR_EFFECT, ATTR_EFFECT,
ATTR_FLASH, ATTR_FLASH,
ATTR_HS_COLOR, ATTR_HS_COLOR,
ATTR_MAX_MIREDS, ATTR_MAX_MIREDS,
ATTR_MIN_MIREDS, ATTR_MIN_MIREDS,
ATTR_SUPPORTED_COLOR_MODES,
ATTR_TRANSITION, ATTR_TRANSITION,
ATTR_XY_COLOR,
COLOR_MODE_COLOR_TEMP,
COLOR_MODE_ONOFF,
COLOR_MODE_XY,
DOMAIN as LIGHT_DOMAIN, DOMAIN as LIGHT_DOMAIN,
EFFECT_COLORLOOP, EFFECT_COLORLOOP,
FLASH_LONG, FLASH_LONG,
@ -73,7 +79,7 @@ async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket):
"bri": 255, "bri": 255,
"colormode": "xy", "colormode": "xy",
"effect": "colorloop", "effect": "colorloop",
"xy": (500, 500), "xy": (0.5, 0.5),
"reachable": True, "reachable": True,
}, },
"type": "Extended color light", "type": "Extended color light",
@ -117,16 +123,22 @@ async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket):
rgb_light = hass.states.get("light.rgb_light") rgb_light = hass.states.get("light.rgb_light")
assert rgb_light.state == STATE_ON assert rgb_light.state == STATE_ON
assert rgb_light.attributes[ATTR_BRIGHTNESS] == 255 assert rgb_light.attributes[ATTR_BRIGHTNESS] == 255
assert rgb_light.attributes[ATTR_HS_COLOR] == (224.235, 100.0) assert rgb_light.attributes[ATTR_XY_COLOR] == (0.5, 0.5)
assert rgb_light.attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_XY]
assert rgb_light.attributes[ATTR_COLOR_MODE] == COLOR_MODE_XY
assert rgb_light.attributes[ATTR_SUPPORTED_FEATURES] == 44
assert rgb_light.attributes["is_deconz_group"] is False assert rgb_light.attributes["is_deconz_group"] is False
assert rgb_light.attributes[ATTR_SUPPORTED_FEATURES] == 61
tunable_white_light = hass.states.get("light.tunable_white_light") tunable_white_light = hass.states.get("light.tunable_white_light")
assert tunable_white_light.state == STATE_ON assert tunable_white_light.state == STATE_ON
assert tunable_white_light.attributes[ATTR_COLOR_TEMP] == 2500 assert tunable_white_light.attributes[ATTR_COLOR_TEMP] == 2500
assert tunable_white_light.attributes[ATTR_MAX_MIREDS] == 454 assert tunable_white_light.attributes[ATTR_MAX_MIREDS] == 454
assert tunable_white_light.attributes[ATTR_MIN_MIREDS] == 155 assert tunable_white_light.attributes[ATTR_MIN_MIREDS] == 155
assert tunable_white_light.attributes[ATTR_SUPPORTED_FEATURES] == 2 assert tunable_white_light.attributes[ATTR_SUPPORTED_COLOR_MODES] == [
COLOR_MODE_COLOR_TEMP
]
assert tunable_white_light.attributes[ATTR_COLOR_MODE] == COLOR_MODE_COLOR_TEMP
assert tunable_white_light.attributes[ATTR_SUPPORTED_FEATURES] == 0
tunable_white_light_bad_maxmin = hass.states.get( tunable_white_light_bad_maxmin = hass.states.get(
"light.tunable_white_light_with_bad_maxmin_values" "light.tunable_white_light_with_bad_maxmin_values"
@ -135,10 +147,12 @@ async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket):
assert tunable_white_light_bad_maxmin.attributes[ATTR_COLOR_TEMP] == 2500 assert tunable_white_light_bad_maxmin.attributes[ATTR_COLOR_TEMP] == 2500
assert tunable_white_light_bad_maxmin.attributes[ATTR_MAX_MIREDS] == 650 assert tunable_white_light_bad_maxmin.attributes[ATTR_MAX_MIREDS] == 650
assert tunable_white_light_bad_maxmin.attributes[ATTR_MIN_MIREDS] == 140 assert tunable_white_light_bad_maxmin.attributes[ATTR_MIN_MIREDS] == 140
assert tunable_white_light_bad_maxmin.attributes[ATTR_SUPPORTED_FEATURES] == 2 assert tunable_white_light_bad_maxmin.attributes[ATTR_SUPPORTED_FEATURES] == 0
on_off_light = hass.states.get("light.on_off_light") on_off_light = hass.states.get("light.on_off_light")
assert on_off_light.state == STATE_ON assert on_off_light.state == STATE_ON
assert on_off_light.attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_ONOFF]
assert on_off_light.attributes[ATTR_COLOR_MODE] == COLOR_MODE_ONOFF
assert on_off_light.attributes[ATTR_SUPPORTED_FEATURES] == 0 assert on_off_light.attributes[ATTR_SUPPORTED_FEATURES] == 0
assert hass.states.get("light.light_group").state == STATE_ON assert hass.states.get("light.light_group").state == STATE_ON
@ -191,7 +205,7 @@ async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket):
SERVICE_TURN_ON, SERVICE_TURN_ON,
{ {
ATTR_ENTITY_ID: "light.rgb_light", ATTR_ENTITY_ID: "light.rgb_light",
ATTR_HS_COLOR: (20, 30), ATTR_XY_COLOR: (0.411, 0.351),
ATTR_FLASH: FLASH_LONG, ATTR_FLASH: FLASH_LONG,
ATTR_EFFECT: "None", ATTR_EFFECT: "None",
}, },
@ -598,4 +612,4 @@ async def test_verify_group_supported_features(hass, aioclient_mock):
assert len(hass.states.async_all()) == 4 assert len(hass.states.async_all()) == 4
assert hass.states.get("light.group").state == STATE_ON assert hass.states.get("light.group").state == STATE_ON
assert hass.states.get("light.group").attributes[ATTR_SUPPORTED_FEATURES] == 63 assert hass.states.get("light.group").attributes[ATTR_SUPPORTED_FEATURES] == 44