Home Assistant Unofficial Reference 2024.12.1
alarm_control_panel.py
Go to the documentation of this file.
1 """Elmax sensor platform."""
2 
3 from __future__ import annotations
4 
5 from elmax_api.exceptions import ElmaxApiError
6 from elmax_api.model.alarm_status import AlarmArmStatus, AlarmStatus
7 from elmax_api.model.command import AreaCommand
8 from elmax_api.model.panel import PanelStatus
9 
11  AlarmControlPanelEntity,
12  AlarmControlPanelEntityFeature,
13  AlarmControlPanelState,
14  CodeFormat,
15 )
16 from homeassistant.config_entries import ConfigEntry
17 from homeassistant.core import HomeAssistant, callback
18 from homeassistant.exceptions import HomeAssistantError, InvalidStateError
19 from homeassistant.helpers.entity_platform import AddEntitiesCallback
20 
21 from .const import DOMAIN
22 from .coordinator import ElmaxCoordinator
23 from .entity import ElmaxEntity
24 
25 
27  hass: HomeAssistant,
28  config_entry: ConfigEntry,
29  async_add_entities: AddEntitiesCallback,
30 ) -> None:
31  """Set up the Elmax area platform."""
32  coordinator: ElmaxCoordinator = hass.data[DOMAIN][config_entry.entry_id]
33  known_devices = set()
34 
35  def _discover_new_devices():
36  panel_status: PanelStatus = coordinator.data
37  # In case the panel is offline, its status will be None. In that case, simply do nothing
38  if panel_status is None:
39  return
40 
41  # Otherwise, add all the entities we found
42  entities = [
43  ElmaxArea(
44  elmax_device=area,
45  panel_version=panel_status.release,
46  coordinator=coordinator,
47  )
48  for area in panel_status.areas
49  if area.endpoint_id not in known_devices
50  ]
51 
52  if entities:
53  async_add_entities(entities)
54  known_devices.update([e.unique_id for e in entities])
55 
56  # Register a listener for the discovery of new devices
57  config_entry.async_on_unload(coordinator.async_add_listener(_discover_new_devices))
58 
59  # Immediately run a discovery, so we don't need to wait for the next update
60  _discover_new_devices()
61 
62 
64  """Elmax Area entity implementation."""
65 
66  _attr_code_format = CodeFormat.NUMBER
67  _attr_code_arm_required = False
68  _attr_has_entity_name = True
69  _attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY
70  _pending_state: AlarmControlPanelState | None = None
71 
72  async def async_alarm_arm_away(self, code: str | None = None) -> None:
73  """Send arm away command."""
74  if self._attr_alarm_state_attr_alarm_state == AlarmStatus.NOT_ARMED_NOT_ARMABLE:
75  raise InvalidStateError(
76  f"Cannot arm {self.name}: please check for open windows/doors first"
77  )
78 
79  self._pending_state_pending_state = AlarmControlPanelState.ARMING
80  self.async_write_ha_stateasync_write_ha_state()
81 
82  try:
83  await self.coordinator.http_client.execute_command(
84  endpoint_id=self._device_device.endpoint_id,
85  command=AreaCommand.ARM_TOTALLY,
86  extra_payload={"code": code},
87  )
88  except ElmaxApiError as err:
89  raise HomeAssistantError(
90  translation_domain=DOMAIN,
91  translation_key="alarm_operation_failed_generic",
92  translation_placeholders={"operation": "arm"},
93  ) from err
94  finally:
95  await self.coordinator.async_refresh()
96 
97  async def async_alarm_disarm(self, code: str | None = None) -> None:
98  """Send disarm command."""
99  # Elmax alarm panels do always require a code to be passed for disarm operations
100  if code is None or code == "":
101  raise ValueError("Please input the disarm code.")
102 
103  self._pending_state_pending_state = AlarmControlPanelState.DISARMING
104  self.async_write_ha_stateasync_write_ha_state()
105 
106  try:
107  await self.coordinator.http_client.execute_command(
108  endpoint_id=self._device_device.endpoint_id,
109  command=AreaCommand.DISARM,
110  extra_payload={"code": code},
111  )
112  except ElmaxApiError as err:
113  if err.status_code == 403:
114  raise HomeAssistantError(
115  translation_domain=DOMAIN, translation_key="invalid_disarm_code"
116  ) from err
117  raise HomeAssistantError(
118  translation_domain=DOMAIN,
119  translation_key="alarm_operation_failed_generic",
120  translation_placeholders={"operation": "disarm"},
121  ) from err
122  finally:
123  await self.coordinator.async_refresh()
124 
125  @property
126  def alarm_state(self) -> AlarmControlPanelState | None:
127  """Return the state of the entity."""
128  if self._pending_state_pending_state is not None:
129  return self._pending_state_pending_state
130  if (
131  state := self.coordinator.get_area_state(self._device_device.endpoint_id)
132  ) is not None:
133  if state.status == AlarmStatus.TRIGGERED:
134  return ALARM_STATE_TO_HA.get(AlarmStatus.TRIGGERED)
135  return ALARM_STATE_TO_HA.get(state.armed_status)
136  return None
137 
138  @callback
139  def _handle_coordinator_update(self) -> None:
140  """Handle updated data from the coordinator."""
141  # Just reset the local pending_state so that it no longer overrides the one from coordinator.
142  self._pending_state_pending_state = None
144 
145 
146 ALARM_STATE_TO_HA = {
147  AlarmArmStatus.ARMED_TOTALLY: AlarmControlPanelState.ARMED_AWAY,
148  AlarmArmStatus.ARMED_P1_P2: AlarmControlPanelState.ARMED_AWAY,
149  AlarmArmStatus.ARMED_P2: AlarmControlPanelState.ARMED_AWAY,
150  AlarmArmStatus.ARMED_P1: AlarmControlPanelState.ARMED_AWAY,
151  AlarmArmStatus.NOT_ARMED: AlarmControlPanelState.DISARMED,
152  AlarmStatus.TRIGGERED: AlarmControlPanelState.TRIGGERED,
153 }
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)