Home Assistant Unofficial Reference 2024.12.1
alarm_control_panel.py
Go to the documentation of this file.
1 """Support for Risco alarms."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 import logging
7 from typing import Any
8 
9 from pyrisco.common import Partition
10 from pyrisco.local.partition import Partition as LocalPartition
11 
13  AlarmControlPanelEntity,
14  AlarmControlPanelEntityFeature,
15  AlarmControlPanelState,
16  CodeFormat,
17 )
18 from homeassistant.config_entries import ConfigEntry
19 from homeassistant.const import CONF_PIN
20 from homeassistant.core import HomeAssistant
21 from homeassistant.helpers.device_registry import DeviceInfo
22 from homeassistant.helpers.entity_platform import AddEntitiesCallback
23 
24 from . import LocalData, is_local
25 from .const import (
26  CONF_CODE_ARM_REQUIRED,
27  CONF_CODE_DISARM_REQUIRED,
28  CONF_HA_STATES_TO_RISCO,
29  CONF_RISCO_STATES_TO_HA,
30  DATA_COORDINATOR,
31  DEFAULT_OPTIONS,
32  DOMAIN,
33  RISCO_ARM,
34  RISCO_GROUPS,
35  RISCO_PARTIAL_ARM,
36 )
37 from .coordinator import RiscoDataUpdateCoordinator
38 from .entity import RiscoCloudEntity
39 
40 _LOGGER = logging.getLogger(__name__)
41 
42 STATES_TO_SUPPORTED_FEATURES = {
43  AlarmControlPanelState.ARMED_AWAY: AlarmControlPanelEntityFeature.ARM_AWAY,
44  AlarmControlPanelState.ARMED_CUSTOM_BYPASS: AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS,
45  AlarmControlPanelState.ARMED_HOME: AlarmControlPanelEntityFeature.ARM_HOME,
46  AlarmControlPanelState.ARMED_NIGHT: AlarmControlPanelEntityFeature.ARM_NIGHT,
47 }
48 
49 
51  hass: HomeAssistant,
52  config_entry: ConfigEntry,
53  async_add_entities: AddEntitiesCallback,
54 ) -> None:
55  """Set up the Risco alarm control panel."""
56  options = {**DEFAULT_OPTIONS, **config_entry.options}
57  if is_local(config_entry):
58  local_data: LocalData = hass.data[DOMAIN][config_entry.entry_id]
61  local_data.system.id,
62  partition_id,
63  partition,
64  local_data.partition_updates,
65  config_entry.data[CONF_PIN],
66  options,
67  )
68  for partition_id, partition in local_data.system.partitions.items()
69  )
70  else:
71  coordinator: RiscoDataUpdateCoordinator = hass.data[DOMAIN][
72  config_entry.entry_id
73  ][DATA_COORDINATOR]
76  coordinator, partition_id, config_entry.data[CONF_PIN], options
77  )
78  for partition_id in coordinator.data.partitions
79  )
80 
81 
83  """Representation of a Risco cloud partition."""
84 
85  _attr_code_format = CodeFormat.NUMBER
86  _attr_has_entity_name = True
87  _attr_name = None
88 
89  def __init__(
90  self,
91  *,
92  partition_id: int,
93  partition: Partition,
94  code: str,
95  options: dict[str, Any],
96  **kwargs: Any,
97  ) -> None:
98  """Init the partition."""
99  super().__init__(**kwargs)
100  self._partition_id_partition_id = partition_id
101  self._partition_partition = partition
102  self._code_code = code
103  self._attr_code_arm_required_attr_code_arm_required = options[CONF_CODE_ARM_REQUIRED]
104  self._code_disarm_required_code_disarm_required = options[CONF_CODE_DISARM_REQUIRED]
105  self._risco_to_ha_risco_to_ha = options[CONF_RISCO_STATES_TO_HA]
106  self._ha_to_risco_ha_to_risco = options[CONF_HA_STATES_TO_RISCO]
107  for state in self._ha_to_risco_ha_to_risco:
108  self._attr_supported_features |= STATES_TO_SUPPORTED_FEATURES[state]
109 
110  @property
111  def alarm_state(self) -> AlarmControlPanelState | None:
112  """Return the state of the device."""
113  if self._partition_partition.triggered:
114  return AlarmControlPanelState.TRIGGERED
115  if self._partition_partition.arming:
116  return AlarmControlPanelState.ARMING
117  if self._partition_partition.disarmed:
118  return AlarmControlPanelState.DISARMED
119  if self._partition_partition.armed:
120  return self._risco_to_ha_risco_to_ha[RISCO_ARM]
121  if self._partition_partition.partially_armed:
122  for group, armed in self._partition_partition.groups.items():
123  if armed:
124  return self._risco_to_ha_risco_to_ha[group]
125 
126  return self._risco_to_ha_risco_to_ha[RISCO_PARTIAL_ARM]
127 
128  return None
129 
130  def _validate_code(self, code: str | None) -> bool:
131  """Validate given code."""
132  return code == self._code_code
133 
134  async def async_alarm_disarm(self, code: str | None = None) -> None:
135  """Send disarm command."""
136  if self._code_disarm_required_code_disarm_required and not self._validate_code_validate_code(code):
137  _LOGGER.warning("Wrong code entered for disarming")
138  return
139  await self._call_alarm_method_call_alarm_method("disarm")
140 
141  async def async_alarm_arm_home(self, code: str | None = None) -> None:
142  """Send arm home command."""
143  await self._arm_arm(AlarmControlPanelState.ARMED_HOME, code)
144 
145  async def async_alarm_arm_away(self, code: str | None = None) -> None:
146  """Send arm away command."""
147  await self._arm_arm(AlarmControlPanelState.ARMED_AWAY, code)
148 
149  async def async_alarm_arm_night(self, code: str | None = None) -> None:
150  """Send arm night command."""
151  await self._arm_arm(AlarmControlPanelState.ARMED_NIGHT, code)
152 
153  async def async_alarm_arm_custom_bypass(self, code: str | None = None) -> None:
154  """Send arm custom bypass command."""
155  await self._arm_arm(AlarmControlPanelState.ARMED_CUSTOM_BYPASS, code)
156 
157  async def _arm(self, mode: AlarmControlPanelState, code: str | None) -> None:
158  if self.code_arm_requiredcode_arm_required and not self._validate_code_validate_code(code):
159  _LOGGER.warning("Wrong code entered for %s", mode)
160  return
161 
162  if not (risco_state := self._ha_to_risco_ha_to_risco[mode]):
163  _LOGGER.warning("No mapping for mode %s", mode)
164  return
165 
166  if risco_state in RISCO_GROUPS:
167  await self._call_alarm_method_call_alarm_method("group_arm", risco_state)
168  else:
169  await self._call_alarm_method_call_alarm_method(risco_state)
170 
171  async def _call_alarm_method(self, method: str, *args: Any) -> None:
172  raise NotImplementedError
173 
174 
176  """Representation of a Risco partition."""
177 
178  def __init__(
179  self,
180  coordinator: RiscoDataUpdateCoordinator,
181  partition_id: int,
182  code: str,
183  options: dict[str, Any],
184  ) -> None:
185  """Init the partition."""
186  super().__init__(
187  partition_id=partition_id,
188  partition=coordinator.data.partitions[partition_id],
189  coordinator=coordinator,
190  code=code,
191  options=options,
192  )
193  self._attr_unique_id_attr_unique_id = f"{self._risco.site_uuid}_{partition_id}"
194  self._attr_device_info_attr_device_info = DeviceInfo(
195  identifiers={(DOMAIN, self._attr_unique_id_attr_unique_id)},
196  name=f"Risco {self._risco.site_name} Partition {partition_id}",
197  manufacturer="Risco",
198  )
199 
200  def _get_data_from_coordinator(self) -> None:
201  self._partition_partition_partition = self.coordinator.data.partitions[self._partition_id_partition_id]
202 
203  async def _call_alarm_method(self, method: str, *args: Any) -> None:
204  alarm = await getattr(self._risco_risco, method)(self._partition_id_partition_id, *args)
205  self._partition_partition_partition = alarm.partitions[self._partition_id_partition_id]
206  self.async_write_ha_stateasync_write_ha_state()
207 
208 
210  """Representation of a Risco local, partition."""
211 
212  _attr_should_poll = False
213 
214  def __init__(
215  self,
216  system_id: str,
217  partition_id: int,
218  partition: LocalPartition,
219  partition_updates: dict[int, Callable[[], Any]],
220  code: str,
221  options: dict[str, Any],
222  ) -> None:
223  """Init the partition."""
224  super().__init__(
225  partition_id=partition_id, partition=partition, code=code, options=options
226  )
227  self._system_id_system_id = system_id
228  self._partition_updates_partition_updates = partition_updates
229  self._attr_unique_id_attr_unique_id = f"{system_id}_{partition_id}_local"
230  self._attr_device_info_attr_device_info = DeviceInfo(
231  identifiers={(DOMAIN, self._attr_unique_id_attr_unique_id)},
232  name=partition.name,
233  manufacturer="Risco",
234  )
235 
236  async def async_added_to_hass(self) -> None:
237  """Subscribe to updates."""
238  self._partition_updates_partition_updates[self._partition_id_partition_id] = self.async_write_ha_stateasync_write_ha_state
239 
240  async def _call_alarm_method(self, method: str, *args: Any) -> None:
241  await getattr(self._partition_partition, method)(*args)
None _arm(self, AlarmControlPanelState mode, str|None code)
None __init__(self, *int partition_id, Partition partition, str code, dict[str, Any] options, **Any kwargs)
None __init__(self, RiscoDataUpdateCoordinator coordinator, int partition_id, str code, dict[str, Any] options)
None __init__(self, str system_id, int partition_id, LocalPartition partition, dict[int, Callable[[], Any]] partition_updates, str code, dict[str, Any] options)
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
bool is_local(ConfigEntry entry)
Definition: __init__.py:58