Coverage for custom_components/autoarm/const.py: 100%
79 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-08 20:27 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-08 20:27 +0000
1"""The Auto Arm integration"""
3import logging
4from dataclasses import dataclass
5from enum import StrEnum, auto
6from typing import Any
8import voluptuous as vol
9from homeassistant.components.alarm_control_panel.const import AlarmControlPanelState
10from homeassistant.components.calendar import CalendarEvent
11from homeassistant.const import (
12 CONF_ALIAS,
13 CONF_CONDITIONS,
14 CONF_DELAY_TIME,
15 CONF_ENTITY_ID,
16 CONF_SERVICE,
17 STATE_HOME,
18 STATE_NOT_HOME,
19)
20from homeassistant.helpers import config_validation as cv
21from homeassistant.helpers.typing import ConfigType
23_LOGGER = logging.getLogger(__name__)
25DOMAIN = "autoarm"
27ATTR_ACTION = "action"
28ATTR_RESET = "reset"
29CONF_DATA = "data"
30CONF_NOTIFY = "notify"
31CONF_ALARM_PANEL = "alarm_panel"
32CONF_ALARM_STATES = "alarm_states"
34ALARM_STATES = [k.lower() for k in AlarmControlPanelState.__members__]
36NO_CAL_EVENT_MODE_AUTO = "auto"
37NO_CAL_EVENT_MODE_MANUAL = "manual"
38NO_CAL_EVENT_OPTIONS: list[str] = [NO_CAL_EVENT_MODE_AUTO, NO_CAL_EVENT_MODE_MANUAL, *ALARM_STATES]
40NOTIFY_COMMON = "common"
41NOTIFY_QUIET = "quiet"
42NOTIFY_NORMAL = "normal"
43NOTIFY_CATEGORIES = [NOTIFY_COMMON, NOTIFY_QUIET, NOTIFY_NORMAL]
45NOTIFY_DEF_SCHEMA = vol.Schema({vol.Optional(CONF_SERVICE): cv.service, vol.Optional(CONF_DATA): dict})
47NOTIFY_SCHEMA = vol.Schema({
48 vol.Optional(NOTIFY_COMMON): {vol.Required(CONF_SERVICE): cv.service, vol.Optional(CONF_DATA): dict},
49 vol.Optional(NOTIFY_QUIET): NOTIFY_DEF_SCHEMA,
50 vol.Optional(NOTIFY_NORMAL): NOTIFY_DEF_SCHEMA,
51})
53DEFAULT_CALENDAR_MAPPINGS = {
54 AlarmControlPanelState.ARMED_AWAY: "Away",
55 AlarmControlPanelState.DISARMED: "Disarmed",
56 AlarmControlPanelState.ARMED_HOME: "Home",
57 AlarmControlPanelState.ARMED_VACATION: "Vacation",
58 AlarmControlPanelState.ARMED_VACATION: "Night",
59}
61CONF_CALENDAR_CONTROL = "calendar_control"
62CONF_CALENDARS = "calendars"
63CONF_CALENDAR_POLL_INTERVAL = "poll_interval"
64CONF_CALENDAR_EVENT_STATES = "state_patterns"
65CONF_CALENDAR_NO_EVENT = "no_event_mode"
66CALENDAR_SCHEMA = vol.Schema({
67 vol.Required(CONF_ENTITY_ID): cv.entity_id,
68 vol.Optional(CONF_ALIAS): cv.string,
69 vol.Optional(CONF_CALENDAR_POLL_INTERVAL, default=30): cv.positive_int,
70 vol.Optional(CONF_CALENDAR_EVENT_STATES, default=DEFAULT_CALENDAR_MAPPINGS): dict[ # type: ignore
71 vol.In(ALARM_STATES), vol.All(cv.ensure_list, [cv.string])
72 ],
73})
74CALENDAR_CONTROL_SCHEMA = vol.Schema({
75 vol.Optional(CONF_CALENDAR_NO_EVENT, default=NO_CAL_EVENT_MODE_AUTO): vol.All(vol.Lower, vol.In(NO_CAL_EVENT_OPTIONS)),
76 vol.Optional(CONF_CALENDARS, default=[]): vol.All(cv.ensure_list, [CALENDAR_SCHEMA]),
77})
79CONF_TRANSITIONS = "transitions"
80TRANSITION_SCHEMA = vol.Schema({vol.Optional(CONF_ALIAS): cv.string, vol.Required(CONF_CONDITIONS): cv.CONDITIONS_SCHEMA})
82CONF_BUTTONS = "buttons"
83BUTTON_OPTIONS = [ATTR_RESET, *ALARM_STATES]
84BUTTON_SCHEMA = vol.Schema({
85 vol.Optional(CONF_ALIAS): cv.string,
86 vol.Optional(CONF_DELAY_TIME): vol.All(cv.time_period, cv.positive_timedelta),
87 vol.Required(CONF_ENTITY_ID): vol.All(cv.ensure_list, [cv.entity_id]),
88})
90CONF_RATE_LIMIT = "rate_limit"
91CONF_RATE_LIMIT_CALLS = "max_calls"
92CONF_RATE_LIMIT_PERIOD = "period"
93RATE_LIMIT_SCHEMA = vol.Schema({
94 vol.Optional(CONF_RATE_LIMIT_PERIOD, default=60): vol.All(cv.time_period, cv.positive_timedelta),
95 vol.Optional(CONF_RATE_LIMIT_CALLS, default=6): cv.positive_int,
96})
98CONF_OCCUPANCY = "occupancy"
99CONF_DAY = "day"
100CONF_NIGHT = "night"
101CONF_OCCUPANCY_DEFAULT = "default_state"
102OCCUPANCY_SCHEMA = vol.Schema({
103 vol.Required(CONF_ENTITY_ID, default=[]): vol.All(cv.ensure_list, [cv.entity_id]),
104 vol.Optional(CONF_OCCUPANCY_DEFAULT, default={CONF_DAY: AlarmControlPanelState.ARMED_HOME}): {
105 vol.In([CONF_DAY, CONF_NIGHT]): vol.In(ALARM_STATES)
106 },
107 vol.Optional(CONF_DELAY_TIME): {vol.In([STATE_HOME, STATE_NOT_HOME]): vol.All(cv.time_period, cv.positive_timedelta)},
108})
110CONF_DIURNAL = "diurnal"
111CONF_SUNRISE = "sunrise"
112CONF_EARLIEST = "earliest"
114CONFIG_SCHEMA = vol.Schema(
115 {
116 DOMAIN: vol.Schema({
117 vol.Required(CONF_ALARM_PANEL): vol.Schema({
118 vol.Optional(CONF_ALIAS): cv.string,
119 vol.Required(CONF_ENTITY_ID): cv.entity_id,
120 }),
121 vol.Optional(CONF_DIURNAL): vol.Schema({
122 vol.Optional(CONF_SUNRISE): vol.Schema({vol.Optional(CONF_EARLIEST): cv.time})
123 }),
124 vol.Optional(CONF_TRANSITIONS): {vol.In(ALARM_STATES): TRANSITION_SCHEMA},
125 vol.Optional(CONF_CALENDAR_CONTROL): CALENDAR_CONTROL_SCHEMA,
126 vol.Optional(CONF_BUTTONS): {vol.In(BUTTON_OPTIONS): BUTTON_SCHEMA},
127 vol.Optional(CONF_OCCUPANCY, default={}): OCCUPANCY_SCHEMA,
128 vol.Optional(CONF_NOTIFY, default={}): NOTIFY_SCHEMA,
129 vol.Optional(CONF_RATE_LIMIT, default={}): RATE_LIMIT_SCHEMA,
130 })
131 },
132 extra=vol.ALLOW_EXTRA, # validation fails without this by trying to include all of HASS config
133)
135DEFAULT_TRANSITIONS: dict[str, Any] = {
136 "armed_home": [
137 "{{ autoarm.occupied and not autoarm.night }}",
138 "{{ autoarm.computed and autoarm.occupied_daytime_state == 'armed_home'}}",
139 ],
140 "armed_away": "{{ not autoarm.occupied and autoarm.computed}}",
141 "disarmed": [
142 "{{ autoarm.occupied and not autoarm.night }}",
143 "{{ autoarm.computed and autoarm.occupied_daytime_state == 'disarmed'}}",
144 ],
145 "armed_night": "{{ autoarm.occupied and autoarm.night and autoarm.computed}}",
146 "armed_vacation": "{{ autoarm.vacation }}",
147}
150@dataclass
151class ConditionVariables:
152 """Field with sub-fields added to the template context of Transition Conditions"""
154 occupied: bool
155 night: bool
156 state: AlarmControlPanelState
157 occupied_defaults: dict[str, AlarmControlPanelState]
158 calendar_event: CalendarEvent | None = None
159 at_home: list[str] | None = None
160 not_home: list[str] | None = None
162 def as_dict(self) -> ConfigType:
163 """Generate the field to be exposed in the context, stringifying alarm states"""
164 return {
165 "daytime": not self.night,
166 "occupied": self.occupied,
167 "at_home": self.at_home or [],
168 "not_home": self.at_home or [],
169 "vacation": self.state == AlarmControlPanelState.ARMED_VACATION,
170 "night": self.night,
171 "bypass": self.state == AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
172 "manual": self.state in (AlarmControlPanelState.ARMED_VACATION, AlarmControlPanelState.ARMED_CUSTOM_BYPASS),
173 "calendar_event": self.calendar_event,
174 "state": str(self.state),
175 "occupied_daytime_state": self.occupied_defaults.get(CONF_DAY, AlarmControlPanelState.ARMED_HOME),
176 "disarmed": self.state == AlarmControlPanelState.DISARMED,
177 "computed": not self.calendar_event
178 and self.state not in (AlarmControlPanelState.ARMED_VACATION, AlarmControlPanelState.ARMED_CUSTOM_BYPASS),
179 }
182class ChangeSource(StrEnum):
183 """Enumeration of all the known ways to trigger a state change"""
185 CALENDAR = auto()
186 MOBILE = auto()
187 OCCUPANCY = auto()
188 INTERNAL = auto()
189 ALARM_PANEL = auto()
190 BUTTON = auto()
191 ACTION = auto()
192 NOTIFY = auto()
193 SUNRISE = auto()
194 SUNSET = auto()
195 ZOMBIFICATION = auto()
196 STARTUP = auto()