mirror of
https://github.com/home-assistant/core.git
synced 2025-04-22 16:27:56 +00:00
Add support for light color modes (#47720)
* Add support for light color modes * Update tests * Update comments * Fix bugs, add tests * Suppress lint errors * Don't suppress brightness when state is ambiguous * Improve reproduce_state + add tests * Add comment * Change COLOR_MODE_* constants, rename COLOR_MODE_DIMMER to COLOR_MODE_BRIGHTNESS * Fix tests * Tweaks
This commit is contained in:
parent
333e6a215a
commit
5f2326fb57
@ -6,7 +6,7 @@ import dataclasses
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import os
|
||||
from typing import Dict, List, Optional, Tuple, cast
|
||||
from typing import Dict, List, Optional, Set, Tuple, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@ -38,19 +38,49 @@ DATA_PROFILES = "light_profiles"
|
||||
ENTITY_ID_FORMAT = DOMAIN + ".{}"
|
||||
|
||||
# Bitfield of features supported by the light entity
|
||||
SUPPORT_BRIGHTNESS = 1
|
||||
SUPPORT_COLOR_TEMP = 2
|
||||
SUPPORT_BRIGHTNESS = 1 # Deprecated, replaced by color modes
|
||||
SUPPORT_COLOR_TEMP = 2 # Deprecated, replaced by color modes
|
||||
SUPPORT_EFFECT = 4
|
||||
SUPPORT_FLASH = 8
|
||||
SUPPORT_COLOR = 16
|
||||
SUPPORT_COLOR = 16 # Deprecated, replaced by color modes
|
||||
SUPPORT_TRANSITION = 32
|
||||
SUPPORT_WHITE_VALUE = 128
|
||||
SUPPORT_WHITE_VALUE = 128 # Deprecated, replaced by color modes
|
||||
|
||||
# Color mode of the light
|
||||
ATTR_COLOR_MODE = "color_mode"
|
||||
# List of color modes supported by the light
|
||||
ATTR_SUPPORTED_COLOR_MODES = "supported_color_modes"
|
||||
# Possible color modes
|
||||
COLOR_MODE_UNKNOWN = "unknown" # Ambiguous color mode
|
||||
COLOR_MODE_ONOFF = "onoff" # Must be the only supported mode
|
||||
COLOR_MODE_BRIGHTNESS = "brightness" # Must be the only supported mode
|
||||
COLOR_MODE_COLOR_TEMP = "color_temp"
|
||||
COLOR_MODE_HS = "hs"
|
||||
COLOR_MODE_XY = "xy"
|
||||
COLOR_MODE_RGB = "rgb"
|
||||
COLOR_MODE_RGBW = "rgbw"
|
||||
COLOR_MODE_RGBWW = "rgbww"
|
||||
|
||||
VALID_COLOR_MODES = {
|
||||
COLOR_MODE_ONOFF,
|
||||
COLOR_MODE_BRIGHTNESS,
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
COLOR_MODE_HS,
|
||||
COLOR_MODE_XY,
|
||||
COLOR_MODE_RGB,
|
||||
COLOR_MODE_RGBW,
|
||||
COLOR_MODE_RGBWW,
|
||||
}
|
||||
COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {COLOR_MODE_ONOFF}
|
||||
COLOR_MODES_COLOR = {COLOR_MODE_HS, COLOR_MODE_RGB, COLOR_MODE_XY}
|
||||
|
||||
# Float that represents transition time in seconds to make change.
|
||||
ATTR_TRANSITION = "transition"
|
||||
|
||||
# Lists holding color values
|
||||
ATTR_RGB_COLOR = "rgb_color"
|
||||
ATTR_RGBW_COLOR = "rgbw_color"
|
||||
ATTR_RGBWW_COLOR = "rgbww_color"
|
||||
ATTR_XY_COLOR = "xy_color"
|
||||
ATTR_HS_COLOR = "hs_color"
|
||||
ATTR_COLOR_TEMP = "color_temp"
|
||||
@ -104,7 +134,13 @@ LIGHT_TURN_ON_SCHEMA = {
|
||||
vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT,
|
||||
vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string,
|
||||
vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All(
|
||||
vol.ExactSequence((cv.byte, cv.byte, cv.byte)), vol.Coerce(tuple)
|
||||
vol.ExactSequence((cv.byte,) * 3), vol.Coerce(tuple)
|
||||
),
|
||||
vol.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): vol.All(
|
||||
vol.ExactSequence((cv.byte,) * 4), vol.Coerce(tuple)
|
||||
),
|
||||
vol.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): vol.All(
|
||||
vol.ExactSequence((cv.byte,) * 5), vol.Coerce(tuple)
|
||||
),
|
||||
vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All(
|
||||
vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple)
|
||||
@ -166,14 +202,6 @@ def preprocess_turn_on_alternatives(hass, params):
|
||||
if brightness_pct is not None:
|
||||
params[ATTR_BRIGHTNESS] = round(255 * brightness_pct / 100)
|
||||
|
||||
xy_color = params.pop(ATTR_XY_COLOR, None)
|
||||
if xy_color is not None:
|
||||
params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
|
||||
|
||||
rgb_color = params.pop(ATTR_RGB_COLOR, None)
|
||||
if rgb_color is not None:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
|
||||
|
||||
def filter_turn_off_params(params):
|
||||
"""Filter out params not used in turn off."""
|
||||
@ -228,6 +256,52 @@ async def async_setup(hass, config):
|
||||
if ATTR_PROFILE not in params:
|
||||
profiles.apply_default(light.entity_id, params)
|
||||
|
||||
supported_color_modes = light.supported_color_modes
|
||||
# Backwards compatibility: if an RGBWW color is specified, convert to RGB + W
|
||||
# for legacy lights
|
||||
if ATTR_RGBW_COLOR in params:
|
||||
legacy_supported_color_modes = (
|
||||
light._light_internal_supported_color_modes # pylint: disable=protected-access
|
||||
)
|
||||
if (
|
||||
COLOR_MODE_RGBW in legacy_supported_color_modes
|
||||
and not supported_color_modes
|
||||
):
|
||||
rgbw_color = params.pop(ATTR_RGBW_COLOR)
|
||||
params[ATTR_RGB_COLOR] = rgbw_color[0:3]
|
||||
params[ATTR_WHITE_VALUE] = rgbw_color[3]
|
||||
|
||||
# If a color is specified, convert to the color space supported by the light
|
||||
# Backwards compatibility: Fall back to hs color if light.supported_color_modes
|
||||
# is not implemented
|
||||
if not supported_color_modes:
|
||||
if (rgb_color := params.pop(ATTR_RGB_COLOR, None)) is not None:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif (xy_color := params.pop(ATTR_XY_COLOR, None)) is not None:
|
||||
params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
|
||||
elif ATTR_HS_COLOR in params and COLOR_MODE_HS not in supported_color_modes:
|
||||
hs_color = params.pop(ATTR_HS_COLOR)
|
||||
if COLOR_MODE_RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
|
||||
elif COLOR_MODE_XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
|
||||
elif ATTR_RGB_COLOR in params and COLOR_MODE_RGB not in supported_color_modes:
|
||||
rgb_color = params.pop(ATTR_RGB_COLOR)
|
||||
if COLOR_MODE_HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
elif COLOR_MODE_XY in supported_color_modes:
|
||||
params[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
elif ATTR_XY_COLOR in params and COLOR_MODE_XY not in supported_color_modes:
|
||||
xy_color = params.pop(ATTR_XY_COLOR)
|
||||
if COLOR_MODE_HS in supported_color_modes:
|
||||
params[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
|
||||
elif COLOR_MODE_RGB in supported_color_modes:
|
||||
params[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color)
|
||||
|
||||
# Remove deprecated white value if the light supports color mode
|
||||
if supported_color_modes:
|
||||
params.pop(ATTR_WHITE_VALUE, None)
|
||||
|
||||
# Zero brightness: Light will be turned off
|
||||
if params.get(ATTR_BRIGHTNESS) == 0:
|
||||
await light.async_turn_off(**filter_turn_off_params(params))
|
||||
@ -411,11 +485,83 @@ class LightEntity(ToggleEntity):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def color_mode(self) -> Optional[str]:
|
||||
"""Return the color mode of the light."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def _light_internal_color_mode(self) -> str:
|
||||
"""Return the color mode of the light with backwards compatibility."""
|
||||
color_mode = self.color_mode
|
||||
|
||||
if color_mode is None:
|
||||
# Backwards compatibility for color_mode added in 2021.4
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
supported = self._light_internal_supported_color_modes
|
||||
|
||||
if (
|
||||
COLOR_MODE_RGBW in supported
|
||||
and self.white_value is not None
|
||||
and self.hs_color is not None
|
||||
):
|
||||
return COLOR_MODE_RGBW
|
||||
if COLOR_MODE_HS in supported and self.hs_color is not None:
|
||||
return COLOR_MODE_HS
|
||||
if COLOR_MODE_COLOR_TEMP in supported and self.color_temp is not None:
|
||||
return COLOR_MODE_COLOR_TEMP
|
||||
if COLOR_MODE_BRIGHTNESS in supported and self.brightness is not None:
|
||||
return COLOR_MODE_BRIGHTNESS
|
||||
if COLOR_MODE_ONOFF in supported:
|
||||
return COLOR_MODE_ONOFF
|
||||
return COLOR_MODE_UNKNOWN
|
||||
|
||||
return color_mode
|
||||
|
||||
@property
|
||||
def hs_color(self) -> Optional[Tuple[float, float]]:
|
||||
"""Return the hue and saturation color value [float, float]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def xy_color(self) -> Optional[Tuple[float, float]]:
|
||||
"""Return the xy color value [float, float]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def rgb_color(self) -> Optional[Tuple[int, int, int]]:
|
||||
"""Return the rgb color value [int, int, int]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def rgbw_color(self) -> Optional[Tuple[int, int, int, int]]:
|
||||
"""Return the rgbw color value [int, int, int, int]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def _light_internal_rgbw_color(self) -> Optional[Tuple[int, int, int, int]]:
|
||||
"""Return the rgbw color value [int, int, int, int]."""
|
||||
rgbw_color = self.rgbw_color
|
||||
if (
|
||||
rgbw_color is None
|
||||
and self.hs_color is not None
|
||||
and self.white_value is not None
|
||||
):
|
||||
# Backwards compatibility for rgbw_color added in 2021.4
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
r, g, b = color_util.color_hs_to_RGB( # pylint: disable=invalid-name
|
||||
*self.hs_color
|
||||
)
|
||||
w = self.white_value # pylint: disable=invalid-name
|
||||
rgbw_color = (r, g, b, w)
|
||||
|
||||
return rgbw_color
|
||||
|
||||
@property
|
||||
def rgbww_color(self) -> Optional[Tuple[int, int, int, int, int]]:
|
||||
"""Return the rgbww color value [int, int, int, int, int]."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def color_temp(self) -> Optional[int]:
|
||||
"""Return the CT color value in mireds."""
|
||||
@ -463,6 +609,29 @@ class LightEntity(ToggleEntity):
|
||||
if supported_features & SUPPORT_EFFECT:
|
||||
data[ATTR_EFFECT_LIST] = self.effect_list
|
||||
|
||||
data[ATTR_SUPPORTED_COLOR_MODES] = sorted(
|
||||
list(self._light_internal_supported_color_modes)
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
def _light_internal_convert_color(self, color_mode: str) -> dict:
|
||||
data: Dict[str, Tuple] = {}
|
||||
if color_mode == COLOR_MODE_HS and self.hs_color:
|
||||
hs_color = self.hs_color
|
||||
data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3))
|
||||
data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
|
||||
data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
|
||||
elif color_mode == COLOR_MODE_XY and self.xy_color:
|
||||
xy_color = self.xy_color
|
||||
data[ATTR_HS_COLOR] = color_util.color_xy_to_hs(*xy_color)
|
||||
data[ATTR_RGB_COLOR] = color_util.color_xy_to_RGB(*xy_color)
|
||||
data[ATTR_XY_COLOR] = (round(xy_color[0], 6), round(xy_color[1], 6))
|
||||
elif color_mode == COLOR_MODE_RGB and self.rgb_color:
|
||||
rgb_color = self.rgb_color
|
||||
data[ATTR_HS_COLOR] = color_util.color_RGB_to_hs(*rgb_color)
|
||||
data[ATTR_RGB_COLOR] = tuple(int(x) for x in rgb_color[0:3])
|
||||
data[ATTR_XY_COLOR] = color_util.color_RGB_to_xy(*rgb_color)
|
||||
return data
|
||||
|
||||
@property
|
||||
@ -473,27 +642,85 @@ class LightEntity(ToggleEntity):
|
||||
|
||||
data = {}
|
||||
supported_features = self.supported_features
|
||||
color_mode = self._light_internal_color_mode
|
||||
|
||||
if supported_features & SUPPORT_BRIGHTNESS:
|
||||
if color_mode not in self._light_internal_supported_color_modes:
|
||||
# Increase severity to warning in 2021.6, reject in 2021.10
|
||||
_LOGGER.debug(
|
||||
"%s: set to unsupported color_mode: %s, supported_color_modes: %s",
|
||||
self.entity_id,
|
||||
color_mode,
|
||||
self._light_internal_supported_color_modes,
|
||||
)
|
||||
|
||||
data[ATTR_COLOR_MODE] = color_mode
|
||||
|
||||
if color_mode in COLOR_MODES_BRIGHTNESS:
|
||||
data[ATTR_BRIGHTNESS] = self.brightness
|
||||
elif supported_features & SUPPORT_BRIGHTNESS:
|
||||
# Backwards compatibility for ambiguous / incomplete states
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
data[ATTR_BRIGHTNESS] = self.brightness
|
||||
|
||||
if supported_features & SUPPORT_COLOR_TEMP:
|
||||
if color_mode == COLOR_MODE_COLOR_TEMP:
|
||||
data[ATTR_COLOR_TEMP] = self.color_temp
|
||||
|
||||
if supported_features & SUPPORT_COLOR and self.hs_color:
|
||||
hs_color = self.hs_color
|
||||
data[ATTR_HS_COLOR] = (round(hs_color[0], 3), round(hs_color[1], 3))
|
||||
data[ATTR_RGB_COLOR] = color_util.color_hs_to_RGB(*hs_color)
|
||||
data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
|
||||
if color_mode in COLOR_MODES_COLOR:
|
||||
data.update(self._light_internal_convert_color(color_mode))
|
||||
|
||||
if supported_features & SUPPORT_WHITE_VALUE:
|
||||
if color_mode == COLOR_MODE_RGBW:
|
||||
data[ATTR_RGBW_COLOR] = self._light_internal_rgbw_color
|
||||
|
||||
if color_mode == COLOR_MODE_RGBWW:
|
||||
data[ATTR_RGBWW_COLOR] = self.rgbww_color
|
||||
|
||||
if supported_features & SUPPORT_COLOR_TEMP and not self.supported_color_modes:
|
||||
# Backwards compatibility
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
data[ATTR_COLOR_TEMP] = self.color_temp
|
||||
|
||||
if supported_features & SUPPORT_WHITE_VALUE and not self.supported_color_modes:
|
||||
# Backwards compatibility
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
data[ATTR_WHITE_VALUE] = self.white_value
|
||||
if self.hs_color is not None:
|
||||
data.update(self._light_internal_convert_color(COLOR_MODE_HS))
|
||||
|
||||
if supported_features & SUPPORT_EFFECT:
|
||||
data[ATTR_EFFECT] = self.effect
|
||||
|
||||
return {key: val for key, val in data.items() if val is not None}
|
||||
|
||||
@property
|
||||
def _light_internal_supported_color_modes(self) -> Set:
|
||||
"""Calculate supported color modes with backwards compatibility."""
|
||||
supported_color_modes = self.supported_color_modes
|
||||
|
||||
if supported_color_modes is None:
|
||||
# Backwards compatibility for supported_color_modes added in 2021.4
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
supported_features = self.supported_features
|
||||
supported_color_modes = set()
|
||||
|
||||
if supported_features & SUPPORT_COLOR_TEMP:
|
||||
supported_color_modes.add(COLOR_MODE_COLOR_TEMP)
|
||||
if supported_features & SUPPORT_COLOR:
|
||||
supported_color_modes.add(COLOR_MODE_HS)
|
||||
if supported_features & SUPPORT_WHITE_VALUE:
|
||||
supported_color_modes.add(COLOR_MODE_RGBW)
|
||||
if supported_features & SUPPORT_BRIGHTNESS and not supported_color_modes:
|
||||
supported_color_modes = {COLOR_MODE_BRIGHTNESS}
|
||||
|
||||
if not supported_color_modes:
|
||||
supported_color_modes = {COLOR_MODE_ONOFF}
|
||||
|
||||
return supported_color_modes
|
||||
|
||||
@property
|
||||
def supported_color_modes(self) -> Optional[Set]:
|
||||
"""Flag supported color modes."""
|
||||
return None
|
||||
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Flag supported features."""
|
||||
|
@ -17,6 +17,7 @@ from homeassistant.helpers.typing import HomeAssistantType
|
||||
from . import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_BRIGHTNESS_PCT,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_COLOR_NAME,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_EFFECT,
|
||||
@ -25,9 +26,18 @@ from . import (
|
||||
ATTR_KELVIN,
|
||||
ATTR_PROFILE,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_TRANSITION,
|
||||
ATTR_WHITE_VALUE,
|
||||
ATTR_XY_COLOR,
|
||||
COLOR_MODE_COLOR_TEMP,
|
||||
COLOR_MODE_HS,
|
||||
COLOR_MODE_RGB,
|
||||
COLOR_MODE_RGBW,
|
||||
COLOR_MODE_RGBWW,
|
||||
COLOR_MODE_UNKNOWN,
|
||||
COLOR_MODE_XY,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
@ -48,6 +58,8 @@ COLOR_GROUP = [
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_COLOR_TEMP,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_RGBWW_COLOR,
|
||||
ATTR_XY_COLOR,
|
||||
# The following color attributes are deprecated
|
||||
ATTR_PROFILE,
|
||||
@ -55,6 +67,15 @@ COLOR_GROUP = [
|
||||
ATTR_KELVIN,
|
||||
]
|
||||
|
||||
COLOR_MODE_TO_ATTRIBUTE = {
|
||||
COLOR_MODE_COLOR_TEMP: ATTR_COLOR_TEMP,
|
||||
COLOR_MODE_HS: ATTR_HS_COLOR,
|
||||
COLOR_MODE_RGB: ATTR_RGB_COLOR,
|
||||
COLOR_MODE_RGBW: ATTR_RGBW_COLOR,
|
||||
COLOR_MODE_RGBWW: ATTR_RGBWW_COLOR,
|
||||
COLOR_MODE_XY: ATTR_XY_COLOR,
|
||||
}
|
||||
|
||||
DEPRECATED_GROUP = [
|
||||
ATTR_BRIGHTNESS_PCT,
|
||||
ATTR_COLOR_NAME,
|
||||
@ -114,11 +135,29 @@ async def _async_reproduce_state(
|
||||
if attr in state.attributes:
|
||||
service_data[attr] = state.attributes[attr]
|
||||
|
||||
for color_attr in COLOR_GROUP:
|
||||
# Choose the first color that is specified
|
||||
if color_attr in state.attributes:
|
||||
if (
|
||||
state.attributes.get(ATTR_COLOR_MODE, COLOR_MODE_UNKNOWN)
|
||||
!= COLOR_MODE_UNKNOWN
|
||||
):
|
||||
# Remove deprecated white value if we got a valid color mode
|
||||
service_data.pop(ATTR_WHITE_VALUE, None)
|
||||
color_mode = state.attributes[ATTR_COLOR_MODE]
|
||||
if color_attr := COLOR_MODE_TO_ATTRIBUTE.get(color_mode):
|
||||
if color_attr not in state.attributes:
|
||||
_LOGGER.warning(
|
||||
"Color mode %s specified but attribute %s missing for: %s",
|
||||
color_mode,
|
||||
color_attr,
|
||||
state.entity_id,
|
||||
)
|
||||
return
|
||||
service_data[color_attr] = state.attributes[color_attr]
|
||||
break
|
||||
else:
|
||||
# Fall back to Choosing the first color that is specified
|
||||
for color_attr in COLOR_GROUP:
|
||||
if color_attr in state.attributes:
|
||||
service_data[color_attr] = state.attributes[color_attr]
|
||||
break
|
||||
|
||||
elif state.state == STATE_OFF:
|
||||
service = SERVICE_TURN_OFF
|
||||
|
@ -8,10 +8,15 @@ from homeassistant import setup
|
||||
from homeassistant.components.kulersky.light import DOMAIN
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_WHITE_VALUE,
|
||||
ATTR_XY_COLOR,
|
||||
COLOR_MODE_HS,
|
||||
COLOR_MODE_RGBW,
|
||||
SCAN_INTERVAL,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
@ -65,6 +70,7 @@ async def test_init(hass, mock_light):
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_WHITE_VALUE,
|
||||
@ -168,6 +174,7 @@ async def test_light_update(hass, mock_light):
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_WHITE_VALUE,
|
||||
@ -183,6 +190,7 @@ async def test_light_update(hass, mock_light):
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_WHITE_VALUE,
|
||||
@ -198,12 +206,15 @@ async def test_light_update(hass, mock_light):
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "Bedroom",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS, COLOR_MODE_RGBW],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS
|
||||
| SUPPORT_COLOR
|
||||
| SUPPORT_WHITE_VALUE,
|
||||
ATTR_COLOR_MODE: COLOR_MODE_RGBW,
|
||||
ATTR_BRIGHTNESS: 200,
|
||||
ATTR_HS_COLOR: (200, 60),
|
||||
ATTR_RGB_COLOR: (102, 203, 255),
|
||||
ATTR_RGBW_COLOR: (102, 203, 255, 240),
|
||||
ATTR_WHITE_VALUE: 240,
|
||||
ATTR_XY_COLOR: (0.184, 0.261),
|
||||
}
|
||||
|
@ -915,3 +915,482 @@ invalid_no_brightness_no_color_no_transition,,,
|
||||
"invalid_no_brightness_no_color_no_transition",
|
||||
):
|
||||
assert invalid_profile_name not in profiles.data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("light_state", (STATE_ON, STATE_OFF))
|
||||
async def test_light_backwards_compatibility_supported_color_modes(hass, light_state):
|
||||
"""Test supported_color_modes if not implemented by the entity."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("Test_0", light_state))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_1", light_state))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_2", light_state))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_3", light_state))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_4", light_state))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_5", light_state))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_6", light_state))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_features = light.SUPPORT_BRIGHTNESS
|
||||
|
||||
entity2 = platform.ENTITIES[2]
|
||||
entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP
|
||||
|
||||
entity3 = platform.ENTITIES[3]
|
||||
entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR
|
||||
|
||||
entity4 = platform.ENTITIES[4]
|
||||
entity4.supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE
|
||||
)
|
||||
|
||||
entity5 = platform.ENTITIES[5]
|
||||
entity5.supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP
|
||||
)
|
||||
|
||||
entity6 = platform.ENTITIES[6]
|
||||
entity6.supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS
|
||||
| light.SUPPORT_COLOR
|
||||
| light.SUPPORT_COLOR_TEMP
|
||||
| light.SUPPORT_WHITE_VALUE
|
||||
)
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF]
|
||||
if light_state == STATE_OFF:
|
||||
assert "color_mode" not in state.attributes
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_ONOFF
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_BRIGHTNESS]
|
||||
if light_state == STATE_OFF:
|
||||
assert "color_mode" not in state.attributes
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN
|
||||
|
||||
state = hass.states.get(entity2.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_COLOR_TEMP]
|
||||
if light_state == STATE_OFF:
|
||||
assert "color_mode" not in state.attributes
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS]
|
||||
if light_state == STATE_OFF:
|
||||
assert "color_mode" not in state.attributes
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN
|
||||
|
||||
state = hass.states.get(entity4.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.COLOR_MODE_HS,
|
||||
light.COLOR_MODE_RGBW,
|
||||
]
|
||||
if light_state == STATE_OFF:
|
||||
assert "color_mode" not in state.attributes
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN
|
||||
|
||||
state = hass.states.get(entity5.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.COLOR_MODE_COLOR_TEMP,
|
||||
light.COLOR_MODE_HS,
|
||||
]
|
||||
if light_state == STATE_OFF:
|
||||
assert "color_mode" not in state.attributes
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN
|
||||
|
||||
state = hass.states.get(entity6.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.COLOR_MODE_COLOR_TEMP,
|
||||
light.COLOR_MODE_HS,
|
||||
light.COLOR_MODE_RGBW,
|
||||
]
|
||||
if light_state == STATE_OFF:
|
||||
assert "color_mode" not in state.attributes
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_UNKNOWN
|
||||
|
||||
|
||||
async def test_light_backwards_compatibility_color_mode(hass):
|
||||
"""Test color_mode if not implemented by the entity."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("Test_0", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_1", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_2", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_3", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_4", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_5", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_6", STATE_ON))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_features = light.SUPPORT_BRIGHTNESS
|
||||
entity1.brightness = 100
|
||||
|
||||
entity2 = platform.ENTITIES[2]
|
||||
entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP
|
||||
entity2.color_temp = 100
|
||||
|
||||
entity3 = platform.ENTITIES[3]
|
||||
entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR
|
||||
entity3.hs_color = (240, 100)
|
||||
|
||||
entity4 = platform.ENTITIES[4]
|
||||
entity4.supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE
|
||||
)
|
||||
entity4.hs_color = (240, 100)
|
||||
entity4.white_value = 100
|
||||
|
||||
entity5 = platform.ENTITIES[5]
|
||||
entity5.supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP
|
||||
)
|
||||
entity5.hs_color = (240, 100)
|
||||
entity5.color_temp = 100
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF]
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_ONOFF
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_BRIGHTNESS]
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_BRIGHTNESS
|
||||
|
||||
state = hass.states.get(entity2.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_COLOR_TEMP]
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_COLOR_TEMP
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS]
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_HS
|
||||
|
||||
state = hass.states.get(entity4.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.COLOR_MODE_HS,
|
||||
light.COLOR_MODE_RGBW,
|
||||
]
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_RGBW
|
||||
|
||||
state = hass.states.get(entity5.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.COLOR_MODE_COLOR_TEMP,
|
||||
light.COLOR_MODE_HS,
|
||||
]
|
||||
# hs color prioritized over color_temp, light should report mode COLOR_MODE_HS
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_HS
|
||||
|
||||
|
||||
async def test_light_service_call_rgbw(hass):
|
||||
"""Test backwards compatibility for rgbw functionality in service calls."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("Test_legacy_white_value", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE
|
||||
)
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {light.COLOR_MODE_RGBW}
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.COLOR_MODE_HS,
|
||||
light.COLOR_MODE_RGBW,
|
||||
]
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_RGBW]
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": [entity0.entity_id, entity1.entity_id],
|
||||
"brightness_pct": 100,
|
||||
"rgbw_color": (10, 20, 30, 40),
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
_, data = entity0.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "hs_color": (210.0, 66.667), "white_value": 40}
|
||||
_, data = entity1.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "rgbw_color": (10, 20, 30, 40)}
|
||||
|
||||
|
||||
async def test_light_state_rgbw(hass):
|
||||
"""Test rgbw color conversion in state updates."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("Test_legacy_white_value", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
legacy_supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_WHITE_VALUE
|
||||
)
|
||||
entity0.supported_features = legacy_supported_features
|
||||
entity0.hs_color = (210.0, 66.667)
|
||||
entity0.rgb_color = "Invalid" # Should be ignored
|
||||
entity0.rgbww_color = "Invalid" # Should be ignored
|
||||
entity0.white_value = 40
|
||||
entity0.xy_color = "Invalid" # Should be ignored
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {light.COLOR_MODE_RGBW}
|
||||
entity1.color_mode = light.COLOR_MODE_RGBW
|
||||
entity1.hs_color = "Invalid" # Should be ignored
|
||||
entity1.rgb_color = "Invalid" # Should be ignored
|
||||
entity1.rgbw_color = (1, 2, 3, 4)
|
||||
entity1.rgbww_color = "Invalid" # Should be ignored
|
||||
entity1.white_value = "Invalid" # Should be ignored
|
||||
entity1.xy_color = "Invalid" # Should be ignored
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.attributes == {
|
||||
"color_mode": light.COLOR_MODE_RGBW,
|
||||
"friendly_name": "Test_legacy_white_value",
|
||||
"supported_color_modes": [light.COLOR_MODE_HS, light.COLOR_MODE_RGBW],
|
||||
"supported_features": legacy_supported_features,
|
||||
"hs_color": (210.0, 66.667),
|
||||
"rgb_color": (84, 169, 255),
|
||||
"rgbw_color": (84, 169, 255, 40),
|
||||
"white_value": 40,
|
||||
"xy_color": (0.173, 0.207),
|
||||
}
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes == {
|
||||
"color_mode": light.COLOR_MODE_RGBW,
|
||||
"friendly_name": "Test_rgbw",
|
||||
"supported_color_modes": [light.COLOR_MODE_RGBW],
|
||||
"supported_features": 0,
|
||||
"rgbw_color": (1, 2, 3, 4),
|
||||
}
|
||||
|
||||
|
||||
async def test_light_service_call_color_conversion(hass):
|
||||
"""Test color conversion in service calls."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_all", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {light.COLOR_MODE_HS}
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {light.COLOR_MODE_RGB}
|
||||
|
||||
entity2 = platform.ENTITIES[2]
|
||||
entity2.supported_color_modes = {light.COLOR_MODE_XY}
|
||||
|
||||
entity3 = platform.ENTITIES[3]
|
||||
entity3.supported_color_modes = {
|
||||
light.COLOR_MODE_HS,
|
||||
light.COLOR_MODE_RGB,
|
||||
light.COLOR_MODE_XY,
|
||||
}
|
||||
|
||||
entity4 = platform.ENTITIES[4]
|
||||
entity4.supported_features = light.SUPPORT_COLOR
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS]
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_RGB]
|
||||
|
||||
state = hass.states.get(entity2.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_XY]
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.COLOR_MODE_HS,
|
||||
light.COLOR_MODE_RGB,
|
||||
light.COLOR_MODE_XY,
|
||||
]
|
||||
|
||||
state = hass.states.get(entity4.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_HS]
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": [
|
||||
entity0.entity_id,
|
||||
entity1.entity_id,
|
||||
entity2.entity_id,
|
||||
entity3.entity_id,
|
||||
entity4.entity_id,
|
||||
],
|
||||
"brightness_pct": 100,
|
||||
"hs_color": (240, 100),
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
_, data = entity0.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "hs_color": (240.0, 100.0)}
|
||||
_, data = entity1.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "rgb_color": (0, 0, 255)}
|
||||
_, data = entity2.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "xy_color": (0.136, 0.04)}
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "hs_color": (240.0, 100.0)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "hs_color": (240.0, 100.0)}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": [
|
||||
entity0.entity_id,
|
||||
entity1.entity_id,
|
||||
entity2.entity_id,
|
||||
entity3.entity_id,
|
||||
entity4.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgb_color": (128, 0, 0),
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
_, data = entity0.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "hs_color": (0.0, 100.0)}
|
||||
_, data = entity1.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (128, 0, 0)}
|
||||
_, data = entity2.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "xy_color": (0.701, 0.299)}
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (128, 0, 0)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "hs_color": (0.0, 100.0)}
|
||||
|
||||
await hass.services.async_call(
|
||||
"light",
|
||||
"turn_on",
|
||||
{
|
||||
"entity_id": [
|
||||
entity0.entity_id,
|
||||
entity1.entity_id,
|
||||
entity2.entity_id,
|
||||
entity3.entity_id,
|
||||
entity4.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"xy_color": (0.1, 0.8),
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
_, data = entity0.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "hs_color": (125.176, 100.0)}
|
||||
_, data = entity1.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (0, 255, 22)}
|
||||
_, data = entity2.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "xy_color": (0.1, 0.8)}
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "xy_color": (0.1, 0.8)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "hs_color": (125.176, 100.0)}
|
||||
|
||||
|
||||
async def test_light_state_color_conversion(hass):
|
||||
"""Test color conversion in state updates."""
|
||||
platform = getattr(hass.components, "test.light")
|
||||
platform.init(empty=True)
|
||||
|
||||
platform.ENTITIES.append(platform.MockLight("Test_hs", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_rgb", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_xy", STATE_ON))
|
||||
platform.ENTITIES.append(platform.MockLight("Test_legacy", STATE_ON))
|
||||
|
||||
entity0 = platform.ENTITIES[0]
|
||||
entity0.supported_color_modes = {light.COLOR_MODE_HS}
|
||||
entity0.color_mode = light.COLOR_MODE_HS
|
||||
entity0.hs_color = (240, 100)
|
||||
entity0.rgb_color = "Invalid" # Should be ignored
|
||||
entity0.xy_color = "Invalid" # Should be ignored
|
||||
|
||||
entity1 = platform.ENTITIES[1]
|
||||
entity1.supported_color_modes = {light.COLOR_MODE_RGB}
|
||||
entity1.color_mode = light.COLOR_MODE_RGB
|
||||
entity1.hs_color = "Invalid" # Should be ignored
|
||||
entity1.rgb_color = (128, 0, 0)
|
||||
entity1.xy_color = "Invalid" # Should be ignored
|
||||
|
||||
entity2 = platform.ENTITIES[2]
|
||||
entity2.supported_color_modes = {light.COLOR_MODE_XY}
|
||||
entity2.color_mode = light.COLOR_MODE_XY
|
||||
entity2.hs_color = "Invalid" # Should be ignored
|
||||
entity2.rgb_color = "Invalid" # Should be ignored
|
||||
entity2.xy_color = (0.1, 0.8)
|
||||
|
||||
entity3 = platform.ENTITIES[3]
|
||||
entity3.hs_color = (240, 100)
|
||||
entity3.supported_features = light.SUPPORT_COLOR
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_HS
|
||||
assert state.attributes["hs_color"] == (240, 100)
|
||||
assert state.attributes["rgb_color"] == (0, 0, 255)
|
||||
assert state.attributes["xy_color"] == (0.136, 0.04)
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_RGB
|
||||
assert state.attributes["hs_color"] == (0.0, 100.0)
|
||||
assert state.attributes["rgb_color"] == (128, 0, 0)
|
||||
assert state.attributes["xy_color"] == (0.701, 0.299)
|
||||
|
||||
state = hass.states.get(entity2.entity_id)
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_XY
|
||||
assert state.attributes["hs_color"] == (125.176, 100.0)
|
||||
assert state.attributes["rgb_color"] == (0, 255, 22)
|
||||
assert state.attributes["xy_color"] == (0.1, 0.8)
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state.attributes["color_mode"] == light.COLOR_MODE_HS
|
||||
assert state.attributes["hs_color"] == (240, 100)
|
||||
assert state.attributes["rgb_color"] == (0, 0, 255)
|
||||
assert state.attributes["xy_color"] == (0.136, 0.04)
|
||||
|
@ -1,4 +1,7 @@
|
||||
"""Test reproduce state for Light."""
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import light
|
||||
from homeassistant.components.light.reproduce_state import DEPRECATION_WARNING
|
||||
from homeassistant.core import State
|
||||
|
||||
@ -15,6 +18,8 @@ VALID_HS_COLOR = {"hs_color": (345, 75)}
|
||||
VALID_KELVIN = {"kelvin": 4000}
|
||||
VALID_PROFILE = {"profile": "relax"}
|
||||
VALID_RGB_COLOR = {"rgb_color": (255, 63, 111)}
|
||||
VALID_RGBW_COLOR = {"rgbw_color": (255, 63, 111, 10)}
|
||||
VALID_RGBWW_COLOR = {"rgbww_color": (255, 63, 111, 10, 20)}
|
||||
VALID_XY_COLOR = {"xy_color": (0.59, 0.274)}
|
||||
|
||||
|
||||
@ -91,51 +96,51 @@ async def test_reproducing_states(hass, caplog):
|
||||
|
||||
expected_calls = []
|
||||
|
||||
expected_off = VALID_BRIGHTNESS
|
||||
expected_off = dict(VALID_BRIGHTNESS)
|
||||
expected_off["entity_id"] = "light.entity_off"
|
||||
expected_calls.append(expected_off)
|
||||
|
||||
expected_bright = VALID_WHITE_VALUE
|
||||
expected_bright = dict(VALID_WHITE_VALUE)
|
||||
expected_bright["entity_id"] = "light.entity_bright"
|
||||
expected_calls.append(expected_bright)
|
||||
|
||||
expected_white = VALID_FLASH
|
||||
expected_white = dict(VALID_FLASH)
|
||||
expected_white["entity_id"] = "light.entity_white"
|
||||
expected_calls.append(expected_white)
|
||||
|
||||
expected_flash = VALID_EFFECT
|
||||
expected_flash = dict(VALID_EFFECT)
|
||||
expected_flash["entity_id"] = "light.entity_flash"
|
||||
expected_calls.append(expected_flash)
|
||||
|
||||
expected_effect = VALID_TRANSITION
|
||||
expected_effect = dict(VALID_TRANSITION)
|
||||
expected_effect["entity_id"] = "light.entity_effect"
|
||||
expected_calls.append(expected_effect)
|
||||
|
||||
expected_trans = VALID_COLOR_NAME
|
||||
expected_trans = dict(VALID_COLOR_NAME)
|
||||
expected_trans["entity_id"] = "light.entity_trans"
|
||||
expected_calls.append(expected_trans)
|
||||
|
||||
expected_name = VALID_COLOR_TEMP
|
||||
expected_name = dict(VALID_COLOR_TEMP)
|
||||
expected_name["entity_id"] = "light.entity_name"
|
||||
expected_calls.append(expected_name)
|
||||
|
||||
expected_temp = VALID_HS_COLOR
|
||||
expected_temp = dict(VALID_HS_COLOR)
|
||||
expected_temp["entity_id"] = "light.entity_temp"
|
||||
expected_calls.append(expected_temp)
|
||||
|
||||
expected_hs = VALID_KELVIN
|
||||
expected_hs = dict(VALID_KELVIN)
|
||||
expected_hs["entity_id"] = "light.entity_hs"
|
||||
expected_calls.append(expected_hs)
|
||||
|
||||
expected_kelvin = VALID_PROFILE
|
||||
expected_kelvin = dict(VALID_PROFILE)
|
||||
expected_kelvin["entity_id"] = "light.entity_kelvin"
|
||||
expected_calls.append(expected_kelvin)
|
||||
|
||||
expected_profile = VALID_RGB_COLOR
|
||||
expected_profile = dict(VALID_RGB_COLOR)
|
||||
expected_profile["entity_id"] = "light.entity_profile"
|
||||
expected_calls.append(expected_profile)
|
||||
|
||||
expected_rgb = VALID_XY_COLOR
|
||||
expected_rgb = dict(VALID_XY_COLOR)
|
||||
expected_rgb["entity_id"] = "light.entity_rgb"
|
||||
expected_calls.append(expected_rgb)
|
||||
|
||||
@ -156,6 +161,59 @@ async def test_reproducing_states(hass, caplog):
|
||||
assert turn_off_calls[0].data == {"entity_id": "light.entity_xy"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"color_mode",
|
||||
(
|
||||
light.COLOR_MODE_COLOR_TEMP,
|
||||
light.COLOR_MODE_BRIGHTNESS,
|
||||
light.COLOR_MODE_HS,
|
||||
light.COLOR_MODE_ONOFF,
|
||||
light.COLOR_MODE_RGB,
|
||||
light.COLOR_MODE_RGBW,
|
||||
light.COLOR_MODE_RGBWW,
|
||||
light.COLOR_MODE_UNKNOWN,
|
||||
light.COLOR_MODE_XY,
|
||||
),
|
||||
)
|
||||
async def test_filter_color_modes(hass, caplog, color_mode):
|
||||
"""Test filtering of parameters according to color mode."""
|
||||
hass.states.async_set("light.entity", "off", {})
|
||||
all_colors = {
|
||||
**VALID_WHITE_VALUE,
|
||||
**VALID_COLOR_NAME,
|
||||
**VALID_COLOR_TEMP,
|
||||
**VALID_HS_COLOR,
|
||||
**VALID_KELVIN,
|
||||
**VALID_RGB_COLOR,
|
||||
**VALID_RGBW_COLOR,
|
||||
**VALID_RGBWW_COLOR,
|
||||
**VALID_XY_COLOR,
|
||||
}
|
||||
|
||||
turn_on_calls = async_mock_service(hass, "light", "turn_on")
|
||||
|
||||
await hass.helpers.state.async_reproduce_state(
|
||||
[State("light.entity", "on", {**all_colors, "color_mode": color_mode})]
|
||||
)
|
||||
|
||||
expected_map = {
|
||||
light.COLOR_MODE_COLOR_TEMP: VALID_COLOR_TEMP,
|
||||
light.COLOR_MODE_BRIGHTNESS: {},
|
||||
light.COLOR_MODE_HS: VALID_HS_COLOR,
|
||||
light.COLOR_MODE_ONOFF: {},
|
||||
light.COLOR_MODE_RGB: VALID_RGB_COLOR,
|
||||
light.COLOR_MODE_RGBW: VALID_RGBW_COLOR,
|
||||
light.COLOR_MODE_RGBWW: VALID_RGBWW_COLOR,
|
||||
light.COLOR_MODE_UNKNOWN: {**VALID_HS_COLOR, **VALID_WHITE_VALUE},
|
||||
light.COLOR_MODE_XY: VALID_XY_COLOR,
|
||||
}
|
||||
expected = expected_map[color_mode]
|
||||
|
||||
assert len(turn_on_calls) == 1
|
||||
assert turn_on_calls[0].domain == "light"
|
||||
assert dict(turn_on_calls[0].data) == {"entity_id": "light.entity", **expected}
|
||||
|
||||
|
||||
async def test_deprecation_warning(hass, caplog):
|
||||
"""Test deprecation warning."""
|
||||
hass.states.async_set("light.entity_off", "off", {})
|
||||
|
@ -430,6 +430,8 @@ async def test_device_types(hass: HomeAssistant):
|
||||
"effect_list": YEELIGHT_MONO_EFFECT_LIST,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
"brightness": bright,
|
||||
"color_mode": "brightness",
|
||||
"supported_color_modes": ["brightness"],
|
||||
},
|
||||
)
|
||||
|
||||
@ -441,6 +443,8 @@ async def test_device_types(hass: HomeAssistant):
|
||||
"effect_list": YEELIGHT_MONO_EFFECT_LIST,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
"brightness": bright,
|
||||
"color_mode": "brightness",
|
||||
"supported_color_modes": ["brightness"],
|
||||
},
|
||||
)
|
||||
|
||||
@ -463,8 +467,14 @@ async def test_device_types(hass: HomeAssistant):
|
||||
"hs_color": hs_color,
|
||||
"rgb_color": rgb_color,
|
||||
"xy_color": xy_color,
|
||||
"color_mode": "hs",
|
||||
"supported_color_modes": ["color_temp", "hs"],
|
||||
},
|
||||
{
|
||||
"supported_features": 0,
|
||||
"color_mode": "onoff",
|
||||
"supported_color_modes": ["onoff"],
|
||||
},
|
||||
{"supported_features": 0},
|
||||
)
|
||||
|
||||
# WhiteTemp
|
||||
@ -483,11 +493,15 @@ async def test_device_types(hass: HomeAssistant):
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"color_temp": ct,
|
||||
"color_mode": "color_temp",
|
||||
"supported_color_modes": ["color_temp"],
|
||||
},
|
||||
{
|
||||
"effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
"brightness": nl_br,
|
||||
"color_mode": "brightness",
|
||||
"supported_color_modes": ["brightness"],
|
||||
},
|
||||
)
|
||||
|
||||
@ -512,11 +526,15 @@ async def test_device_types(hass: HomeAssistant):
|
||||
),
|
||||
"brightness": current_brightness,
|
||||
"color_temp": ct,
|
||||
"color_mode": "color_temp",
|
||||
"supported_color_modes": ["color_temp"],
|
||||
},
|
||||
{
|
||||
"effect_list": YEELIGHT_TEMP_ONLY_EFFECT_LIST,
|
||||
"supported_features": SUPPORT_YEELIGHT,
|
||||
"brightness": nl_br,
|
||||
"color_mode": "brightness",
|
||||
"supported_color_modes": ["brightness"],
|
||||
},
|
||||
)
|
||||
await _async_test(
|
||||
@ -532,6 +550,8 @@ async def test_device_types(hass: HomeAssistant):
|
||||
"hs_color": bg_hs_color,
|
||||
"rgb_color": bg_rgb_color,
|
||||
"xy_color": bg_xy_color,
|
||||
"color_mode": "hs",
|
||||
"supported_color_modes": ["color_temp", "hs"],
|
||||
},
|
||||
name=f"{UNIQUE_NAME} ambilight",
|
||||
entity_id=f"{ENTITY_LIGHT}_ambilight",
|
||||
|
@ -7,9 +7,12 @@ import pyzerproc
|
||||
from homeassistant import setup
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
ATTR_XY_COLOR,
|
||||
COLOR_MODE_HS,
|
||||
SCAN_INTERVAL,
|
||||
SUPPORT_BRIGHTNESS,
|
||||
SUPPORT_COLOR,
|
||||
@ -96,6 +99,7 @@ async def test_init(hass, mock_entry):
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR,
|
||||
ATTR_ICON: "mdi:string-lights",
|
||||
}
|
||||
@ -104,8 +108,10 @@ async def test_init(hass, mock_entry):
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "LEDBlue-33445566",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR,
|
||||
ATTR_ICON: "mdi:string-lights",
|
||||
ATTR_COLOR_MODE: COLOR_MODE_HS,
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
ATTR_HS_COLOR: (221.176, 100.0),
|
||||
ATTR_RGB_COLOR: (0, 80, 255),
|
||||
@ -272,6 +278,7 @@ async def test_light_update(hass, mock_light):
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR,
|
||||
ATTR_ICON: "mdi:string-lights",
|
||||
}
|
||||
@ -290,6 +297,7 @@ async def test_light_update(hass, mock_light):
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR,
|
||||
ATTR_ICON: "mdi:string-lights",
|
||||
}
|
||||
@ -307,6 +315,7 @@ async def test_light_update(hass, mock_light):
|
||||
assert state.state == STATE_OFF
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR,
|
||||
ATTR_ICON: "mdi:string-lights",
|
||||
}
|
||||
@ -324,8 +333,10 @@ async def test_light_update(hass, mock_light):
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes == {
|
||||
ATTR_FRIENDLY_NAME: "LEDBlue-CCDDEEFF",
|
||||
ATTR_SUPPORTED_COLOR_MODES: [COLOR_MODE_HS],
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR,
|
||||
ATTR_ICON: "mdi:string-lights",
|
||||
ATTR_COLOR_MODE: COLOR_MODE_HS,
|
||||
ATTR_BRIGHTNESS: 220,
|
||||
ATTR_HS_COLOR: (261.429, 31.818),
|
||||
ATTR_RGB_COLOR: (202, 173, 255),
|
||||
|
@ -37,4 +37,17 @@ class MockLight(MockToggleEntity, LightEntity):
|
||||
"""Mock light class."""
|
||||
|
||||
brightness = None
|
||||
supported_color_modes = None
|
||||
supported_features = 0
|
||||
|
||||
color_mode = None
|
||||
|
||||
hs_color = None
|
||||
xy_color = None
|
||||
rgb_color = None
|
||||
rgbw_color = None
|
||||
rgbww_color = None
|
||||
|
||||
color_temp = None
|
||||
|
||||
white_value = None
|
||||
|
Loading…
x
Reference in New Issue
Block a user