1 """Support for manual alarms controllable via MQTT."""
3 from __future__
import annotations
9 import voluptuous
as vol
13 AlarmControlPanelEntity,
14 AlarmControlPanelEntityFeature,
15 AlarmControlPanelState,
21 CONF_DISARM_AFTER_TRIGGER,
32 async_track_point_in_time,
33 async_track_state_change_event,
38 _LOGGER = logging.getLogger(__name__)
40 CONF_CODE_TEMPLATE =
"code_template"
41 CONF_CODE_ARM_REQUIRED =
"code_arm_required"
43 CONF_PAYLOAD_DISARM =
"payload_disarm"
44 CONF_PAYLOAD_ARM_HOME =
"payload_arm_home"
45 CONF_PAYLOAD_ARM_AWAY =
"payload_arm_away"
46 CONF_PAYLOAD_ARM_NIGHT =
"payload_arm_night"
47 CONF_PAYLOAD_ARM_VACATION =
"payload_arm_vacation"
48 CONF_PAYLOAD_ARM_CUSTOM_BYPASS =
"payload_arm_custom_bypass"
50 CONF_ALARM_ARMED_AWAY =
"armed_away"
51 CONF_ALARM_ARMED_CUSTOM_BYPASS =
"armed_custom_bypass"
52 CONF_ALARM_ARMED_HOME =
"armed_home"
53 CONF_ALARM_ARMED_NIGHT =
"armed_night"
54 CONF_ALARM_ARMED_VACATION =
"armed_vacation"
55 CONF_ALARM_DISARMED =
"disarmed"
56 CONF_ALARM_PENDING =
"pending"
57 CONF_ALARM_TRIGGERED =
"triggered"
59 DEFAULT_ALARM_NAME =
"HA Alarm"
60 DEFAULT_DELAY_TIME = datetime.timedelta(seconds=0)
61 DEFAULT_PENDING_TIME = datetime.timedelta(seconds=60)
62 DEFAULT_TRIGGER_TIME = datetime.timedelta(seconds=120)
63 DEFAULT_DISARM_AFTER_TRIGGER =
False
64 DEFAULT_ARM_AWAY =
"ARM_AWAY"
65 DEFAULT_ARM_HOME =
"ARM_HOME"
66 DEFAULT_ARM_NIGHT =
"ARM_NIGHT"
67 DEFAULT_ARM_VACATION =
"ARM_VACATION"
68 DEFAULT_ARM_CUSTOM_BYPASS =
"ARM_CUSTOM_BYPASS"
69 DEFAULT_DISARM =
"DISARM"
72 AlarmControlPanelState.DISARMED,
73 AlarmControlPanelState.ARMED_AWAY,
74 AlarmControlPanelState.ARMED_HOME,
75 AlarmControlPanelState.ARMED_NIGHT,
76 AlarmControlPanelState.ARMED_VACATION,
77 AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
78 AlarmControlPanelState.TRIGGERED,
81 SUPPORTED_PRETRIGGER_STATES = [
82 state
for state
in SUPPORTED_STATES
if state != AlarmControlPanelState.TRIGGERED
85 SUPPORTED_PENDING_STATES = [
86 state
for state
in SUPPORTED_STATES
if state != AlarmControlPanelState.DISARMED
89 ATTR_PRE_PENDING_STATE =
"pre_pending_state"
90 ATTR_POST_PENDING_STATE =
"post_pending_state"
94 """Validate the state."""
95 for state
in SUPPORTED_PRETRIGGER_STATES:
96 if CONF_DELAY_TIME
not in config[state]:
97 config[state] = config[state] | {CONF_DELAY_TIME: config[CONF_DELAY_TIME]}
98 if CONF_TRIGGER_TIME
not in config[state]:
99 config[state] = config[state] | {
100 CONF_TRIGGER_TIME: config[CONF_TRIGGER_TIME]
102 for state
in SUPPORTED_PENDING_STATES:
103 if CONF_PENDING_TIME
not in config[state]:
104 config[state] = config[state] | {
105 CONF_PENDING_TIME: config[CONF_PENDING_TIME]
112 """Validate the state."""
114 if state
in SUPPORTED_PRETRIGGER_STATES:
115 schema[vol.Optional(CONF_DELAY_TIME)] = vol.All(
116 cv.time_period, cv.positive_timedelta
118 schema[vol.Optional(CONF_TRIGGER_TIME)] = vol.All(
119 cv.time_period, cv.positive_timedelta
121 if state
in SUPPORTED_PENDING_STATES:
122 schema[vol.Optional(CONF_PENDING_TIME)] = vol.All(
123 cv.time_period, cv.positive_timedelta
125 return vol.Schema(schema)
128 PLATFORM_SCHEMA = vol.Schema(
130 mqtt.config.MQTT_BASE_SCHEMA.extend(
132 vol.Required(CONF_PLATFORM):
"manual_mqtt",
133 vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string,
134 vol.Exclusive(CONF_CODE,
"code validation"): cv.string,
135 vol.Exclusive(CONF_CODE_TEMPLATE,
"code validation"): cv.template,
136 vol.Optional(CONF_DELAY_TIME, default=DEFAULT_DELAY_TIME): vol.All(
137 cv.time_period, cv.positive_timedelta
139 vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME): vol.All(
140 cv.time_period, cv.positive_timedelta
142 vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME): vol.All(
143 cv.time_period, cv.positive_timedelta
146 CONF_DISARM_AFTER_TRIGGER, default=DEFAULT_DISARM_AFTER_TRIGGER
148 vol.Optional(CONF_ALARM_ARMED_AWAY, default={}):
_state_schema(
149 AlarmControlPanelState.ARMED_AWAY
151 vol.Optional(CONF_ALARM_ARMED_HOME, default={}):
_state_schema(
152 AlarmControlPanelState.ARMED_HOME
154 vol.Optional(CONF_ALARM_ARMED_NIGHT, default={}):
_state_schema(
155 AlarmControlPanelState.ARMED_NIGHT
157 vol.Optional(CONF_ALARM_ARMED_VACATION, default={}):
_state_schema(
158 AlarmControlPanelState.ARMED_VACATION
160 vol.Optional(CONF_ALARM_ARMED_CUSTOM_BYPASS, default={}):
_state_schema(
161 AlarmControlPanelState.ARMED_CUSTOM_BYPASS
163 vol.Optional(CONF_ALARM_DISARMED, default={}):
_state_schema(
164 AlarmControlPanelState.DISARMED
166 vol.Optional(CONF_ALARM_TRIGGERED, default={}):
_state_schema(
167 AlarmControlPanelState.TRIGGERED
169 vol.Required(mqtt.CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
170 vol.Required(mqtt.CONF_STATE_TOPIC): mqtt.valid_subscribe_topic,
171 vol.Optional(CONF_CODE_ARM_REQUIRED, default=
True): cv.boolean,
173 CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY
176 CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME
179 CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT
182 CONF_PAYLOAD_ARM_VACATION, default=DEFAULT_ARM_VACATION
185 CONF_PAYLOAD_ARM_CUSTOM_BYPASS, default=DEFAULT_ARM_CUSTOM_BYPASS
187 vol.Optional(CONF_PAYLOAD_DISARM, default=DEFAULT_DISARM): cv.string,
198 add_entities: AddEntitiesCallback,
199 discovery_info: DiscoveryInfoType |
None =
None,
201 """Set up the manual MQTT alarm platform."""
205 if not await mqtt.async_wait_for_mqtt_client(hass):
206 _LOGGER.error(
"MQTT integration is not available")
213 config.get(CONF_CODE),
214 config.get(CONF_CODE_TEMPLATE),
215 config.get(CONF_DISARM_AFTER_TRIGGER, DEFAULT_DISARM_AFTER_TRIGGER),
216 config.get(mqtt.CONF_STATE_TOPIC),
217 config.get(mqtt.CONF_COMMAND_TOPIC),
218 config.get(mqtt.CONF_QOS),
219 config.get(CONF_CODE_ARM_REQUIRED),
220 config.get(CONF_PAYLOAD_DISARM),
221 config.get(CONF_PAYLOAD_ARM_HOME),
222 config.get(CONF_PAYLOAD_ARM_AWAY),
223 config.get(CONF_PAYLOAD_ARM_NIGHT),
224 config.get(CONF_PAYLOAD_ARM_VACATION),
225 config.get(CONF_PAYLOAD_ARM_CUSTOM_BYPASS),
233 """Representation of an alarm status.
235 When armed, will be pending for 'pending_time', after that armed.
236 When triggered, will be pending for the triggering state's 'delay_time'
237 plus the triggered state's 'pending_time'.
238 After that will be triggered for 'trigger_time', after that we return to
239 the previous state or disarm if `disarm_after_trigger` is true.
240 A trigger_time of zero disables the alarm_trigger service.
243 _attr_should_poll =
False
244 _attr_supported_features = (
245 AlarmControlPanelEntityFeature.ARM_HOME
246 | AlarmControlPanelEntityFeature.ARM_AWAY
247 | AlarmControlPanelEntityFeature.ARM_NIGHT
248 | AlarmControlPanelEntityFeature.ARM_VACATION
249 | AlarmControlPanelEntityFeature.TRIGGER
250 | AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS
259 disarm_after_trigger,
268 payload_arm_vacation,
269 payload_arm_custom_bypass,
272 """Init the manual MQTT alarm panel."""
273 self.
_state_state = AlarmControlPanelState.DISARMED
279 self.
_code_code = code
or None
285 state: config[state][CONF_DELAY_TIME]
286 for state
in SUPPORTED_PRETRIGGER_STATES
289 state: config[state][CONF_TRIGGER_TIME]
290 for state
in SUPPORTED_PRETRIGGER_STATES
293 state: config[state][CONF_PENDING_TIME]
294 for state
in SUPPORTED_PENDING_STATES
310 """Return the state of the device."""
311 if self.
_state_state == AlarmControlPanelState.TRIGGERED:
313 return AlarmControlPanelState.PENDING
317 ) < dt_util.utcnow():
319 return AlarmControlPanelState.DISARMED
326 return AlarmControlPanelState.PENDING
332 """Get the current state."""
338 """Get the pending time."""
340 if state == AlarmControlPanelState.TRIGGERED:
345 """Get if the action is in the pending time window."""
350 """Return one or more digits/characters."""
351 if self.
_code_code
is None:
353 if isinstance(self.
_code_code, str)
and self.
_code_code.isdigit():
354 return CodeFormat.NUMBER
355 return CodeFormat.TEXT
358 """Send disarm command."""
360 self.
_state_state = AlarmControlPanelState.DISARMED
361 self.
_state_ts_state_ts = dt_util.utcnow()
365 """Send arm home command."""
370 """Send arm away command."""
375 """Send arm night command."""
380 """Send arm vacation command."""
385 """Send arm custom bypass command."""
390 """Send alarm trigger command.
392 No code needed, a trigger time of zero for the current state
400 """Update the state."""
401 if self.
_state_state == state:
406 self.
_state_ts_state_ts = dt_util.utcnow()
410 if state == AlarmControlPanelState.TRIGGERED:
419 self.
_state_ts_state_ts + pending_time + trigger_time,
421 elif state
in SUPPORTED_PENDING_STATES
and pending_time:
427 """Validate given code."""
429 state != AlarmControlPanelState.DISARMED
and not self.
code_arm_requiredcode_arm_required
430 )
or self.
_code_code
is None:
433 if isinstance(self.
_code_code, str):
434 alarm_code = self.
_code_code
436 alarm_code = self.
_code_code.async_render(
437 from_state=self.
_state_state, to_state=state, parse_result=
False
440 if not alarm_code
or code == alarm_code:
447 """Return the state attributes."""
452 ATTR_POST_PENDING_STATE: self.
_state_state,
457 """Update state at a scheduled point in time."""
461 """Subscribe to MQTT events."""
466 async
def message_received(msg):
467 """Run when new MQTT message has been received."""
481 _LOGGER.warning(
"Received unexpected payload: %s", msg.payload)
484 await mqtt.async_subscribe(
489 self, event: Event[EventStateChangedData]
491 """Publish state change to MQTT."""
492 if (new_state := event.data[
"new_state"])
is None:
494 await mqtt.async_publish(
bool code_arm_required(self)
None async_alarm_arm_home(self, str|None code=None)
None async_alarm_arm_night(self, str|None code=None)
None async_alarm_arm_away(self, str|None code=None)
None async_alarm_arm_vacation(self, str|None code=None)
None async_alarm_arm_custom_bypass(self, str|None code=None)
None async_alarm_disarm(self, str|None code=None)
None _async_update_state(self, str state)
def __init__(self, hass, name, code, code_template, disarm_after_trigger, state_topic, command_topic, qos, code_arm_required, payload_disarm, payload_arm_home, payload_arm_away, payload_arm_night, payload_arm_vacation, payload_arm_custom_bypass, config)
def _within_pending_time(self, state)
AlarmControlPanelState alarm_state(self)
def _pending_time(self, state)
None _async_state_changed_listener(self, Event[EventStateChangedData] event)
None async_alarm_disarm(self, str|None code=None)
None async_alarm_arm_away(self, str|None code=None)
None async_alarm_arm_home(self, str|None code=None)
_payload_arm_custom_bypass
None async_added_to_hass(self)
CodeFormat|None code_format(self)
None async_alarm_trigger(self, str|None code=None)
def async_scheduled_update(self, now)
None async_alarm_arm_custom_bypass(self, str|None code=None)
None async_alarm_arm_night(self, str|None code=None)
dict[str, Any] extra_state_attributes(self)
None async_alarm_arm_vacation(self, str|None code=None)
def _async_validate_code(self, code, state)
None async_write_ha_state(self)
None add_entities(AsusWrtRouter router, AddEntitiesCallback async_add_entities, set[str] tracked)
def _state_validator(config)
None async_setup_platform(HomeAssistant hass, ConfigType config, AddEntitiesCallback add_entities, DiscoveryInfoType|None discovery_info=None)
CALLBACK_TYPE async_track_state_change_event(HomeAssistant hass, str|Iterable[str] entity_ids, Callable[[Event[EventStateChangedData]], Any] action, HassJobType|None job_type=None)
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)