Adjust color_mode checks when lights render effects (#108737)

* Adjust color_mode checks when lights render effects

* Improve comment

* Avoid calling effect property if light does not support effects

* Fix test
This commit is contained in:
Erik Montnemery 2024-01-24 15:44:45 +01:00 committed by GitHub
parent 431e4b38ac
commit c3de193e2e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 101 additions and 11 deletions

View file

@ -234,6 +234,7 @@ ATTR_EFFECT_LIST = "effect_list"
# Apply an effect to the light, can be EFFECT_COLORLOOP.
ATTR_EFFECT = "effect"
EFFECT_COLORLOOP = "colorloop"
EFFECT_OFF = "off"
EFFECT_RANDOM = "random"
EFFECT_WHITE = "white"
@ -1060,6 +1061,51 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
return data
def __validate_color_mode(
self,
color_mode: ColorMode | str | None,
supported_color_modes: set[ColorMode] | set[str],
effect: str | None,
) -> None:
"""Validate the color mode."""
if color_mode is None:
# The light is turned off
return
if not effect or effect == EFFECT_OFF:
# No effect is active, the light must set color mode to one of the supported
# color modes
if color_mode in supported_color_modes:
return
# Increase severity to warning in 2024.3, reject in 2025.3
_LOGGER.debug(
"%s: set to unsupported color_mode: %s, supported_color_modes: %s",
self.entity_id,
color_mode,
supported_color_modes,
)
return
# When an effect is active, the color mode should indicate what adjustments are
# supported by the effect. To make this possible, we allow the light to set its
# color mode to on_off, and to brightness if the light allows adjusting
# brightness, in addition to the otherwise supported color modes.
effect_color_modes = supported_color_modes | {ColorMode.ONOFF}
if brightness_supported(effect_color_modes):
effect_color_modes.add(ColorMode.BRIGHTNESS)
if color_mode in effect_color_modes:
return
# Increase severity to warning in 2024.3, reject in 2025.3
_LOGGER.debug(
"%s: set to unsupported color_mode: %s, supported for effect: %s",
self.entity_id,
color_mode,
effect_color_modes,
)
return
@final
@property
def state_attributes(self) -> dict[str, Any] | None:
@ -1074,14 +1120,13 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
_is_on = self.is_on
color_mode = self._light_internal_color_mode if _is_on else None
if color_mode and color_mode not in legacy_supported_color_modes:
# Increase severity to warning in 2024.3, reject in 2025.3
_LOGGER.debug(
"%s: set to unsupported color_mode: %s, supported_color_modes: %s",
self.entity_id,
color_mode,
legacy_supported_color_modes,
)
effect: str | None
if LightEntityFeature.EFFECT in supported_features:
data[ATTR_EFFECT] = effect = self.effect if _is_on else None
else:
effect = None
self.__validate_color_mode(color_mode, legacy_supported_color_modes, effect)
data[ATTR_COLOR_MODE] = color_mode
@ -1140,9 +1185,6 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
if color_mode:
data.update(self._light_internal_convert_color(color_mode))
if LightEntityFeature.EFFECT in supported_features:
data[ATTR_EFFECT] = self.effect if _is_on else None
return data
@property

View file

@ -2610,3 +2610,51 @@ def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) ->
caplog.clear()
assert entity.supported_features_compat is light.LightEntityFeature(1)
assert "is using deprecated supported features values" not in caplog.text
@pytest.mark.parametrize(
("color_mode", "supported_color_modes", "effect", "warning_expected"),
[
(light.ColorMode.ONOFF, {light.ColorMode.ONOFF}, None, False),
# A light which supports brightness should not set its color mode to on_off
(light.ColorMode.ONOFF, {light.ColorMode.BRIGHTNESS}, None, True),
(light.ColorMode.ONOFF, {light.ColorMode.BRIGHTNESS}, light.EFFECT_OFF, True),
# Unless it renders an effect
(light.ColorMode.ONOFF, {light.ColorMode.BRIGHTNESS}, "effect", False),
(light.ColorMode.BRIGHTNESS, {light.ColorMode.BRIGHTNESS}, "effect", False),
(light.ColorMode.BRIGHTNESS, {light.ColorMode.BRIGHTNESS}, None, False),
# A light which supports color should not set its color mode to brightnes
(light.ColorMode.BRIGHTNESS, {light.ColorMode.HS}, None, True),
(light.ColorMode.BRIGHTNESS, {light.ColorMode.HS}, light.EFFECT_OFF, True),
(light.ColorMode.ONOFF, {light.ColorMode.HS}, None, True),
(light.ColorMode.ONOFF, {light.ColorMode.HS}, light.EFFECT_OFF, True),
# Unless it renders an effect
(light.ColorMode.BRIGHTNESS, {light.ColorMode.HS}, "effect", False),
(light.ColorMode.ONOFF, {light.ColorMode.HS}, "effect", False),
(light.ColorMode.HS, {light.ColorMode.HS}, "effect", False),
# A light which supports brightness should not set its color mode to hs even
# if rendering an effect
(light.ColorMode.HS, {light.ColorMode.BRIGHTNESS}, "effect", True),
],
)
def test_report_invalid_color_mode(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
color_mode: str,
supported_color_modes: set[str],
effect: str | None,
warning_expected: bool,
) -> None:
"""Test a light setting an invalid color mode."""
class MockLightEntityEntity(light.LightEntity):
_attr_color_mode = color_mode
_attr_effect = effect
_attr_is_on = True
_attr_supported_features = light.LightEntityFeature.EFFECT
_attr_supported_color_modes = supported_color_modes
entity = MockLightEntityEntity()
entity._async_calculate_state()
expected_warning = f"set to unsupported color_mode: {color_mode}"
assert (expected_warning in caplog.text) is warning_expected