1 """Support for manual alarms."""
3 from __future__
import annotations
8 import voluptuous
as vol
11 PLATFORM_SCHEMA
as ALARM_CONTROL_PANEL_PLATFORM_SCHEMA,
12 AlarmControlPanelEntity,
13 AlarmControlPanelEntityFeature,
14 AlarmControlPanelState,
21 CONF_DISARM_AFTER_TRIGGER,
38 CONF_ARMING_STATES =
"arming_states"
39 CONF_CODE_TEMPLATE =
"code_template"
40 CONF_CODE_ARM_REQUIRED =
"code_arm_required"
42 CONF_ALARM_ARMED_AWAY =
"armed_away"
43 CONF_ALARM_ARMED_CUSTOM_BYPASS =
"armed_custom_bypass"
44 CONF_ALARM_ARMED_HOME =
"armed_home"
45 CONF_ALARM_ARMED_NIGHT =
"armed_night"
46 CONF_ALARM_ARMED_VACATION =
"armed_vacation"
47 CONF_ALARM_ARMING =
"arming"
48 CONF_ALARM_DISARMED =
"disarmed"
49 CONF_ALARM_PENDING =
"pending"
50 CONF_ALARM_TRIGGERED =
"triggered"
52 DEFAULT_ALARM_NAME =
"HA Alarm"
53 DEFAULT_DELAY_TIME = datetime.timedelta(seconds=60)
54 DEFAULT_ARMING_TIME = datetime.timedelta(seconds=60)
55 DEFAULT_TRIGGER_TIME = datetime.timedelta(seconds=120)
56 DEFAULT_DISARM_AFTER_TRIGGER =
False
59 AlarmControlPanelState.DISARMED,
60 AlarmControlPanelState.ARMED_AWAY,
61 AlarmControlPanelState.ARMED_HOME,
62 AlarmControlPanelState.ARMED_NIGHT,
63 AlarmControlPanelState.ARMED_VACATION,
64 AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
65 AlarmControlPanelState.TRIGGERED,
68 SUPPORTED_PRETRIGGER_STATES = [
69 state
for state
in SUPPORTED_STATES
if state != AlarmControlPanelState.TRIGGERED
72 SUPPORTED_ARMING_STATES = [
74 for state
in SUPPORTED_STATES
77 AlarmControlPanelState.DISARMED,
78 AlarmControlPanelState.TRIGGERED,
82 SUPPORTED_ARMING_STATE_TO_FEATURE = {
83 AlarmControlPanelState.ARMED_AWAY: AlarmControlPanelEntityFeature.ARM_AWAY,
84 AlarmControlPanelState.ARMED_HOME: AlarmControlPanelEntityFeature.ARM_HOME,
85 AlarmControlPanelState.ARMED_NIGHT: AlarmControlPanelEntityFeature.ARM_NIGHT,
86 AlarmControlPanelState.ARMED_VACATION: AlarmControlPanelEntityFeature.ARM_VACATION,
87 AlarmControlPanelState.ARMED_CUSTOM_BYPASS: AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS,
90 ATTR_PREVIOUS_STATE =
"previous_state"
91 ATTR_NEXT_STATE =
"next_state"
95 config: dict[AlarmControlPanelState | str, Any],
97 """Validate the state."""
98 state: AlarmControlPanelState
99 for state
in SUPPORTED_PRETRIGGER_STATES:
100 if CONF_DELAY_TIME
not in config[state]:
101 config[state] = config[state] | {CONF_DELAY_TIME: config[CONF_DELAY_TIME]}
102 if CONF_TRIGGER_TIME
not in config[state]:
103 config[state] = config[state] | {
104 CONF_TRIGGER_TIME: config[CONF_TRIGGER_TIME]
106 for state
in SUPPORTED_ARMING_STATES:
107 if CONF_ARMING_TIME
not in config[state]:
108 config[state] = config[state] | {CONF_ARMING_TIME: config[CONF_ARMING_TIME]}
114 """Validate the state."""
116 if state
in SUPPORTED_PRETRIGGER_STATES:
117 schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
118 cv.time_period, cv.positive_timedelta
120 schema[vol.Optional(CONF_TRIGGER_TIME)] = vol.All(
121 cv.time_period, cv.positive_timedelta
123 if state
in SUPPORTED_ARMING_STATES:
124 schema[vol.Optional(CONF_ARMING_TIME)] = vol.All(
125 cv.time_period, cv.positive_timedelta
127 return vol.Schema(schema)
130 PLATFORM_SCHEMA = vol.Schema(
132 ALARM_CONTROL_PANEL_PLATFORM_SCHEMA.extend(
134 vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
135 vol.Optional(CONF_UNIQUE_ID): cv.string,
136 vol.Exclusive(CONF_CODE,
"code validation"): cv.string,
137 vol.Exclusive(CONF_CODE_TEMPLATE,
"code validation"): cv.template,
138 vol.Optional(CONF_CODE_ARM_REQUIRED, default=
True): cv.boolean,
139 vol.Optional(CONF_DELAY_TIME, default=DEFAULT_DELAY_TIME): vol.All(
140 cv.time_period, cv.positive_timedelta
142 vol.Optional(CONF_ARMING_TIME, default=DEFAULT_ARMING_TIME): vol.All(
143 cv.time_period, cv.positive_timedelta
145 vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME): vol.All(
146 cv.time_period, cv.positive_timedelta
149 CONF_DISARM_AFTER_TRIGGER, default=DEFAULT_DISARM_AFTER_TRIGGER
152 CONF_ARMING_STATES, default=SUPPORTED_ARMING_STATES
153 ): vol.All(cv.ensure_list, [vol.In(SUPPORTED_ARMING_STATES)]),
154 vol.Optional(CONF_ALARM_ARMED_AWAY, default={}):
_state_schema(
155 AlarmControlPanelState.ARMED_AWAY
157 vol.Optional(CONF_ALARM_ARMED_HOME, default={}):
_state_schema(
158 AlarmControlPanelState.ARMED_HOME
160 vol.Optional(CONF_ALARM_ARMED_NIGHT, default={}):
_state_schema(
161 AlarmControlPanelState.ARMED_NIGHT
163 vol.Optional(CONF_ALARM_ARMED_VACATION, default={}):
_state_schema(
164 AlarmControlPanelState.ARMED_VACATION
166 vol.Optional(CONF_ALARM_ARMED_CUSTOM_BYPASS, default={}):
_state_schema(
167 AlarmControlPanelState.ARMED_CUSTOM_BYPASS
169 vol.Optional(CONF_ALARM_DISARMED, default={}):
_state_schema(
170 AlarmControlPanelState.DISARMED
172 vol.Optional(CONF_ALARM_TRIGGERED, default={}):
_state_schema(
173 AlarmControlPanelState.TRIGGERED
185 async_add_entities: AddEntitiesCallback,
186 discovery_info: DiscoveryInfoType |
None =
None,
188 """Set up the manual alarm platform."""
194 config.get(CONF_UNIQUE_ID),
195 config.get(CONF_CODE),
196 config.get(CONF_CODE_TEMPLATE),
197 config[CONF_CODE_ARM_REQUIRED],
198 config[CONF_DISARM_AFTER_TRIGGER],
206 """Representation of an alarm status.
208 When armed, will be arming for 'arming_time', after that armed.
209 When triggered, will be pending for the triggering state's 'delay_time'.
210 After that will be triggered for 'trigger_time', after that we return to
211 the previous state or disarm if `disarm_after_trigger` is true.
212 A trigger_time of zero disables the alarm_trigger service.
215 _attr_should_poll =
False
221 unique_id: str |
None,
223 code_template: Template |
None,
224 code_arm_required: bool,
225 disarm_after_trigger: bool,
226 config: dict[str, Any],
228 """Init the manual alarm panel."""
229 self.
_state_state: AlarmControlPanelState = AlarmControlPanelState.DISARMED
233 self.
_code_code = code_template
or code
or None
237 self.
_state_ts_state_ts: datetime.datetime = dt_util.utcnow()
239 self._delay_time_by_state: dict[AlarmControlPanelState, Any] = {
240 state: config[state][CONF_DELAY_TIME]
241 for state
in SUPPORTED_PRETRIGGER_STATES
243 self._trigger_time_by_state: dict[AlarmControlPanelState, Any] = {
244 state: config[state][CONF_TRIGGER_TIME]
245 for state
in SUPPORTED_PRETRIGGER_STATES
247 self._arming_time_by_state: dict[AlarmControlPanelState, Any] = {
248 state: config[state][CONF_ARMING_TIME]
for state
in SUPPORTED_ARMING_STATES
252 for arming_state
in config.get(CONF_ARMING_STATES, SUPPORTED_ARMING_STATES):
259 """Return the state of the device."""
260 if self.
_state_state == AlarmControlPanelState.TRIGGERED:
262 return AlarmControlPanelState.PENDING
263 trigger_time: datetime.timedelta = self._trigger_time_by_state[
268 ) < dt_util.utcnow():
270 return AlarmControlPanelState.DISARMED
277 return AlarmControlPanelState.ARMING
283 """Get the current state."""
285 AlarmControlPanelState.PENDING,
286 AlarmControlPanelState.ARMING,
291 def _arming_time(self, state: AlarmControlPanelState) -> datetime.timedelta:
292 """Get the arming time."""
293 arming_time: datetime.timedelta = self._arming_time_by_state[state]
296 def _pending_time(self, state: AlarmControlPanelState) -> datetime.timedelta:
297 """Get the pending time."""
298 delay_time: datetime.timedelta = self._delay_time_by_state[self.
_previous_state_previous_state]
302 """Get if the action is in the arming time window."""
306 """Get if the action is in the pending time window."""
311 """Return one or more digits/characters."""
312 if self.
_code_code
is None:
314 if isinstance(self.
_code_code, str)
and self.
_code_code.isdigit():
315 return CodeFormat.NUMBER
316 return CodeFormat.TEXT
319 """Send disarm command."""
321 self.
_state_state = AlarmControlPanelState.DISARMED
326 """Send arm home command."""
331 """Send arm away command."""
336 """Send arm night command."""
341 """Send arm vacation command."""
346 """Send arm custom bypass command."""
351 """Send alarm trigger command.
353 No code needed, a trigger time of zero for the current state
356 if not self._trigger_time_by_state[self.
_active_state_active_state]:
361 """Update the state."""
362 if self.
_state_state == state:
367 self.
_state_ts_state_ts = dt_util.utcnow()
373 if state == AlarmControlPanelState.TRIGGERED:
379 trigger_time = self._trigger_time_by_state[self.
_previous_state_previous_state]
383 self.
_state_ts_state_ts + pending_time + trigger_time,
385 elif state
in SUPPORTED_ARMING_STATES:
395 """Validate given code."""
397 state != AlarmControlPanelState.DISARMED
and not self.
code_arm_requiredcode_arm_required
398 )
or self.
_code_code
is None:
401 if isinstance(self.
_code_code, str):
402 alarm_code = self.
_code_code
404 alarm_code = self.
_code_code.async_render(
405 parse_result=
False, from_state=self.
_state_state, to_state=state
408 if not alarm_code
or code == alarm_code:
412 "Invalid alarm code provided",
413 translation_domain=DOMAIN,
414 translation_key=
"invalid_code",
419 """Return the state attributes."""
421 AlarmControlPanelState.PENDING,
422 AlarmControlPanelState.ARMING,
425 state: str |
None = self.
_state_state
432 return {ATTR_PREVIOUS_STATE: prev_state, ATTR_NEXT_STATE: state}
436 """Update state at a scheduled point in time."""
440 """Run when entity about to be added to hass."""
443 self.
_state_ts_state_ts = state.last_updated
444 if next_state := state.attributes.get(ATTR_NEXT_STATE):
447 self.
_state_state = AlarmControlPanelState(next_state)
449 self.
_state_state = AlarmControlPanelState(state.state)
451 if prev_state := state.attributes.get(ATTR_PREVIOUS_STATE):
bool code_arm_required(self)
None async_alarm_arm_custom_bypass(self, str|None code=None)
bool _within_arming_time(self, AlarmControlPanelState state)
AlarmControlPanelState alarm_state(self)
None __init__(self, HomeAssistant hass, str name, str|None unique_id, str|None code, Template|None code_template, bool code_arm_required, bool disarm_after_trigger, dict[str, Any] config)
AlarmControlPanelState _active_state(self)
CodeFormat|None code_format(self)
datetime.timedelta _pending_time(self, AlarmControlPanelState state)
None async_alarm_arm_vacation(self, str|None code=None)
bool _within_pending_time(self, AlarmControlPanelState state)
None _async_validate_code(self, str|None code, str state)
None async_alarm_arm_home(self, str|None code=None)
None async_alarm_trigger(self, str|None code=None)
None async_scheduled_update(self, datetime.datetime now)
None _async_update_state(self, AlarmControlPanelState state)
None async_alarm_arm_away(self, str|None code=None)
dict[str, Any] extra_state_attributes(self)
None async_added_to_hass(self)
None async_alarm_disarm(self, str|None code=None)
None async_alarm_arm_night(self, str|None code=None)
datetime.timedelta _arming_time(self, AlarmControlPanelState state)
None _async_set_state_update_events(self)
None async_write_ha_state(self)
State|None async_get_last_state(self)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback async_add_entities, DiscoveryInfoType|None discovery_info=None)
dict[str, Any] _state_validator(dict[AlarmControlPanelState|str, Any] config)
vol.Schema _state_schema(str state)
CALLBACK_TYPE async_track_point_in_time(HomeAssistant hass, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action, datetime point_in_time)