Coverage for custom_components/supernotify/__init__.py: 99%

197 statements  

« prev     ^ index     » next       coverage.py v7.6.8, created at 2025-05-31 23:25 +0000

1"""The SuperNotification integration""" 

2 

3from dataclasses import dataclass, field 

4from enum import StrEnum 

5 

6import voluptuous as vol 

7from homeassistant.components.notify import PLATFORM_SCHEMA 

8from homeassistant.const import ( 

9 ATTR_DOMAIN, 

10 ATTR_SERVICE, 

11 CONF_ACTION, 

12 CONF_ALIAS, 

13 CONF_CONDITION, 

14 CONF_DEFAULT, 

15 CONF_DESCRIPTION, 

16 CONF_EMAIL, 

17 CONF_ENABLED, 

18 CONF_ENTITIES, 

19 CONF_ICON, 

20 CONF_ID, 

21 CONF_NAME, 

22 CONF_PLATFORM, 

23 CONF_TARGET, 

24 CONF_URL, 

25 STATE_HOME, 

26 STATE_NOT_HOME, 

27 Platform, 

28) 

29from homeassistant.helpers import config_validation as cv 

30 

31from custom_components.supernotify.common import format_timestamp as format_timestamp 

32 

33DOMAIN = "supernotify" 

34 

35PLATFORMS = [Platform.NOTIFY] 

36TEMPLATE_DIR = "/config/templates/supernotify" 

37MEDIA_DIR = "supernotify/media" 

38 

39CONF_ACTIONS = "actions" 

40CONF_TITLE = "title" 

41CONF_URI = "uri" 

42CONF_RECIPIENTS = "recipients" 

43CONF_TEMPLATE_PATH = "template_path" 

44CONF_MEDIA_PATH = "media_path" 

45CONF_HOUSEKEEPING = "housekeeping" 

46CONF_HOUSEKEEPING_TIME = "housekeeping_time" 

47CONF_ARCHIVE_PATH = "archive_path" 

48CONF_ARCHIVE = "archive" 

49CONF_ARCHIVE_DAYS = "archive_days" 

50CONF_TEMPLATE = "template" 

51CONF_LINKS = "links" 

52CONF_PERSON = "person" 

53CONF_METHOD = "method" 

54CONF_METHODS = "methods" 

55CONF_DELIVERY = "delivery" 

56CONF_SELECTION = "selection" 

57 

58CONF_DATA: str = "data" 

59CONF_OPTIONS: str = "options" 

60CONF_MOBILE: str = "mobile" 

61CONF_NOTIFY: str = "notify" 

62CONF_NOTIFY_ACTION: str = "notify_action" 

63CONF_PHONE_NUMBER: str = "phone_number" 

64CONF_PRIORITY: str = "priority" 

65CONF_OCCUPANCY: str = "occupancy" 

66CONF_SCENARIOS: str = "scenarios" 

67CONF_MANUFACTURER: str = "manufacturer" 

68CONF_DEVICE_TRACKER: str = "device_tracker" 

69CONF_DEVICE_NAME: str = "device_name" 

70CONF_DEVICE_LABELS: str = "device_labels" 

71CONF_MODEL: str = "model" 

72CONF_MESSAGE: str = "message" 

73CONF_TARGETS_REQUIRED: str = "targets_required" 

74CONF_MOBILE_DEVICES: str = "mobile_devices" 

75CONF_MOBILE_DISCOVERY: str = "mobile_discovery" 

76CONF_ACTION_TEMPLATE: str = "action_template" 

77CONF_ACTION_GROUPS: str = "action_groups" 

78CONF_TITLE_TEMPLATE: str = "title_template" 

79CONF_DELIVERY_SELECTION: str = "delivery_selection" 

80CONF_MEDIA: str = "media" 

81CONF_CAMERA: str = "camera" 

82CONF_CLIP_URL: str = "clip_url" 

83CONF_SNAPSHOT_URL: str = "snapshot_url" 

84CONF_PTZ_DELAY: str = "ptz_delay" 

85CONF_PTZ_METHOD: str = "ptz_method" 

86CONF_PTZ_PRESET_DEFAULT: str = "ptz_default_preset" 

87CONF_ALT_CAMERA: str = "alt_camera" 

88CONF_CAMERAS: str = "cameras" 

89 

90OCCUPANCY_ANY_IN = "any_in" 

91OCCUPANCY_ANY_OUT = "any_out" 

92OCCUPANCY_ALL_IN = "all_in" 

93OCCUPANCY_ALL = "all" 

94OCCUPANCY_NONE = "none" 

95OCCUPANCY_ALL_OUT = "all_out" 

96OCCUPANCY_ONLY_IN = "only_in" 

97OCCUPANCY_ONLY_OUT = "only_out" 

98 

99ATTR_PRIORITY = "priority" 

100ATTR_ACTION = "action" 

101ATTR_SCENARIOS_REQUIRE = "require_scenarios" 

102ATTR_SCENARIOS_APPLY = "apply_scenarios" 

103ATTR_SCENARIOS_CONSTRAIN = "constrain_scenarios" 

104ATTR_DELIVERY = "delivery" 

105ATTR_DEFAULT = "default" 

106ATTR_NOTIFICATION_ID = "notification_id" 

107ATTR_DELIVERY_SELECTION = "delivery_selection" 

108ATTR_RECIPIENTS = "recipients" 

109ATTR_DATA = "data" 

110ATTR_MEDIA = "media" 

111ATTR_TITLE = "title" 

112ATTR_MEDIA_SNAPSHOT_URL = "snapshot_url" 

113ATTR_MEDIA_CAMERA_ENTITY_ID = "camera_entity_id" 

114ATTR_MEDIA_CAMERA_DELAY = "camera_delay" 

115ATTR_MEDIA_CAMERA_PTZ_PRESET = "camera_ptz_preset" 

116ATTR_MEDIA_CLIP_URL = "clip_url" 

117ATTR_ACTION_GROUPS = "action_groups" 

118CONF_ACTION_GROUP_NAMES = "action_groups" 

119ATTR_ACTION_CATEGORY = "action_category" 

120ATTR_ACTION_URL = "action_url" 

121ATTR_ACTION_URL_TITLE = "action_url_title" 

122ATTR_MESSAGE_HTML = "message_html" 

123ATTR_JPEG_FLAGS = "jpeg_flags" 

124ATTR_TIMESTAMP = "timestamp" 

125ATTR_DEBUG = "debug" 

126ATTR_ACTIONS = "actions" 

127ATTR_USER_ID = "user_id" 

128 

129DELIVERY_SELECTION_IMPLICIT = "implicit" 

130DELIVERY_SELECTION_EXPLICIT = "explicit" 

131DELIVERY_SELECTION_FIXED = "fixed" 

132 

133DELIVERY_SELECTION_VALUES = [DELIVERY_SELECTION_EXPLICIT, DELIVERY_SELECTION_FIXED, DELIVERY_SELECTION_IMPLICIT] 

134PTZ_METHOD_ONVIF = "onvif" 

135PTZ_METHOD_FRIGATE = "frigate" 

136PTZ_METHOD_VALUES = [PTZ_METHOD_ONVIF, PTZ_METHOD_FRIGATE] 

137 

138SELECTION_FALLBACK_ON_ERROR = "fallback_on_error" 

139SELECTION_FALLBACK = "fallback" 

140SELECTION_BY_SCENARIO = "scenario" 

141SELECTION_DEFAULT = "default" 

142SELECTION_VALUES = [SELECTION_FALLBACK_ON_ERROR, SELECTION_BY_SCENARIO, SELECTION_DEFAULT, SELECTION_FALLBACK] 

143 

144OCCUPANCY_VALUES = [ 

145 OCCUPANCY_ALL_IN, 

146 OCCUPANCY_ALL_OUT, 

147 OCCUPANCY_ANY_IN, 

148 OCCUPANCY_ANY_OUT, 

149 OCCUPANCY_ONLY_IN, 

150 OCCUPANCY_ONLY_OUT, 

151 OCCUPANCY_ALL, 

152 OCCUPANCY_NONE, 

153] 

154 

155PRIORITY_CRITICAL = "critical" 

156PRIORITY_HIGH = "high" 

157PRIORITY_MEDIUM = "medium" 

158PRIORITY_LOW = "low" 

159 

160PRIORITY_VALUES = [PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_CRITICAL] 

161METHOD_SMS = "sms" 

162METHOD_EMAIL = "email" 

163METHOD_ALEXA = "alexa" 

164METHOD_MOBILE_PUSH = "mobile_push" 

165METHOD_MEDIA = "media" 

166METHOD_CHIME = "chime" 

167METHOD_GENERIC = "generic" 

168METHOD_PERSISTENT = "persistent" 

169METHOD_VALUES = [ 

170 METHOD_SMS, 

171 METHOD_ALEXA, 

172 METHOD_MOBILE_PUSH, 

173 METHOD_CHIME, 

174 METHOD_EMAIL, 

175 METHOD_MEDIA, 

176 METHOD_PERSISTENT, 

177 METHOD_GENERIC, 

178] 

179 

180SCENARIO_DEFAULT = "DEFAULT" 

181SCENARIO_NULL = "NULL" 

182 

183RESERVED_DELIVERY_NAMES = ["ALL"] 

184RESERVED_SCENARIO_NAMES = [SCENARIO_DEFAULT, SCENARIO_NULL] 

185RESERVED_DATA_KEYS = [ATTR_DOMAIN, ATTR_SERVICE, "action"] 

186 

187CONF_DUPE_CHECK = "dupe_check" 

188CONF_DUPE_POLICY = "dupe_policy" 

189CONF_TTL = "ttl" 

190CONF_SIZE = "size" 

191ATTR_DUPE_POLICY_MTSLP = "dupe_policy_message_title_same_or_lower_priority" 

192ATTR_DUPE_POLICY_NONE = "dupe_policy_none" 

193 

194DATA_SCHEMA = vol.Schema({vol.NotIn(RESERVED_DATA_KEYS): vol.Any(str, int, bool, float, dict, list)}) 

195MOBILE_DEVICE_SCHEMA = vol.Schema({ 

196 vol.Optional(CONF_MANUFACTURER): cv.string, 

197 vol.Optional(CONF_MODEL): cv.string, 

198 vol.Optional(CONF_NOTIFY_ACTION): cv.string, 

199 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id, 

200}) 

201NOTIFICATION_DUPE_SCHEMA = vol.Schema({ 

202 vol.Optional(CONF_TTL): cv.positive_int, 

203 vol.Optional(CONF_SIZE, default=100): cv.positive_int, # type: ignore 

204 vol.Optional(CONF_DUPE_POLICY, default=ATTR_DUPE_POLICY_MTSLP): vol.In( # type: ignore 

205 [ATTR_DUPE_POLICY_MTSLP, ATTR_DUPE_POLICY_NONE] 

206 ), # type: ignore 

207}) 

208DELIVERY_CUSTOMIZE_SCHEMA = vol.Schema({ 

209 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

210 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

211 vol.Optional(CONF_ENABLED, default=True): cv.boolean, # type: ignore 

212 vol.Optional(CONF_DATA): DATA_SCHEMA, 

213}) 

214LINK_SCHEMA = vol.Schema({ 

215 vol.Optional(CONF_ID): cv.string, 

216 vol.Required(CONF_URL): cv.url, 

217 vol.Optional(CONF_ICON): cv.icon, 

218 vol.Required(CONF_DESCRIPTION): cv.string, 

219 vol.Optional(CONF_NAME): cv.string, 

220}) 

221METHOD_DEFAULTS_SCHEMA = vol.Schema({ 

222 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

223 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

224 vol.Optional(CONF_ACTION): cv.service, 

225 vol.Optional(CONF_TARGETS_REQUIRED): cv.boolean, 

226 vol.Optional(CONF_OPTIONS, default=dict): dict, # type: ignore 

227 vol.Optional(CONF_DATA): DATA_SCHEMA, 

228}) 

229RECIPIENT_SCHEMA = vol.Schema({ 

230 vol.Required(CONF_PERSON): cv.entity_id, 

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

232 vol.Optional(CONF_EMAIL): cv.string, 

233 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

234 vol.Optional(CONF_PHONE_NUMBER): cv.string, 

235 vol.Optional(CONF_MOBILE_DISCOVERY, default=True): cv.boolean, # type: ignore 

236 vol.Optional(CONF_MOBILE_DEVICES, default=list): vol.All(cv.ensure_list, [MOBILE_DEVICE_SCHEMA]), # type: ignore 

237 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_CUSTOMIZE_SCHEMA}, # type: ignore 

238}) 

239CAMERA_SCHEMA = vol.Schema({ 

240 vol.Required(CONF_CAMERA): cv.entity_id, 

241 vol.Optional(CONF_ALT_CAMERA): vol.All(cv.ensure_list, [cv.entity_id]), 

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

243 vol.Optional(CONF_URL): cv.url, 

244 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id, 

245 vol.Optional(CONF_PTZ_PRESET_DEFAULT, default=1): vol.Any(cv.positive_int, cv.string), # type: ignore 

246 vol.Optional(CONF_PTZ_DELAY, default=0): int, # type: ignore 

247 vol.Optional(CONF_PTZ_METHOD, default=PTZ_METHOD_ONVIF): vol.In(PTZ_METHOD_VALUES), # type: ignore 

248}) 

249MEDIA_SCHEMA = vol.Schema({ 

250 vol.Optional(ATTR_MEDIA_CAMERA_ENTITY_ID): cv.entity_id, 

251 vol.Optional(ATTR_MEDIA_CAMERA_DELAY, default=0): int, # type: ignore 

252 vol.Optional(ATTR_MEDIA_CAMERA_PTZ_PRESET): vol.Any(cv.positive_int, cv.string), 

253 # URL fragments allowed 

254 vol.Optional(ATTR_MEDIA_CLIP_URL): vol.Any(cv.url, cv.string), 

255 vol.Optional(ATTR_MEDIA_SNAPSHOT_URL): vol.Any(cv.url, cv.string), 

256 vol.Optional(ATTR_JPEG_FLAGS): dict, 

257}) 

258DELIVERY_SCHEMA = vol.Schema({ 

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

260 vol.Required(CONF_METHOD): vol.In(METHOD_VALUES), 

261 vol.Optional(CONF_ACTION): cv.service, # previously 'service:' 

262 vol.Optional(CONF_PLATFORM): cv.string, 

263 vol.Optional(CONF_TEMPLATE): cv.string, 

264 vol.Optional(CONF_DEFAULT, default=False): cv.boolean, # type: ignore 

265 vol.Optional(CONF_SELECTION, default=[SELECTION_DEFAULT]): vol.All( # type: ignore 

266 cv.ensure_list, [vol.In(SELECTION_VALUES)] 

267 ), 

268 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]), 

269 vol.Optional(CONF_ENTITIES): vol.All(cv.ensure_list, [cv.entity_id]), 

270 vol.Optional(CONF_MESSAGE): vol.Any(None, cv.string), 

271 vol.Optional(CONF_TITLE): vol.Any(None, cv.string), 

272 vol.Optional(CONF_DATA): DATA_SCHEMA, 

273 vol.Optional(CONF_ENABLED, default=True): cv.boolean, # type: ignore # type: ignore 

274 vol.Optional(CONF_OPTIONS, default=dict): dict, # type: ignore 

275 vol.Optional(CONF_PRIORITY, default=PRIORITY_VALUES): vol.All( # type: ignore 

276 cv.ensure_list, [vol.In(PRIORITY_VALUES)] 

277 ), 

278 vol.Optional(CONF_OCCUPANCY, default=OCCUPANCY_ALL): vol.In(OCCUPANCY_VALUES), # type: ignore 

279 vol.Optional(CONF_CONDITION): cv.CONDITION_SCHEMA, 

280}) 

281 

282SCENARIO_SCHEMA = vol.Schema({ 

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

284 vol.Optional(CONF_CONDITION): cv.CONDITION_SCHEMA, 

285 vol.Optional(CONF_MEDIA): MEDIA_SCHEMA, 

286 vol.Optional(CONF_ACTION_GROUP_NAMES, default=[]): vol.All(cv.ensure_list, [cv.string]), # type: ignore 

287 vol.Optional(CONF_DELIVERY_SELECTION): vol.In(DELIVERY_SELECTION_VALUES), 

288 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}, # type: ignore 

289}) 

290ACTION_CALL_SCHEMA = vol.Schema( 

291 { 

292 vol.Optional(ATTR_ACTION): cv.string, 

293 vol.Optional(ATTR_TITLE): cv.string, 

294 vol.Optional(ATTR_ACTION_CATEGORY): cv.string, 

295 vol.Optional(ATTR_ACTION_URL): cv.url, 

296 vol.Optional(ATTR_ACTION_URL_TITLE): cv.string, 

297 }, 

298 extra=vol.ALLOW_EXTRA, 

299) 

300ACTION_SCHEMA = vol.Schema( 

301 { 

302 vol.Exclusive(CONF_ACTION, CONF_ACTION_TEMPLATE): cv.string, 

303 vol.Exclusive(CONF_TITLE, CONF_TITLE_TEMPLATE): cv.string, 

304 vol.Optional(CONF_URI): cv.url, 

305 vol.Optional(CONF_ICON): cv.string, 

306 }, 

307 extra=vol.ALLOW_EXTRA, 

308) 

309 

310 

311ARCHIVE_SCHEMA = vol.Schema({ 

312 vol.Optional(CONF_ARCHIVE_PATH): cv.path, 

313 vol.Optional(CONF_ENABLED, default=False): cv.boolean, # type: ignore 

314 vol.Optional(CONF_ARCHIVE_DAYS, default=3): cv.positive_int, # type: ignore 

315}) 

316 

317HOUSEKEEPING_SCHEMA = vol.Schema({ 

318 vol.Optional(CONF_HOUSEKEEPING_TIME, default="00:00:01"): cv.time, # type: ignore 

319}) 

320 

321PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 

322 vol.Optional(CONF_TEMPLATE_PATH, default=TEMPLATE_DIR): cv.path, # type: ignore 

323 vol.Optional(CONF_MEDIA_PATH, default=MEDIA_DIR): cv.path, # type: ignore 

324 vol.Optional(CONF_ARCHIVE, default={CONF_ENABLED: False}): ARCHIVE_SCHEMA, # type: ignore 

325 vol.Optional(CONF_HOUSEKEEPING, default=dict): HOUSEKEEPING_SCHEMA, # type: ignore 

326 vol.Optional(CONF_DUPE_CHECK, default=dict): NOTIFICATION_DUPE_SCHEMA, # type: ignore 

327 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_SCHEMA}, # type: ignore 

328 vol.Optional(CONF_ACTION_GROUPS, default=dict): {cv.string: [ACTION_SCHEMA]}, # type: ignore 

329 vol.Optional(CONF_RECIPIENTS, default=list): vol.All(cv.ensure_list, [RECIPIENT_SCHEMA]), # type: ignore # type: ignore 

330 vol.Optional(CONF_LINKS, default=list): vol.All(cv.ensure_list, [LINK_SCHEMA]), # type: ignore 

331 vol.Optional(CONF_SCENARIOS, default=dict): {cv.string: SCENARIO_SCHEMA}, # type: ignore 

332 vol.Optional(CONF_METHODS, default=dict): {cv.string: METHOD_DEFAULTS_SCHEMA}, # type: ignore 

333 vol.Optional(CONF_CAMERAS, default=list): vol.All(cv.ensure_list, [CAMERA_SCHEMA]), # type: ignore 

334}) 

335 

336ACTION_DATA_SCHEMA = vol.Schema( 

337 { 

338 vol.Optional(ATTR_DELIVERY): vol.Any(cv.string, [cv.string], {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}), 

339 vol.Optional(ATTR_PRIORITY): vol.In(PRIORITY_VALUES), 

340 vol.Optional(ATTR_SCENARIOS_REQUIRE): vol.All(cv.ensure_list, [cv.string]), 

341 vol.Optional(ATTR_SCENARIOS_APPLY): vol.All(cv.ensure_list, [cv.string]), 

342 vol.Optional(ATTR_SCENARIOS_CONSTRAIN): vol.All(cv.ensure_list, [cv.string]), 

343 vol.Optional(ATTR_DELIVERY_SELECTION): vol.In(DELIVERY_SELECTION_VALUES), 

344 vol.Optional(ATTR_RECIPIENTS): vol.All(cv.ensure_list, [cv.entity_id]), 

345 vol.Optional(ATTR_MEDIA): MEDIA_SCHEMA, 

346 vol.Optional(ATTR_MESSAGE_HTML): cv.string, 

347 vol.Optional(ATTR_ACTION_GROUPS, default=[]): vol.All(cv.ensure_list, [cv.string]), # type: ignore 

348 vol.Optional(ATTR_ACTIONS, default=[]): vol.All(cv.ensure_list, [ACTION_CALL_SCHEMA]), # type: ignore 

349 vol.Optional(ATTR_DEBUG, default=False): cv.boolean, # type: ignore 

350 vol.Optional(ATTR_DATA): vol.Any(None, DATA_SCHEMA), 

351 }, 

352 extra=vol.ALLOW_EXTRA, # allow other data, e.g. the android/ios mobile push 

353) 

354 

355STRICT_ACTION_DATA_SCHEMA = ACTION_DATA_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA) 

356 

357 

358class TargetType(StrEnum): 

359 pass 

360 

361 

362class GlobalTargetType(TargetType): 

363 NONCRITICAL = "NONCRITICAL" 

364 EVERYTHING = "EVERYTHING" 

365 

366 

367class RecipientType(StrEnum): 

368 USER = "USER" 

369 EVERYONE = "EVERYONE" 

370 

371 

372class QualifiedTargetType(TargetType): 

373 METHOD = "METHOD" 

374 DELIVERY = "DELIVERY" 

375 CAMERA = "CAMERA" 

376 PRIORITY = "PRIORITY" 

377 ACTION = "ACTION" 

378 

379 

380class CommandType(StrEnum): 

381 SNOOZE = "SNOOZE" 

382 SILENCE = "SILENCE" 

383 NORMAL = "NORMAL" 

384 

385 

386@dataclass 

387class ConditionVariables: 

388 """Variables presented to all condition evaluations 

389 

390 Attributes 

391 ---------- 

392 applied_scenarios (list[str]): Scenarios that have been applied 

393 required_scenarios (list[str]): Scenarios that must be applied 

394 constrain_scenarios (list[str]): Only scenarios in this list, or in explicit apply_scenarios, can be applied 

395 notification_priority (str): Priority of the notification 

396 notification_message (str): Message of the notification 

397 notification_title (str): Title of the notification 

398 occupancy (list[str]): List of occupancy scenarios 

399 

400 """ 

401 

402 applied_scenarios: list[str] = field(default_factory=list) 

403 required_scenarios: list[str] = field(default_factory=list) 

404 constrain_scenarios: list[str] = field(default_factory=list) 

405 notification_priority: str = PRIORITY_MEDIUM 

406 notification_message: str = "" 

407 notification_title: str = "" 

408 occupancy: list[str] = field(default_factory=list) 

409 

410 def __init__( 

411 self, 

412 applied_scenarios: list[str] | None = None, 

413 required_scenarios: list[str] | None = None, 

414 constrain_scenarios: list[str] | None = None, 

415 delivery_priority: str | None = PRIORITY_MEDIUM, 

416 occupiers: dict | None = None, 

417 message: str | None = None, 

418 title: str | None = None, 

419 ) -> None: 

420 occupiers = occupiers or {} 

421 self.occupancy = [] 

422 if not occupiers.get(STATE_NOT_HOME) and occupiers.get(STATE_HOME): 

423 self.occupancy.append("ALL_HOME") 

424 elif occupiers.get(STATE_NOT_HOME) and not occupiers.get(STATE_HOME): 

425 self.occupancy.append("ALL_AWAY") 

426 if len(occupiers.get(STATE_HOME, [])) == 1: 

427 self.occupancy.extend(["LONE_HOME", "SOME_HOME"]) 

428 elif len(occupiers.get(STATE_HOME, [])) > 1 and occupiers.get(STATE_NOT_HOME): 

429 self.occupancy.extend(["MULTI_HOME", "SOME_HOME"]) 

430 self.applied_scenarios = applied_scenarios or [] 

431 self.required_scenarios = required_scenarios or [] 

432 self.constrain_scenarios = constrain_scenarios or [] 

433 self.notification_priority = delivery_priority or PRIORITY_MEDIUM 

434 self.notification_message = message or "" 

435 self.notification_title = title or ""