Update light turn_on schema to coerce colors to tuple before asserting sequence type (#58670)

* Make color_name_to_rgb return a tuple

* Tweak

* Tweak

* Update test

* Tweak test
pull/58977/head
Erik Montnemery 2021-10-29 15:51:14 +02:00 committed by Paulus Schoutsen
parent 78082afa94
commit e9b67b3590
4 changed files with 273 additions and 9 deletions

View File

@ -3,6 +3,7 @@ from __future__ import annotations
from collections import Counter from collections import Counter
import itertools import itertools
import logging
from typing import Any, Set, cast from typing import Any, Set, cast
import voluptuous as vol import voluptuous as vol
@ -66,6 +67,8 @@ SUPPORT_GROUP_LIGHT = (
SUPPORT_EFFECT | SUPPORT_FLASH | SUPPORT_TRANSITION | SUPPORT_WHITE_VALUE SUPPORT_EFFECT | SUPPORT_FLASH | SUPPORT_TRANSITION | SUPPORT_WHITE_VALUE
) )
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform( async def async_setup_platform(
hass: HomeAssistant, hass: HomeAssistant,
@ -152,6 +155,8 @@ class LightGroup(GroupEntity, light.LightEntity):
} }
data[ATTR_ENTITY_ID] = self._entity_ids data[ATTR_ENTITY_ID] = self._entity_ids
_LOGGER.debug("Forwarded turn_on command: %s", data)
await self.hass.services.async_call( await self.hass.services.async_call(
light.DOMAIN, light.DOMAIN,
light.SERVICE_TURN_ON, light.SERVICE_TURN_ON,

View File

@ -202,25 +202,25 @@ LIGHT_TURN_ON_SCHEMA = {
), ),
vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): cv.positive_int, vol.Exclusive(ATTR_KELVIN, COLOR_GROUP): cv.positive_int,
vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All( vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All(
vol.Coerce(tuple),
vol.ExactSequence( vol.ExactSequence(
( (
vol.All(vol.Coerce(float), vol.Range(min=0, max=360)), vol.All(vol.Coerce(float), vol.Range(min=0, max=360)),
vol.All(vol.Coerce(float), vol.Range(min=0, max=100)), vol.All(vol.Coerce(float), vol.Range(min=0, max=100)),
) )
), ),
vol.Coerce(tuple),
), ),
vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All( vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All(
vol.ExactSequence((cv.byte,) * 3), vol.Coerce(tuple) vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 3)
), ),
vol.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): vol.All( vol.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): vol.All(
vol.ExactSequence((cv.byte,) * 4), vol.Coerce(tuple) vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 4)
), ),
vol.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): vol.All( vol.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): vol.All(
vol.ExactSequence((cv.byte,) * 5), vol.Coerce(tuple) vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 5)
), ),
vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All( vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All(
vol.ExactSequence((cv.small_float, cv.small_float)), vol.Coerce(tuple) vol.Coerce(tuple), vol.ExactSequence((cv.small_float, cv.small_float))
), ),
vol.Exclusive(ATTR_WHITE, COLOR_GROUP): VALID_BRIGHTNESS, vol.Exclusive(ATTR_WHITE, COLOR_GROUP): VALID_BRIGHTNESS,
ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)), ATTR_WHITE_VALUE: vol.All(vol.Coerce(int), vol.Range(min=0, max=255)),

View File

@ -3,12 +3,15 @@ from os import path
import unittest.mock import unittest.mock
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import pytest
from homeassistant import config as hass_config from homeassistant import config as hass_config
from homeassistant.components.group import DOMAIN, SERVICE_RELOAD from homeassistant.components.group import DOMAIN, SERVICE_RELOAD
import homeassistant.components.group.light as group import homeassistant.components.group.light as group
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_MODE, ATTR_COLOR_MODE,
ATTR_COLOR_NAME,
ATTR_COLOR_TEMP, ATTR_COLOR_TEMP,
ATTR_EFFECT, ATTR_EFFECT,
ATTR_EFFECT_LIST, ATTR_EFFECT_LIST,
@ -28,6 +31,7 @@ from homeassistant.components.light import (
COLOR_MODE_COLOR_TEMP, COLOR_MODE_COLOR_TEMP,
COLOR_MODE_HS, COLOR_MODE_HS,
COLOR_MODE_ONOFF, COLOR_MODE_ONOFF,
COLOR_MODE_RGB,
COLOR_MODE_RGBW, COLOR_MODE_RGBW,
COLOR_MODE_RGBWW, COLOR_MODE_RGBWW,
COLOR_MODE_WHITE, COLOR_MODE_WHITE,
@ -261,6 +265,77 @@ async def test_color_hs(hass, enable_custom_integrations):
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
async def test_color_rgb(hass, enable_custom_integrations):
"""Test rgbw color reporting."""
platform = getattr(hass.components, "test.light")
platform.init(empty=True)
platform.ENTITIES.append(platform.MockLight("test1", STATE_ON))
platform.ENTITIES.append(platform.MockLight("test2", STATE_OFF))
entity0 = platform.ENTITIES[0]
entity0.supported_color_modes = {COLOR_MODE_RGB}
entity0.color_mode = COLOR_MODE_RGB
entity0.brightness = 255
entity0.rgb_color = (0, 64, 128)
entity1 = platform.ENTITIES[1]
entity1.supported_color_modes = {COLOR_MODE_RGB}
entity1.color_mode = COLOR_MODE_RGB
entity1.brightness = 255
entity1.rgb_color = (255, 128, 64)
assert await async_setup_component(
hass,
LIGHT_DOMAIN,
{
LIGHT_DOMAIN: [
{"platform": "test"},
{
"platform": DOMAIN,
"entities": ["light.test1", "light.test2"],
},
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
state = hass.states.get("light.light_group")
assert state.state == STATE_ON
assert state.attributes[ATTR_COLOR_MODE] == "rgb"
assert state.attributes[ATTR_RGB_COLOR] == (0, 64, 128)
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgb"]
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
await hass.services.async_call(
"light",
"turn_on",
{"entity_id": [entity1.entity_id]},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get("light.light_group")
assert state.attributes[ATTR_COLOR_MODE] == "rgb"
assert state.attributes[ATTR_RGB_COLOR] == (127, 96, 96)
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgb"]
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
await hass.services.async_call(
"light",
"turn_off",
{"entity_id": [entity0.entity_id]},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get("light.light_group")
assert state.attributes[ATTR_COLOR_MODE] == "rgb"
assert state.attributes[ATTR_RGB_COLOR] == (255, 128, 64)
assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == ["rgb"]
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0
async def test_color_rgbw(hass, enable_custom_integrations): async def test_color_rgbw(hass, enable_custom_integrations):
"""Test rgbw color reporting.""" """Test rgbw color reporting."""
platform = getattr(hass.components, "test.light") platform = getattr(hass.components, "test.light")
@ -1039,14 +1114,40 @@ async def test_supported_features(hass):
assert state.attributes[ATTR_SUPPORTED_FEATURES] == 40 assert state.attributes[ATTR_SUPPORTED_FEATURES] == 40
async def test_service_calls(hass): @pytest.mark.parametrize("supported_color_modes", [COLOR_MODE_HS, COLOR_MODE_RGB])
async def test_service_calls(hass, enable_custom_integrations, supported_color_modes):
"""Test service calls.""" """Test service calls."""
platform = getattr(hass.components, "test.light")
platform.init(empty=True)
platform.ENTITIES.append(platform.MockLight("bed_light", STATE_ON))
platform.ENTITIES.append(platform.MockLight("ceiling_lights", STATE_OFF))
platform.ENTITIES.append(platform.MockLight("kitchen_lights", STATE_OFF))
entity0 = platform.ENTITIES[0]
entity0.supported_color_modes = {supported_color_modes}
entity0.color_mode = supported_color_modes
entity0.brightness = 255
entity0.rgb_color = (0, 64, 128)
entity1 = platform.ENTITIES[1]
entity1.supported_color_modes = {supported_color_modes}
entity1.color_mode = supported_color_modes
entity1.brightness = 255
entity1.rgb_color = (255, 128, 64)
entity2 = platform.ENTITIES[2]
entity2.supported_color_modes = {supported_color_modes}
entity2.color_mode = supported_color_modes
entity2.brightness = 255
entity2.rgb_color = (255, 128, 64)
await async_setup_component( await async_setup_component(
hass, hass,
LIGHT_DOMAIN, LIGHT_DOMAIN,
{ {
LIGHT_DOMAIN: [ LIGHT_DOMAIN: [
{"platform": "demo"}, {"platform": "test"},
{ {
"platform": DOMAIN, "platform": DOMAIN,
"entities": [ "entities": [
@ -1062,14 +1163,16 @@ async def test_service_calls(hass):
await hass.async_start() await hass.async_start()
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("light.light_group").state == STATE_ON group_state = hass.states.get("light.light_group")
assert group_state.state == STATE_ON
assert group_state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [supported_color_modes]
await hass.services.async_call( await hass.services.async_call(
LIGHT_DOMAIN, LIGHT_DOMAIN,
SERVICE_TOGGLE, SERVICE_TOGGLE,
{ATTR_ENTITY_ID: "light.light_group"}, {ATTR_ENTITY_ID: "light.light_group"},
blocking=True, blocking=True,
) )
assert hass.states.get("light.bed_light").state == STATE_OFF assert hass.states.get("light.bed_light").state == STATE_OFF
assert hass.states.get("light.ceiling_lights").state == STATE_OFF assert hass.states.get("light.ceiling_lights").state == STATE_OFF
assert hass.states.get("light.kitchen_lights").state == STATE_OFF assert hass.states.get("light.kitchen_lights").state == STATE_OFF
@ -1096,6 +1199,84 @@ async def test_service_calls(hass):
assert hass.states.get("light.ceiling_lights").state == STATE_OFF assert hass.states.get("light.ceiling_lights").state == STATE_OFF
assert hass.states.get("light.kitchen_lights").state == STATE_OFF assert hass.states.get("light.kitchen_lights").state == STATE_OFF
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "light.light_group",
ATTR_BRIGHTNESS: 128,
ATTR_RGB_COLOR: (42, 255, 255),
},
blocking=True,
)
state = hass.states.get("light.bed_light")
assert state.state == STATE_ON
assert state.attributes[ATTR_BRIGHTNESS] == 128
assert state.attributes[ATTR_RGB_COLOR] == (42, 255, 255)
state = hass.states.get("light.ceiling_lights")
assert state.state == STATE_ON
assert state.attributes[ATTR_BRIGHTNESS] == 128
assert state.attributes[ATTR_RGB_COLOR] == (42, 255, 255)
state = hass.states.get("light.kitchen_lights")
assert state.state == STATE_ON
assert state.attributes[ATTR_BRIGHTNESS] == 128
assert state.attributes[ATTR_RGB_COLOR] == (42, 255, 255)
await hass.services.async_call(
LIGHT_DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: "light.light_group",
ATTR_BRIGHTNESS: 128,
ATTR_COLOR_NAME: "red",
},
blocking=True,
)
state = hass.states.get("light.bed_light")
assert state.state == STATE_ON
assert state.attributes[ATTR_BRIGHTNESS] == 128
assert state.attributes[ATTR_RGB_COLOR] == (255, 0, 0)
state = hass.states.get("light.ceiling_lights")
assert state.state == STATE_ON
assert state.attributes[ATTR_BRIGHTNESS] == 128
assert state.attributes[ATTR_RGB_COLOR] == (255, 0, 0)
state = hass.states.get("light.kitchen_lights")
assert state.state == STATE_ON
assert state.attributes[ATTR_BRIGHTNESS] == 128
assert state.attributes[ATTR_RGB_COLOR] == (255, 0, 0)
async def test_service_call_effect(hass):
"""Test service calls."""
await async_setup_component(
hass,
LIGHT_DOMAIN,
{
LIGHT_DOMAIN: [
{"platform": "demo"},
{
"platform": DOMAIN,
"entities": [
"light.bed_light",
"light.ceiling_lights",
"light.kitchen_lights",
],
},
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert hass.states.get("light.light_group").state == STATE_ON
await hass.services.async_call( await hass.services.async_call(
LIGHT_DOMAIN, LIGHT_DOMAIN,
SERVICE_TURN_ON, SERVICE_TURN_ON,

View File

@ -18,6 +18,7 @@ from homeassistant.const import (
) )
from homeassistant.exceptions import Unauthorized from homeassistant.exceptions import Unauthorized
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.color as color_util
from tests.common import async_mock_service from tests.common import async_mock_service
@ -1589,6 +1590,83 @@ async def test_light_service_call_color_conversion(hass, enable_custom_integrati
assert data == {"brightness": 128, "rgbww_color": (0, 75, 140, 255, 255)} assert data == {"brightness": 128, "rgbww_color": (0, 75, 140, 255, 255)}
async def test_light_service_call_color_conversion_named_tuple(
hass, enable_custom_integrations
):
"""Test a named tuple (RGBColor) is handled correctly."""
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))
platform.ENTITIES.append(platform.MockLight("Test_rgbw", STATE_ON))
platform.ENTITIES.append(platform.MockLight("Test_rgbww", 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
entity5 = platform.ENTITIES[5]
entity5.supported_color_modes = {light.COLOR_MODE_RGBW}
entity6 = platform.ENTITIES[6]
entity6.supported_color_modes = {light.COLOR_MODE_RGBWW}
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
await hass.async_block_till_done()
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,
entity5.entity_id,
entity6.entity_id,
],
"brightness_pct": 25,
"rgb_color": color_util.RGBColor(128, 0, 0),
},
blocking=True,
)
_, data = entity0.last_call("turn_on")
assert data == {"brightness": 64, "hs_color": (0.0, 100.0)}
_, data = entity1.last_call("turn_on")
assert data == {"brightness": 64, "rgb_color": (128, 0, 0)}
_, data = entity2.last_call("turn_on")
assert data == {"brightness": 64, "xy_color": (0.701, 0.299)}
_, data = entity3.last_call("turn_on")
assert data == {"brightness": 64, "rgb_color": (128, 0, 0)}
_, data = entity4.last_call("turn_on")
assert data == {"brightness": 64, "hs_color": (0.0, 100.0)}
_, data = entity5.last_call("turn_on")
assert data == {"brightness": 64, "rgbw_color": (128, 0, 0, 0)}
_, data = entity6.last_call("turn_on")
assert data == {"brightness": 64, "rgbww_color": (128, 0, 0, 0, 0)}
async def test_light_service_call_color_temp_emulation( async def test_light_service_call_color_temp_emulation(
hass, enable_custom_integrations hass, enable_custom_integrations
): ):