core/homeassistant/components/tplink/siren.py

165 lines
5.3 KiB
Python

"""Support for TPLink siren entity."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import math
from typing import TYPE_CHECKING, Any, cast
from kasa import Device, Module
from homeassistant.components.siren import (
ATTR_DURATION,
ATTR_TONE,
ATTR_VOLUME_LEVEL,
DOMAIN as SIREN_DOMAIN,
SirenEntity,
SirenEntityDescription,
SirenEntityFeature,
SirenTurnOnServiceParameters,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import TPLinkConfigEntry, legacy_device_id
from .const import DOMAIN
from .coordinator import TPLinkDataUpdateCoordinator
from .entity import (
CoordinatedTPLinkModuleEntity,
TPLinkModuleEntityDescription,
async_refresh_after,
)
# Coordinator is used to centralize the data updates
# For actions the integration handles locking of concurrent device request
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class TPLinkSirenEntityDescription(
SirenEntityDescription, TPLinkModuleEntityDescription
):
"""Base class for siren entity description."""
unique_id_fn: Callable[[Device, TPLinkModuleEntityDescription], str] = (
lambda device, desc: legacy_device_id(device)
if desc.key == "siren"
else f"{legacy_device_id(device)}-{desc.key}"
)
SIREN_DESCRIPTIONS: tuple[TPLinkSirenEntityDescription, ...] = (
TPLinkSirenEntityDescription(
key="siren",
exists_fn=lambda dev, _: Module.Alarm in dev.modules,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: TPLinkConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up siren entities."""
data = config_entry.runtime_data
parent_coordinator = data.parent_coordinator
device = parent_coordinator.device
known_child_device_ids: set[str] = set()
first_check = True
def _check_device() -> None:
entities = CoordinatedTPLinkModuleEntity.entities_for_device_and_its_children(
hass=hass,
device=device,
coordinator=parent_coordinator,
entity_class=TPLinkSirenEntity,
descriptions=SIREN_DESCRIPTIONS,
platform_domain=SIREN_DOMAIN,
known_child_device_ids=known_child_device_ids,
first_check=first_check,
)
async_add_entities(entities)
_check_device()
first_check = False
config_entry.async_on_unload(parent_coordinator.async_add_listener(_check_device))
class TPLinkSirenEntity(CoordinatedTPLinkModuleEntity, SirenEntity):
"""Representation of a tplink siren entity."""
_attr_name = None
_attr_supported_features = (
SirenEntityFeature.TURN_OFF
| SirenEntityFeature.TURN_ON
| SirenEntityFeature.TONES
| SirenEntityFeature.DURATION
| SirenEntityFeature.VOLUME_SET
)
entity_description: TPLinkSirenEntityDescription
def __init__(
self,
device: Device,
coordinator: TPLinkDataUpdateCoordinator,
description: TPLinkSirenEntityDescription,
*,
parent: Device | None = None,
) -> None:
"""Initialize the siren entity."""
super().__init__(device, coordinator, description, parent=parent)
self._alarm_module = device.modules[Module.Alarm]
alarm_vol_feat = self._alarm_module.get_feature("alarm_volume")
alarm_duration_feat = self._alarm_module.get_feature("alarm_duration")
if TYPE_CHECKING:
assert alarm_vol_feat
assert alarm_duration_feat
self._alarm_volume_max = alarm_vol_feat.maximum_value
self._alarm_duration_max = alarm_duration_feat.maximum_value
@async_refresh_after
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the siren on."""
turn_on_params = cast(SirenTurnOnServiceParameters, kwargs)
if (volume := kwargs.get(ATTR_VOLUME_LEVEL)) is not None:
# service parameter is a % so we round up to the nearest int
volume = math.ceil(volume * self._alarm_volume_max)
if (duration := kwargs.get(ATTR_DURATION)) is not None:
if duration < 1 or duration > self._alarm_duration_max:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_alarm_duration",
translation_placeholders={
"duration": str(duration),
"duration_max": str(self._alarm_duration_max),
},
)
await self._alarm_module.play(
duration=turn_on_params.get(ATTR_DURATION),
volume=volume,
sound=kwargs.get(ATTR_TONE),
)
@async_refresh_after
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the siren off."""
await self._alarm_module.stop()
@callback
def _async_update_attrs(self) -> bool:
"""Update the entity's attributes."""
self._attr_is_on = self._alarm_module.active
# alarm_sounds returns list[str], so we need to widen the type
self._attr_available_tones = cast(
list[str | int], self._alarm_module.alarm_sounds
)
return True