Coverage for custom_components/supernotify/__init__.py: 98%
237 statements
« prev ^ index » next coverage.py v7.10.6, created at 2026-01-07 15:35 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2026-01-07 15:35 +0000
1"""The Supernotify integration"""
3import logging
4import re
5from collections.abc import Callable
6from enum import StrEnum
7from typing import Final
9import voluptuous as vol
10from homeassistant.components.notify import PLATFORM_SCHEMA
11from homeassistant.const import (
12 ATTR_DOMAIN,
13 ATTR_SERVICE,
14 CONF_ACTION,
15 CONF_ALIAS,
16 CONF_CONDITION,
17 CONF_CONDITIONS,
18 CONF_DEBUG,
19 CONF_DESCRIPTION,
20 CONF_DOMAIN,
21 CONF_EMAIL,
22 CONF_ENABLED,
23 CONF_ICON,
24 CONF_ID,
25 CONF_NAME,
26 CONF_TARGET,
27 CONF_URL,
28 Platform,
29)
30from homeassistant.helpers import config_validation as cv
31from homeassistant.helpers.typing import TemplateVarsType
33_LOGGER = logging.getLogger(__name__)
36class SelectionRank(StrEnum):
37 FIRST = "FIRST"
38 ANY = "ANY"
39 LAST = "LAST"
42type ConditionsFunc = Callable[[TemplateVarsType], bool]
44DOMAIN = "supernotify"
46PLATFORMS = [Platform.NOTIFY]
47TEMPLATE_DIR: str = "/config/templates/supernotify"
48MEDIA_DIR: str = "supernotify/media"
51CONF_ACTIONS: Final[str] = "actions"
52CONF_TITLE: Final[str] = "title"
53CONF_URI: Final[str] = "uri"
54CONF_RECIPIENTS: Final[str] = "recipients"
55CONF_RECIPIENTS_DISCOVERY: Final[str] = "recipients_discovery"
56CONF_TEMPLATE_PATH: Final[str] = "template_path"
57CONF_MEDIA_PATH: Final[str] = "media_path"
58CONF_HOUSEKEEPING: Final[str] = "housekeeping"
59CONF_HOUSEKEEPING_TIME: Final[str] = "housekeeping_time"
60CONF_ARCHIVE_PATH: Final[str] = "file_path"
61CONF_ARCHIVE: Final[str] = "archive"
62CONF_ARCHIVE_DAYS: Final[str] = "file_retention_days"
63CONF_ARCHIVE_MQTT_TOPIC: Final[str] = "mqtt_topic"
64CONF_ARCHIVE_MQTT_QOS: Final[str] = "mqtt_qos"
65CONF_ARCHIVE_MQTT_RETAIN: Final[str] = "mqtt_retain"
66CONF_TEMPLATE: Final[str] = "template"
67CONF_DELIVERY_DEFAULTS: Final[str] = "delivery_defaults"
68CONF_LINKS: Final[str] = "links"
69CONF_PERSON: Final[str] = "person"
70CONF_TRANSPORT: Final[str] = "transport"
71CONF_TRANSPORTS: Final[str] = "transports"
72CONF_DELIVERY: Final[str] = "delivery"
73CONF_SELECTION: Final[str] = "selection"
74CONF_SELECTION_RANK: Final[str] = "selection_rank"
77CONF_DATA: Final[str] = "data"
78CONF_OPTIONS: Final[str] = "options"
79CONF_MOBILE: Final[str] = "mobile"
80CONF_NOTIFY: Final[str] = "notify"
82CONF_PRIORITY: Final[str] = "priority"
83CONF_OCCUPANCY: Final[str] = "occupancy"
84CONF_SCENARIOS: Final[str] = "scenarios"
85CONF_MANUFACTURER: Final[str] = "manufacturer"
86CONF_CLASS: Final[str] = "class"
89CONF_MODEL: Final[str] = "model"
90CONF_MESSAGE: Final[str] = "message"
91CONF_TARGET_REQUIRED: Final[str] = "target_required"
92CONF_MOBILE_DEVICES: Final[str] = "mobile_devices"
93CONF_MOBILE_DISCOVERY: Final[str] = "mobile_discovery"
94CONF_ACTION_TEMPLATE: Final[str] = "action_template"
95CONF_ACTION_GROUPS: Final[str] = "action_groups"
96CONF_TITLE_TEMPLATE: Final[str] = "title_template"
97CONF_MEDIA: Final[str] = "media"
98CONF_CAMERA: Final[str] = "camera"
99CONF_CLIP_URL: Final[str] = "clip_url"
100CONF_PTZ_DELAY: Final[str] = "ptz_delay"
101CONF_PTZ_METHOD: Final[str] = "ptz_method"
102CONF_PTZ_CAMERA: Final[str] = "ptz_camera"
103CONF_PTZ_PRESET_DEFAULT: Final[str] = "ptz_default_preset"
104CONF_ALT_CAMERA: Final[str] = "alt_camera"
105CONF_CAMERAS: Final[str] = "cameras"
106CONF_ARCHIVE_PURGE_INTERVAL: Final[str] = "purge_interval"
107CONF_MEDIA_STORAGE_DAYS: Final[str] = "media_storage_days"
109OCCUPANCY_ANY_IN = "any_in"
110OCCUPANCY_ANY_OUT = "any_out"
111OCCUPANCY_ALL_IN = "all_in"
112OCCUPANCY_ALL = "all"
113OCCUPANCY_NONE = "none"
114OCCUPANCY_ALL_OUT = "all_out"
115OCCUPANCY_ONLY_IN = "only_in"
116OCCUPANCY_ONLY_OUT = "only_out"
118ATTR_ENABLED = "enabled"
119ATTR_PRIORITY = "priority"
120ATTR_ACTION = "action"
121ATTR_SCENARIOS_REQUIRE = "require_scenarios"
122ATTR_SCENARIOS_APPLY = "apply_scenarios"
123ATTR_SCENARIOS_CONSTRAIN = "constrain_scenarios"
124ATTR_DELIVERY = "delivery"
125ATTR_DEFAULT = "default"
126ATTR_NOTIFICATION_ID = "notification_id"
127ATTR_DELIVERY_SELECTION = "delivery_selection"
128ATTR_RECIPIENTS = "recipients"
129ATTR_DATA = "data"
130ATTR_MEDIA = "media"
131ATTR_TITLE = "title"
132ATTR_MEDIA_SNAPSHOT_URL = "snapshot_url"
133ATTR_MEDIA_CAMERA_ENTITY_ID = "camera_entity_id"
134ATTR_MEDIA_CAMERA_DELAY = "camera_delay"
135ATTR_MEDIA_CAMERA_PTZ_PRESET = "camera_ptz_preset"
136ATTR_MEDIA_CLIP_URL = "clip_url"
137ATTR_MEDIA_SNAPSHOT_PATH = "snapshot_image_path"
138ATTR_ACTION_GROUPS = "action_groups"
139CONF_ACTION_GROUP_NAMES = "action_groups"
140ATTR_ACTION_CATEGORY = "action_category"
141ATTR_ACTION_URL = "action_url"
142ATTR_ACTION_URL_TITLE = "action_url_title"
143ATTR_MESSAGE_HTML = "message_html"
144ATTR_JPEG_OPTS = "jpeg_opts"
145ATTR_PNG_OPTS = "png_opts"
146ATTR_TIMESTAMP = "timestamp"
147ATTR_DEBUG = "debug"
148ATTR_ACTIONS = "actions"
149ATTR_USER_ID = "user_id"
150ATTR_PERSON_ID = "person_id"
151ATTR_MOBILE_APP_ID = "mobile_app_id"
152ATTR_EMAIL = "email"
153ATTR_PHONE = "phone"
154ATTR_ALIAS = "alias"
156DELIVERY_SELECTION_IMPLICIT = "implicit"
157DELIVERY_SELECTION_EXPLICIT = "explicit"
158DELIVERY_SELECTION_FIXED = "fixed"
160DELIVERY_SELECTION_VALUES = [DELIVERY_SELECTION_EXPLICIT, DELIVERY_SELECTION_FIXED, DELIVERY_SELECTION_IMPLICIT]
161PTZ_METHOD_ONVIF = "onvif"
162PTZ_METHOD_FRIGATE = "frigate"
163PTZ_METHOD_VALUES = [PTZ_METHOD_ONVIF, PTZ_METHOD_FRIGATE]
165SELECTION_FALLBACK_ON_ERROR = "fallback_on_error"
166SELECTION_FALLBACK = "fallback"
167SELECTION_BY_SCENARIO = "scenario"
168SELECTION_DEFAULT = "default"
169SELECTION_EXPLICIT = "explicit"
170SELECTION_VALUES = [
171 SELECTION_FALLBACK_ON_ERROR,
172 SELECTION_EXPLICIT,
173 SELECTION_BY_SCENARIO,
174 SELECTION_DEFAULT,
175 SELECTION_FALLBACK,
176]
178OCCUPANCY_VALUES = [
179 OCCUPANCY_ALL_IN,
180 OCCUPANCY_ALL_OUT,
181 OCCUPANCY_ANY_IN,
182 OCCUPANCY_ANY_OUT,
183 OCCUPANCY_ONLY_IN,
184 OCCUPANCY_ONLY_OUT,
185 OCCUPANCY_ALL,
186 OCCUPANCY_NONE,
187]
189PRIORITY_CRITICAL = "critical"
190PRIORITY_HIGH = "high"
191PRIORITY_MEDIUM = "medium"
192PRIORITY_LOW = "low"
193PRIORITY_MINIMUM = "minimum"
195PRIORITY_VALUES: dict[str, int] = {
196 PRIORITY_MINIMUM: 1,
197 PRIORITY_LOW: 2,
198 PRIORITY_MEDIUM: 3,
199 PRIORITY_HIGH: 4,
200 PRIORITY_CRITICAL: 5,
201}
203CONF_TARGET_USAGE = "target_usage"
204TARGET_USE_ON_NO_DELIVERY_TARGETS = "no_delivery"
205TARGET_USE_ON_NO_ACTION_TARGETS = "no_action"
206TARGET_USE_FIXED = "fixed"
207TARGET_USE_MERGE_ALWAYS = "merge_always"
208TARGET_USE_MERGE_ON_DELIVERY_TARGETS = "merge_delivery"
210OPTION_SIMPLIFY_TEXT = "simplify_text"
211OPTION_STRIP_URLS = "strip_urls"
212OPTION_MESSAGE_USAGE = "message_usage"
213OPTION_RAW = "raw"
214OPTION_JPEG = "jpeg_opts"
215OPTION_PNG = "png_opts"
216OPTION_TTS_ENTITY_ID = "tts_entity_id"
217MEDIA_OPTION_REPROCESS = "reprocess"
218OPTION_TARGET_CATEGORIES = "target_categories"
219OPTION_UNIQUE_TARGETS = "unique_targets"
220OPTION_TARGET_INCLUDE_RE = "target_include_re" # deprecated v1.9.0
221OPTION_TARGET_SELECT = "target_select"
222OPTION_CHIME_ALIASES = "chime_aliases"
223OPTION_DATA_KEYS_SELECT = "data_keys_select"
224OPTION_DATA_KEYS_INCLUDE_RE = "data_keys_include_re" # deprecated v1.9.0
225OPTION_DATA_KEYS_EXCLUDE_RE = "data_keys_exclude_re" # deprecated v1.9.0
226OPTION_GENERIC_DOMAIN_STYLE = "handle_as_domain"
227OPTION_STRICT_TEMPLATE = "strict_template"
229SELECT_INCLUDE = "include"
230SELECT_EXCLUDE = "exclude"
232RE_DEVICE_ID = r"^[0-9a-f]{32}$"
234RESERVED_DELIVERY_NAMES: list[str] = ["ALL"]
235RESERVED_SCENARIO_NAMES: list[str] = ["NULL"]
236RESERVED_DATA_KEYS: list[str] = [ATTR_DOMAIN, ATTR_SERVICE, "action"]
239CONF_DUPE_CHECK: Final[str] = "dupe_check"
240CONF_DUPE_POLICY: Final[str] = "dupe_policy"
241CONF_TTL: Final[str] = "ttl"
242CONF_SIZE: Final[str] = "size"
243ATTR_DUPE_POLICY_MTSLP: Final[str] = "dupe_policy_message_title_same_or_lower_priority"
244ATTR_DUPE_POLICY_NONE: Final[str] = "dupe_policy_none"
245CONF_MOBILE_APP_ID: Final[str] = "mobile_app_id"
246CONF_TRANSPORT_DATA: Final[str] = "transport_data"
249def phone(value: str) -> str:
250 """Validate a phone number"""
251 regex = re.compile(r"^(\+\d{1,3})?\s?\(?\d{1,4}\)?[\s.-]?\d{3}[\s.-]?\d{4}$")
252 if not regex.match(value):
253 raise vol.Invalid("Invalid Phone Number")
254 return str(value)
257# TARGET_FIELDS includes entity, device, area, floor, label ids
258TARGET_SCHEMA = vol.Any( # order of schema matters, voluptuous forces into first it finds that works
259 cv.TARGET_FIELDS
260 | {
261 vol.Optional(ATTR_EMAIL): vol.All(cv.ensure_list, [vol.Email]),
262 vol.Optional(ATTR_PHONE): vol.All(cv.ensure_list, [phone]),
263 vol.Optional(ATTR_MOBILE_APP_ID): vol.All(cv.ensure_list, [cv.service]),
264 vol.Optional(ATTR_PERSON_ID): vol.All(cv.ensure_list, [cv.entity_id]),
265 vol.Optional(cv.string): vol.All(cv.ensure_list, [str]),
266 },
267 str,
268 list[str],
269)
271DATA_SCHEMA = vol.Schema({vol.NotIn(RESERVED_DATA_KEYS): vol.Any(str, int, bool, float, dict, list)})
273CONF_DEVICE_TRACKER: Final[str] = "device_tracker"
274MOBILE_DEVICE_SCHEMA = vol.Schema({
275 vol.Optional(CONF_MANUFACTURER): cv.string,
276 vol.Optional(CONF_MODEL): cv.string,
277 vol.Optional(CONF_CLASS): cv.string,
278 vol.Optional(CONF_MOBILE_APP_ID): cv.string,
279 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id,
280 vol.Optional(CONF_ENABLED, default=True): cv.boolean,
281})
282NOTIFICATION_DUPE_SCHEMA = vol.Schema({
283 vol.Optional(CONF_TTL): cv.positive_int,
284 vol.Optional(CONF_SIZE, default=100): cv.positive_int,
285 vol.Optional(CONF_DUPE_POLICY, default=ATTR_DUPE_POLICY_MTSLP): vol.In([ATTR_DUPE_POLICY_MTSLP, ATTR_DUPE_POLICY_NONE]),
286})
289DELIVERY_CUSTOMIZE_SCHEMA = vol.All(
290 vol.Schema(
291 {
292 vol.Optional(CONF_TARGET): TARGET_SCHEMA,
293 vol.Optional(CONF_ENABLED): vol.Any(None, cv.boolean),
294 vol.Optional(CONF_DATA): DATA_SCHEMA,
295 },
296 ),
297)
298LINK_SCHEMA = vol.Schema({
299 vol.Required(CONF_URL): cv.url,
300 vol.Required(CONF_DESCRIPTION): cv.string,
301 vol.Optional(CONF_ID): cv.string,
302 vol.Optional(CONF_ICON): cv.icon,
303 vol.Optional(CONF_NAME): cv.string,
304})
307CONF_DEVICE_NAME: Final[str] = "device_name"
308CONF_DEVICE_LABELS: Final[str] = "device_labels"
310OPTION_DEVICE_DOMAIN: Final[str] = "device_domain"
312OPTION_DEVICE_MODEL_SELECT: Final[str] = "device_model_select"
313OPTION_DEVICE_MANUFACTURER_SELECT: Final[str] = "device_manufacturer_select"
314OPTION_DEVICE_OS_SELECT: Final[str] = "device_os_select"
315OPTION_DEVICE_LABEL_SELECT: Final[str] = "device_label_select"
316OPTION_DEVICE_AREA_SELECT: Final[str] = "device_area_select"
317OPTION_DEVICE_DISCOVERY: Final[str] = "device_discovery"
320TARGET_REQUIRE_ALWAYS = "always"
321TARGET_REQUIRE_NEVER = "never"
322TARGET_REQUIRE_OPTIONAL = "optional"
324DELIVERY_CONFIG_SCHEMA = vol.Schema({ # shared by Transport Defaults and Delivery definitions
325 # defaults set in model.DeliveryConfig
326 vol.Optional(CONF_ACTION): cv.service, # previously 'service:'
327 vol.Optional(CONF_DEBUG): cv.boolean,
328 vol.Optional(CONF_OPTIONS): dict, # transport tuning
329 vol.Optional(CONF_DATA): DATA_SCHEMA,
330 vol.Optional(CONF_TARGET): TARGET_SCHEMA,
331 vol.Optional(CONF_TARGET_REQUIRED): vol.Any(
332 cv.boolean, vol.In([TARGET_REQUIRE_ALWAYS, TARGET_REQUIRE_NEVER, TARGET_REQUIRE_OPTIONAL])
333 ),
334 vol.Optional(CONF_TARGET_USAGE): vol.In([
335 TARGET_USE_ON_NO_DELIVERY_TARGETS,
336 TARGET_USE_ON_NO_ACTION_TARGETS,
337 TARGET_USE_MERGE_ON_DELIVERY_TARGETS,
338 TARGET_USE_MERGE_ALWAYS,
339 TARGET_USE_FIXED,
340 ]),
341 vol.Optional(CONF_SELECTION): vol.All(cv.ensure_list, [vol.In(SELECTION_VALUES)]),
342 vol.Optional(CONF_PRIORITY): vol.All(cv.ensure_list, [vol.Any(int, str, vol.In(list(PRIORITY_VALUES.keys())))]),
343 vol.Optional(CONF_SELECTION_RANK): vol.In([
344 SelectionRank.ANY,
345 SelectionRank.FIRST,
346 SelectionRank.LAST,
347 ]),
348})
350TRANSPORT_SMS = "sms"
351TRANSPORT_TTS = "tts"
352TRANSPORT_MQTT = "mqtt"
353TRANSPORT_EMAIL = "email"
354TRANSPORT_ALEXA = "alexa_devices"
355TRANSPORT_ALEXA_MEDIA_PLAYER = "alexa_media_player"
356TRANSPORT_MOBILE_PUSH = "mobile_push"
357TRANSPORT_MEDIA = "media"
358TRANSPORT_CHIME = "chime"
359TRANSPORT_GENERIC = "generic"
360TRANSPORT_NOTIFY_ENTITY = "notify_entity"
361TRANSPORT_PERSISTENT = "persistent"
362TRANSPORT_VALUES = [
363 TRANSPORT_SMS,
364 TRANSPORT_MQTT,
365 TRANSPORT_ALEXA,
366 TRANSPORT_ALEXA_MEDIA_PLAYER,
367 TRANSPORT_MOBILE_PUSH,
368 TRANSPORT_CHIME,
369 TRANSPORT_EMAIL,
370 TRANSPORT_MEDIA,
371 TRANSPORT_PERSISTENT,
372 TRANSPORT_TTS,
373 TRANSPORT_GENERIC,
374 TRANSPORT_NOTIFY_ENTITY,
375]
377DELIVERY_SCHEMA = vol.All(
378 cv.deprecated(key=CONF_CONDITION),
379 DELIVERY_CONFIG_SCHEMA.extend({
380 vol.Required(CONF_TRANSPORT): vol.In(TRANSPORT_VALUES),
381 vol.Optional(CONF_ALIAS): cv.string,
382 vol.Optional(CONF_TEMPLATE): cv.string,
383 vol.Optional(CONF_MESSAGE): vol.Any(None, cv.string),
384 vol.Optional(CONF_TITLE): vol.Any(None, cv.string),
385 vol.Optional(CONF_ENABLED): cv.boolean,
386 vol.Optional(CONF_OCCUPANCY, default=OCCUPANCY_ALL): vol.In(OCCUPANCY_VALUES),
387 vol.Optional(CONF_CONDITION): cv.CONDITIONS_SCHEMA,
388 vol.Optional(CONF_CONDITIONS): cv.CONDITIONS_SCHEMA,
389 }),
390)
392CONF_DEVICE_DISCOVERY: Final[str] = "device_discovery"
393CONF_DEVICE_DOMAIN: Final[str] = OPTION_DEVICE_DOMAIN
394CONF_DEVICE_MODEL_INCLUDE: Final[str] = "device_model_include"
395CONF_DEVICE_MODEL_EXCLUDE: Final[str] = "device_model_exclude"
396TRANSPORT_SCHEMA = vol.All(
397 cv.deprecated(key=CONF_DEVICE_DOMAIN), # deprecated v1.9.0
398 cv.deprecated(key=CONF_DEVICE_DISCOVERY), # deprecated v1.9.0
399 cv.deprecated(key=CONF_DEVICE_MODEL_INCLUDE), # deprecated v1.9.0
400 cv.deprecated(key=CONF_DEVICE_MODEL_EXCLUDE), # deprecated v1.9.0
401 vol.Schema({
402 vol.Optional(CONF_ALIAS): cv.string,
403 vol.Optional(CONF_DEVICE_DOMAIN): vol.All(cv.ensure_list, [cv.string]),
404 vol.Optional(CONF_DEVICE_MODEL_INCLUDE): vol.All(cv.ensure_list, [cv.string]),
405 vol.Optional(CONF_DEVICE_MODEL_EXCLUDE): vol.All(cv.ensure_list, [cv.string]),
406 vol.Optional(CONF_DEVICE_DISCOVERY): cv.boolean,
407 vol.Optional(CONF_ENABLED, default=True): cv.boolean,
408 vol.Optional(CONF_DELIVERY_DEFAULTS): DELIVERY_CONFIG_SCHEMA,
409 }),
410)
411# Idea - differentiate enabled as recipient vs as occupant, for ALL_IN etc check
412# May need condition, and also enabled if delivery disabled
413# CONF_OCCUPANCY="occupancy"
414# OPTION_OCCUPANCY_DEFAULT="default"
415# OPTIONS_OCCUPANCY=[OPTION_OCCUPANCY_DEFAULT,OPTION_OCCUPANCY_EXCLUDE]
416# OPTION_OCCUPANCY_EXCLUDE="exclude"
418CONF_PHONE_NUMBER: str = "phone_number"
420RECIPIENT_SCHEMA = vol.Schema({
421 vol.Required(CONF_PERSON): cv.entity_id,
422 vol.Optional(CONF_ALIAS): cv.string,
423 vol.Optional(CONF_EMAIL): cv.string,
424 vol.Optional(CONF_ENABLED, default=True): cv.boolean,
425 # vol.Optional(CONF_OCCUPANCY,default=OPTION_OCCUPANCY_DEFAULT):vol.In(OPTIONS_OCCUPANCY),
426 vol.Optional(CONF_TARGET): TARGET_SCHEMA,
427 vol.Optional(CONF_PHONE_NUMBER): cv.string,
428 vol.Optional(CONF_MOBILE_DISCOVERY, default=True): cv.boolean,
429 vol.Optional(CONF_MOBILE_DEVICES, default=list): vol.All(cv.ensure_list, [MOBILE_DEVICE_SCHEMA]),
430 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_CUSTOMIZE_SCHEMA},
431})
432CAMERA_SCHEMA = vol.Schema({
433 vol.Required(CONF_CAMERA): cv.entity_id,
434 vol.Optional(CONF_ALT_CAMERA): vol.All(cv.ensure_list, [cv.entity_id]),
435 vol.Optional(CONF_ALIAS): cv.string,
436 vol.Optional(CONF_URL): cv.url,
437 vol.Optional(CONF_DEVICE_TRACKER): cv.entity_id,
438 vol.Optional(CONF_PTZ_CAMERA): cv.entity_id,
439 vol.Optional(CONF_PTZ_PRESET_DEFAULT, default=1): vol.Any(cv.positive_int, cv.string),
440 vol.Optional(CONF_PTZ_DELAY, default=0): int,
441 vol.Optional(CONF_PTZ_METHOD, default=PTZ_METHOD_ONVIF): vol.In(PTZ_METHOD_VALUES),
442})
443MEDIA_SCHEMA = vol.Schema({
444 vol.Optional(ATTR_MEDIA_CAMERA_ENTITY_ID): cv.entity_id,
445 vol.Optional(ATTR_MEDIA_CAMERA_DELAY, default=0): int,
446 vol.Optional(ATTR_MEDIA_CAMERA_PTZ_PRESET): vol.Any(cv.positive_int, cv.string),
447 # URL fragments allowed
448 vol.Optional(ATTR_MEDIA_CLIP_URL): vol.Any(cv.url, cv.string),
449 vol.Optional(ATTR_MEDIA_SNAPSHOT_URL): vol.Any(cv.url, cv.string),
450 vol.Optional(ATTR_JPEG_OPTS): dict,
451 vol.Optional(ATTR_PNG_OPTS): dict,
452})
455SCENARIO_SCHEMA = vol.All(
456 cv.deprecated(key=CONF_CONDITION),
457 cv.deprecated(key="delivery_selection"),
458 vol.Schema({
459 vol.Optional(CONF_ALIAS): cv.string,
460 vol.Optional(CONF_ENABLED, default=True): cv.boolean,
461 vol.Optional(CONF_CONDITION): cv.CONDITIONS_SCHEMA,
462 vol.Optional(CONF_CONDITIONS): cv.CONDITIONS_SCHEMA,
463 vol.Optional(CONF_MEDIA): MEDIA_SCHEMA,
464 vol.Optional(CONF_ACTION_GROUP_NAMES, default=[]): vol.All(cv.ensure_list, [cv.string]),
465 vol.Optional("delivery_selection"): cv.string,
466 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)},
467 }),
468)
469MOBILE_ACTION_CALL_SCHEMA = vol.Schema(
470 {
471 vol.Optional(ATTR_ACTION): cv.string,
472 vol.Optional(ATTR_TITLE): cv.string,
473 vol.Optional(ATTR_ACTION_CATEGORY): cv.string,
474 vol.Optional(ATTR_ACTION_URL): cv.url,
475 vol.Optional(ATTR_ACTION_URL_TITLE): cv.string,
476 },
477 extra=vol.ALLOW_EXTRA,
478)
479MOBILE_ACTION_SCHEMA = vol.Schema(
480 {
481 vol.Exclusive(CONF_ACTION, CONF_ACTION_TEMPLATE): cv.string,
482 vol.Exclusive(CONF_TITLE, CONF_TITLE_TEMPLATE): cv.string,
483 vol.Optional(CONF_URI): cv.url,
484 vol.Optional(CONF_ICON): cv.string,
485 },
486 extra=vol.ALLOW_EXTRA,
487)
490ARCHIVE_SCHEMA = vol.Schema({
491 vol.Optional(CONF_ARCHIVE_PATH): cv.path,
492 vol.Optional(CONF_ENABLED, default=False): cv.boolean,
493 vol.Optional(CONF_ARCHIVE_DAYS, default=3): cv.positive_int,
494 vol.Optional(CONF_ARCHIVE_MQTT_TOPIC): cv.string,
495 vol.Optional(CONF_ARCHIVE_MQTT_QOS, default=0): cv.positive_int,
496 vol.Optional(CONF_ARCHIVE_MQTT_RETAIN, default=True): cv.boolean,
497 vol.Optional(CONF_ARCHIVE_PURGE_INTERVAL, default=60): cv.positive_int,
498 vol.Optional(CONF_DEBUG, default=False): cv.boolean,
499})
501HOUSEKEEPING_SCHEMA = vol.Schema({
502 vol.Optional(CONF_HOUSEKEEPING_TIME, default="00:00:01"): cv.time,
503 vol.Optional(CONF_MEDIA_STORAGE_DAYS, default=7): cv.positive_int,
504})
506PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
507 vol.Optional(CONF_TEMPLATE_PATH, default=TEMPLATE_DIR): cv.path,
508 vol.Optional(CONF_MEDIA_PATH, default=MEDIA_DIR): cv.path,
509 vol.Optional(CONF_ARCHIVE, default={CONF_ENABLED: False}): ARCHIVE_SCHEMA,
510 vol.Optional(CONF_HOUSEKEEPING, default={}): HOUSEKEEPING_SCHEMA,
511 vol.Optional(CONF_DUPE_CHECK, default=dict): NOTIFICATION_DUPE_SCHEMA,
512 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_SCHEMA},
513 vol.Optional(CONF_ACTION_GROUPS, default=dict): {cv.string: [MOBILE_ACTION_SCHEMA]},
514 vol.Optional(CONF_MOBILE_DISCOVERY, default=True): cv.boolean,
515 vol.Optional(CONF_RECIPIENTS_DISCOVERY, default=True): cv.boolean,
516 vol.Optional(CONF_RECIPIENTS, default=list): vol.All(cv.ensure_list, [RECIPIENT_SCHEMA]),
517 vol.Optional(CONF_LINKS, default=list): vol.All(cv.ensure_list, [LINK_SCHEMA]),
518 vol.Optional(CONF_SCENARIOS, default=dict): {cv.string: SCENARIO_SCHEMA},
519 vol.Optional(CONF_TRANSPORTS, default=dict): {cv.string: TRANSPORT_SCHEMA},
520 vol.Optional(CONF_CAMERAS, default=list): vol.All(cv.ensure_list, [CAMERA_SCHEMA]),
521})
522SUPERNOTIFY_SCHEMA = PLATFORM_SCHEMA
525CONF_TUNE: Final[str] = "tune"
526CONF_VOLUME: Final[str] = "volume"
527CONF_DURATION: Final[str] = "duration"
529OPTIONS_CHIME_DOMAINS = ["media_player", "switch", "script", "rest_command", "siren", "alexa_devices"]
531CHIME_ALIASES_SCHEMA = vol.Schema({
532 vol.Required(OPTION_CHIME_ALIASES, default=dict): vol.Schema({
533 cv.string: vol.Schema({
534 cv.string: vol.Any(
535 vol.Any(None, cv.string, vol.In(OPTIONS_CHIME_DOMAINS)),
536 vol.Schema({
537 vol.Optional(CONF_ALIAS): cv.string,
538 vol.Optional(CONF_DOMAIN): cv.string,
539 vol.Optional(CONF_TUNE): cv.string,
540 vol.Optional(CONF_DATA): DATA_SCHEMA,
541 vol.Optional(CONF_VOLUME): float,
542 vol.Optional(CONF_TARGET): TARGET_SCHEMA,
543 vol.Optional(CONF_DURATION): cv.positive_int,
544 }),
545 )
546 })
547 })
548})
551ACTION_DATA_SCHEMA = vol.Schema(
552 {
553 vol.Optional(ATTR_DELIVERY): vol.Any(cv.string, [cv.string], {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}),
554 vol.Optional(ATTR_PRIORITY): vol.Any(int, str, vol.In(list(PRIORITY_VALUES.keys()))),
555 vol.Optional(ATTR_SCENARIOS_REQUIRE): vol.All(cv.ensure_list, [cv.string]),
556 vol.Optional(ATTR_SCENARIOS_APPLY): vol.All(cv.ensure_list, [cv.string]),
557 vol.Optional(ATTR_SCENARIOS_CONSTRAIN): vol.All(cv.ensure_list, [cv.string]),
558 vol.Optional(ATTR_DELIVERY_SELECTION): vol.In(DELIVERY_SELECTION_VALUES),
559 vol.Optional(ATTR_RECIPIENTS): vol.All(cv.ensure_list, [cv.entity_id]),
560 vol.Optional(ATTR_MEDIA): MEDIA_SCHEMA,
561 vol.Optional(ATTR_MESSAGE_HTML): cv.string,
562 vol.Optional(ATTR_ACTION_GROUPS, default=[]): vol.All(cv.ensure_list, [cv.string]),
563 vol.Optional(ATTR_ACTIONS, default=[]): vol.All(cv.ensure_list, [MOBILE_ACTION_CALL_SCHEMA]),
564 vol.Optional(ATTR_DEBUG, default=False): cv.boolean,
565 vol.Optional(ATTR_DATA): vol.Any(None, DATA_SCHEMA),
566 vol.Optional(ATTR_TIMESTAMP): cv.string,
567 },
568 extra=vol.ALLOW_EXTRA, # allow other data, e.g. the android/ios mobile push
569)
571STRICT_ACTION_DATA_SCHEMA = ACTION_DATA_SCHEMA.extend({}, extra=vol.REMOVE_EXTRA)