mirror of
https://github.com/home-assistant/core.git
synced 2025-07-14 16:57:10 +00:00
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:
parent
9097f41219
commit
b1022ce84e
@ -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."""
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user