Coverage for custom_components/autoarm/const.py: 100%

78 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2026-01-26 21:24 +0000

1"""The Auto Arm integration""" 

2 

3import logging 

4from dataclasses import dataclass 

5from enum import StrEnum, auto 

6 

7import voluptuous as vol 

8from homeassistant.components.alarm_control_panel.const import AlarmControlPanelState 

9from homeassistant.components.calendar import CalendarEvent 

10from homeassistant.const import ( 

11 CONF_ALIAS, 

12 CONF_CONDITIONS, 

13 CONF_DELAY_TIME, 

14 CONF_ENTITY_ID, 

15 CONF_SERVICE, 

16 STATE_HOME, 

17 STATE_NOT_HOME, 

18) 

19from homeassistant.helpers import config_validation as cv 

20from homeassistant.helpers.typing import ConfigType 

21 

22_LOGGER = logging.getLogger(__name__) 

23 

24DOMAIN = "autoarm" 

25 

26ATTR_ACTION = "action" 

27ATTR_RESET = "reset" 

28CONF_DATA = "data" 

29CONF_NOTIFY = "notify" 

30CONF_ALARM_PANEL = "alarm_panel" 

31CONF_ALARM_STATES = "alarm_states" 

32 

33ALARM_STATES = [k.lower() for k in AlarmControlPanelState.__members__] 

34 

35NO_CAL_EVENT_MODE_AUTO = "auto" 

36NO_CAL_EVENT_MODE_MANUAL = "manual" 

37NO_CAL_EVENT_OPTIONS: list[str] = [NO_CAL_EVENT_MODE_AUTO, NO_CAL_EVENT_MODE_MANUAL, *ALARM_STATES] 

38 

39NOTIFY_COMMON = "common" 

40NOTIFY_QUIET = "quiet" 

41NOTIFY_NORMAL = "normal" 

42NOTIFY_CATEGORIES = [NOTIFY_COMMON, NOTIFY_QUIET, NOTIFY_NORMAL] 

43 

44NOTIFY_DEF_SCHEMA = vol.Schema({vol.Optional(CONF_SERVICE): cv.service, vol.Optional(CONF_DATA): dict}) 

45 

46NOTIFY_SCHEMA = vol.Schema({ 

47 vol.Optional(NOTIFY_COMMON): {vol.Required(CONF_SERVICE): cv.service, vol.Optional(CONF_DATA): dict}, 

48 vol.Optional(NOTIFY_QUIET): NOTIFY_DEF_SCHEMA, 

49 vol.Optional(NOTIFY_NORMAL): NOTIFY_DEF_SCHEMA, 

50}) 

51 

52DEFAULT_CALENDAR_MAPPINGS = { 

53 AlarmControlPanelState.ARMED_AWAY: "Away", 

54 AlarmControlPanelState.DISARMED: "Disarmed", 

55 AlarmControlPanelState.ARMED_HOME: "Home", 

56 AlarmControlPanelState.ARMED_VACATION: "Vacation", 

57 AlarmControlPanelState.ARMED_VACATION: "Night", 

58} 

59 

60CONF_CALENDAR_CONTROL = "calendar_control" 

61CONF_CALENDARS = "calendars" 

62CONF_CALENDAR_POLL_INTERVAL = "poll_interval" 

63CONF_CALENDAR_EVENT_STATES = "state_patterns" 

64CONF_CALENDAR_NO_EVENT = "no_event_mode" 

65CALENDAR_SCHEMA = vol.Schema({ 

66 vol.Required(CONF_ENTITY_ID): cv.entity_id, 

67 vol.Optional(CONF_ALIAS): cv.string, 

68 vol.Optional(CONF_CALENDAR_POLL_INTERVAL, default=30): cv.positive_int, 

69 vol.Optional(CONF_CALENDAR_EVENT_STATES, default=DEFAULT_CALENDAR_MAPPINGS): { 

70 vol.In(ALARM_STATES): vol.All(cv.ensure_list, [cv.string]) 

71 }, 

72}) 

73CALENDAR_CONTROL_SCHEMA = vol.Schema({ 

74 vol.Optional(CONF_CALENDAR_NO_EVENT, default=NO_CAL_EVENT_MODE_AUTO): vol.All(vol.Lower, vol.In(NO_CAL_EVENT_OPTIONS)), 

75 vol.Optional(CONF_CALENDARS, default=[]): vol.All(cv.ensure_list, [CALENDAR_SCHEMA]), 

76}) 

77 

78CONF_TRANSITIONS = "transitions" 

79TRANSITION_SCHEMA = vol.Schema({vol.Optional(CONF_ALIAS): cv.string, vol.Required(CONF_CONDITIONS): cv.CONDITIONS_SCHEMA}) 

80 

81CONF_BUTTONS = "buttons" 

82BUTTON_OPTIONS = [ATTR_RESET, *ALARM_STATES] 

83BUTTON_SCHEMA = vol.Schema({ 

84 vol.Optional(CONF_ALIAS): cv.string, 

85 vol.Optional(CONF_DELAY_TIME): vol.All(cv.time_period, cv.positive_timedelta), 

86 vol.Required(CONF_ENTITY_ID): vol.All(cv.ensure_list, [cv.entity_id]), 

87}) 

88 

89CONF_RATE_LIMIT = "rate_limit" 

90CONF_RATE_LIMIT_CALLS = "max_calls" 

91CONF_RATE_LIMIT_PERIOD = "period" 

92RATE_LIMIT_SCHEMA = vol.Schema({ 

93 vol.Optional(CONF_RATE_LIMIT_PERIOD, default=60): vol.All(cv.time_period, cv.positive_timedelta), 

94 vol.Optional(CONF_RATE_LIMIT_CALLS, default=6): cv.positive_int, 

95}) 

96 

97CONF_OCCUPANCY = "occupancy" 

98CONF_DAY = "day" 

99CONF_NIGHT = "night" 

100CONF_OCCUPANCY_DEFAULT = "default_state" 

101OCCUPANCY_SCHEMA = vol.Schema({ 

102 vol.Required(CONF_ENTITY_ID, default=[]): vol.All(cv.ensure_list, [cv.entity_id]), 

103 vol.Optional(CONF_OCCUPANCY_DEFAULT, default={CONF_DAY: AlarmControlPanelState.ARMED_HOME}): { 

104 vol.In([CONF_DAY, CONF_NIGHT]): vol.In(ALARM_STATES) 

105 }, 

106 vol.Optional(CONF_DELAY_TIME): {vol.In([STATE_HOME, STATE_NOT_HOME]): vol.All(cv.time_period, cv.positive_timedelta)}, 

107}) 

108 

109CONF_DIURNAL = "diurnal" 

110CONF_SUNRISE = "sunrise" 

111CONF_EARLIEST = "earliest" 

112 

113CONFIG_SCHEMA = vol.Schema( 

114 { 

115 DOMAIN: vol.Schema({ 

116 vol.Required(CONF_ALARM_PANEL): vol.Schema({ 

117 vol.Optional(CONF_ALIAS): cv.string, 

118 vol.Required(CONF_ENTITY_ID): cv.entity_id, 

119 }), 

120 vol.Optional(CONF_DIURNAL): vol.Schema({ 

121 vol.Optional(CONF_SUNRISE): vol.Schema({vol.Optional(CONF_EARLIEST): cv.time}) 

122 }), 

123 vol.Optional(CONF_TRANSITIONS): {vol.In(ALARM_STATES): TRANSITION_SCHEMA}, 

124 vol.Optional(CONF_CALENDAR_CONTROL): CALENDAR_CONTROL_SCHEMA, 

125 vol.Optional(CONF_BUTTONS): {vol.In(BUTTON_OPTIONS): BUTTON_SCHEMA}, 

126 vol.Optional(CONF_OCCUPANCY, default={}): OCCUPANCY_SCHEMA, 

127 vol.Optional(CONF_NOTIFY, default={}): NOTIFY_SCHEMA, 

128 vol.Optional(CONF_RATE_LIMIT, default={}): RATE_LIMIT_SCHEMA, 

129 }) 

130 }, 

131 extra=vol.ALLOW_EXTRA, # validation fails without this by trying to include all of HASS config 

132) 

133 

134DEFAULT_TRANSITIONS: dict[str, str | list[str]] = { 

135 "armed_home": [ 

136 "{{ autoarm.occupied and not autoarm.night }}", 

137 "{{ autoarm.computed and autoarm.occupied_daytime_state == 'armed_home'}}", 

138 ], 

139 "armed_away": "{{ not autoarm.occupied and autoarm.computed}}", 

140 "disarmed": [ 

141 "{{ autoarm.occupied and not autoarm.night }}", 

142 "{{ autoarm.computed and autoarm.occupied_daytime_state == 'disarmed'}}", 

143 ], 

144 "armed_night": "{{ autoarm.occupied and autoarm.night and autoarm.computed}}", 

145 "armed_vacation": "{{ autoarm.vacation }}", 

146} 

147 

148 

149@dataclass 

150class ConditionVariables: 

151 """Field with sub-fields added to the template context of Transition Conditions""" 

152 

153 occupied: bool 

154 night: bool 

155 state: AlarmControlPanelState 

156 occupied_defaults: dict[str, AlarmControlPanelState] 

157 calendar_event: CalendarEvent | None = None 

158 at_home: list[str] | None = None 

159 not_home: list[str] | None = None 

160 

161 def as_dict(self) -> ConfigType: 

162 """Generate the field to be exposed in the context, stringifying alarm states""" 

163 return { 

164 "daytime": not self.night, 

165 "occupied": self.occupied, 

166 "at_home": self.at_home or [], 

167 "not_home": self.at_home or [], 

168 "vacation": self.state == AlarmControlPanelState.ARMED_VACATION, 

169 "night": self.night, 

170 "bypass": self.state == AlarmControlPanelState.ARMED_CUSTOM_BYPASS, 

171 "manual": self.state in (AlarmControlPanelState.ARMED_VACATION, AlarmControlPanelState.ARMED_CUSTOM_BYPASS), 

172 "calendar_event": self.calendar_event, 

173 "state": str(self.state), 

174 "occupied_daytime_state": self.occupied_defaults.get(CONF_DAY, AlarmControlPanelState.ARMED_HOME), 

175 "disarmed": self.state == AlarmControlPanelState.DISARMED, 

176 "computed": not self.calendar_event 

177 and self.state not in (AlarmControlPanelState.ARMED_VACATION, AlarmControlPanelState.ARMED_CUSTOM_BYPASS), 

178 } 

179 

180 

181class ChangeSource(StrEnum): 

182 """Enumeration of all the known ways to trigger a state change""" 

183 

184 CALENDAR = auto() 

185 MOBILE = auto() 

186 OCCUPANCY = auto() 

187 INTERNAL = auto() 

188 ALARM_PANEL = auto() 

189 BUTTON = auto() 

190 ACTION = auto() 

191 NOTIFY = auto() 

192 SUNRISE = auto() 

193 SUNSET = auto() 

194 ZOMBIFICATION = auto() 

195 STARTUP = auto()