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

194 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-11-21 23:31 +0000

1"""The Supernotify integration""" 

2 

3import logging 

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_DEBUG, 

15 CONF_DESCRIPTION, 

16 CONF_EMAIL, 

17 CONF_ENABLED, 

18 CONF_ICON, 

19 CONF_ID, 

20 CONF_NAME, 

21 CONF_TARGET, 

22 CONF_URL, 

23 Platform, 

24) 

25from homeassistant.helpers import config_validation as cv 

26 

27_LOGGER = logging.getLogger(__name__) 

28 

29 

30class SelectionRank(StrEnum): 

31 FIRST = "FIRST" 

32 ANY = "ANY" 

33 LAST = "LAST" 

34 

35 

36DOMAIN = "supernotify" 

37 

38PLATFORMS = [Platform.NOTIFY] 

39TEMPLATE_DIR = "/config/templates/supernotify" 

40MEDIA_DIR = "supernotify/media" 

41 

42 

43CONF_ACTIONS = "actions" 

44CONF_TITLE = "title" 

45CONF_URI = "uri" 

46CONF_RECIPIENTS = "recipients" 

47CONF_TEMPLATE_PATH = "template_path" 

48CONF_MEDIA_PATH = "media_path" 

49CONF_HOUSEKEEPING = "housekeeping" 

50CONF_HOUSEKEEPING_TIME = "housekeeping_time" 

51CONF_ARCHIVE_PATH = "file_path" 

52CONF_ARCHIVE = "archive" 

53CONF_ARCHIVE_DAYS = "file_retention_days" 

54CONF_ARCHIVE_MQTT_TOPIC = "mqtt_topic" 

55CONF_ARCHIVE_MQTT_QOS = "mqtt_qos" 

56CONF_ARCHIVE_MQTT_RETAIN = "mqtt_retain" 

57CONF_TEMPLATE = "template" 

58CONF_DELIVERY_DEFAULTS = "delivery_defaults" 

59CONF_LINKS = "links" 

60CONF_PERSON = "person" 

61CONF_TRANSPORT = "transport" 

62CONF_TRANSPORTS = "transports" 

63CONF_DELIVERY = "delivery" 

64CONF_SELECTION = "selection" 

65CONF_SELECTION_RANK = "selection_rank" 

66 

67CONF_DATA: str = "data" 

68CONF_OPTIONS: str = "options" 

69CONF_MOBILE: str = "mobile" 

70CONF_NOTIFY: str = "notify" 

71CONF_MOBILE_APP_ID: str = "mobile_app_id" 

72CONF_PHONE_NUMBER: str = "phone_number" 

73CONF_PRIORITY: str = "priority" 

74CONF_OCCUPANCY: str = "occupancy" 

75CONF_SCENARIOS: str = "scenarios" 

76CONF_MANUFACTURER: str = "manufacturer" 

77CONF_DEVICE_DISCOVERY: str = "device_discovery" 

78CONF_DEVICE_TRACKER: str = "device_tracker" 

79CONF_DEVICE_NAME: str = "device_name" 

80CONF_DEVICE_LABELS: str = "device_labels" 

81CONF_DEVICE_DOMAIN: str = "device_domain" 

82 

83CONF_MODEL: str = "model" 

84CONF_MESSAGE: str = "message" 

85CONF_TARGET_REQUIRED: str = "target_required" 

86CONF_MOBILE_DEVICES: str = "mobile_devices" 

87CONF_MOBILE_DISCOVERY: str = "mobile_discovery" 

88CONF_ACTION_TEMPLATE: str = "action_template" 

89CONF_ACTION_GROUPS: str = "action_groups" 

90CONF_TITLE_TEMPLATE: str = "title_template" 

91CONF_DELIVERY_SELECTION: str = "delivery_selection" 

92CONF_MEDIA: str = "media" 

93CONF_CAMERA: str = "camera" 

94CONF_CLIP_URL: str = "clip_url" 

95CONF_SNAPSHOT_URL: str = "snapshot_url" 

96CONF_PTZ_DELAY: str = "ptz_delay" 

97CONF_PTZ_METHOD: str = "ptz_method" 

98CONF_PTZ_PRESET_DEFAULT: str = "ptz_default_preset" 

99CONF_ALT_CAMERA: str = "alt_camera" 

100CONF_CAMERAS: str = "cameras" 

101CONF_ARCHIVE_PURGE_INTERVAL: str = "purge_interval" 

102 

103OCCUPANCY_ANY_IN = "any_in" 

104OCCUPANCY_ANY_OUT = "any_out" 

105OCCUPANCY_ALL_IN = "all_in" 

106OCCUPANCY_ALL = "all" 

107OCCUPANCY_NONE = "none" 

108OCCUPANCY_ALL_OUT = "all_out" 

109OCCUPANCY_ONLY_IN = "only_in" 

110OCCUPANCY_ONLY_OUT = "only_out" 

111 

112ATTR_ENABLED = "enabled" 

113ATTR_PRIORITY = "priority" 

114ATTR_ACTION = "action" 

115ATTR_SCENARIOS_REQUIRE = "require_scenarios" 

116ATTR_SCENARIOS_APPLY = "apply_scenarios" 

117ATTR_SCENARIOS_CONSTRAIN = "constrain_scenarios" 

118ATTR_DELIVERY = "delivery" 

119ATTR_DEFAULT = "default" 

120ATTR_NOTIFICATION_ID = "notification_id" 

121ATTR_DELIVERY_SELECTION = "delivery_selection" 

122ATTR_RECIPIENTS = "recipients" 

123ATTR_DATA = "data" 

124ATTR_MEDIA = "media" 

125ATTR_TITLE = "title" 

126ATTR_MEDIA_SNAPSHOT_URL = "snapshot_url" 

127ATTR_MEDIA_CAMERA_ENTITY_ID = "camera_entity_id" 

128ATTR_MEDIA_CAMERA_DELAY = "camera_delay" 

129ATTR_MEDIA_CAMERA_PTZ_PRESET = "camera_ptz_preset" 

130ATTR_MEDIA_CLIP_URL = "clip_url" 

131ATTR_ACTION_GROUPS = "action_groups" 

132CONF_ACTION_GROUP_NAMES = "action_groups" 

133ATTR_ACTION_CATEGORY = "action_category" 

134ATTR_ACTION_URL = "action_url" 

135ATTR_ACTION_URL_TITLE = "action_url_title" 

136ATTR_MESSAGE_HTML = "message_html" 

137ATTR_JPEG_OPTS = "jpeg_opts" 

138ATTR_TIMESTAMP = "timestamp" 

139ATTR_DEBUG = "debug" 

140ATTR_ACTIONS = "actions" 

141ATTR_USER_ID = "user_id" 

142ATTR_PERSON_ID = "person_id" 

143ATTR_MOBILE_APP_ID = "mobile_app_id" 

144ATTR_EMAIL = "email" 

145ATTR_PHONE = "phone" 

146 

147DELIVERY_SELECTION_IMPLICIT = "implicit" 

148DELIVERY_SELECTION_EXPLICIT = "explicit" 

149DELIVERY_SELECTION_FIXED = "fixed" 

150 

151DELIVERY_SELECTION_VALUES = [DELIVERY_SELECTION_EXPLICIT, DELIVERY_SELECTION_FIXED, DELIVERY_SELECTION_IMPLICIT] 

152PTZ_METHOD_ONVIF = "onvif" 

153PTZ_METHOD_FRIGATE = "frigate" 

154PTZ_METHOD_VALUES = [PTZ_METHOD_ONVIF, PTZ_METHOD_FRIGATE] 

155 

156SELECTION_FALLBACK_ON_ERROR = "fallback_on_error" 

157SELECTION_FALLBACK = "fallback" 

158SELECTION_BY_SCENARIO = "scenario" 

159SELECTION_DEFAULT = "default" 

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

161 

162OCCUPANCY_VALUES = [ 

163 OCCUPANCY_ALL_IN, 

164 OCCUPANCY_ALL_OUT, 

165 OCCUPANCY_ANY_IN, 

166 OCCUPANCY_ANY_OUT, 

167 OCCUPANCY_ONLY_IN, 

168 OCCUPANCY_ONLY_OUT, 

169 OCCUPANCY_ALL, 

170 OCCUPANCY_NONE, 

171] 

172 

173PRIORITY_CRITICAL = "critical" 

174PRIORITY_HIGH = "high" 

175PRIORITY_MEDIUM = "medium" 

176PRIORITY_LOW = "low" 

177 

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

179TRANSPORT_SMS = "sms" 

180TRANSPORT_MQTT = "mqtt" 

181TRANSPORT_EMAIL = "email" 

182TRANSPORT_ALEXA = "alexa_devices" 

183TRANSPORT_ALEXA_MEDIA_PLAYER = "alexa_media_player" 

184TRANSPORT_MOBILE_PUSH = "mobile_push" 

185TRANSPORT_MEDIA = "media" 

186TRANSPORT_CHIME = "chime" 

187TRANSPORT_GENERIC = "generic" 

188TRANSPORT_NOTIFY_ENTITY = "notify_entity" 

189TRANSPORT_PERSISTENT = "persistent" 

190TRANSPORT_VALUES = [ 

191 TRANSPORT_SMS, 

192 TRANSPORT_MQTT, 

193 TRANSPORT_ALEXA, 

194 TRANSPORT_ALEXA_MEDIA_PLAYER, 

195 TRANSPORT_MOBILE_PUSH, 

196 TRANSPORT_CHIME, 

197 TRANSPORT_EMAIL, 

198 TRANSPORT_MEDIA, 

199 TRANSPORT_PERSISTENT, 

200 TRANSPORT_GENERIC, 

201 TRANSPORT_NOTIFY_ENTITY, 

202] 

203 

204CONF_TARGET_USAGE = "target_usage" 

205TARGET_USE_ON_NO_DELIVERY_TARGETS = "no_delivery" 

206TARGET_USE_ON_NO_ACTION_TARGETS = "no_action" 

207TARGET_USE_FIXED = "fixed" 

208TARGET_USE_MERGE_ALWAYS = "merge_always" 

209TARGET_USE_MERGE_ON_DELIVERY_TARGETS = "merge_delivery" 

210 

211OPTION_SIMPLIFY_TEXT = "simplify_text" 

212OPTION_STRIP_URLS = "strip_urls" 

213OPTION_MESSAGE_USAGE = "message_usage" 

214OPTION_JPEG = "jpeg_opts" 

215OPTION_TARGET_CATEGORIES = "target_categories" 

216OPTION_UNIQUE_TARGETS = "unique_targets" 

217OPTION_TARGET_INCLUDE_RE = "target_include_re" 

218OPTION_CHIME_ALIASES = "chime_aliases" 

219 

220RE_DEVICE_ID = r"^[0-9a-f]{32}$" 

221 

222TARGET_REQUIRE_ALWAYS = "always" 

223TARGET_REQUIRE_NEVER = "never" 

224TARGET_REQUIRE_OPTIONAL = "optional" 

225 

226SCENARIO_NULL = "NULL" 

227SCENARIO_TEMPLATE_ATTRS = ("message_template", "title_template") 

228 

229RESERVED_DELIVERY_NAMES = ["ALL"] 

230RESERVED_SCENARIO_NAMES = [SCENARIO_NULL] 

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

232 

233 

234CONF_DUPE_CHECK = "dupe_check" 

235CONF_DUPE_POLICY = "dupe_policy" 

236CONF_TTL = "ttl" 

237CONF_SIZE = "size" 

238ATTR_DUPE_POLICY_MTSLP = "dupe_policy_message_title_same_or_lower_priority" 

239ATTR_DUPE_POLICY_NONE = "dupe_policy_none" 

240# TARGET_FIELDS includes entity, device, area, floor, label ids 

241TARGET_SCHEMA = vol.Any(dict[str, list[str]], dict[str, str], str, list[str], cv.TARGET_FIELDS) 

242 

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

244MOBILE_DEVICE_SCHEMA = vol.Schema({ 

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

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

247 vol.Optional(CONF_MOBILE_APP_ID): cv.string, 

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

249}) 

250NOTIFICATION_DUPE_SCHEMA = vol.Schema({ 

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

252 vol.Optional(CONF_SIZE, default=100): cv.positive_int, 

253 vol.Optional(CONF_DUPE_POLICY, default=ATTR_DUPE_POLICY_MTSLP): vol.In([ATTR_DUPE_POLICY_MTSLP, ATTR_DUPE_POLICY_NONE]), 

254}) 

255DELIVERY_CUSTOMIZE_SCHEMA = vol.Schema({ 

256 vol.Optional(CONF_TARGET): TARGET_SCHEMA, 

257 vol.Optional(CONF_ENABLED, default=True): cv.boolean, 

258 vol.Optional(CONF_DATA): DATA_SCHEMA, 

259}) 

260LINK_SCHEMA = vol.Schema({ 

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

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

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

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

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

266}) 

267DELIVERY_CONFIG_SCHEMA = vol.Schema({ # shared by Transport Defaults and Delivery definitions 

268 # defaults set in model.DeliveryConfig 

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

270 vol.Optional(CONF_DEBUG): cv.boolean, 

271 vol.Optional(CONF_OPTIONS): dict, # transport tuning 

272 vol.Optional(CONF_DATA): DATA_SCHEMA, 

273 vol.Optional(CONF_TARGET): TARGET_SCHEMA, 

274 vol.Optional(CONF_TARGET_REQUIRED): vol.Any( 

275 cv.boolean, vol.In([TARGET_REQUIRE_ALWAYS, TARGET_REQUIRE_NEVER, TARGET_REQUIRE_OPTIONAL]) 

276 ), 

277 vol.Optional(CONF_TARGET_USAGE): vol.In([ 

278 TARGET_USE_ON_NO_DELIVERY_TARGETS, 

279 TARGET_USE_ON_NO_ACTION_TARGETS, 

280 TARGET_USE_MERGE_ON_DELIVERY_TARGETS, 

281 TARGET_USE_MERGE_ALWAYS, 

282 TARGET_USE_FIXED, 

283 ]), 

284 vol.Optional(CONF_SELECTION): vol.All(cv.ensure_list, [vol.In(SELECTION_VALUES)]), 

285 vol.Optional(CONF_PRIORITY): vol.All(cv.ensure_list, [vol.In(PRIORITY_VALUES)]), 

286 vol.Optional(CONF_SELECTION_RANK): vol.In([ 

287 SelectionRank.ANY, 

288 SelectionRank.FIRST, 

289 SelectionRank.LAST, 

290 ]), 

291}) 

292DELIVERY_SCHEMA = DELIVERY_CONFIG_SCHEMA.extend({ 

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

294 vol.Required(CONF_TRANSPORT): vol.In(TRANSPORT_VALUES), 

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

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

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

298 vol.Optional(CONF_ENABLED): cv.boolean, 

299 vol.Optional(CONF_OCCUPANCY, default=OCCUPANCY_ALL): vol.In(OCCUPANCY_VALUES), 

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

301}) 

302TRANSPORT_SCHEMA = vol.Schema({ 

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

304 vol.Optional(CONF_DEVICE_DOMAIN): vol.All(cv.ensure_list, [cv.string]), 

305 vol.Optional(CONF_DEVICE_DISCOVERY, default=False): cv.boolean, 

306 vol.Optional(CONF_ENABLED, default=True): cv.boolean, 

307 vol.Optional(CONF_DELIVERY_DEFAULTS): DELIVERY_CONFIG_SCHEMA, 

308}) 

309RECIPIENT_SCHEMA = vol.Schema({ 

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

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

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

313 vol.Optional(CONF_TARGET): TARGET_SCHEMA, 

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

315 vol.Optional(CONF_MOBILE_DISCOVERY, default=True): cv.boolean, 

316 vol.Optional(CONF_MOBILE_DEVICES, default=list): vol.All(cv.ensure_list, [MOBILE_DEVICE_SCHEMA]), 

317 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_CUSTOMIZE_SCHEMA}, 

318}) 

319CAMERA_SCHEMA = vol.Schema({ 

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

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

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

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

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

325 vol.Optional(CONF_PTZ_PRESET_DEFAULT, default=1): vol.Any(cv.positive_int, cv.string), 

326 vol.Optional(CONF_PTZ_DELAY, default=0): int, 

327 vol.Optional(CONF_PTZ_METHOD, default=PTZ_METHOD_ONVIF): vol.In(PTZ_METHOD_VALUES), 

328}) 

329MEDIA_SCHEMA = vol.Schema({ 

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

331 vol.Optional(ATTR_MEDIA_CAMERA_DELAY, default=0): int, 

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

333 # URL fragments allowed 

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

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

336 vol.Optional(ATTR_JPEG_OPTS): dict, 

337}) 

338 

339 

340SCENARIO_SCHEMA = vol.Schema({ 

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

342 vol.Optional(CONF_ENABLED, default=True): cv.boolean, 

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

344 vol.Optional(CONF_MEDIA): MEDIA_SCHEMA, 

345 vol.Optional(CONF_ACTION_GROUP_NAMES, default=[]): vol.All(cv.ensure_list, [cv.string]), 

346 vol.Optional(CONF_DELIVERY_SELECTION, default=DELIVERY_SELECTION_IMPLICIT): vol.In([ 

347 DELIVERY_SELECTION_IMPLICIT, 

348 DELIVERY_SELECTION_EXPLICIT, 

349 ]), 

350 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: vol.Any(None, DELIVERY_CUSTOMIZE_SCHEMA)}, 

351}) 

352ACTION_CALL_SCHEMA = vol.Schema( 

353 { 

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

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

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

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

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

359 }, 

360 extra=vol.ALLOW_EXTRA, 

361) 

362ACTION_SCHEMA = vol.Schema( 

363 { 

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

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

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

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

368 }, 

369 extra=vol.ALLOW_EXTRA, 

370) 

371 

372 

373ARCHIVE_SCHEMA = vol.Schema({ 

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

375 vol.Optional(CONF_ENABLED, default=False): cv.boolean, 

376 vol.Optional(CONF_ARCHIVE_DAYS, default=3): cv.positive_int, 

377 vol.Optional(CONF_ARCHIVE_MQTT_TOPIC): cv.string, 

378 vol.Optional(CONF_ARCHIVE_MQTT_QOS, default=0): cv.positive_int, 

379 vol.Optional(CONF_ARCHIVE_MQTT_RETAIN, default=True): cv.boolean, 

380 vol.Optional(CONF_ARCHIVE_PURGE_INTERVAL, default=60): cv.positive_int, 

381 vol.Optional(CONF_DEBUG, default=False): cv.boolean, 

382}) 

383 

384HOUSEKEEPING_SCHEMA = vol.Schema({ 

385 vol.Optional(CONF_HOUSEKEEPING_TIME, default="00:00:01"): cv.time, 

386}) 

387 

388PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 

389 vol.Optional(CONF_TEMPLATE_PATH, default=TEMPLATE_DIR): cv.path, 

390 vol.Optional(CONF_MEDIA_PATH, default=MEDIA_DIR): cv.path, 

391 vol.Optional(CONF_ARCHIVE, default={CONF_ENABLED: False}): ARCHIVE_SCHEMA, 

392 vol.Optional(CONF_HOUSEKEEPING, default={}): HOUSEKEEPING_SCHEMA, 

393 vol.Optional(CONF_DUPE_CHECK, default=dict): NOTIFICATION_DUPE_SCHEMA, 

394 vol.Optional(CONF_DELIVERY, default=dict): {cv.string: DELIVERY_SCHEMA}, 

395 vol.Optional(CONF_ACTION_GROUPS, default=dict): {cv.string: [ACTION_SCHEMA]}, 

396 vol.Optional(CONF_RECIPIENTS, default=list): vol.All(cv.ensure_list, [RECIPIENT_SCHEMA]), 

397 vol.Optional(CONF_LINKS, default=list): vol.All(cv.ensure_list, [LINK_SCHEMA]), 

398 vol.Optional(CONF_SCENARIOS, default=dict): {cv.string: SCENARIO_SCHEMA}, 

399 vol.Optional(CONF_TRANSPORTS, default=dict): {cv.string: TRANSPORT_SCHEMA}, 

400 vol.Optional(CONF_CAMERAS, default=list): vol.All(cv.ensure_list, [CAMERA_SCHEMA]), 

401}) 

402SUPERNOTIFY_SCHEMA = PLATFORM_SCHEMA 

403 

404ACTION_DATA_SCHEMA = vol.Schema( 

405 { 

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

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

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

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

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

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

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

413 vol.Optional(ATTR_MEDIA): MEDIA_SCHEMA, 

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

415 vol.Optional(ATTR_ACTION_GROUPS, default=[]): vol.All(cv.ensure_list, [cv.string]), 

416 vol.Optional(ATTR_ACTIONS, default=[]): vol.All(cv.ensure_list, [ACTION_CALL_SCHEMA]), 

417 vol.Optional(ATTR_DEBUG, default=False): cv.boolean, 

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

419 vol.Optional(ATTR_TIMESTAMP): cv.string, 

420 }, 

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

422) 

423 

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