Coverage for custom_components/supernotify/__init__.py: 99%
214 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-10-26 08:54 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-10-26 08:54 +0000
1"""The SuperNotification integration"""
3from dataclasses import dataclass, field
4from enum import StrEnum
5from typing import Any
7import voluptuous as vol
8from homeassistant.components.notify import PLATFORM_SCHEMA
9from homeassistant.const import (
10 ATTR_DOMAIN,
11 ATTR_SERVICE,
12 CONF_ACTION,
13 CONF_ALIAS,
14 CONF_CONDITION,
15 CONF_DEFAULT,
16 CONF_DESCRIPTION,
17 CONF_EMAIL,
18 CONF_ENABLED,
19 CONF_ICON,
20 CONF_ID,
21 CONF_NAME,
22 CONF_TARGET,
23 CONF_URL,
24 STATE_HOME,
25 STATE_NOT_HOME,
26 Platform,
27)
28from homeassistant.helpers import config_validation as cv
30from custom_components.supernotify.common import format_timestamp as format_timestamp
32DOMAIN = "supernotify"
34PLATFORMS = [Platform.NOTIFY]
35TEMPLATE_DIR = "/config/templates/supernotify"
36MEDIA_DIR = "supernotify/media"
38CONF_ACTIONS = "actions"
39CONF_TITLE = "title"
40CONF_URI = "uri"
41CONF_RECIPIENTS = "recipients"
42CONF_TEMPLATE_PATH = "template_path"
43CONF_MEDIA_PATH = "media_path"
44CONF_HOUSEKEEPING = "housekeeping"
45CONF_HOUSEKEEPING_TIME = "housekeeping_time"
46CONF_ARCHIVE_PATH = "archive_path"
47CONF_ARCHIVE = "archive"
48CONF_ARCHIVE_DAYS = "archive_days"
49CONF_ARCHIVE_MQTT_TOPIC = "archive_mqtt_topic"
50CONF_ARCHIVE_MQTT_QOS = "archive_mqtt_qos"
51CONF_ARCHIVE_MQTT_RETAIN = "archive_mqtt_retain"
52CONF_TEMPLATE = "template"
53CONF_LINKS = "links"
54CONF_PERSON = "person"
55CONF_METHOD = "method"
56CONF_METHODS = "methods"
57CONF_DELIVERY = "delivery"
58CONF_SELECTION = "selection"
60CONF_DATA: str = "data"
61CONF_OPTIONS: str = "options"
62CONF_MOBILE: str = "mobile"
63CONF_NOTIFY: str = "notify"
64CONF_NOTIFY_ACTION: str = "notify_action"
65CONF_PHONE_NUMBER: str = "phone_number"
66CONF_PRIORITY: str = "priority"
67CONF_OCCUPANCY: str = "occupancy"
68CONF_SCENARIOS: str = "scenarios"
69CONF_MANUFACTURER: str = "manufacturer"
70CONF_DEVICE_DISCOVERY: str = "device_discovery"
71CONF_DEVICE_TRACKER: str = "device_tracker"
72CONF_DEVICE_NAME: str = "device_name"
73CONF_DEVICE_LABELS: str = "device_labels"
74CONF_DEVICE_DOMAIN: str = "device_domain"
75CONF_MODEL: str = "model"
76CONF_MESSAGE: str = "message"
77CONF_TARGETS_REQUIRED: str = "targets_required"
78CONF_MOBILE_DEVICES: str = "mobile_devices"
79CONF_MOBILE_DISCOVERY: str = "mobile_discovery"
80CONF_ACTION_TEMPLATE: str = "action_template"
81CONF_ACTION_GROUPS: str = "action_groups"
82CONF_TITLE_TEMPLATE: str = "title_template"
83CONF_DELIVERY_SELECTION: str = "delivery_selection"
84CONF_MEDIA: str = "media"
85CONF_CAMERA: str = "camera"
86CONF_CLIP_URL: str = "clip_url"
87CONF_SNAPSHOT_URL: str = "snapshot_url"
88CONF_PTZ_DELAY: str = "ptz_delay"
89CONF_PTZ_METHOD: str = "ptz_method"
90CONF_PTZ_PRESET_DEFAULT: str = "ptz_default_preset"
91CONF_ALT_CAMERA: str = "alt_camera"
92CONF_CAMERAS: str = "cameras"
93CONF_DEFAULT_ACTION: str = "default_action"
95OCCUPANCY_ANY_IN = "any_in"
96OCCUPANCY_ANY_OUT = "any_out"
97OCCUPANCY_ALL_IN = "all_in"
98OCCUPANCY_ALL = "all"
99OCCUPANCY_NONE = "none"
100OCCUPANCY_ALL_OUT = "all_out"
101OCCUPANCY_ONLY_IN = "only_in"
102OCCUPANCY_ONLY_OUT = "only_out"
104ATTR_PRIORITY = "priority"
105ATTR_ACTION = "action"
106ATTR_SCENARIOS_REQUIRE = "require_scenarios"
107ATTR_SCENARIOS_APPLY = "apply_scenarios"
108ATTR_SCENARIOS_CONSTRAIN = "constrain_scenarios"
109ATTR_DELIVERY = "delivery"
110ATTR_DEFAULT = "default"
111ATTR_NOTIFICATION_ID = "notification_id"
112ATTR_DELIVERY_SELECTION = "delivery_selection"
113ATTR_RECIPIENTS = "recipients"
114ATTR_DATA = "data"
115ATTR_MEDIA = "media"
116ATTR_TITLE = "title"
117ATTR_MEDIA_SNAPSHOT_URL = "snapshot_url"
118ATTR_MEDIA_CAMERA_ENTITY_ID = "camera_entity_id"
119ATTR_MEDIA_CAMERA_DELAY = "camera_delay"
120ATTR_MEDIA_CAMERA_PTZ_PRESET = "camera_ptz_preset"
121ATTR_MEDIA_CLIP_URL = "clip_url"
122ATTR_ACTION_GROUPS = "action_groups"
123CONF_ACTION_GROUP_NAMES = "action_groups"
124ATTR_ACTION_CATEGORY = "action_category"
125ATTR_ACTION_URL = "action_url"
126ATTR_ACTION_URL_TITLE = "action_url_title"
127ATTR_MESSAGE_HTML = "message_html"
128ATTR_JPEG_OPTS = "jpeg_opts"
129ATTR_TIMESTAMP = "timestamp"
130ATTR_DEBUG = "debug"
131ATTR_ACTIONS = "actions"
132ATTR_USER_ID = "user_id"
134DELIVERY_SELECTION_IMPLICIT = "implicit"
135DELIVERY_SELECTION_EXPLICIT = "explicit"
136DELIVERY_SELECTION_FIXED = "fixed"
138DELIVERY_SELECTION_VALUES = [DELIVERY_SELECTION_EXPLICIT, DELIVERY_SELECTION_FIXED, DELIVERY_SELECTION_IMPLICIT]
139PTZ_METHOD_ONVIF = "onvif"
140PTZ_METHOD_FRIGATE = "frigate"
141PTZ_METHOD_VALUES = [PTZ_METHOD_ONVIF, PTZ_METHOD_FRIGATE]
143SELECTION_FALLBACK_ON_ERROR = "fallback_on_error"
144SELECTION_FALLBACK = "fallback"
145SELECTION_BY_SCENARIO = "scenario"
146SELECTION_DEFAULT = "default"
147SELECTION_VALUES = [SELECTION_FALLBACK_ON_ERROR, SELECTION_BY_SCENARIO, SELECTION_DEFAULT, SELECTION_FALLBACK]
149OCCUPANCY_VALUES = [
150 OCCUPANCY_ALL_IN,
151 OCCUPANCY_ALL_OUT,
152 OCCUPANCY_ANY_IN,
153 OCCUPANCY_ANY_OUT,
154 OCCUPANCY_ONLY_IN,
155 OCCUPANCY_ONLY_OUT,
156 OCCUPANCY_ALL,
157 OCCUPANCY_NONE,
158]
160PRIORITY_CRITICAL = "critical"
161PRIORITY_HIGH = "high"
162PRIORITY_MEDIUM = "medium"
163PRIORITY_LOW = "low"
165PRIORITY_VALUES = [PRIORITY_LOW, PRIORITY_MEDIUM, PRIORITY_HIGH, PRIORITY_CRITICAL]
166METHOD_SMS = "sms"
167METHOD_EMAIL = "email"
168METHOD_ALEXA = "alexa_devices"
169METHOD_ALEXA_MEDIA_PLAYER = "alexa_media_player"
170METHOD_MOBILE_PUSH = "mobile_push"
171METHOD_MEDIA = "media"
172METHOD_CHIME = "chime"
173METHOD_GENERIC = "generic"
174METHOD_PERSISTENT = "persistent"
175METHOD_VALUES = [
176 METHOD_SMS,
177 METHOD_ALEXA,
178 METHOD_ALEXA_MEDIA_PLAYER,
179 METHOD_MOBILE_PUSH,
180 METHOD_CHIME,
181 METHOD_EMAIL,
182 METHOD_MEDIA,
183 METHOD_PERSISTENT,
184 METHOD_GENERIC,
185]
187SCENARIO_DEFAULT = "DEFAULT"
188SCENARIO_NULL = "NULL"
189SCENARIO_TEMPLATE_ATTRS = ("message_template", "title_template")
191RESERVED_DELIVERY_NAMES = ["ALL"]
192RESERVED_SCENARIO_NAMES = [SCENARIO_DEFAULT, SCENARIO_NULL]
193RESERVED_DATA_KEYS = [ATTR_DOMAIN, ATTR_SERVICE, "action"]
195CONF_DUPE_CHECK = "dupe_check"
196CONF_DUPE_POLICY = "dupe_policy"
197CONF_TTL = "ttl"
198CONF_SIZE = "size"
199ATTR_DUPE_POLICY_MTSLP = "dupe_policy_message_title_same_or_lower_priority"
200ATTR_DUPE_POLICY_NONE = "dupe_policy_none"
202DATA_SCHEMA = vol.Schema({vol.NotIn(RESERVED_DATA_KEYS): vol.Any(str, int, bool, float, dict, list)})
203MOBILE_DEVICE_SCHEMA = vol.Schema({
204 vol.Optional(CONF_MANUFACTURER): cv.string,
205 vol.Optional(CONF_MODEL): cv.string,
206 vol.Optional(CONF_NOTIFY_ACTION): cv.string,
207 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id,
208})
209NOTIFICATION_DUPE_SCHEMA = vol.Schema({
210 vol.Optional(CONF_TTL): cv.positive_int,
211 vol.Optional(CONF_SIZE, default=100): cv.positive_int,
212 vol.Optional(CONF_DUPE_POLICY, default=ATTR_DUPE_POLICY_MTSLP): vol.In([ATTR_DUPE_POLICY_MTSLP, ATTR_DUPE_POLICY_NONE]),
213})
214DELIVERY_CUSTOMIZE_SCHEMA = vol.Schema({
215 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]),
216 vol.Optional(CONF_ENABLED, default=True): cv.boolean,
217 vol.Optional(CONF_DATA): DATA_SCHEMA,
218})
219LINK_SCHEMA = vol.Schema({
220 vol.Optional(CONF_ID): cv.string,
221 vol.Required(CONF_URL): cv.url,
222 vol.Optional(CONF_ICON): cv.icon,
223 vol.Required(CONF_DESCRIPTION): cv.string,
224 vol.Optional(CONF_NAME): cv.string,
225})
226DELIVERY_CONFIG_SCHEMA = vol.Schema({
227 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]),
228 vol.Optional(CONF_ACTION): cv.service, # previously 'service:'
229 vol.Optional(CONF_OPTIONS, default=dict): dict,
230 vol.Optional(CONF_DATA): DATA_SCHEMA,
231 vol.Optional(CONF_SELECTION, default=[SELECTION_DEFAULT]): vol.All(cv.ensure_list, [vol.In(SELECTION_VALUES)]),
232 vol.Optional(CONF_PRIORITY, default=PRIORITY_VALUES): vol.All(cv.ensure_list, [vol.In(PRIORITY_VALUES)]),
233})
234METHOD_SCHEMA = vol.Schema({
235 vol.Optional(CONF_TARGETS_REQUIRED): cv.boolean,
236 vol.Optional(CONF_DEVICE_DOMAIN): vol.All(cv.ensure_list, [cv.string]),
237 vol.Optional(CONF_DEVICE_DISCOVERY): cv.boolean,
238 vol.Optional(CONF_DEFAULT): DELIVERY_CONFIG_SCHEMA,
239})
240RECIPIENT_SCHEMA = vol.Schema({
241 vol.Required(CONF_PERSON): cv.entity_id,
242 vol.Optional(CONF_ALIAS): cv.string,
243 vol.Optional(CONF_EMAIL): cv.string,
244 vol.Optional(CONF_TARGET): vol.All(cv.ensure_list, [cv.string]),
245 vol.Optional(CONF_PHONE_NUMBER): cv.string,
246 vol.Optional(CONF_MOBILE_DISCOVERY, default=True): cv.boolean,
247 vol.Optional(CONF_MOBILE_DEVICES, default=list): vol.All(cv.ensure_list, [MOBILE_DEVICE_SCHEMA]),
248 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_CUSTOMIZE_SCHEMA},
249})
250CAMERA_SCHEMA = vol.Schema({
251 vol.Required(CONF_CAMERA): cv.entity_id,
252 vol.Optional(CONF_ALT_CAMERA): vol.All(cv.ensure_list, [cv.entity_id]),
253 vol.Optional(CONF_ALIAS): cv.string,
254 vol.Optional(CONF_URL): cv.url,
255 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id,
256 vol.Optional(CONF_PTZ_PRESET_DEFAULT, default=1): vol.Any(cv.positive_int, cv.string),
257 vol.Optional(CONF_PTZ_DELAY, default=0): int,
258 vol.Optional(CONF_PTZ_METHOD, default=PTZ_METHOD_ONVIF): vol.In(PTZ_METHOD_VALUES),
259})
260MEDIA_SCHEMA = vol.Schema({
261 vol.Optional(ATTR_MEDIA_CAMERA_ENTITY_ID): cv.entity_id,
262 vol.Optional(ATTR_MEDIA_CAMERA_DELAY, default=0): int,
263 vol.Optional(ATTR_MEDIA_CAMERA_PTZ_PRESET): vol.Any(cv.positive_int, cv.string),
264 # URL fragments allowed
265 vol.Optional(ATTR_MEDIA_CLIP_URL): vol.Any(cv.url, cv.string),
266 vol.Optional(ATTR_MEDIA_SNAPSHOT_URL): vol.Any(cv.url, cv.string),
267 vol.Optional(ATTR_JPEG_OPTS): dict,
268})
270DELIVERY_SCHEMA = DELIVERY_CONFIG_SCHEMA.extend({
271 vol.Optional(CONF_ALIAS): cv.string,
272 vol.Required(CONF_METHOD): vol.In(METHOD_VALUES),
273 vol.Optional(CONF_TEMPLATE): cv.string,
274 vol.Optional(CONF_DEFAULT, default=False): cv.boolean,
275 vol.Optional(CONF_MESSAGE): vol.Any(None, cv.string),
276 vol.Optional(CONF_TITLE): vol.Any(None, cv.string),
277 vol.Optional(CONF_ENABLED, default=True): cv.boolean,
278 vol.Optional(CONF_OCCUPANCY, default=OCCUPANCY_ALL): vol.In(OCCUPANCY_VALUES),
279 vol.Optional(CONF_CONDITION): cv.CONDITION_SCHEMA,
280})
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]),
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)},
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)
311ARCHIVE_SCHEMA = vol.Schema({
312 vol.Optional(CONF_ARCHIVE_PATH): cv.path,
313 vol.Optional(CONF_ENABLED, default=False): cv.boolean,
314 vol.Optional(CONF_ARCHIVE_DAYS, default=3): cv.positive_int,
315 vol.Optional(CONF_ARCHIVE_MQTT_TOPIC): cv.string,
316 vol.Optional(CONF_ARCHIVE_MQTT_QOS, default=0): cv.positive_int,
317 vol.Optional(CONF_ARCHIVE_MQTT_RETAIN, default=True): cv.boolean,
318})
320HOUSEKEEPING_SCHEMA = vol.Schema({
321 vol.Optional(CONF_HOUSEKEEPING_TIME, default="00:00:01"): cv.time,
322})
324PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
325 vol.Optional(CONF_TEMPLATE_PATH, default=TEMPLATE_DIR): cv.path,
326 vol.Optional(CONF_MEDIA_PATH, default=MEDIA_DIR): cv.path,
327 vol.Optional(CONF_ARCHIVE, default={CONF_ENABLED: False}): ARCHIVE_SCHEMA,
328 vol.Optional(CONF_HOUSEKEEPING, default={}): HOUSEKEEPING_SCHEMA,
329 vol.Optional(CONF_DUPE_CHECK, default=dict): NOTIFICATION_DUPE_SCHEMA,
330 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_SCHEMA},
331 vol.Optional(CONF_ACTION_GROUPS, default=dict): {cv.string: [ACTION_SCHEMA]},
332 vol.Optional(CONF_RECIPIENTS, default=list): vol.All(cv.ensure_list, [RECIPIENT_SCHEMA]),
333 vol.Optional(CONF_LINKS, default=list): vol.All(cv.ensure_list, [LINK_SCHEMA]),
334 vol.Optional(CONF_SCENARIOS, default=dict): {cv.string: SCENARIO_SCHEMA},
335 vol.Optional(CONF_METHODS, default=dict): {cv.string: METHOD_SCHEMA},
336 vol.Optional(CONF_CAMERAS, default=list): vol.All(cv.ensure_list, [CAMERA_SCHEMA]),
337})
338SUPERNOTIFY_SCHEMA = PLATFORM_SCHEMA
340ACTION_DATA_SCHEMA = vol.Schema(
341 {
342 vol.Optional(ATTR_DELIVERY): vol.Any(cv.string, [cv.string], {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}),
343 vol.Optional(ATTR_PRIORITY): vol.In(PRIORITY_VALUES),
344 vol.Optional(ATTR_SCENARIOS_REQUIRE): vol.All(cv.ensure_list, [cv.string]),
345 vol.Optional(ATTR_SCENARIOS_APPLY): vol.All(cv.ensure_list, [cv.string]),
346 vol.Optional(ATTR_SCENARIOS_CONSTRAIN): vol.All(cv.ensure_list, [cv.string]),
347 vol.Optional(ATTR_DELIVERY_SELECTION): vol.In(DELIVERY_SELECTION_VALUES),
348 vol.Optional(ATTR_RECIPIENTS): vol.All(cv.ensure_list, [cv.entity_id]),
349 vol.Optional(ATTR_MEDIA): MEDIA_SCHEMA,
350 vol.Optional(ATTR_MESSAGE_HTML): cv.string,
351 vol.Optional(ATTR_ACTION_GROUPS, default=[]): vol.All(cv.ensure_list, [cv.string]),
352 vol.Optional(ATTR_ACTIONS, default=[]): vol.All(cv.ensure_list, [ACTION_CALL_SCHEMA]),
353 vol.Optional(ATTR_DEBUG, default=False): cv.boolean,
354 vol.Optional(ATTR_DATA): vol.Any(None, DATA_SCHEMA),
355 },
356 extra=vol.ALLOW_EXTRA, # allow other data, e.g. the android/ios mobile push
357)
359STRICT_ACTION_DATA_SCHEMA = ACTION_DATA_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA)
362class TargetType(StrEnum):
363 pass
366class GlobalTargetType(TargetType):
367 NONCRITICAL = "NONCRITICAL"
368 EVERYTHING = "EVERYTHING"
371class RecipientType(StrEnum):
372 USER = "USER"
373 EVERYONE = "EVERYONE"
376class QualifiedTargetType(TargetType):
377 METHOD = "METHOD"
378 DELIVERY = "DELIVERY"
379 CAMERA = "CAMERA"
380 PRIORITY = "PRIORITY"
381 ACTION = "ACTION"
384class CommandType(StrEnum):
385 SNOOZE = "SNOOZE"
386 SILENCE = "SILENCE"
387 NORMAL = "NORMAL"
390class MessageOnlyPolicy(StrEnum):
391 STANDARD = "STANDARD" # independent title and message
392 USE_TITLE = "USE_TITLE" # use title in place of message, no title
393 COMBINE_TITLE = "COMBINE_TITLE" # use combined title and message as message, no title
396@dataclass
397class ConditionVariables:
398 """Variables presented to all condition evaluations
400 Attributes
401 ----------
402 applied_scenarios (list[str]): Scenarios that have been applied
403 required_scenarios (list[str]): Scenarios that must be applied
404 constrain_scenarios (list[str]): Only scenarios in this list, or in explicit apply_scenarios, can be applied
405 notification_priority (str): Priority of the notification
406 notification_message (str): Message of the notification
407 notification_title (str): Title of the notification
408 occupancy (list[str]): List of occupancy scenarios
410 """
412 applied_scenarios: list[str] = field(default_factory=list)
413 required_scenarios: list[str] = field(default_factory=list)
414 constrain_scenarios: list[str] = field(default_factory=list)
415 notification_priority: str = PRIORITY_MEDIUM
416 notification_message: str = ""
417 notification_title: str = ""
418 occupancy: list[str] = field(default_factory=list)
420 def __init__(
421 self,
422 applied_scenarios: list[str] | None = None,
423 required_scenarios: list[str] | None = None,
424 constrain_scenarios: list[str] | None = None,
425 delivery_priority: str | None = PRIORITY_MEDIUM,
426 occupiers: dict[str, list[dict[str, Any]]] | None = None,
427 message: str | None = None,
428 title: str | None = None,
429 ) -> None:
430 occupiers = occupiers or {}
431 self.occupancy = []
432 if not occupiers.get(STATE_NOT_HOME) and occupiers.get(STATE_HOME):
433 self.occupancy.append("ALL_HOME")
434 elif occupiers.get(STATE_NOT_HOME) and not occupiers.get(STATE_HOME):
435 self.occupancy.append("ALL_AWAY")
436 if len(occupiers.get(STATE_HOME, [])) == 1:
437 self.occupancy.extend(["LONE_HOME", "SOME_HOME"])
438 elif len(occupiers.get(STATE_HOME, [])) > 1 and occupiers.get(STATE_NOT_HOME):
439 self.occupancy.extend(["MULTI_HOME", "SOME_HOME"])
440 self.applied_scenarios = applied_scenarios or []
441 self.required_scenarios = required_scenarios or []
442 self.constrain_scenarios = constrain_scenarios or []
443 self.notification_priority = delivery_priority or PRIORITY_MEDIUM
444 self.notification_message = message or ""
445 self.notification_title = title or ""
447 def as_dict(self) -> dict[str, Any]:
448 return {
449 "applied_scenarios": self.applied_scenarios,
450 "required_scenarios": self.required_scenarios,
451 "constrain_scenarios": self.constrain_scenarios,
452 "notification_message": self.notification_message,
453 "notification_title": self.notification_title,
454 "occupancy": self.occupancy,
455 }