Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Component to interface with various sirens/chimes."""
2 
3 from __future__ import annotations
4 
5 from datetime import timedelta
6 from functools import partial
7 import logging
8 from typing import Any, TypedDict, cast, final
9 
10 from propcache import cached_property
11 import voluptuous as vol
12 
13 from homeassistant.config_entries import ConfigEntry
14 from homeassistant.const import SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON
15 from homeassistant.core import HomeAssistant, ServiceCall
16 from homeassistant.helpers import config_validation as cv
18  all_with_deprecated_constants,
19  check_if_deprecated_constant,
20  dir_with_deprecated_constants,
21 )
22 from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
23 from homeassistant.helpers.entity_component import EntityComponent
24 from homeassistant.helpers.typing import ConfigType, VolDictType
25 from homeassistant.util.hass_dict import HassKey
26 
27 from .const import ( # noqa: F401
28  _DEPRECATED_SUPPORT_DURATION,
29  _DEPRECATED_SUPPORT_TONES,
30  _DEPRECATED_SUPPORT_TURN_OFF,
31  _DEPRECATED_SUPPORT_TURN_ON,
32  _DEPRECATED_SUPPORT_VOLUME_SET,
33  ATTR_AVAILABLE_TONES,
34  ATTR_DURATION,
35  ATTR_TONE,
36  ATTR_VOLUME_LEVEL,
37  DOMAIN,
38  SirenEntityFeature,
39 )
40 
41 _LOGGER = logging.getLogger(__name__)
42 
43 DATA_COMPONENT: HassKey[EntityComponent[SirenEntity]] = HassKey(DOMAIN)
44 PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA
45 PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE
46 SCAN_INTERVAL = timedelta(seconds=60)
47 
48 TURN_ON_SCHEMA: VolDictType = {
49  vol.Optional(ATTR_TONE): vol.Any(vol.Coerce(int), cv.string),
50  vol.Optional(ATTR_DURATION): cv.positive_int,
51  vol.Optional(ATTR_VOLUME_LEVEL): cv.small_float,
52 }
53 
54 
55 class SirenTurnOnServiceParameters(TypedDict, total=False):
56  """Represent possible parameters to siren.turn_on service data dict type."""
57 
58  tone: int | str
59  duration: int
60  volume_level: float
61 
62 
63 # mypy: disallow-any-generics
64 
65 
67  siren: SirenEntity, params: SirenTurnOnServiceParameters
68 ) -> SirenTurnOnServiceParameters:
69  """Process turn_on service params.
70 
71  Filters out unsupported params and validates the rest.
72  """
73 
74  if not siren.supported_features & SirenEntityFeature.TONES:
75  params.pop(ATTR_TONE, None)
76  elif (tone := params.get(ATTR_TONE)) is not None:
77  # Raise an exception if the specified tone isn't available
78  is_tone_dict_value = bool(
79  isinstance(siren.available_tones, dict)
80  and tone in siren.available_tones.values()
81  )
82  if (
83  not siren.available_tones
84  or tone not in siren.available_tones
85  and not is_tone_dict_value
86  ):
87  raise ValueError(
88  f"Invalid tone specified for entity {siren.entity_id}: {tone}, "
89  "check the available_tones attribute for valid tones to pass in"
90  )
91 
92  # If available tones is a dict, and the tone provided is a dict value, we need
93  # to transform it to the corresponding dict key before returning
94  if is_tone_dict_value:
95  assert isinstance(siren.available_tones, dict)
96  params[ATTR_TONE] = next(
97  key for key, value in siren.available_tones.items() if value == tone
98  )
99 
100  if not siren.supported_features & SirenEntityFeature.DURATION:
101  params.pop(ATTR_DURATION, None)
102  if not siren.supported_features & SirenEntityFeature.VOLUME_SET:
103  params.pop(ATTR_VOLUME_LEVEL, None)
104 
105  return params
106 
107 
108 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
109  """Set up siren devices."""
110  component = hass.data[DATA_COMPONENT] = EntityComponent[SirenEntity](
111  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
112  )
113  await component.async_setup(config)
114 
115  async def async_handle_turn_on_service(
116  siren: SirenEntity, call: ServiceCall
117  ) -> None:
118  """Handle turning a siren on."""
119  data = {
120  k: v
121  for k, v in call.data.items()
122  if k in (ATTR_TONE, ATTR_DURATION, ATTR_VOLUME_LEVEL)
123  }
124  await siren.async_turn_on(
125  **process_turn_on_params(siren, cast(SirenTurnOnServiceParameters, data))
126  )
127 
128  component.async_register_entity_service(
129  SERVICE_TURN_ON,
130  TURN_ON_SCHEMA,
131  async_handle_turn_on_service,
132  [SirenEntityFeature.TURN_ON],
133  )
134  component.async_register_entity_service(
135  SERVICE_TURN_OFF, None, "async_turn_off", [SirenEntityFeature.TURN_OFF]
136  )
137  component.async_register_entity_service(
138  SERVICE_TOGGLE,
139  None,
140  "async_toggle",
141  [SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF],
142  )
143 
144  return True
145 
146 
147 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
148  """Set up a config entry."""
149  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
150 
151 
152 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
153  """Unload a config entry."""
154  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
155 
156 
157 class SirenEntityDescription(ToggleEntityDescription, frozen_or_thawed=True):
158  """A class that describes siren entities."""
159 
160  available_tones: list[int | str] | dict[int, str] | None = None
161 
162 
163 CACHED_PROPERTIES_WITH_ATTR_ = {
164  "available_tones",
165  "supported_features",
166 }
167 
168 
169 class SirenEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
170  """Representation of a siren device."""
171 
172  _entity_component_unrecorded_attributes = frozenset({ATTR_AVAILABLE_TONES})
173 
174  entity_description: SirenEntityDescription
175  _attr_available_tones: list[int | str] | dict[int, str] | None
176  _attr_supported_features: SirenEntityFeature = SirenEntityFeature(0)
177 
178  @final
179  @property
180  def capability_attributes(self) -> dict[str, Any] | None:
181  """Return capability attributes."""
182  if (
183  self.supported_featuressupported_featuressupported_features & SirenEntityFeature.TONES
184  and self.available_tonesavailable_tones is not None
185  ):
186  return {ATTR_AVAILABLE_TONES: self.available_tonesavailable_tones}
187 
188  return None
189 
190  @cached_property
191  def available_tones(self) -> list[int | str] | dict[int, str] | None:
192  """Return a list of available tones.
193 
194  Requires SirenEntityFeature.TONES.
195  """
196  if hasattr(self, "_attr_available_tones"):
197  return self._attr_available_tones
198  if hasattr(self, "entity_description"):
199  return self.entity_description.available_tones
200  return None
201 
202  @cached_property
203  def supported_features(self) -> SirenEntityFeature:
204  """Return the list of supported features."""
205  features = self._attr_supported_features
206  if type(features) is int: # noqa: E721
207  new_features = SirenEntityFeature(features)
208  self._report_deprecated_supported_features_values_report_deprecated_supported_features_values(new_features)
209  return new_features
210  return features
211 
212 
213 # As we import deprecated constants from the const module, we need to add these two functions
214 # otherwise this module will be logged for using deprecated constants and not the custom component
215 # These can be removed if no deprecated constant are in this module anymore
216 __getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
217 __dir__ = partial(
218  dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
219 )
220 __all__ = all_with_deprecated_constants(globals())
SirenEntityFeature supported_features(self)
Definition: __init__.py:203
dict[str, Any]|None capability_attributes(self)
Definition: __init__.py:180
list[int|str]|dict[int, str]|None available_tones(self)
Definition: __init__.py:191
None _report_deprecated_supported_features_values(self, IntFlag replacement)
Definition: entity.py:1645
int|None supported_features(self)
Definition: entity.py:861
SirenTurnOnServiceParameters process_turn_on_params(SirenEntity siren, SirenTurnOnServiceParameters params)
Definition: __init__.py:68
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:108
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:152
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:147
list[str] all_with_deprecated_constants(dict[str, Any] module_globals)
Definition: deprecation.py:356