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

1"""The Auto Arm integration""" 

2 

3import logging 

4from dataclasses import dataclass 

5from enum import StrEnum, auto 

6from typing import Any 

7 

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 

22 

23_LOGGER = logging.getLogger(__name__) 

24 

25DOMAIN = "autoarm" 

26 

27ATTR_ACTION = "action" 

28ATTR_RESET = "reset" 

29CONF_DATA = "data" 

30CONF_NOTIFY = "notify" 

31CONF_ALARM_PANEL = "alarm_panel" 

32CONF_ALARM_STATES = "alarm_states" 

33 

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

35 

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] 

39 

40NOTIFY_COMMON = "common" 

41NOTIFY_QUIET = "quiet" 

42NOTIFY_NORMAL = "normal" 

43NOTIFY_CATEGORIES = [NOTIFY_COMMON, NOTIFY_QUIET, NOTIFY_NORMAL] 

44 

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

46 

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}) 

52 

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} 

60 

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}) 

78 

79CONF_TRANSITIONS = "transitions" 

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

81 

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}) 

89 

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}) 

97 

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}) 

109 

110CONF_DIURNAL = "diurnal" 

111CONF_SUNRISE = "sunrise" 

112CONF_EARLIEST = "earliest" 

113 

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) 

134 

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} 

148 

149 

150@dataclass 

151class ConditionVariables: 

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

153 

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 

161 

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 } 

180 

181 

182class ChangeSource(StrEnum): 

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

184 

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()