Home Assistant Unofficial Reference 2024.12.1
alarm_control_panel.py
Go to the documentation of this file.
1 """Support for alarm control panels that can be controlled through IFTTT."""
2 
3 from __future__ import annotations
4 
5 import logging
6 
7 import voluptuous as vol
8 
10  PLATFORM_SCHEMA as ALARM_CONTROL_PANEL_PLATFORM_SCHEMA,
11  AlarmControlPanelEntity,
12  AlarmControlPanelEntityFeature,
13  AlarmControlPanelState,
14  CodeFormat,
15 )
16 from homeassistant.const import (
17  ATTR_ENTITY_ID,
18  ATTR_STATE,
19  CONF_CODE,
20  CONF_NAME,
21  CONF_OPTIMISTIC,
22 )
23 from homeassistant.core import HomeAssistant, ServiceCall
25 from homeassistant.helpers.entity_platform import AddEntitiesCallback
26 from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
27 
28 from . import ATTR_EVENT, DOMAIN, SERVICE_PUSH_ALARM_STATE, SERVICE_TRIGGER
29 
30 _LOGGER = logging.getLogger(__name__)
31 
32 ALLOWED_STATES = [
33  AlarmControlPanelState.DISARMED,
34  AlarmControlPanelState.ARMED_NIGHT,
35  AlarmControlPanelState.ARMED_AWAY,
36  AlarmControlPanelState.ARMED_HOME,
37 ]
38 
39 DATA_IFTTT_ALARM = "ifttt_alarm"
40 DEFAULT_NAME = "Home"
41 
42 CONF_EVENT_AWAY = "event_arm_away"
43 CONF_EVENT_HOME = "event_arm_home"
44 CONF_EVENT_NIGHT = "event_arm_night"
45 CONF_EVENT_DISARM = "event_disarm"
46 
47 DEFAULT_EVENT_AWAY = "alarm_arm_away"
48 DEFAULT_EVENT_HOME = "alarm_arm_home"
49 DEFAULT_EVENT_NIGHT = "alarm_arm_night"
50 DEFAULT_EVENT_DISARM = "alarm_disarm"
51 
52 CONF_CODE_ARM_REQUIRED = "code_arm_required"
53 
54 PLATFORM_SCHEMA = ALARM_CONTROL_PANEL_PLATFORM_SCHEMA.extend(
55  {
56  vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
57  vol.Optional(CONF_CODE): cv.string,
58  vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
59  vol.Optional(CONF_EVENT_AWAY, default=DEFAULT_EVENT_AWAY): cv.string,
60  vol.Optional(CONF_EVENT_HOME, default=DEFAULT_EVENT_HOME): cv.string,
61  vol.Optional(CONF_EVENT_NIGHT, default=DEFAULT_EVENT_NIGHT): cv.string,
62  vol.Optional(CONF_EVENT_DISARM, default=DEFAULT_EVENT_DISARM): cv.string,
63  vol.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
64  }
65 )
66 
67 PUSH_ALARM_STATE_SERVICE_SCHEMA = vol.Schema(
68  {vol.Required(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_STATE): cv.string}
69 )
70 
71 
73  hass: HomeAssistant,
74  config: ConfigType,
75  add_entities: AddEntitiesCallback,
76  discovery_info: DiscoveryInfoType | None = None,
77 ) -> None:
78  """Set up a control panel managed through IFTTT."""
79  if DATA_IFTTT_ALARM not in hass.data:
80  hass.data[DATA_IFTTT_ALARM] = []
81 
82  name: str = config[CONF_NAME]
83  code: str | None = config.get(CONF_CODE)
84  code_arm_required: bool = config[CONF_CODE_ARM_REQUIRED]
85  event_away: str = config[CONF_EVENT_AWAY]
86  event_home: str = config[CONF_EVENT_HOME]
87  event_night: str = config[CONF_EVENT_NIGHT]
88  event_disarm: str = config[CONF_EVENT_DISARM]
89  optimistic: bool = config[CONF_OPTIMISTIC]
90 
91  alarmpanel = IFTTTAlarmPanel(
92  name,
93  code,
94  code_arm_required,
95  event_away,
96  event_home,
97  event_night,
98  event_disarm,
99  optimistic,
100  )
101  hass.data[DATA_IFTTT_ALARM].append(alarmpanel)
102  add_entities([alarmpanel])
103 
104  async def push_state_update(service: ServiceCall) -> None:
105  """Set the service state as device state attribute."""
106  entity_ids = service.data.get(ATTR_ENTITY_ID)
107  state = service.data.get(ATTR_STATE)
108  devices = hass.data[DATA_IFTTT_ALARM]
109  if entity_ids:
110  devices = [d for d in devices if d.entity_id in entity_ids]
111 
112  for device in devices:
113  device.push_alarm_state(state)
114  device.async_schedule_update_ha_state()
115 
116  hass.services.register(
117  DOMAIN,
118  SERVICE_PUSH_ALARM_STATE,
119  push_state_update,
120  schema=PUSH_ALARM_STATE_SERVICE_SCHEMA,
121  )
122 
123 
125  """Representation of an alarm control panel controlled through IFTTT."""
126 
127  _attr_assumed_state = True
128  _attr_supported_features = (
129  AlarmControlPanelEntityFeature.ARM_HOME
130  | AlarmControlPanelEntityFeature.ARM_AWAY
131  | AlarmControlPanelEntityFeature.ARM_NIGHT
132  )
133 
134  def __init__(
135  self,
136  name: str,
137  code: str | None,
138  code_arm_required: bool,
139  event_away: str,
140  event_home: str,
141  event_night: str,
142  event_disarm: str,
143  optimistic: bool,
144  ) -> None:
145  """Initialize the alarm control panel."""
146  self._attr_name_attr_name = name
147  self._code_code = code
148  self._code_arm_required_code_arm_required = code_arm_required
149  self._event_away_event_away = event_away
150  self._event_home_event_home = event_home
151  self._event_night_event_night = event_night
152  self._event_disarm_event_disarm = event_disarm
153  self._optimistic_optimistic = optimistic
154 
155  @property
156  def code_format(self) -> CodeFormat | None:
157  """Return one or more digits/characters."""
158  if self._code_code is None:
159  return None
160  if isinstance(self._code_code, str) and self._code_code.isdigit():
161  return CodeFormat.NUMBER
162  return CodeFormat.TEXT
163 
164  def alarm_disarm(self, code: str | None = None) -> None:
165  """Send disarm command."""
166  if not self._check_code_check_code(code):
167  return
168  self.set_alarm_stateset_alarm_state(self._event_disarm_event_disarm, AlarmControlPanelState.DISARMED)
169 
170  def alarm_arm_away(self, code: str | None = None) -> None:
171  """Send arm away command."""
172  if self._code_arm_required_code_arm_required and not self._check_code_check_code(code):
173  return
174  self.set_alarm_stateset_alarm_state(self._event_away_event_away, AlarmControlPanelState.ARMED_AWAY)
175 
176  def alarm_arm_home(self, code: str | None = None) -> None:
177  """Send arm home command."""
178  if self._code_arm_required_code_arm_required and not self._check_code_check_code(code):
179  return
180  self.set_alarm_stateset_alarm_state(self._event_home_event_home, AlarmControlPanelState.ARMED_HOME)
181 
182  def alarm_arm_night(self, code: str | None = None) -> None:
183  """Send arm night command."""
184  if self._code_arm_required_code_arm_required and not self._check_code_check_code(code):
185  return
186  self.set_alarm_stateset_alarm_state(self._event_night_event_night, AlarmControlPanelState.ARMED_NIGHT)
187 
188  def set_alarm_state(self, event: str, state: AlarmControlPanelState) -> None:
189  """Call the IFTTT trigger service to change the alarm state."""
190  data = {ATTR_EVENT: event}
191 
192  self.hasshass.services.call(DOMAIN, SERVICE_TRIGGER, data)
193  _LOGGER.debug("Called IFTTT integration to trigger event %s", event)
194  if self._optimistic_optimistic:
195  self._attr_alarm_state_attr_alarm_state = state
196 
197  def push_alarm_state(self, value: str) -> None:
198  """Push the alarm state to the given value."""
199  value = AlarmControlPanelState(value)
200  if value in ALLOWED_STATES:
201  _LOGGER.debug("Pushed the alarm state to %s", value)
202  self._attr_alarm_state_attr_alarm_state = value
203 
204  def _check_code(self, code: str | None) -> bool:
205  return self._code_code is None or self._code_code == code
None set_alarm_state(self, str event, AlarmControlPanelState state)
None __init__(self, str name, str|None code, bool code_arm_required, str event_away, str event_home, str event_night, str event_disarm, bool optimistic)
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
None setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)