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:
parent
431e4b38ac
commit
c3de193e2e
2 changed files with 101 additions and 11 deletions
|
@ -234,6 +234,7 @@ ATTR_EFFECT_LIST = "effect_list"
|
||||||
# Apply an effect to the light, can be EFFECT_COLORLOOP.
|
# Apply an effect to the light, can be EFFECT_COLORLOOP.
|
||||||
ATTR_EFFECT = "effect"
|
ATTR_EFFECT = "effect"
|
||||||
EFFECT_COLORLOOP = "colorloop"
|
EFFECT_COLORLOOP = "colorloop"
|
||||||
|
EFFECT_OFF = "off"
|
||||||
EFFECT_RANDOM = "random"
|
EFFECT_RANDOM = "random"
|
||||||
EFFECT_WHITE = "white"
|
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)
|
data[ATTR_XY_COLOR] = color_util.color_hs_to_xy(*hs_color)
|
||||||
return data
|
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
|
@final
|
||||||
@property
|
@property
|
||||||
def state_attributes(self) -> dict[str, Any] | None:
|
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
|
_is_on = self.is_on
|
||||||
color_mode = self._light_internal_color_mode if _is_on else None
|
color_mode = self._light_internal_color_mode if _is_on else None
|
||||||
|
|
||||||
if color_mode and color_mode not in legacy_supported_color_modes:
|
effect: str | None
|
||||||
# Increase severity to warning in 2024.3, reject in 2025.3
|
if LightEntityFeature.EFFECT in supported_features:
|
||||||
_LOGGER.debug(
|
data[ATTR_EFFECT] = effect = self.effect if _is_on else None
|
||||||
"%s: set to unsupported color_mode: %s, supported_color_modes: %s",
|
else:
|
||||||
self.entity_id,
|
effect = None
|
||||||
color_mode,
|
|
||||||
legacy_supported_color_modes,
|
self.__validate_color_mode(color_mode, legacy_supported_color_modes, effect)
|
||||||
)
|
|
||||||
|
|
||||||
data[ATTR_COLOR_MODE] = color_mode
|
data[ATTR_COLOR_MODE] = color_mode
|
||||||
|
|
||||||
|
@ -1140,9 +1185,6 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||||
if color_mode:
|
if color_mode:
|
||||||
data.update(self._light_internal_convert_color(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
|
return data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -2610,3 +2610,51 @@ def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) ->
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
assert entity.supported_features_compat is light.LightEntityFeature(1)
|
assert entity.supported_features_compat is light.LightEntityFeature(1)
|
||||||
assert "is using deprecated supported features values" not in caplog.text
|
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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue