diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index c66562a53af..ebd3696d61f 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -345,11 +345,11 @@ def filter_turn_off_params( light: LightEntity, params: dict[str, Any] ) -> dict[str, Any]: """Filter out params not used in turn off or not supported by the light.""" - supported_features = light.supported_features + supported_features = light.supported_features_compat - if not supported_features & LightEntityFeature.FLASH: + if LightEntityFeature.FLASH not in supported_features: params.pop(ATTR_FLASH, None) - if not supported_features & LightEntityFeature.TRANSITION: + if LightEntityFeature.TRANSITION not in supported_features: params.pop(ATTR_TRANSITION, None) return {k: v for k, v in params.items() if k in (ATTR_TRANSITION, ATTR_FLASH)} @@ -357,13 +357,13 @@ def filter_turn_off_params( def filter_turn_on_params(light: LightEntity, params: dict[str, Any]) -> dict[str, Any]: """Filter out params not supported by the light.""" - supported_features = light.supported_features + supported_features = light.supported_features_compat - if not supported_features & LightEntityFeature.EFFECT: + if LightEntityFeature.EFFECT not in supported_features: params.pop(ATTR_EFFECT, None) - if not supported_features & LightEntityFeature.FLASH: + if LightEntityFeature.FLASH not in supported_features: params.pop(ATTR_FLASH, None) - if not supported_features & LightEntityFeature.TRANSITION: + if LightEntityFeature.TRANSITION not in supported_features: params.pop(ATTR_TRANSITION, None) supported_color_modes = ( @@ -989,7 +989,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def capability_attributes(self) -> dict[str, Any]: """Return capability attributes.""" data: dict[str, Any] = {} - supported_features = self.supported_features + supported_features = self.supported_features_compat supported_color_modes = self._light_internal_supported_color_modes if ColorMode.COLOR_TEMP in supported_color_modes: @@ -1007,7 +1007,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): data[ATTR_MAX_MIREDS] = color_util.color_temperature_kelvin_to_mired( self.min_color_temp_kelvin ) - if supported_features & LightEntityFeature.EFFECT: + if LightEntityFeature.EFFECT in supported_features: data[ATTR_EFFECT_LIST] = self.effect_list data[ATTR_SUPPORTED_COLOR_MODES] = sorted(supported_color_modes) @@ -1061,8 +1061,9 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def state_attributes(self) -> dict[str, Any] | None: """Return state attributes.""" data: dict[str, Any] = {} - supported_features = self.supported_features + supported_features = self.supported_features_compat supported_color_modes = self._light_internal_supported_color_modes + supported_features_value = supported_features.value color_mode = self._light_internal_color_mode if self.is_on else None if color_mode and color_mode not in supported_color_modes: @@ -1081,7 +1082,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): data[ATTR_BRIGHTNESS] = self.brightness else: data[ATTR_BRIGHTNESS] = None - elif supported_features & SUPPORT_BRIGHTNESS: + elif supported_features_value & SUPPORT_BRIGHTNESS: # Backwards compatibility for ambiguous / incomplete states # Add warning in 2021.6, remove in 2021.10 if self.is_on: @@ -1103,7 +1104,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): else: data[ATTR_COLOR_TEMP_KELVIN] = None data[ATTR_COLOR_TEMP] = None - elif supported_features & SUPPORT_COLOR_TEMP: + elif supported_features_value & SUPPORT_COLOR_TEMP: # Backwards compatibility # Add warning in 2021.6, remove in 2021.10 if self.is_on: @@ -1133,7 +1134,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): if color_mode: data.update(self._light_internal_convert_color(color_mode)) - if supported_features & LightEntityFeature.EFFECT: + if LightEntityFeature.EFFECT in supported_features: data[ATTR_EFFECT] = self.effect if self.is_on else None return data @@ -1146,14 +1147,15 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): # Backwards compatibility for supported_color_modes added in 2021.4 # Add warning in 2021.6, remove in 2021.10 - supported_features = self.supported_features + supported_features = self.supported_features_compat + supported_features_value = supported_features.value supported_color_modes: set[ColorMode] = set() - if supported_features & SUPPORT_COLOR_TEMP: + if supported_features_value & SUPPORT_COLOR_TEMP: supported_color_modes.add(ColorMode.COLOR_TEMP) - if supported_features & SUPPORT_COLOR: + if supported_features_value & SUPPORT_COLOR: supported_color_modes.add(ColorMode.HS) - if supported_features & SUPPORT_BRIGHTNESS and not supported_color_modes: + if not supported_color_modes and supported_features_value & SUPPORT_BRIGHTNESS: supported_color_modes = {ColorMode.BRIGHTNESS} if not supported_color_modes: @@ -1170,3 +1172,34 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): def supported_features(self) -> LightEntityFeature: """Flag supported features.""" return self._attr_supported_features + + @property + def supported_features_compat(self) -> LightEntityFeature: + """Return the supported features as LightEntityFeature. + + Remove this compatibility shim in 2025.1 or later. + """ + features = self.supported_features + if type(features) is not int: # noqa: E721 + return features + new_features = LightEntityFeature(features) + if self._deprecated_supported_features_reported is True: + return new_features + self._deprecated_supported_features_reported = True + report_issue = self._suggest_report_issue() + report_issue += ( + " and reference " + "https://developers.home-assistant.io/blog/2023/12/28/support-feature-magic-numbers-deprecation" + ) + _LOGGER.warning( + ( + "Entity %s (%s) is using deprecated supported features" + " values which will be removed in HA Core 2025.1. Instead it should use" + " %s and color modes, please %s" + ), + self.entity_id, + type(self), + repr(new_features), + report_issue, + ) + return new_features diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 962c5500f06..903002063e8 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -2589,3 +2589,24 @@ def test_filter_supported_color_modes() -> None: # ColorMode.BRIGHTNESS has priority over ColorMode.ONOFF supported = {light.ColorMode.ONOFF, light.ColorMode.BRIGHTNESS} assert light.filter_supported_color_modes(supported) == {light.ColorMode.BRIGHTNESS} + + +def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None: + """Test deprecated supported features ints.""" + + class MockLightEntityEntity(light.LightEntity): + @property + def supported_features(self) -> int: + """Return supported features.""" + return 1 + + entity = MockLightEntityEntity() + assert entity.supported_features_compat is light.LightEntityFeature(1) + assert "MockLightEntityEntity" in caplog.text + assert "is using deprecated supported features values" in caplog.text + assert "Instead it should use" in caplog.text + assert "LightEntityFeature" in caplog.text + assert "and color modes" in caplog.text + caplog.clear() + assert entity.supported_features_compat is light.LightEntityFeature(1) + assert "is using deprecated supported features values" not in caplog.text