From 0cbc77ad3f3dc21ab7545098a1f2923f3d1ce3f5 Mon Sep 17 00:00:00 2001 From: "Steven B." <51370195+sdb9696@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:36:57 +0000 Subject: [PATCH] Make tplink entities unavailable if camera is off (#133877) --- homeassistant/components/tplink/binary_sensor.py | 3 ++- homeassistant/components/tplink/button.py | 7 ++++++- homeassistant/components/tplink/camera.py | 4 +++- homeassistant/components/tplink/climate.py | 5 +++-- homeassistant/components/tplink/entity.py | 13 +++++++++---- homeassistant/components/tplink/fan.py | 3 ++- homeassistant/components/tplink/light.py | 7 +++++-- homeassistant/components/tplink/number.py | 3 ++- homeassistant/components/tplink/select.py | 3 ++- homeassistant/components/tplink/sensor.py | 3 ++- homeassistant/components/tplink/siren.py | 3 ++- homeassistant/components/tplink/switch.py | 3 ++- 12 files changed, 40 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/tplink/binary_sensor.py b/homeassistant/components/tplink/binary_sensor.py index e14ecf01749..f3a7e7a7ce7 100644 --- a/homeassistant/components/tplink/binary_sensor.py +++ b/homeassistant/components/tplink/binary_sensor.py @@ -96,6 +96,7 @@ class TPLinkBinarySensorEntity(CoordinatedTPLinkFeatureEntity, BinarySensorEntit entity_description: TPLinkBinarySensorEntityDescription @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" self._attr_is_on = cast(bool | None, self._feature.value) + return True diff --git a/homeassistant/components/tplink/button.py b/homeassistant/components/tplink/button.py index 6e0d34864d9..753efcf89f4 100644 --- a/homeassistant/components/tplink/button.py +++ b/homeassistant/components/tplink/button.py @@ -52,15 +52,19 @@ BUTTON_DESCRIPTIONS: Final = [ ), TPLinkButtonEntityDescription( key="pan_left", + available_fn=lambda dev: dev.is_on, ), TPLinkButtonEntityDescription( key="pan_right", + available_fn=lambda dev: dev.is_on, ), TPLinkButtonEntityDescription( key="tilt_up", + available_fn=lambda dev: dev.is_on, ), TPLinkButtonEntityDescription( key="tilt_down", + available_fn=lambda dev: dev.is_on, ), ] @@ -100,5 +104,6 @@ class TPLinkButtonEntity(CoordinatedTPLinkFeatureEntity, ButtonEntity): """Execute action.""" await self._feature.set_value(True) - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """No need to update anything.""" + return self.entity_description.available_fn(self._device) diff --git a/homeassistant/components/tplink/camera.py b/homeassistant/components/tplink/camera.py index 8aa49cf3c93..413bb40b422 100644 --- a/homeassistant/components/tplink/camera.py +++ b/homeassistant/components/tplink/camera.py @@ -40,6 +40,7 @@ CAMERA_DESCRIPTIONS: tuple[TPLinkCameraEntityDescription, ...] = ( TPLinkCameraEntityDescription( key="live_view", translation_key="live_view", + available_fn=lambda dev: dev.is_on, ), ) @@ -111,9 +112,10 @@ class TPLinkCameraEntity(CoordinatedTPLinkEntity, Camera): return f"{legacy_device_id(self._device)}-{self.entity_description.key}" @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" self._attr_is_on = self._camera_module.is_on + return self.entity_description.available_fn(self._device) async def stream_source(self) -> str | None: """Return the source of the stream.""" diff --git a/homeassistant/components/tplink/climate.py b/homeassistant/components/tplink/climate.py index 75a6599959d..f53a0d093ac 100644 --- a/homeassistant/components/tplink/climate.py +++ b/homeassistant/components/tplink/climate.py @@ -113,7 +113,7 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity): await self._state_feature.set_value(False) @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" self._attr_current_temperature = cast(float | None, self._temp_feature.value) self._attr_target_temperature = cast(float | None, self._target_feature.value) @@ -131,11 +131,12 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity): self._mode_feature.value, ) self._attr_hvac_action = HVACAction.OFF - return + return True self._attr_hvac_action = STATE_TO_ACTION[ cast(ThermostatState, self._mode_feature.value) ] + return True def _get_unique_id(self) -> str: """Return unique id.""" diff --git a/homeassistant/components/tplink/entity.py b/homeassistant/components/tplink/entity.py index d7b02b80177..935857e5db1 100644 --- a/homeassistant/components/tplink/entity.py +++ b/homeassistant/components/tplink/entity.py @@ -89,6 +89,7 @@ class TPLinkFeatureEntityDescription(EntityDescription): """Base class for a TPLink feature based entity description.""" deprecated_info: DeprecatedInfo | None = None + available_fn: Callable[[Device], bool] = lambda _: True @dataclass(frozen=True, kw_only=True) @@ -96,6 +97,7 @@ class TPLinkModuleEntityDescription(EntityDescription): """Base class for a TPLink module based entity description.""" deprecated_info: DeprecatedInfo | None = None + available_fn: Callable[[Device], bool] = lambda _: True def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P]( @@ -207,15 +209,18 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB @abstractmethod @callback - def _async_update_attrs(self) -> None: - """Platforms implement this to update the entity internals.""" + def _async_update_attrs(self) -> bool: + """Platforms implement this to update the entity internals. + + The return value is used to the set the entity available attribute. + """ raise NotImplementedError @callback def _async_call_update_attrs(self) -> None: """Call update_attrs and make entity unavailable on errors.""" try: - self._async_update_attrs() + available = self._async_update_attrs() except Exception as ex: # noqa: BLE001 if self._attr_available: _LOGGER.warning( @@ -226,7 +231,7 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB ) self._attr_available = False else: - self._attr_available = True + self._attr_available = available @callback def _handle_coordinator_update(self) -> None: diff --git a/homeassistant/components/tplink/fan.py b/homeassistant/components/tplink/fan.py index 64ad01eb671..a1e62e4ed69 100644 --- a/homeassistant/components/tplink/fan.py +++ b/homeassistant/components/tplink/fan.py @@ -106,7 +106,7 @@ class TPLinkFanEntity(CoordinatedTPLinkEntity, FanEntity): await self.fan_module.set_fan_speed_level(value_in_range) @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" fan_speed = self.fan_module.fan_speed_level self._attr_is_on = fan_speed != 0 @@ -114,3 +114,4 @@ class TPLinkFanEntity(CoordinatedTPLinkEntity, FanEntity): self._attr_percentage = ranged_value_to_percentage(SPEED_RANGE, fan_speed) else: self._attr_percentage = None + return True diff --git a/homeassistant/components/tplink/light.py b/homeassistant/components/tplink/light.py index f3207d754f3..91e2a784af2 100644 --- a/homeassistant/components/tplink/light.py +++ b/homeassistant/components/tplink/light.py @@ -335,7 +335,7 @@ class TPLinkLightEntity(CoordinatedTPLinkEntity, LightEntity): return ColorMode.HS @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" light_module = self._light_module self._attr_is_on = light_module.state.light_on is True @@ -349,6 +349,8 @@ class TPLinkLightEntity(CoordinatedTPLinkEntity, LightEntity): hue, saturation, _ = light_module.hsv self._attr_hs_color = hue, saturation + return True + class TPLinkLightEffectEntity(TPLinkLightEntity): """Representation of a TPLink Smart Light Strip.""" @@ -368,7 +370,7 @@ class TPLinkLightEffectEntity(TPLinkLightEntity): _attr_supported_features = LightEntityFeature.TRANSITION | LightEntityFeature.EFFECT @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" super()._async_update_attrs() effect_module = self._effect_module @@ -381,6 +383,7 @@ class TPLinkLightEffectEntity(TPLinkLightEntity): self._attr_effect_list = effect_list else: self._attr_effect_list = None + return True @async_refresh_after async def async_turn_on(self, **kwargs: Any) -> None: diff --git a/homeassistant/components/tplink/number.py b/homeassistant/components/tplink/number.py index 489805029ea..3f7fa9c3e0f 100644 --- a/homeassistant/components/tplink/number.py +++ b/homeassistant/components/tplink/number.py @@ -114,6 +114,7 @@ class TPLinkNumberEntity(CoordinatedTPLinkFeatureEntity, NumberEntity): await self._feature.set_value(int(value)) @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" self._attr_native_value = cast(float | None, self._feature.value) + return True diff --git a/homeassistant/components/tplink/select.py b/homeassistant/components/tplink/select.py index 3755a1d0be2..5dd8e54fca8 100644 --- a/homeassistant/components/tplink/select.py +++ b/homeassistant/components/tplink/select.py @@ -91,6 +91,7 @@ class TPLinkSelectEntity(CoordinatedTPLinkFeatureEntity, SelectEntity): await self._feature.set_value(option) @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" self._attr_current_option = cast(str | None, self._feature.value) + return True diff --git a/homeassistant/components/tplink/sensor.py b/homeassistant/components/tplink/sensor.py index 8b7351f8d7d..da4bf72122d 100644 --- a/homeassistant/components/tplink/sensor.py +++ b/homeassistant/components/tplink/sensor.py @@ -153,7 +153,7 @@ class TPLinkSensorEntity(CoordinatedTPLinkFeatureEntity, SensorEntity): entity_description: TPLinkSensorEntityDescription @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" value = self._feature.value if value is not None and self._feature.precision_hint is not None: @@ -171,3 +171,4 @@ class TPLinkSensorEntity(CoordinatedTPLinkFeatureEntity, SensorEntity): # Map to homeassistant units and fallback to upstream one if none found if (unit := self._feature.unit) is not None: self._attr_native_unit_of_measurement = UNIT_MAPPING.get(unit, unit) + return True diff --git a/homeassistant/components/tplink/siren.py b/homeassistant/components/tplink/siren.py index c4ece56f0f6..141ea696358 100644 --- a/homeassistant/components/tplink/siren.py +++ b/homeassistant/components/tplink/siren.py @@ -56,6 +56,7 @@ class TPLinkSirenEntity(CoordinatedTPLinkEntity, SirenEntity): await self._alarm_module.stop() @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" self._attr_is_on = self._alarm_module.active + return True diff --git a/homeassistant/components/tplink/switch.py b/homeassistant/components/tplink/switch.py index 28dedc7e7a1..7a879fb3c70 100644 --- a/homeassistant/components/tplink/switch.py +++ b/homeassistant/components/tplink/switch.py @@ -109,6 +109,7 @@ class TPLinkSwitch(CoordinatedTPLinkFeatureEntity, SwitchEntity): await self._feature.set_value(False) @callback - def _async_update_attrs(self) -> None: + def _async_update_attrs(self) -> bool: """Update the entity's attributes.""" self._attr_is_on = cast(bool | None, self._feature.value) + return True