diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 06b6829d31c49..107ed80b0ddd1 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -74,6 +74,20 @@ COLOR_MODES_BRIGHTNESS = VALID_COLOR_MODES - {COLOR_MODE_ONOFF} COLOR_MODES_COLOR = {COLOR_MODE_HS, COLOR_MODE_RGB, COLOR_MODE_XY} + +def validate_supported_color_modes(color_modes): + """Validate the given color modes.""" + color_modes = set(color_modes) + if ( + not color_modes + or COLOR_MODE_UNKNOWN in color_modes + or (COLOR_MODE_BRIGHTNESS in color_modes and len(color_modes) > 1) + or (COLOR_MODE_ONOFF in color_modes and len(color_modes) > 1) + ): + raise vol.Error(f"Invalid supported_color_modes {sorted(color_modes)}") + return color_modes + + # Float that represents transition time in seconds to make change. ATTR_TRANSITION = "transition" @@ -609,9 +623,16 @@ def capability_attributes(self): if supported_features & SUPPORT_EFFECT: data[ATTR_EFFECT_LIST] = self.effect_list - data[ATTR_SUPPORTED_COLOR_MODES] = sorted( - self._light_internal_supported_color_modes - ) + supported_color_modes = self._light_internal_supported_color_modes + try: + supported_color_modes = validate_supported_color_modes( + supported_color_modes + ) + except vol.Error as ex: + _LOGGER.warning("Light %s: %s", self.entity_id, ex) + supported_color_modes = {COLOR_MODE_ONOFF} + + data[ATTR_SUPPORTED_COLOR_MODES] = sorted(supported_color_modes) return data diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 3adb146a22515..c77974796937f 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -1099,6 +1099,57 @@ async def test_light_backwards_compatibility_color_mode(hass): assert state.attributes["color_mode"] == light.COLOR_MODE_HS +async def test_invalid_color_modes(hass, caplog): + """Test invalid combinations of color modes.""" + 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)) + + entity0 = platform.ENTITIES[0] + entity0.supported_color_modes = [] + + entity1 = platform.ENTITIES[1] + entity1.supported_color_modes = [light.COLOR_MODE_ONOFF, light.COLOR_MODE_RGB] + + entity2 = platform.ENTITIES[2] + entity2.supported_color_modes = [light.COLOR_MODE_BRIGHTNESS, light.COLOR_MODE_RGB] + + entity3 = platform.ENTITIES[3] + entity3.supported_color_modes = [light.COLOR_MODE_UNKNOWN] + + assert await async_setup_component(hass, "light", {"light": {"platform": "test"}}) + await hass.async_block_till_done() + + assert "Light light.test_0: Invalid supported_color_modes []" in caplog.text + assert ( + "Light light.test_1: Invalid supported_color_modes ['onoff', 'rgb']" + in caplog.text + ) + assert ( + "Light light.test_2: Invalid supported_color_modes ['brightness', 'rgb']" + in caplog.text + ) + assert ( + "Light light.test_3: Invalid supported_color_modes ['unknown']" in caplog.text + ) + + state = hass.states.get(entity0.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF] + + state = hass.states.get(entity1.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF] + + state = hass.states.get(entity2.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF] + + state = hass.states.get(entity3.entity_id) + assert state.attributes["supported_color_modes"] == [light.COLOR_MODE_ONOFF] + + async def test_light_service_call_rgbw(hass): """Test backwards compatibility for rgbw functionality in service calls.""" platform = getattr(hass.components, "test.light")