Make tplink entities unavailable if camera is off (#133877)

This commit is contained in:
Steven B. 2024-12-23 15:36:57 +00:00 committed by GitHub
parent 5487e8673c
commit 0cbc77ad3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 40 additions and 17 deletions

View File

@ -96,6 +96,7 @@ class TPLinkBinarySensorEntity(CoordinatedTPLinkFeatureEntity, BinarySensorEntit
entity_description: TPLinkBinarySensorEntityDescription entity_description: TPLinkBinarySensorEntityDescription
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_is_on = cast(bool | None, self._feature.value) self._attr_is_on = cast(bool | None, self._feature.value)
return True

View File

@ -52,15 +52,19 @@ BUTTON_DESCRIPTIONS: Final = [
), ),
TPLinkButtonEntityDescription( TPLinkButtonEntityDescription(
key="pan_left", key="pan_left",
available_fn=lambda dev: dev.is_on,
), ),
TPLinkButtonEntityDescription( TPLinkButtonEntityDescription(
key="pan_right", key="pan_right",
available_fn=lambda dev: dev.is_on,
), ),
TPLinkButtonEntityDescription( TPLinkButtonEntityDescription(
key="tilt_up", key="tilt_up",
available_fn=lambda dev: dev.is_on,
), ),
TPLinkButtonEntityDescription( TPLinkButtonEntityDescription(
key="tilt_down", key="tilt_down",
available_fn=lambda dev: dev.is_on,
), ),
] ]
@ -100,5 +104,6 @@ class TPLinkButtonEntity(CoordinatedTPLinkFeatureEntity, ButtonEntity):
"""Execute action.""" """Execute action."""
await self._feature.set_value(True) await self._feature.set_value(True)
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""No need to update anything.""" """No need to update anything."""
return self.entity_description.available_fn(self._device)

View File

@ -40,6 +40,7 @@ CAMERA_DESCRIPTIONS: tuple[TPLinkCameraEntityDescription, ...] = (
TPLinkCameraEntityDescription( TPLinkCameraEntityDescription(
key="live_view", key="live_view",
translation_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}" return f"{legacy_device_id(self._device)}-{self.entity_description.key}"
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_is_on = self._camera_module.is_on self._attr_is_on = self._camera_module.is_on
return self.entity_description.available_fn(self._device)
async def stream_source(self) -> str | None: async def stream_source(self) -> str | None:
"""Return the source of the stream.""" """Return the source of the stream."""

View File

@ -113,7 +113,7 @@ class TPLinkClimateEntity(CoordinatedTPLinkEntity, ClimateEntity):
await self._state_feature.set_value(False) await self._state_feature.set_value(False)
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_current_temperature = cast(float | None, self._temp_feature.value) self._attr_current_temperature = cast(float | None, self._temp_feature.value)
self._attr_target_temperature = cast(float | None, self._target_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._mode_feature.value,
) )
self._attr_hvac_action = HVACAction.OFF self._attr_hvac_action = HVACAction.OFF
return return True
self._attr_hvac_action = STATE_TO_ACTION[ self._attr_hvac_action = STATE_TO_ACTION[
cast(ThermostatState, self._mode_feature.value) cast(ThermostatState, self._mode_feature.value)
] ]
return True
def _get_unique_id(self) -> str: def _get_unique_id(self) -> str:
"""Return unique id.""" """Return unique id."""

View File

@ -89,6 +89,7 @@ class TPLinkFeatureEntityDescription(EntityDescription):
"""Base class for a TPLink feature based entity description.""" """Base class for a TPLink feature based entity description."""
deprecated_info: DeprecatedInfo | None = None deprecated_info: DeprecatedInfo | None = None
available_fn: Callable[[Device], bool] = lambda _: True
@dataclass(frozen=True, kw_only=True) @dataclass(frozen=True, kw_only=True)
@ -96,6 +97,7 @@ class TPLinkModuleEntityDescription(EntityDescription):
"""Base class for a TPLink module based entity description.""" """Base class for a TPLink module based entity description."""
deprecated_info: DeprecatedInfo | None = None deprecated_info: DeprecatedInfo | None = None
available_fn: Callable[[Device], bool] = lambda _: True
def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P]( def async_refresh_after[_T: CoordinatedTPLinkEntity, **_P](
@ -207,15 +209,18 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
@abstractmethod @abstractmethod
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Platforms implement this to update the entity internals.""" """Platforms implement this to update the entity internals.
The return value is used to the set the entity available attribute.
"""
raise NotImplementedError raise NotImplementedError
@callback @callback
def _async_call_update_attrs(self) -> None: def _async_call_update_attrs(self) -> None:
"""Call update_attrs and make entity unavailable on errors.""" """Call update_attrs and make entity unavailable on errors."""
try: try:
self._async_update_attrs() available = self._async_update_attrs()
except Exception as ex: # noqa: BLE001 except Exception as ex: # noqa: BLE001
if self._attr_available: if self._attr_available:
_LOGGER.warning( _LOGGER.warning(
@ -226,7 +231,7 @@ class CoordinatedTPLinkEntity(CoordinatorEntity[TPLinkDataUpdateCoordinator], AB
) )
self._attr_available = False self._attr_available = False
else: else:
self._attr_available = True self._attr_available = available
@callback @callback
def _handle_coordinator_update(self) -> None: def _handle_coordinator_update(self) -> None:

View File

@ -106,7 +106,7 @@ class TPLinkFanEntity(CoordinatedTPLinkEntity, FanEntity):
await self.fan_module.set_fan_speed_level(value_in_range) await self.fan_module.set_fan_speed_level(value_in_range)
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
fan_speed = self.fan_module.fan_speed_level fan_speed = self.fan_module.fan_speed_level
self._attr_is_on = fan_speed != 0 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) self._attr_percentage = ranged_value_to_percentage(SPEED_RANGE, fan_speed)
else: else:
self._attr_percentage = None self._attr_percentage = None
return True

View File

@ -335,7 +335,7 @@ class TPLinkLightEntity(CoordinatedTPLinkEntity, LightEntity):
return ColorMode.HS return ColorMode.HS
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
light_module = self._light_module light_module = self._light_module
self._attr_is_on = light_module.state.light_on is True self._attr_is_on = light_module.state.light_on is True
@ -349,6 +349,8 @@ class TPLinkLightEntity(CoordinatedTPLinkEntity, LightEntity):
hue, saturation, _ = light_module.hsv hue, saturation, _ = light_module.hsv
self._attr_hs_color = hue, saturation self._attr_hs_color = hue, saturation
return True
class TPLinkLightEffectEntity(TPLinkLightEntity): class TPLinkLightEffectEntity(TPLinkLightEntity):
"""Representation of a TPLink Smart Light Strip.""" """Representation of a TPLink Smart Light Strip."""
@ -368,7 +370,7 @@ class TPLinkLightEffectEntity(TPLinkLightEntity):
_attr_supported_features = LightEntityFeature.TRANSITION | LightEntityFeature.EFFECT _attr_supported_features = LightEntityFeature.TRANSITION | LightEntityFeature.EFFECT
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
super()._async_update_attrs() super()._async_update_attrs()
effect_module = self._effect_module effect_module = self._effect_module
@ -381,6 +383,7 @@ class TPLinkLightEffectEntity(TPLinkLightEntity):
self._attr_effect_list = effect_list self._attr_effect_list = effect_list
else: else:
self._attr_effect_list = None self._attr_effect_list = None
return True
@async_refresh_after @async_refresh_after
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:

View File

@ -114,6 +114,7 @@ class TPLinkNumberEntity(CoordinatedTPLinkFeatureEntity, NumberEntity):
await self._feature.set_value(int(value)) await self._feature.set_value(int(value))
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_native_value = cast(float | None, self._feature.value) self._attr_native_value = cast(float | None, self._feature.value)
return True

View File

@ -91,6 +91,7 @@ class TPLinkSelectEntity(CoordinatedTPLinkFeatureEntity, SelectEntity):
await self._feature.set_value(option) await self._feature.set_value(option)
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_current_option = cast(str | None, self._feature.value) self._attr_current_option = cast(str | None, self._feature.value)
return True

View File

@ -153,7 +153,7 @@ class TPLinkSensorEntity(CoordinatedTPLinkFeatureEntity, SensorEntity):
entity_description: TPLinkSensorEntityDescription entity_description: TPLinkSensorEntityDescription
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
value = self._feature.value value = self._feature.value
if value is not None and self._feature.precision_hint is not None: 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 # Map to homeassistant units and fallback to upstream one if none found
if (unit := self._feature.unit) is not None: if (unit := self._feature.unit) is not None:
self._attr_native_unit_of_measurement = UNIT_MAPPING.get(unit, unit) self._attr_native_unit_of_measurement = UNIT_MAPPING.get(unit, unit)
return True

View File

@ -56,6 +56,7 @@ class TPLinkSirenEntity(CoordinatedTPLinkEntity, SirenEntity):
await self._alarm_module.stop() await self._alarm_module.stop()
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_is_on = self._alarm_module.active self._attr_is_on = self._alarm_module.active
return True

View File

@ -109,6 +109,7 @@ class TPLinkSwitch(CoordinatedTPLinkFeatureEntity, SwitchEntity):
await self._feature.set_value(False) await self._feature.set_value(False)
@callback @callback
def _async_update_attrs(self) -> None: def _async_update_attrs(self) -> bool:
"""Update the entity's attributes.""" """Update the entity's attributes."""
self._attr_is_on = cast(bool | None, self._feature.value) self._attr_is_on = cast(bool | None, self._feature.value)
return True