Filter unsupported parameters from light service calls (#51084)

* Filter unsupported parameters from light service calls

* Silence pylint

* Fix deconz tests

* Fix mqtt tests

* Fix scene tests

* Fix trådfri emulated CT

* Fix mqtt tests
This commit is contained in:
Erik Montnemery 2021-05-26 08:02:59 +02:00 committed by GitHub
parent 9b13350b01
commit 154c849eac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 243 additions and 15 deletions

View file

@ -247,11 +247,52 @@ def preprocess_turn_on_alternatives(hass, params):
params[ATTR_BRIGHTNESS] = round(255 * brightness_pct / 100)
def filter_turn_off_params(params):
def filter_turn_off_params(light, params):
"""Filter out params not used in turn off."""
supported_features = light.supported_features
if not supported_features & SUPPORT_FLASH:
params.pop(ATTR_FLASH, None)
if not supported_features & SUPPORT_TRANSITION:
params.pop(ATTR_TRANSITION, None)
return {k: v for k, v in params.items() if k in (ATTR_TRANSITION, ATTR_FLASH)}
def filter_turn_on_params(light, params):
"""Filter out params not used in turn off."""
supported_features = light.supported_features
if not supported_features & SUPPORT_EFFECT:
params.pop(ATTR_EFFECT, None)
if not supported_features & SUPPORT_FLASH:
params.pop(ATTR_FLASH, None)
if not supported_features & SUPPORT_TRANSITION:
params.pop(ATTR_TRANSITION, None)
if not supported_features & SUPPORT_WHITE_VALUE:
params.pop(ATTR_WHITE_VALUE, None)
supported_color_modes = (
light._light_internal_supported_color_modes # pylint:disable=protected-access
)
if not brightness_supported(supported_color_modes):
params.pop(ATTR_BRIGHTNESS, None)
if COLOR_MODE_COLOR_TEMP not in supported_color_modes:
params.pop(ATTR_COLOR_TEMP, None)
if COLOR_MODE_HS not in supported_color_modes:
params.pop(ATTR_HS_COLOR, None)
if COLOR_MODE_RGB not in supported_color_modes:
params.pop(ATTR_RGB_COLOR, None)
if COLOR_MODE_RGBW not in supported_color_modes:
params.pop(ATTR_RGBW_COLOR, None)
if COLOR_MODE_RGBWW not in supported_color_modes:
params.pop(ATTR_RGBWW_COLOR, None)
if COLOR_MODE_XY not in supported_color_modes:
params.pop(ATTR_XY_COLOR, None)
return params
async def async_setup(hass, config): # noqa: C901
"""Expose light control via state machine and services."""
component = hass.data[DOMAIN] = EntityComponent(
@ -373,7 +414,7 @@ async def async_setup(hass, config): # noqa: C901
if params.get(ATTR_BRIGHTNESS) == 0:
await async_handle_light_off_service(light, call)
else:
await light.async_turn_on(**params)
await light.async_turn_on(**filter_turn_on_params(light, params))
async def async_handle_light_off_service(light, call):
"""Handle turning off a light."""
@ -382,7 +423,7 @@ async def async_setup(hass, config): # noqa: C901
if ATTR_TRANSITION not in params:
profiles.apply_default(light.entity_id, True, params)
await light.async_turn_off(**filter_turn_off_params(params))
await light.async_turn_off(**filter_turn_off_params(light, params))
async def async_handle_toggle_service(light, call):
"""Handle toggling a light."""

View file

@ -116,7 +116,7 @@ class TradfriLight(TradfriBaseDevice, LightEntity):
if device.light_control.can_set_dimmer:
_features |= SUPPORT_BRIGHTNESS
if device.light_control.can_set_color:
_features |= SUPPORT_COLOR
_features |= SUPPORT_COLOR | SUPPORT_COLOR_TEMP
if device.light_control.can_set_temp:
_features |= SUPPORT_COLOR_TEMP
self._features = _features

View file

@ -179,7 +179,6 @@ async def test_lights_and_groups(hass, aioclient_mock, mock_deconz_websocket):
blocking=True,
)
assert aioclient_mock.mock_calls[1][2] == {
"ct": 2500,
"bri": 200,
"transitiontime": 50,
"alert": "select",

View file

@ -117,6 +117,16 @@ async def test_services(hass, mock_light_profiles, enable_custom_integrations):
await hass.async_block_till_done()
ent1, ent2, ent3 = platform.ENTITIES
ent1.supported_color_modes = [light.COLOR_MODE_HS]
ent3.supported_color_modes = [light.COLOR_MODE_HS]
ent1.supported_features = light.SUPPORT_TRANSITION
ent2.supported_features = (
light.SUPPORT_COLOR
| light.SUPPORT_EFFECT
| light.SUPPORT_TRANSITION
| light.SUPPORT_WHITE_VALUE
)
ent3.supported_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
# Test init
assert light.is_on(hass, ent1.entity_id)
@ -205,6 +215,7 @@ async def test_services(hass, mock_light_profiles, enable_custom_integrations):
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent2.entity_id,
light.ATTR_EFFECT: "fun_effect",
light.ATTR_RGB_COLOR: (255, 255, 255),
light.ATTR_WHITE_VALUE: 255,
},
@ -215,6 +226,7 @@ async def test_services(hass, mock_light_profiles, enable_custom_integrations):
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent3.entity_id,
light.ATTR_FLASH: "short",
light.ATTR_XY_COLOR: (0.4, 0.6),
},
blocking=True,
@ -228,10 +240,14 @@ async def test_services(hass, mock_light_profiles, enable_custom_integrations):
}
_, data = ent2.last_call("turn_on")
assert data == {light.ATTR_HS_COLOR: (0, 0), light.ATTR_WHITE_VALUE: 255}
assert data == {
light.ATTR_EFFECT: "fun_effect",
light.ATTR_HS_COLOR: (0, 0),
light.ATTR_WHITE_VALUE: 255,
}
_, data = ent3.last_call("turn_on")
assert data == {light.ATTR_HS_COLOR: (71.059, 100)}
assert data == {light.ATTR_FLASH: "short", light.ATTR_HS_COLOR: (71.059, 100)}
# Ensure attributes are filtered when light is turned off
await hass.services.async_call(
@ -521,6 +537,8 @@ async def test_light_profiles(
await hass.async_block_till_done()
ent1, _, _ = platform.ENTITIES
ent1.supported_color_modes = [light.COLOR_MODE_HS]
ent1.supported_features = light.SUPPORT_TRANSITION
await hass.services.async_call(
light.DOMAIN,
@ -556,6 +574,8 @@ async def test_default_profiles_group(
mock_light_profiles[profile.name] = profile
ent, _, _ = platform.ENTITIES
ent.supported_color_modes = [light.COLOR_MODE_HS]
ent.supported_features = light.SUPPORT_TRANSITION
await hass.services.async_call(
light.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ent.entity_id}, blocking=True
)
@ -661,6 +681,8 @@ async def test_default_profiles_light(
mock_light_profiles[profile.name] = profile
dev = next(filter(lambda x: x.entity_id == "light.ceiling_2", platform.ENTITIES))
dev.supported_color_modes = [light.COLOR_MODE_HS]
dev.supported_features = light.SUPPORT_TRANSITION
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
@ -1625,3 +1647,157 @@ async def test_light_state_color_conversion(hass, enable_custom_integrations):
assert state.attributes["hs_color"] == (240, 100)
assert state.attributes["rgb_color"] == (0, 0, 255)
assert state.attributes["xy_color"] == (0.136, 0.04)
async def test_services_filter_parameters(
hass, mock_light_profiles, enable_custom_integrations
):
"""Test turn_on and turn_off filters unsupported parameters."""
platform = getattr(hass.components, "test.light")
platform.init()
assert await async_setup_component(
hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: "test"}}
)
await hass.async_block_till_done()
ent1, _, _ = platform.ENTITIES
# turn off the light by setting brightness to 0, this should work even if the light
# doesn't support brightness
await hass.services.async_call(
light.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: ENTITY_MATCH_ALL}, blocking=True
)
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: ENTITY_MATCH_ALL, light.ATTR_BRIGHTNESS: 0},
blocking=True,
)
assert not light.is_on(hass, ent1.entity_id)
# Ensure all unsupported attributes are filtered when light is turned on
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_BRIGHTNESS: 0,
light.ATTR_EFFECT: "fun_effect",
light.ATTR_FLASH: "short",
light.ATTR_TRANSITION: 10,
light.ATTR_WHITE_VALUE: 0,
},
blocking=True,
)
_, data = ent1.last_call("turn_on")
assert data == {}
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_COLOR_TEMP: 153,
},
blocking=True,
)
_, data = ent1.last_call("turn_on")
assert data == {}
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_HS_COLOR: (0, 0),
},
blocking=True,
)
_, data = ent1.last_call("turn_on")
assert data == {}
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_RGB_COLOR: (0, 0, 0),
},
blocking=True,
)
_, data = ent1.last_call("turn_on")
assert data == {}
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_RGBW_COLOR: (0, 0, 0, 0),
},
blocking=True,
)
_, data = ent1.last_call("turn_on")
assert data == {}
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_RGBWW_COLOR: (0, 0, 0, 0, 0),
},
blocking=True,
)
_, data = ent1.last_call("turn_on")
assert data == {}
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_XY_COLOR: (0, 0),
},
blocking=True,
)
_, data = ent1.last_call("turn_on")
assert data == {}
# Ensure all unsupported attributes are filtered when light is turned off
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_ON,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_BRIGHTNESS: 0,
light.ATTR_EFFECT: "fun_effect",
light.ATTR_FLASH: "short",
light.ATTR_TRANSITION: 10,
light.ATTR_WHITE_VALUE: 0,
},
blocking=True,
)
assert not light.is_on(hass, ent1.entity_id)
_, data = ent1.last_call("turn_off")
assert data == {}
await hass.services.async_call(
light.DOMAIN,
SERVICE_TURN_OFF,
{
ATTR_ENTITY_ID: ent1.entity_id,
light.ATTR_FLASH: "short",
light.ATTR_TRANSITION: 10,
},
blocking=True,
)
assert not light.is_on(hass, ent1.entity_id)
_, data = ent1.last_call("turn_off")
assert data == {}

View file

@ -945,6 +945,7 @@ async def test_sending_hs_color(hass, mqtt_mock):
"command_topic": "test_light_rgb/set",
"brightness": True,
"hs": True,
"white_value": True,
}
},
)
@ -1139,6 +1140,7 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock):
"command_topic": "test_light_rgb/set",
"brightness": True,
"rgb": True,
"white_value": True,
}
},
)
@ -1209,6 +1211,7 @@ async def test_sending_rgb_color_with_scaled_brightness(hass, mqtt_mock):
"brightness": True,
"brightness_scale": 100,
"rgb": True,
"white_value": True,
}
},
)
@ -1278,6 +1281,7 @@ async def test_sending_xy_color(hass, mqtt_mock):
"command_topic": "test_light_rgb/set",
"brightness": True,
"xy": True,
"white_value": True,
}
},
)

View file

@ -117,6 +117,8 @@ async def test_activate_scene(hass, entities, enable_custom_integrations):
assert light.is_on(hass, light_2.entity_id)
assert light_2.last_call("turn_on")[1].get("brightness") == 100
await turn_off_lights(hass, [light_2.entity_id])
calls = async_mock_service(hass, "light", "turn_on")
await hass.services.async_call(
@ -156,16 +158,22 @@ async def setup_lights(hass, entities):
await hass.async_block_till_done()
light_1, light_2 = entities
light_1.supported_color_modes = ["brightness"]
light_2.supported_color_modes = ["brightness"]
await hass.services.async_call(
"light",
"turn_off",
{"entity_id": [light_1.entity_id, light_2.entity_id]},
blocking=True,
)
await hass.async_block_till_done()
await turn_off_lights(hass, [light_1.entity_id, light_2.entity_id])
assert not light.is_on(hass, light_1.entity_id)
assert not light.is_on(hass, light_2.entity_id)
return light_1, light_2
async def turn_off_lights(hass, entity_ids):
"""Turn lights off."""
await hass.services.async_call(
"light",
"turn_off",
{"entity_id": entity_ids},
blocking=True,
)
await hass.async_block_till_done()