"""Component to interface with various sirens/chimes.""" from __future__ import annotations from datetime import timedelta import logging from typing import Any, TypedDict, cast, final import voluptuous as vol from homeassistant.config_entries import ConfigEntry from homeassistant.const import SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON from homeassistant.core import ServiceCall import homeassistant.helpers.config_validation as cv from homeassistant.helpers.config_validation import ( # noqa: F401 PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) from homeassistant.helpers.entity import ToggleEntity from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType, HomeAssistantType from .const import ( ATTR_AVAILABLE_TONES, ATTR_DURATION, ATTR_TONE, ATTR_VOLUME_LEVEL, DOMAIN, SUPPORT_DURATION, SUPPORT_TONES, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_SET, ) _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=60) TURN_ON_SCHEMA = { vol.Optional(ATTR_TONE): vol.Any(vol.Coerce(int), cv.string), vol.Optional(ATTR_DURATION): cv.positive_int, vol.Optional(ATTR_VOLUME_LEVEL): cv.small_float, } class SirenTurnOnServiceParameters(TypedDict, total=False): """Represent possible parameters to siren.turn_on service data dict type.""" tone: int | str duration: int volume_level: float def process_turn_on_params( siren: SirenEntity, params: SirenTurnOnServiceParameters ) -> SirenTurnOnServiceParameters: """ Process turn_on service params. Filters out unsupported params and validates the rest. """ supported_features = siren.supported_features or 0 if not supported_features & SUPPORT_TONES: params.pop(ATTR_TONE, None) elif (tone := params.get(ATTR_TONE)) is not None and ( not siren.available_tones or tone not in siren.available_tones ): raise ValueError(f"Invalid tone received for entity {siren.entity_id}: {tone}") if not supported_features & SUPPORT_DURATION: params.pop(ATTR_DURATION, None) if not supported_features & SUPPORT_VOLUME_SET: params.pop(ATTR_VOLUME_LEVEL, None) return params async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: """Set up siren devices.""" component = hass.data[DOMAIN] = EntityComponent( _LOGGER, DOMAIN, hass, SCAN_INTERVAL ) await component.async_setup(config) async def async_handle_turn_on_service( siren: SirenEntity, call: ServiceCall ) -> None: """Handle turning a siren on.""" data = { k: v for k, v in call.data.items() if k in (ATTR_TONE, ATTR_DURATION, ATTR_VOLUME_LEVEL) } await siren.async_turn_on( **process_turn_on_params(siren, cast(SirenTurnOnServiceParameters, data)) ) component.async_register_entity_service( SERVICE_TURN_ON, TURN_ON_SCHEMA, async_handle_turn_on_service, [SUPPORT_TURN_ON] ) component.async_register_entity_service( SERVICE_TURN_OFF, {}, "async_turn_off", [SUPPORT_TURN_OFF] ) component.async_register_entity_service( SERVICE_TOGGLE, {}, "async_toggle", [SUPPORT_TURN_ON & SUPPORT_TURN_OFF] ) return True async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Set up a config entry.""" component: EntityComponent = hass.data[DOMAIN] return await component.async_setup_entry(entry) async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: """Unload a config entry.""" component: EntityComponent = hass.data[DOMAIN] return await component.async_unload_entry(entry) class SirenEntity(ToggleEntity): """Representation of a siren device.""" _attr_available_tones: list[int | str] | None = None @final @property def capability_attributes(self) -> dict[str, Any] | None: """Return capability attributes.""" supported_features = self.supported_features or 0 if supported_features & SUPPORT_TONES and self.available_tones is not None: return {ATTR_AVAILABLE_TONES: self.available_tones} return None @property def available_tones(self) -> list[int | str] | None: """ Return a list of available tones. Requires SUPPORT_TONES. """ return self._attr_available_tones