Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Component to interface with an alarm control panel."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from datetime import timedelta
7 from functools import partial
8 import logging
9 from typing import TYPE_CHECKING, Any, Final, final
10 
11 from propcache import cached_property
12 import voluptuous as vol
13 
14 from homeassistant.config_entries import ConfigEntry
15 from homeassistant.const import (
16  ATTR_CODE,
17  ATTR_CODE_FORMAT,
18  SERVICE_ALARM_ARM_AWAY,
19  SERVICE_ALARM_ARM_CUSTOM_BYPASS,
20  SERVICE_ALARM_ARM_HOME,
21  SERVICE_ALARM_ARM_NIGHT,
22  SERVICE_ALARM_ARM_VACATION,
23  SERVICE_ALARM_DISARM,
24  SERVICE_ALARM_TRIGGER,
25 )
26 from homeassistant.core import HomeAssistant, callback
27 from homeassistant.exceptions import ServiceValidationError
29 from homeassistant.helpers.config_validation import make_entity_service_schema
31  all_with_deprecated_constants,
32  check_if_deprecated_constant,
33  dir_with_deprecated_constants,
34 )
35 from homeassistant.helpers.entity import Entity, EntityDescription
36 from homeassistant.helpers.entity_component import EntityComponent
37 from homeassistant.helpers.entity_platform import EntityPlatform
38 from homeassistant.helpers.frame import ReportBehavior, report_usage
39 from homeassistant.helpers.typing import ConfigType
40 from homeassistant.util.hass_dict import HassKey
41 
42 from .const import ( # noqa: F401
43  _DEPRECATED_FORMAT_NUMBER,
44  _DEPRECATED_FORMAT_TEXT,
45  _DEPRECATED_SUPPORT_ALARM_ARM_AWAY,
46  _DEPRECATED_SUPPORT_ALARM_ARM_CUSTOM_BYPASS,
47  _DEPRECATED_SUPPORT_ALARM_ARM_HOME,
48  _DEPRECATED_SUPPORT_ALARM_ARM_NIGHT,
49  _DEPRECATED_SUPPORT_ALARM_ARM_VACATION,
50  _DEPRECATED_SUPPORT_ALARM_TRIGGER,
51  ATTR_CHANGED_BY,
52  ATTR_CODE_ARM_REQUIRED,
53  DOMAIN,
54  AlarmControlPanelEntityFeature,
55  AlarmControlPanelState,
56  CodeFormat,
57 )
58 
59 _LOGGER: Final = logging.getLogger(__name__)
60 
61 DATA_COMPONENT: HassKey[EntityComponent[AlarmControlPanelEntity]] = HassKey(DOMAIN)
62 ENTITY_ID_FORMAT: Final = DOMAIN + ".{}"
63 PLATFORM_SCHEMA: Final = cv.PLATFORM_SCHEMA
64 PLATFORM_SCHEMA_BASE: Final = cv.PLATFORM_SCHEMA_BASE
65 SCAN_INTERVAL: Final = timedelta(seconds=30)
66 
67 CONF_DEFAULT_CODE = "default_code"
68 
69 ALARM_SERVICE_SCHEMA: Final = make_entity_service_schema(
70  {vol.Optional(ATTR_CODE): cv.string}
71 )
72 
73 
74 # mypy: disallow-any-generics
75 
76 
77 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
78  """Track states and offer events for sensors."""
79  component = hass.data[DATA_COMPONENT] = EntityComponent[AlarmControlPanelEntity](
80  _LOGGER, DOMAIN, hass, SCAN_INTERVAL
81  )
82 
83  await component.async_setup(config)
84 
85  component.async_register_entity_service(
86  SERVICE_ALARM_DISARM,
87  ALARM_SERVICE_SCHEMA,
88  "async_handle_alarm_disarm",
89  )
90  component.async_register_entity_service(
91  SERVICE_ALARM_ARM_HOME,
92  ALARM_SERVICE_SCHEMA,
93  "async_handle_alarm_arm_home",
94  [AlarmControlPanelEntityFeature.ARM_HOME],
95  )
96  component.async_register_entity_service(
97  SERVICE_ALARM_ARM_AWAY,
98  ALARM_SERVICE_SCHEMA,
99  "async_handle_alarm_arm_away",
100  [AlarmControlPanelEntityFeature.ARM_AWAY],
101  )
102  component.async_register_entity_service(
103  SERVICE_ALARM_ARM_NIGHT,
104  ALARM_SERVICE_SCHEMA,
105  "async_handle_alarm_arm_night",
106  [AlarmControlPanelEntityFeature.ARM_NIGHT],
107  )
108  component.async_register_entity_service(
109  SERVICE_ALARM_ARM_VACATION,
110  ALARM_SERVICE_SCHEMA,
111  "async_handle_alarm_arm_vacation",
112  [AlarmControlPanelEntityFeature.ARM_VACATION],
113  )
114  component.async_register_entity_service(
115  SERVICE_ALARM_ARM_CUSTOM_BYPASS,
116  ALARM_SERVICE_SCHEMA,
117  "async_handle_alarm_arm_custom_bypass",
118  [AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS],
119  )
120  component.async_register_entity_service(
121  SERVICE_ALARM_TRIGGER,
122  ALARM_SERVICE_SCHEMA,
123  "async_alarm_trigger",
124  [AlarmControlPanelEntityFeature.TRIGGER],
125  )
126 
127  return True
128 
129 
130 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
131  """Set up a config entry."""
132  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
133 
134 
135 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
136  """Unload a config entry."""
137  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
138 
139 
141  """A class that describes alarm control panel entities."""
142 
143 
144 CACHED_PROPERTIES_WITH_ATTR_ = {
145  "code_format",
146  "changed_by",
147  "code_arm_required",
148  "supported_features",
149  "alarm_state",
150 }
151 
152 
153 class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
154  """An abstract class for alarm control entities."""
155 
156  entity_description: AlarmControlPanelEntityDescription
157  _attr_alarm_state: AlarmControlPanelState | None = None
158  _attr_changed_by: str | None = None
159  _attr_code_arm_required: bool = True
160  _attr_code_format: CodeFormat | None = None
161  _attr_supported_features: AlarmControlPanelEntityFeature = (
163  )
164  _alarm_control_panel_option_default_code: str | None = None
165 
166  __alarm_legacy_state: bool = False
167 
168  def __init_subclass__(cls, **kwargs: Any) -> None:
169  """Post initialisation processing."""
170  super().__init_subclass__(**kwargs)
171  if any(method in cls.__dict__ for method in ("_attr_state", "state")):
172  # Integrations should use the 'alarm_state' property instead of
173  # setting the state directly.
174  cls.__alarm_legacy_state__alarm_legacy_state = True
175 
176  def __setattr__(self, name: str, value: Any, /) -> None:
177  """Set attribute.
178 
179  Deprecation warning if setting '_attr_state' directly
180  unless already reported.
181  """
182  if name == "_attr_state":
183  self._report_deprecated_alarm_state_handling_report_deprecated_alarm_state_handling()
184  return super().__setattr__(name, value)
185 
186  @callback
188  self,
189  hass: HomeAssistant,
190  platform: EntityPlatform,
191  parallel_updates: asyncio.Semaphore | None,
192  ) -> None:
193  """Start adding an entity to a platform."""
194  super().add_to_platform_start(hass, platform, parallel_updates)
195  if self.__alarm_legacy_state__alarm_legacy_state:
196  self._report_deprecated_alarm_state_handling_report_deprecated_alarm_state_handling()
197 
198  @callback
200  """Report on deprecated handling of alarm state.
201 
202  Integrations should implement alarm_state instead of using state directly.
203  """
204  report_usage(
205  "is setting state directly."
206  f" Entity {self.entity_id} ({type(self)}) should implement the 'alarm_state'"
207  " property and return its state using the AlarmControlPanelState enum",
208  core_integration_behavior=ReportBehavior.ERROR,
209  custom_integration_behavior=ReportBehavior.LOG,
210  breaks_in_ha_version="2025.11",
211  integration_domain=self.platformplatform.platform_name if self.platformplatform else None,
212  exclude_integrations={DOMAIN},
213  )
214 
215  @final
216  @property
217  def state(self) -> str | None:
218  """Return the current state."""
219  if (alarm_state := self.alarm_statealarm_state) is not None:
220  return alarm_state
221  if self._attr_state is not None:
222  # Backwards compatibility for integrations that set state directly
223  # Should be removed in 2025.11
224  if TYPE_CHECKING:
225  assert isinstance(self._attr_state, str)
226  return self._attr_state
227  return None
228 
229  @cached_property
230  def alarm_state(self) -> AlarmControlPanelState | None:
231  """Return the current alarm control panel entity state.
232 
233  Integrations should overwrite this or use the '_attr_alarm_state'
234  attribute to set the alarm status using the 'AlarmControlPanelState' enum.
235  """
236  return self._attr_alarm_state
237 
238  @final
239  @callback
240  def code_or_default_code(self, code: str | None) -> str | None:
241  """Return code to use for a service call.
242 
243  If the passed in code is not None, it will be returned. Otherwise return the
244  default code, if set, or None if not set, is returned.
245  """
246  if code:
247  # Return code provided by user
248  return code
249  # Fallback to default code or None if not set
250  return self._alarm_control_panel_option_default_code_alarm_control_panel_option_default_code
251 
252  @cached_property
253  def code_format(self) -> CodeFormat | None:
254  """Code format or None if no code is required."""
255  return self._attr_code_format
256 
257  @cached_property
258  def changed_by(self) -> str | None:
259  """Last change triggered by."""
260  return self._attr_changed_by
261 
262  @cached_property
263  def code_arm_required(self) -> bool:
264  """Whether the code is required for arm actions."""
265  return self._attr_code_arm_required
266 
267  @final
268  @callback
269  def check_code_arm_required(self, code: str | None) -> str | None:
270  """Check if arm code is required, raise if no code is given."""
271  if not (_code := self.code_or_default_codecode_or_default_code(code)) and self.code_arm_requiredcode_arm_required:
273  translation_domain=DOMAIN,
274  translation_key="code_arm_required",
275  translation_placeholders={
276  "entity_id": self.entity_identity_id,
277  },
278  )
279  return _code
280 
281  @final
282  async def async_handle_alarm_disarm(self, code: str | None = None) -> None:
283  """Add default code and disarm."""
284  await self.async_alarm_disarmasync_alarm_disarm(self.code_or_default_codecode_or_default_code(code))
285 
286  def alarm_disarm(self, code: str | None = None) -> None:
287  """Send disarm command."""
288  raise NotImplementedError
289 
290  async def async_alarm_disarm(self, code: str | None = None) -> None:
291  """Send disarm command."""
292  await self.hasshass.async_add_executor_job(self.alarm_disarmalarm_disarm, code)
293 
294  @final
295  async def async_handle_alarm_arm_home(self, code: str | None = None) -> None:
296  """Add default code and arm home."""
297  await self.async_alarm_arm_homeasync_alarm_arm_home(self.check_code_arm_requiredcheck_code_arm_required(code))
298 
299  def alarm_arm_home(self, code: str | None = None) -> None:
300  """Send arm home command."""
301  raise NotImplementedError
302 
303  async def async_alarm_arm_home(self, code: str | None = None) -> None:
304  """Send arm home command."""
305  await self.hasshass.async_add_executor_job(self.alarm_arm_homealarm_arm_home, code)
306 
307  @final
308  async def async_handle_alarm_arm_away(self, code: str | None = None) -> None:
309  """Add default code and arm away."""
310  await self.async_alarm_arm_awayasync_alarm_arm_away(self.check_code_arm_requiredcheck_code_arm_required(code))
311 
312  def alarm_arm_away(self, code: str | None = None) -> None:
313  """Send arm away command."""
314  raise NotImplementedError
315 
316  async def async_alarm_arm_away(self, code: str | None = None) -> None:
317  """Send arm away command."""
318  await self.hasshass.async_add_executor_job(self.alarm_arm_awayalarm_arm_away, code)
319 
320  @final
321  async def async_handle_alarm_arm_night(self, code: str | None = None) -> None:
322  """Add default code and arm night."""
323  await self.async_alarm_arm_nightasync_alarm_arm_night(self.check_code_arm_requiredcheck_code_arm_required(code))
324 
325  def alarm_arm_night(self, code: str | None = None) -> None:
326  """Send arm night command."""
327  raise NotImplementedError
328 
329  async def async_alarm_arm_night(self, code: str | None = None) -> None:
330  """Send arm night command."""
331  await self.hasshass.async_add_executor_job(self.alarm_arm_nightalarm_arm_night, code)
332 
333  @final
334  async def async_handle_alarm_arm_vacation(self, code: str | None = None) -> None:
335  """Add default code and arm vacation."""
336  await self.async_alarm_arm_vacationasync_alarm_arm_vacation(self.check_code_arm_requiredcheck_code_arm_required(code))
337 
338  def alarm_arm_vacation(self, code: str | None = None) -> None:
339  """Send arm vacation command."""
340  raise NotImplementedError
341 
342  async def async_alarm_arm_vacation(self, code: str | None = None) -> None:
343  """Send arm vacation command."""
344  await self.hasshass.async_add_executor_job(self.alarm_arm_vacationalarm_arm_vacation, code)
345 
346  def alarm_trigger(self, code: str | None = None) -> None:
347  """Send alarm trigger command."""
348  raise NotImplementedError
349 
350  async def async_alarm_trigger(self, code: str | None = None) -> None:
351  """Send alarm trigger command."""
352  await self.hasshass.async_add_executor_job(self.alarm_triggeralarm_trigger, code)
353 
354  @final
356  self, code: str | None = None
357  ) -> None:
358  """Add default code and arm custom bypass."""
359  await self.async_alarm_arm_custom_bypassasync_alarm_arm_custom_bypass(self.check_code_arm_requiredcheck_code_arm_required(code))
360 
361  def alarm_arm_custom_bypass(self, code: str | None = None) -> None:
362  """Send arm custom bypass command."""
363  raise NotImplementedError
364 
365  async def async_alarm_arm_custom_bypass(self, code: str | None = None) -> None:
366  """Send arm custom bypass command."""
367  await self.hasshass.async_add_executor_job(self.alarm_arm_custom_bypassalarm_arm_custom_bypass, code)
368 
369  @cached_property
370  def supported_features(self) -> AlarmControlPanelEntityFeature:
371  """Return the list of supported features."""
372  features = self._attr_supported_features
373  if type(features) is int: # noqa: E721
374  new_features = AlarmControlPanelEntityFeature(features)
375  self._report_deprecated_supported_features_values_report_deprecated_supported_features_values(new_features)
376  return new_features
377  return features
378 
379  @final
380  @property
381  def state_attributes(self) -> dict[str, Any] | None:
382  """Return the state attributes."""
383  return {
384  ATTR_CODE_FORMAT: self.code_formatcode_format,
385  ATTR_CHANGED_BY: self.changed_bychanged_by,
386  ATTR_CODE_ARM_REQUIRED: self.code_arm_requiredcode_arm_required,
387  }
388 
389  async def async_internal_added_to_hass(self) -> None:
390  """Call when the alarm control panel entity is added to hass."""
391  await super().async_internal_added_to_hass()
392  if not self.registry_entryregistry_entry:
393  return
394  self._async_read_entity_options_async_read_entity_options()
395 
396  @callback
397  def async_registry_entry_updated(self) -> None:
398  """Run when the entity registry entry has been updated."""
399  self._async_read_entity_options_async_read_entity_options()
400 
401  @callback
402  def _async_read_entity_options(self) -> None:
403  """Read entity options from entity registry.
404 
405  Called when the entity registry entry has been updated and before the
406  alarm control panel is added to the state machine.
407  """
408  assert self.registry_entryregistry_entry
409  if (alarm_options := self.registry_entryregistry_entry.options.get(DOMAIN)) and (
410  default_code := alarm_options.get(CONF_DEFAULT_CODE)
411  ):
412  self._alarm_control_panel_option_default_code_alarm_control_panel_option_default_code = default_code
413  return
414  self._alarm_control_panel_option_default_code_alarm_control_panel_option_default_code = None
415 
416 
417 # As we import constants of the const module here, we need to add the following
418 # functions to check for deprecated constants again
419 # These can be removed if no deprecated constant are in this module anymore
420 __getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
421 __dir__ = partial(
422  dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
423 )
424 __all__ = all_with_deprecated_constants(globals())
None add_to_platform_start(self, HomeAssistant hass, EntityPlatform platform, asyncio.Semaphore|None parallel_updates)
Definition: __init__.py:192
None _report_deprecated_supported_features_values(self, IntFlag replacement)
Definition: entity.py:1645
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:135
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:77
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:130
VolSchemaType make_entity_service_schema(dict|None schema, *int extra=vol.PREVENT_EXTRA)
list[str] all_with_deprecated_constants(dict[str, Any] module_globals)
Definition: deprecation.py:356
None report_usage(str what, *str|None breaks_in_ha_version=None, ReportBehavior core_behavior=ReportBehavior.ERROR, ReportBehavior core_integration_behavior=ReportBehavior.LOG, ReportBehavior custom_integration_behavior=ReportBehavior.LOG, set[str]|None exclude_integrations=None, str|None integration_domain=None, int level=logging.WARNING)
Definition: frame.py:195