core/tests/components/deconz/test_light.py

1305 lines
40 KiB
Python

"""deCONZ light platform tests."""
from collections.abc import Callable
from typing import Any
from unittest.mock import patch
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_COLOR_TEMP,
ATTR_EFFECT,
ATTR_FLASH,
ATTR_HS_COLOR,
ATTR_SUPPORTED_COLOR_MODES,
ATTR_TRANSITION,
ATTR_XY_COLOR,
DOMAIN as LIGHT_DOMAIN,
EFFECT_COLORLOOP,
FLASH_LONG,
FLASH_SHORT,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
ColorMode,
LightEntityFeature,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_SUPPORTED_FEATURES,
STATE_OFF,
STATE_ON,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .conftest import ConfigEntryFactoryType, WebsocketDataType
from tests.common import MockConfigEntry, snapshot_platform
from tests.test_util.aiohttp import AiohttpClientMocker
@pytest.mark.parametrize(
"light_payload",
[
( # RGB light in color temp color mode
{
"colorcapabilities": 31,
"ctmax": 500,
"ctmin": 153,
"etag": "055485a82553e654f156d41c9301b7cf",
"hascolor": True,
"lastannounced": None,
"lastseen": "2021-06-10T20:25Z",
"manufacturername": "Philips",
"modelid": "LLC020",
"name": "Hue Go",
"state": {
"alert": "none",
"bri": 254,
"colormode": "ct",
"ct": 375,
"effect": "none",
"hue": 8348,
"on": True,
"reachable": True,
"sat": 147,
"xy": [0.462, 0.4111],
},
"swversion": "5.127.1.26420",
"type": "Extended color light",
"uniqueid": "00:17:88:01:01:23:45:67-00",
}
),
( # RGB light in XY color mode
{
"colorcapabilities": 0,
"ctmax": 65535,
"ctmin": 0,
"etag": "74c91da78bbb5f4dc4d36edf4ad6857c",
"hascolor": True,
"lastannounced": "2021-01-27T18:05:38Z",
"lastseen": "2021-06-10T20:26Z",
"manufacturername": "Philips",
"modelid": "4090331P9_01",
"name": "Hue Ensis",
"state": {
"alert": "none",
"bri": 254,
"colormode": "xy",
"ct": 316,
"effect": "0",
"hue": 3096,
"on": True,
"reachable": True,
"sat": 48,
"xy": [0.427, 0.373],
},
"swversion": "1.65.9_hB3217DF4",
"type": "Extended color light",
"uniqueid": "00:17:88:01:01:23:45:67-01",
}
),
( # RGB light with only HS color mode
{
"etag": "87a89542bf9b9d0aa8134919056844f8",
"hascolor": True,
"lastannounced": None,
"lastseen": "2020-12-05T22:57Z",
"manufacturername": "_TZE200_s8gkrkxk",
"modelid": "TS0601",
"name": "LIDL xmas light",
"state": {
"bri": 25,
"colormode": "hs",
"effect": "none",
"hue": 53691,
"on": True,
"reachable": True,
"sat": 141,
},
"swversion": None,
"type": "Color dimmable light",
"uniqueid": "58:8e:81:ff:fe:db:7b:be-01",
}
),
( # Tunable white light in CT color mode
{
"colorcapabilities": 16,
"ctmax": 454,
"ctmin": 153,
"etag": "576ffecbedb4abdc3d3f375fd8f17a9e",
"hascolor": True,
"lastannounced": None,
"lastseen": "2021-06-10T20:25Z",
"manufacturername": "Philips",
"modelid": "LTW013",
"name": "Hue White Ambiance",
"state": {
"alert": "none",
"bri": 254,
"colormode": "ct",
"ct": 396,
"on": True,
"reachable": True,
},
"swversion": "1.46.13_r26312",
"type": "Color temperature light",
"uniqueid": "00:17:88:01:01:23:45:67-02",
}
),
( # Dimmable light
{
"etag": "f88e87235e2abce62404edd99b1af323",
"hascolor": False,
"lastannounced": None,
"lastseen": "2021-06-10T20:26Z",
"manufacturername": "Philips",
"modelid": "LWO001",
"name": "Hue Filament",
"state": {"alert": "none", "bri": 254, "on": True, "reachable": True},
"swversion": "1.55.8_r28815",
"type": "Dimmable light",
"uniqueid": "00:17:88:01:01:23:45:67-03",
}
),
( # On/Off light
{
"etag": "99c67fd8f0529c6c2aab94b45e4f6caa",
"hascolor": False,
"lastannounced": "2021-04-26T20:28:11Z",
"lastseen": "2021-06-10T21:15Z",
"manufacturername": "Unknown",
"modelid": "Unknown",
"name": "Simple Light",
"state": {"alert": "none", "on": True, "reachable": True},
"swversion": "2.0",
"type": "Simple light",
"uniqueid": "00:15:8d:00:01:23:45:67-01",
}
),
( # Gradient light
{
"capabilities": {
"alerts": [
"none",
"select",
"lselect",
"blink",
"breathe",
"okay",
"channelchange",
"finish",
"stop",
],
"bri": {"min_dim_level": 0.01},
"color": {
"ct": {"computes_xy": True, "max": 500, "min": 153},
"effects": [
"none",
"colorloop",
"candle",
"fireplace",
"prism",
"sunrise",
],
"gamut_type": "C",
"gradient": {
"max_segments": 9,
"pixel_count": 16,
"pixel_length": 1250,
"styles": ["linear", "mirrored"],
},
"modes": ["ct", "effect", "gradient", "hs", "xy"],
"xy": {
"blue": [0.1532, 0.0475],
"green": [0.17, 0.7],
"red": [0.6915, 0.3083],
},
},
},
"colorcapabilities": 31,
"config": {
"bri": {
"couple_ct": False,
"execute_if_off": True,
"startup": "previous",
},
"color": {
"ct": {"startup": "previous"},
"execute_if_off": True,
"gradient": {"reversed": False},
"xy": {"startup": "previous"},
},
"groups": ["36", "39", "45", "46", "47", "51", "57", "59"],
"on": {"startup": "previous"},
},
"ctmax": 500,
"ctmin": 153,
"etag": "077fb97dd6145f10a3c190f0a1ade499",
"hascolor": True,
"lastannounced": None,
"lastseen": "2024-02-29T18:36Z",
"manufacturername": "Signify Netherlands B.V.",
"modelid": "LCX004",
"name": "Gradient light",
"productid": "Philips-LCX004-1-GALSECLv1",
"productname": "Hue gradient lightstrip",
"state": {
"alert": "none",
"bri": 184,
"colormode": "gradient",
"ct": 396,
"effect": "none",
"gradient": {
"color_adjustment": 0,
"offset": 0,
"offset_adjustment": 0,
"points": [
[0.2728, 0.6226],
[0.163, 0.4262],
[0.1563, 0.1699],
[0.1551, 0.1147],
[0.1534, 0.0579],
],
"segments": 5,
"style": "linear",
},
"hue": 20566,
"on": True,
"reachable": True,
"sat": 254,
"xy": [0.2727, 0.6226],
},
"swconfigid": "F03CAF4D",
"swversion": "1.104.2",
"type": "Extended color light",
"uniqueid": "00:17:88:01:0b:0c:0d:0e-0f",
}
),
],
)
async def test_lights(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
config_entry_factory: ConfigEntryFactoryType,
snapshot: SnapshotAssertion,
) -> None:
"""Test that different light entities are created with expected values."""
with patch("homeassistant.components.deconz.PLATFORMS", [Platform.LIGHT]):
config_entry = await config_entry_factory()
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@pytest.mark.parametrize(
"light_payload",
[
{
"colorcapabilities": 31,
"ctmax": 500,
"ctmin": 153,
"etag": "055485a82553e654f156d41c9301b7cf",
"hascolor": True,
"lastannounced": None,
"lastseen": "2021-06-10T20:25Z",
"manufacturername": "Philips",
"modelid": "LLC020",
"name": "Hue Go",
"state": {
"alert": "none",
"bri": 254,
"colormode": "ct",
"ct": 375,
"effect": "none",
"hue": 8348,
"on": True,
"reachable": True,
"sat": 147,
"xy": [0.462, 0.4111],
},
"swversion": "5.127.1.26420",
"type": "Extended color light",
"uniqueid": "00:17:88:01:01:23:45:67-00",
}
],
)
@pytest.mark.usefixtures("config_entry_setup")
async def test_light_state_change(
hass: HomeAssistant,
light_ws_data: WebsocketDataType,
) -> None:
"""Verify light can change state on websocket event."""
assert hass.states.get("light.hue_go").state == STATE_ON
await light_ws_data({"state": {"on": False}})
assert hass.states.get("light.hue_go").state == STATE_OFF
@pytest.mark.parametrize(
("input", "expected"),
[
( # Turn on light with hue and sat
{
"light_on": True,
"service": SERVICE_TURN_ON,
"call": {
ATTR_ENTITY_ID: "light.hue_go",
ATTR_HS_COLOR: (20, 30),
},
},
{
"on": True,
"xy": (0.411, 0.351),
},
),
( # Turn on light with XY color
{
"light_on": True,
"service": SERVICE_TURN_ON,
"call": {
ATTR_ENTITY_ID: "light.hue_go",
ATTR_XY_COLOR: (0.411, 0.351),
},
},
{
"on": True,
"xy": (0.411, 0.351),
},
),
( # Turn on light without transition time
{
"light_on": True,
"service": SERVICE_TURN_ON,
"call": {
ATTR_ENTITY_ID: "light.hue_go",
ATTR_TRANSITION: 0,
},
},
{
"on": True,
"transitiontime": 0,
},
),
( # Turn on light with short color loop
{
"light_on": False,
"service": SERVICE_TURN_ON,
"call": {
ATTR_ENTITY_ID: "light.hue_go",
ATTR_BRIGHTNESS: 200,
ATTR_COLOR_TEMP: 200,
ATTR_TRANSITION: 5,
ATTR_FLASH: FLASH_SHORT,
ATTR_EFFECT: EFFECT_COLORLOOP,
},
},
{
"bri": 200,
"ct": 200,
"transitiontime": 50,
"alert": "select",
"effect": "colorloop",
},
),
( # Turn on light disabling color loop with long flashing
{
"light_on": False,
"service": SERVICE_TURN_ON,
"call": {
ATTR_ENTITY_ID: "light.hue_go",
ATTR_XY_COLOR: (0.411, 0.351),
ATTR_FLASH: FLASH_LONG,
ATTR_EFFECT: "none",
},
},
{
"xy": (0.411, 0.351),
"alert": "lselect",
"effect": "none",
},
),
( # Turn off light with short flashing
{
"light_on": True,
"service": SERVICE_TURN_OFF,
"call": {
ATTR_ENTITY_ID: "light.hue_go",
ATTR_TRANSITION: 5,
ATTR_FLASH: FLASH_SHORT,
},
},
{
"bri": 0,
"transitiontime": 50,
"alert": "select",
},
),
( # Turn off light without transition time
{
"light_on": True,
"service": SERVICE_TURN_OFF,
"call": {
ATTR_ENTITY_ID: "light.hue_go",
ATTR_TRANSITION: 0,
ATTR_FLASH: FLASH_SHORT,
},
},
{
"bri": 0,
"transitiontime": 0,
"alert": "select",
},
),
( # Turn off light with long flashing
{
"light_on": True,
"service": SERVICE_TURN_OFF,
"call": {ATTR_ENTITY_ID: "light.hue_go", ATTR_FLASH: FLASH_LONG},
},
{"alert": "lselect"},
),
( # Turn off light when light is already off is not supported
{
"light_on": False,
"service": SERVICE_TURN_OFF,
"call": {
ATTR_ENTITY_ID: "light.hue_go",
ATTR_TRANSITION: 5,
ATTR_FLASH: FLASH_SHORT,
},
},
{},
),
],
)
async def test_light_service_calls(
hass: HomeAssistant,
aioclient_mock: AiohttpClientMocker,
config_entry_factory: ConfigEntryFactoryType,
light_payload: dict[str, Any],
mock_put_request: Callable[[str, str], AiohttpClientMocker],
input: dict[str, Any],
expected: dict[str, Any],
) -> None:
"""Verify light can change state on websocket event."""
light_payload[0] = {
"colorcapabilities": 31,
"ctmax": 500,
"ctmin": 153,
"etag": "055485a82553e654f156d41c9301b7cf",
"hascolor": True,
"lastannounced": None,
"lastseen": "2021-06-10T20:25Z",
"manufacturername": "Philips",
"modelid": "LLC020",
"name": "Hue Go",
"state": {
"alert": "none",
"bri": 254,
"colormode": "ct",
"ct": 375,
"effect": "none",
"hue": 8348,
"on": input["light_on"],
"reachable": True,
"sat": 147,
"xy": [0.462, 0.4111],
},
"swversion": "5.127.1.26420",
"type": "Extended color light",
"uniqueid": "00:17:88:01:01:23:45:67-00",
}
await config_entry_factory()
aioclient_mock = mock_put_request("/lights/0/state")
await hass.services.async_call(
LIGHT_DOMAIN,
input["service"],
input["call"],
blocking=True,
)
if expected:
assert aioclient_mock.mock_calls[1][2] == expected
else:
assert len(aioclient_mock.mock_calls) == 1 # not called
@pytest.mark.parametrize(
"light_payload",
[
{
"colorcapabilities": 0,
"ctmax": 65535,
"ctmin": 0,
"etag": "9dd510cd474791481f189d2a68a3c7f1",
"hascolor": True,
"lastannounced": "2020-12-17T17:44:38Z",
"lastseen": "2021-01-11T18:36Z",
"manufacturername": "IKEA of Sweden",
"modelid": "TRADFRI bulb E27 WS opal 1000lm",
"name": "IKEA light",
"state": {
"alert": "none",
"bri": 156,
"colormode": "ct",
"ct": 250,
"on": True,
"reachable": True,
},
"swversion": "2.0.022",
"type": "Color temperature light",
"uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01",
}
],
)
@pytest.mark.usefixtures("config_entry_setup")
async def test_ikea_default_transition_time(
hass: HomeAssistant,
mock_put_request: Callable[[str, str], AiohttpClientMocker],
) -> None:
"""Verify that service calls to IKEA lights always extend with transition tinme 0 if absent."""
aioclient_mock = mock_put_request("/lights/0/state")
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "light.ikea_light",
ATTR_BRIGHTNESS: 100,
},
blocking=True,
)
assert aioclient_mock.mock_calls[1][2] == {
"bri": 100,
"on": True,
"transitiontime": 0,
}
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "light.ikea_light",
ATTR_BRIGHTNESS: 100,
ATTR_TRANSITION: 5,
},
blocking=True,
)
assert aioclient_mock.mock_calls[2][2] == {
"bri": 100,
"on": True,
"transitiontime": 50,
}
@pytest.mark.parametrize(
"light_payload",
[
{
"etag": "87a89542bf9b9d0aa8134919056844f8",
"hascolor": True,
"lastannounced": None,
"lastseen": "2020-12-05T22:57Z",
"manufacturername": "_TZE200_s8gkrkxk",
"modelid": "TS0601",
"name": "LIDL xmas light",
"state": {
"bri": 25,
"colormode": "hs",
"effect": "none",
"hue": 53691,
"on": True,
"reachable": True,
"sat": 141,
},
"swversion": None,
"type": "Color dimmable light",
"uniqueid": "58:8e:81:ff:fe:db:7b:be-01",
}
],
)
@pytest.mark.usefixtures("config_entry_setup")
async def test_lidl_christmas_light(
hass: HomeAssistant,
mock_put_request: Callable[[str, str], AiohttpClientMocker],
) -> None:
"""Test that lights or groups entities are created."""
aioclient_mock = mock_put_request("/lights/0/state")
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "light.lidl_xmas_light",
ATTR_HS_COLOR: (20, 30),
},
blocking=True,
)
assert aioclient_mock.mock_calls[1][2] == {"on": True, "hue": 3640, "sat": 76}
assert hass.states.get("light.lidl_xmas_light")
@pytest.mark.parametrize(
"light_payload",
[
{
"etag": "26839cb118f5bf7ba1f2108256644010",
"hascolor": False,
"lastannounced": None,
"lastseen": "2020-11-22T11:27Z",
"manufacturername": "dresden elektronik",
"modelid": "ConBee II",
"name": "Configuration tool 1",
"state": {"reachable": True},
"swversion": "0x264a0700",
"type": "Configuration tool",
"uniqueid": "00:21:2e:ff:ff:05:a7:a3-01",
}
],
)
@pytest.mark.usefixtures("config_entry_setup")
async def test_configuration_tool(hass: HomeAssistant) -> None:
"""Verify that configuration tool is not created."""
assert len(hass.states.async_all()) == 0
@pytest.mark.parametrize(
"light_payload",
[
{
"1": {
"name": "RGB light",
"state": {
"on": True,
"bri": 50,
"colormode": "xy",
"effect": "colorloop",
"xy": (0.5, 0.5),
"reachable": True,
},
"type": "Extended color light",
"uniqueid": "00:00:00:00:00:00:00:00-00",
},
"2": {
"ctmax": 454,
"ctmin": 155,
"name": "Tunable white light",
"state": {
"on": True,
"colormode": "ct",
"ct": 2500,
"reachable": True,
},
"type": "Tunable white light",
"uniqueid": "00:00:00:00:00:00:00:01-00",
},
"3": {
"name": "Dimmable light",
"type": "Dimmable light",
"state": {"bri": 255, "on": True, "reachable": True},
"uniqueid": "00:00:00:00:00:00:00:02-00",
},
}
],
)
@pytest.mark.parametrize(
"input",
[
({"lights": ["1", "2", "3"]}),
({"lights": ["3", "1", "2"]}),
({"lights": ["2", "3", "1"]}),
],
)
async def test_groups(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
config_entry_factory: ConfigEntryFactoryType,
group_payload: dict[str, Any],
input: dict[str, list[str]],
snapshot: SnapshotAssertion,
) -> None:
"""Test that different group entities are created with expected values."""
group_payload |= {
"0": {
"id": "Light group id",
"name": "Group",
"type": "LightGroup",
"state": {"all_on": False, "any_on": True},
"action": {
"alert": "none",
"bri": 127,
"colormode": "hs",
"ct": 0,
"effect": "none",
"hue": 0,
"on": True,
"sat": 127,
"scene": None,
"xy": [0, 0],
},
"scenes": [],
"lights": input["lights"],
},
}
with patch("homeassistant.components.deconz.PLATFORMS", [Platform.LIGHT]):
config_entry = await config_entry_factory()
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@pytest.mark.parametrize(
"light_payload",
[
{
"1": {
"name": "RGB light",
"state": {
"bri": 255,
"colormode": "xy",
"effect": "colorloop",
"hue": 53691,
"on": True,
"reachable": True,
"sat": 141,
"xy": (0.5, 0.5),
},
"type": "Extended color light",
"uniqueid": "00:00:00:00:00:00:00:00-00",
},
"2": {
"ctmax": 454,
"ctmin": 155,
"name": "Tunable white light",
"state": {
"on": True,
"colormode": "ct",
"ct": 2500,
"reachable": True,
},
"type": "Tunable white light",
"uniqueid": "00:00:00:00:00:00:00:01-00",
},
"3": {
"name": "Dimmable light",
"type": "Dimmable light",
"state": {"bri": 254, "on": True, "reachable": True},
"uniqueid": "00:00:00:00:00:00:00:02-00",
},
}
],
)
@pytest.mark.parametrize(
("input", "expected"),
[
( # Turn on group with short color loop
{
"lights": ["1", "2", "3"],
"group_on": False,
"service": SERVICE_TURN_ON,
"call": {
ATTR_ENTITY_ID: "light.group",
ATTR_BRIGHTNESS: 200,
ATTR_COLOR_TEMP: 200,
ATTR_TRANSITION: 5,
ATTR_FLASH: FLASH_SHORT,
ATTR_EFFECT: EFFECT_COLORLOOP,
},
},
{
"bri": 200,
"ct": 200,
"transitiontime": 50,
"alert": "select",
"effect": "colorloop",
},
),
( # Turn on group with hs colors
{
"lights": ["1", "2", "3"],
"group_on": False,
"service": SERVICE_TURN_ON,
"call": {
ATTR_ENTITY_ID: "light.group",
ATTR_HS_COLOR: (250, 50),
},
},
{
"on": True,
"xy": (0.236, 0.166),
},
),
( # Turn on group with short color loop
{
"lights": ["3", "2", "1"],
"group_on": False,
"service": SERVICE_TURN_ON,
"call": {
ATTR_ENTITY_ID: "light.group",
ATTR_HS_COLOR: (250, 50),
},
},
{
"on": True,
"xy": (0.236, 0.166),
},
),
],
)
async def test_group_service_calls(
hass: HomeAssistant,
config_entry_factory: ConfigEntryFactoryType,
group_payload: dict[str, Any],
mock_put_request: Callable[[str, str], AiohttpClientMocker],
input: dict[str, Any],
expected: dict[str, Any],
) -> None:
"""Verify expected group web request from different service calls."""
group_payload |= {
"0": {
"id": "Light group id",
"name": "Group",
"type": "LightGroup",
"state": {"all_on": False, "any_on": input["group_on"]},
"action": {},
"scenes": [],
"lights": input["lights"],
},
}
await config_entry_factory()
aioclient_mock = mock_put_request("/groups/0/action")
await hass.services.async_call(
LIGHT_DOMAIN,
input["service"],
input["call"],
blocking=True,
)
if expected:
assert aioclient_mock.mock_calls[1][2] == expected
else:
assert len(aioclient_mock.mock_calls) == 1 # not called
@pytest.mark.parametrize(
"group_payload",
[
{
"0": {
"id": "Empty group id",
"name": "Empty group",
"type": "LightGroup",
"state": {},
"action": {},
"scenes": [],
"lights": [],
},
}
],
)
@pytest.mark.usefixtures("config_entry_setup")
async def test_empty_group(hass: HomeAssistant) -> None:
"""Verify that a group without a list of lights is not created."""
assert len(hass.states.async_all()) == 0
assert not hass.states.get("light.empty_group")
@pytest.mark.parametrize(
"group_payload",
[
{
"1": {
"id": "Light group id",
"name": "Light group",
"type": "LightGroup",
"state": {"all_on": False, "any_on": True},
"action": {},
"scenes": [],
"lights": ["0"],
},
"2": {
"id": "Empty group id",
"name": "Empty group",
"type": "LightGroup",
"state": {},
"action": {},
"scenes": [],
"lights": [],
},
}
],
)
@pytest.mark.parametrize(
"light_payload",
[
{
"ctmax": 454,
"ctmin": 155,
"name": "Tunable white light",
"state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True},
"type": "Tunable white light",
"uniqueid": "00:00:00:00:00:00:00:01-00",
}
],
)
@pytest.mark.parametrize("config_entry_options", [{CONF_ALLOW_DECONZ_GROUPS: False}])
async def test_disable_light_groups(
hass: HomeAssistant,
config_entry_setup: MockConfigEntry,
) -> None:
"""Test disallowing light groups work."""
assert len(hass.states.async_all()) == 1
assert hass.states.get("light.tunable_white_light")
assert not hass.states.get("light.light_group")
assert not hass.states.get("light.empty_group")
hass.config_entries.async_update_entry(
config_entry_setup, options={CONF_ALLOW_DECONZ_GROUPS: True}
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
assert hass.states.get("light.light_group")
hass.config_entries.async_update_entry(
config_entry_setup, options={CONF_ALLOW_DECONZ_GROUPS: False}
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
assert not hass.states.get("light.light_group")
@pytest.mark.parametrize(
"group_payload",
[
{
"0": {
"action": {
"alert": "none",
"bri": 127,
"colormode": "hs",
"ct": 0,
"effect": "none",
"hue": 0,
"on": True,
"sat": 127,
"scene": None,
"xy": [0, 0],
},
"devicemembership": [],
"etag": "81e42cf1b47affb72fa72bc2e25ba8bf",
"lights": ["0", "1"],
"name": "Group",
"scenes": [],
"state": {"all_on": False, "any_on": True},
"type": "LightGroup",
}
}
],
)
@pytest.mark.parametrize(
"light_payload",
[
{
"0": {
"ctmax": 500,
"ctmin": 153,
"etag": "026bcfe544ad76c7534e5ca8ed39047c",
"hascolor": True,
"manufacturername": "dresden elektronik",
"modelid": "FLS-PP3",
"name": "Light 1",
"pointsymbol": {},
"state": {
"alert": None,
"bri": 111,
"colormode": "ct",
"ct": 307,
"effect": None,
"hascolor": True,
"hue": 7998,
"on": False,
"reachable": True,
"sat": 172,
"xy": [0.421253, 0.39921],
},
"swversion": "020C.201000A0",
"type": "Extended color light",
"uniqueid": "00:21:2E:FF:FF:EE:DD:CC-0A",
},
"1": {
"colorcapabilities": 0,
"ctmax": 65535,
"ctmin": 0,
"etag": "9dd510cd474791481f189d2a68a3c7f1",
"hascolor": True,
"lastannounced": "2020-12-17T17:44:38Z",
"lastseen": "2021-01-11T18:36Z",
"manufacturername": "IKEA of Sweden",
"modelid": "TRADFRI bulb E27 WS opal 1000lm",
"name": "Küchenlicht",
"state": {
"alert": "none",
"bri": 156,
"colormode": "ct",
"ct": 250,
"on": True,
"reachable": True,
},
"swversion": "2.0.022",
"type": "Color temperature light",
"uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01",
},
}
],
)
@pytest.mark.usefixtures("config_entry_setup")
async def test_non_color_light_reports_color(
hass: HomeAssistant,
light_ws_data: WebsocketDataType,
) -> None:
"""Verify hs_color does not crash when a group gets updated with a bad color value.
After calling a scene color temp light of certain manufacturers
report color temp in color space.
"""
assert len(hass.states.async_all()) == 3
assert hass.states.get("light.group").attributes[ATTR_SUPPORTED_COLOR_MODES] == [
ColorMode.COLOR_TEMP,
ColorMode.HS,
ColorMode.XY,
]
assert (
hass.states.get("light.group").attributes[ATTR_COLOR_MODE]
== ColorMode.COLOR_TEMP
)
assert hass.states.get("light.group").attributes[ATTR_COLOR_TEMP] == 250
# Updating a scene will return a faulty color value
# for a non-color light causing an exception in hs_color
event_changed_light = {
"id": "1",
"state": {
"alert": None,
"bri": 216,
"colormode": "xy",
"ct": 410,
"on": True,
"reachable": True,
},
"uniqueid": "ec:1b:bd:ff:fe:ee:ed:dd-01",
}
await light_ws_data(event_changed_light)
group = hass.states.get("light.group")
assert group.attributes[ATTR_COLOR_MODE] == ColorMode.XY
assert group.attributes[ATTR_HS_COLOR] == (40.571, 41.176)
assert group.attributes.get(ATTR_COLOR_TEMP) is None
@pytest.mark.parametrize(
"group_payload",
[
{
"1": {
"id": "Group1",
"name": "Group",
"type": "LightGroup",
"state": {"all_on": False, "any_on": True},
"action": {},
"scenes": [],
"lights": ["1", "2", "3"],
},
}
],
)
@pytest.mark.parametrize(
"light_payload",
[
{
"1": {
"name": "Dimmable light",
"state": {"on": True, "bri": 255, "reachable": True},
"type": "Light",
"uniqueid": "00:00:00:00:00:00:00:01-00",
},
"2": {
"name": "Color light",
"state": {
"on": True,
"bri": 100,
"colormode": "xy",
"effect": "colorloop",
"xy": (500, 500),
"reachable": True,
},
"type": "Extended color light",
"uniqueid": "00:00:00:00:00:00:00:02-00",
},
"3": {
"ctmax": 454,
"ctmin": 155,
"name": "Tunable light",
"state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True},
"type": "Tunable white light",
"uniqueid": "00:00:00:00:00:00:00:03-00",
},
}
],
)
@pytest.mark.usefixtures("config_entry_setup")
async def test_verify_group_supported_features(hass: HomeAssistant) -> None:
"""Test that group supported features reflect what included lights support."""
assert len(hass.states.async_all()) == 4
group_state = hass.states.get("light.group")
assert group_state.state == STATE_ON
assert group_state.attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
assert (
group_state.attributes[ATTR_SUPPORTED_FEATURES]
== LightEntityFeature.TRANSITION
| LightEntityFeature.FLASH
| LightEntityFeature.EFFECT
)
@pytest.mark.parametrize(
"group_payload",
[
{
"43": {
"action": {
"alert": "none",
"bri": 127,
"colormode": "hs",
"ct": 0,
"effect": "none",
"hue": 0,
"on": True,
"sat": 127,
"scene": "4",
"xy": [0, 0],
},
"devicemembership": [],
"etag": "4548e982c4cfff942f7af80958abb2a0",
"id": "43",
"lights": ["0"],
"name": "Opbergruimte",
"scenes": [
{
"id": "1",
"lightcount": 1,
"name": "Scene Normaal deCONZ",
"transitiontime": 10,
},
{
"id": "2",
"lightcount": 1,
"name": "Scene Fel deCONZ",
"transitiontime": 10,
},
{
"id": "3",
"lightcount": 1,
"name": "Scene Gedimd deCONZ",
"transitiontime": 10,
},
{
"id": "4",
"lightcount": 1,
"name": "Scene Uit deCONZ",
"transitiontime": 10,
},
],
"state": {"all_on": False, "any_on": False},
"type": "LightGroup",
},
}
],
)
@pytest.mark.parametrize(
"light_payload",
[
{
"capabilities": {
"alerts": [
"none",
"select",
"lselect",
"blink",
"breathe",
"okay",
"channelchange",
"finish",
"stop",
],
"bri": {"min_dim_level": 5},
},
"config": {
"bri": {"execute_if_off": True, "startup": "previous"},
"groups": ["43"],
"on": {"startup": "previous"},
},
"etag": "ca0ed7763eca37f5e6b24f6d46f8a518",
"hascolor": False,
"lastannounced": None,
"lastseen": "2024-03-02T20:08Z",
"manufacturername": "Signify Netherlands B.V.",
"modelid": "LWA001",
"name": "Opbergruimte Lamp Plafond",
"productid": "Philips-LWA001-1-A19DLv5",
"productname": "Hue white lamp",
"state": {
"alert": "none",
"bri": 76,
"effect": "none",
"on": False,
"reachable": True,
},
"swconfigid": "87169548",
"swversion": "1.104.2",
"type": "Dimmable light",
"uniqueid": "00:17:88:01:08:11:22:33-01",
}
],
)
@pytest.mark.usefixtures("config_entry_setup")
async def test_verify_group_color_mode_fallback(
hass: HomeAssistant,
mock_websocket_data: WebsocketDataType,
) -> None:
"""Test that group supported features reflect what included lights support."""
group_state = hass.states.get("light.opbergruimte")
assert group_state.state == STATE_OFF
assert group_state.attributes[ATTR_COLOR_MODE] is None
await mock_websocket_data(
{
"id": "0",
"r": "lights",
"state": {
"alert": "none",
"bri": 76,
"effect": "none",
"on": True,
"reachable": True,
},
"uniqueid": "00:17:88:01:08:11:22:33-01",
}
)
await mock_websocket_data(
{
"id": "43",
"r": "groups",
"state": {"all_on": True, "any_on": True},
}
)
group_state = hass.states.get("light.opbergruimte")
assert group_state.state == STATE_ON
assert group_state.attributes[ATTR_COLOR_MODE] is ColorMode.BRIGHTNESS