165 lines
5.3 KiB
Python
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
|