diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index bd1f21f8ecb..27f3bbfc0c6 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -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.""" diff --git a/homeassistant/components/tradfri/light.py b/homeassistant/components/tradfri/light.py index 5da2c2b9b1f..a4c2ee67865 100644 --- a/homeassistant/components/tradfri/light.py +++ b/homeassistant/components/tradfri/light.py @@ -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 diff --git a/tests/components/deconz/test_light.py b/tests/components/deconz/test_light.py index 84c7a9b1078..a5e27709ebf 100644 --- a/tests/components/deconz/test_light.py +++ b/tests/components/deconz/test_light.py @@ -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", diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 71764eec186..842fb305c6c 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -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 == {} diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index f892b6a3bbd..f4bf11df026 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -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, } }, ) diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py index 8263b9c3006..4c5b832ac14 100644 --- a/tests/components/scene/test_init.py +++ b/tests/components/scene/test_init.py @@ -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()