notify_user.yaml 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. blueprint:
  2. domain: script
  3. name: Notify user
  4. description: >-
  5. A script blueprint that takes context into consideration where you
  6. dont have to think about the parallelizm.
  7. However, you should take into account that this script might live
  8. «forever», if you're not cautious. It will spawn into three services,
  9. where 2 of them might live until a script reload or a HA restart.
  10. However 2, this script is perfect if you know what you're doing and dont
  11. want to deal with the hassle of «contexting». E.g if you want to push the
  12. same thing to multiple devices, and let them do actions based on the data.
  13. Here's an example of a «happy birthday» script that will notify every device
  14. and let each device action on it (send an sms or dismiss it) without affecting
  15. other users.
  16. Example usage:
  17. ```
  18. script:
  19. notify_device:
  20. alias: Notify device
  21. fields:
  22. notify_device:
  23. name: "Device notify service"
  24. description: "Device service, e.g notify.mobile_app_x"
  25. selector:
  26. object:
  27. data:
  28. name: Notification data
  29. description: "Notification data, same as for notify-services"
  30. selector:
  31. object:
  32. action_scripts:
  33. name: Action scripts
  34. description: >-
  35. See blueprint
  36. selector:
  37. object:
  38. default: {}
  39. timeout:
  40. name: "Timeout"
  41. description: >-
  42. Timeout before clearing event and stop waiting. 0 equals to never.
  43. Respects «Days» too in YAML mode.
  44. selector:
  45. duration:
  46. max: 200
  47. use_blueprint:
  48. path: notify_user.yaml
  49. input:
  50. notify_device: "{{ notify_device }}"
  51. index: "{{ index if index is defined else 0 }}"
  52. timeout: "{{ timeout if timeout is defined else 0 }}"
  53. data: "{{ data }}"
  54. action_scripts: "{{ action_scripts if action_scripts is defined else {} }}"
  55. ```
  56. and call it:
  57. ```
  58. service: script.turn_on
  59. target:
  60. entity_id: script.notify_device
  61. data:
  62. variables:
  63. notify_device: "{{ notify.mobile_app_device }}"
  64. data: (data as a normal "notify"-service)
  65. message: "..."
  66. data:
  67. actions:
  68. - action: DO_IT_NOW_DISMISS_ALL #Ending action with DISMISS_NOW will remove it from other devices.
  69. title: Do it now
  70. action_scripts:
  71. DO_IT_NOW_DISMISS_ALL:
  72. script: "script.my_script" #script to run if user clicks "Do it now"
  73. variables:
  74. var1: "Variables my script need"
  75. ´´´
  76. Feel free to use it within a loop too:
  77. ```
  78. repeat:
  79. for_each: >-
  80. - device_1
  81. - device_2
  82. sequence:
  83. - service: script.turn_on
  84. [...]
  85. data:
  86. variables:
  87. notify_device: "{{ repeat.item }}"
  88. ```
  89. input:
  90. notify_device:
  91. name: "Device to notify"
  92. description: >-
  93. The name of the notify service, e.g service.mobile_app_x
  94. selector:
  95. object:
  96. timeout:
  97. name: "Timeout"
  98. description: >-
  99. Timeout before clearing notification and stop waiting. 0 equals to never.
  100. Respects «Days» too in YAML mode. Timeout set here will override `data.data.timeout`
  101. and visa-versa.
  102. selector:
  103. duration:
  104. # enable_days: true (HA fire an error on this.)
  105. data:
  106. name: "Message data"
  107. description: "Equal to data field in the notify service"
  108. selector:
  109. object:
  110. # Scripts to run based on `data->actions` set in notify-service. E.g:
  111. # ```
  112. # data:
  113. # actions:
  114. # - action: ACTION_ANCHOR
  115. # title: This test
  116. # ```
  117. # with matching:
  118. # ```
  119. # action_scripts:
  120. # ACTION_ANCHOR: script...
  121. # ```
  122. # The format for `ACTION_ANCHOR` can be just a string, referencing a script,
  123. # or an object with the following format:
  124. # ```
  125. # ACTION_ANCHOR:
  126. # script: script..
  127. # variables:
  128. # var1: ....
  129. # ```
  130. action_scripts:
  131. name: Action scripts
  132. description: >-
  133. Read `action_scripts` comments within blueprint.
  134. selector:
  135. object:
  136. default: {}
  137. tts:
  138. name: "TTS"
  139. description: "Not used yet"
  140. selector:
  141. boolean:
  142. default: "{{ false }}"
  143. mode: parallel
  144. variables:
  145. input_notify_device: !input notify_device
  146. input_timeout: !input timeout
  147. input_data: !input data
  148. action_scripts: !input action_scripts
  149. sequence:
  150. - variables:
  151. device_name: "{{ input_notify_device.replace('notify.mobile_app_', '') }}"
  152. ctx: "{{ context.id ~ '_' ~ device_name }}"
  153. device_id: >-
  154. {{ dict.from_keys(device_attr(device_id('device_tracker.' ~ device_name), 'identifiers')).mobile_app }}
  155. update_timeout: >-
  156. {% if 'data' in input_data and 'timeout' in input_data.data %}
  157. {{ input_data.data.timeout|int }}
  158. {% else %}
  159. {% set seconds = 0 %}
  160. {% if input_timeout is iterable %}
  161. {% if 'seconds' in input_timeout %}
  162. {% set seconds = seconds + input_timeout.seconds|int %}
  163. {% endif %}
  164. {% if 'minutes' in input_timeout %}
  165. {% set seconds = seconds + (input_timeout.minutes|int * 60 * 60) %}
  166. {% endif %}
  167. {% if 'hours' in input_timeout %}
  168. {% set seconds = seconds + ((input_timeout.hours|int) * 60 * 60) %}
  169. {% endif %}
  170. {% if 'days' in input_timeout %}
  171. {% set seconds = seconds + ((input_timeout.days|int) * 60 * 60 * 24) %}
  172. {% endif %}
  173. {% elif input_timeout is not iterable %}
  174. {% set seconds = input_timeout|int %}
  175. {% endif %}
  176. {{ false if seconds <= 0 else (now().timestamp() + seconds) }}
  177. {% endif %}
  178. update_data: >-
  179. {% set data_data = (input_data.data.items() | list) if 'data' in input_data else [] %}
  180. {% if 'data' in input_data %}
  181. {% if 'actions' in input_data.data %}
  182. {% set action = namespace(entities=[]) %}
  183. {% for a in input_data.data.actions %}
  184. {% set update = {"action": ctx ~ '_' ~ a.action}.items() | list %}
  185. {% set current = a.items() | list | rejectattr(
  186. '0', 'eq', update | map(attribute='0') | list
  187. ) | list %}
  188. {% set action.entities = action.entities + [
  189. dict.from_keys(current + update)
  190. ] %}
  191. {% endfor %}
  192. {% set actions = { "actions": action.entities }.items() | list %}
  193. {% set data_data = dict.from_keys(data_data | rejectattr(
  194. '0', 'eq', actions | map(attribute='0') | list
  195. ) | list + actions).items() | list
  196. %}
  197. {% endif %}
  198. {% if 'timeout' not in input_data and update_timeout is not false %}
  199. {% set add = { "timeout": update_timeout|int }.items() | list %}
  200. {% set data_data = dict.from_keys(data_data | rejectattr(
  201. '0', 'eq', add | map(attribute='0') | list
  202. ) | list + add).items() | list
  203. %}
  204. {% endif %}
  205. {% if 'group' not in input_data.data %}
  206. {% set add = { "group": "default-group" }.items() | list %}
  207. {% set data_data = dict.from_keys(data_data | rejectattr(
  208. '0', 'eq', add | map(attribute='0') | list
  209. ) | list + add).items() | list
  210. %}
  211. {% endif %}
  212. {% if (
  213. (
  214. 'alert_once' in input_data.data or
  215. 'actions' in input_data.data or
  216. 'persistent' in input_data.data
  217. ) and 'tag' not in input_data.data
  218. ) or (
  219. update_timeout is not false and 'tag' not in input_data.data
  220. )
  221. %}
  222. {% set add = { "tag": "tag_" + ctx }.items() | list %}
  223. {% set data_data = dict.from_keys(data_data | rejectattr(
  224. '0', 'eq', add | map(attribute='0') | list
  225. ) | list + add).items() | list
  226. %}
  227. {% endif %}
  228. {% else %}
  229. {% if update_timeout is not false %}
  230. {% set add = { "tag": "tag_" + ctx, 'timeout': update_timeout|int }.items() | list %}
  231. {% set data_data = dict.from_keys(data_data | rejectattr(
  232. '0', 'eq', add | map(attribute='0') | list
  233. ) | list + add).items() | list
  234. %}
  235. {% endif %}
  236. {% endif %}
  237. {% if data_data|length != 0 %}
  238. {% set data_data = {"data": dict.from_keys(data_data)}.items() | list %}
  239. {{ dict.from_keys((input_data.items() | list | rejectattr(
  240. '0', 'eq', data_data | map(attribute='0') | list
  241. ) | list) + data_data) }}
  242. {% else %}
  243. {{ input_data }}
  244. {% endif %}
  245. action_handlers: >-
  246. {% set actions = namespace(handlers=[]) %}
  247. {% for ask in action_scripts.keys() %}
  248. {% set askc = ctx ~ '_' ~ ask %}
  249. {% for action in update_data.data.actions if askc == action.action %}
  250. {% set actions.handlers = actions.handlers + [(
  251. askc, action_scripts[ask]
  252. )] %}
  253. {% endfor %}
  254. {% endfor %}
  255. {{ dict.from_keys(actions.handlers) }}
  256. - alias: "Parallize event and action listeners"
  257. parallel:
  258. - sequence: []
  259. - alias: "Listen for event if actions are given"
  260. if: "{{ action_handlers|length > 0 or update_timeout is not false }}"
  261. then:
  262. - alias: "Loop for events until criterias are met"
  263. repeat:
  264. while: "{{ (update_timeout - now().timestamp() > 0) or update_timeout == 0 }}"
  265. sequence:
  266. - if: "{{ update_timeout == 0 }}"
  267. then:
  268. alias: "Wait for app event, without timeout"
  269. wait_for_trigger:
  270. - platform: event
  271. event_type:
  272. - mobile_app_notification_action
  273. - mobile_app_notification_cleared
  274. event_data:
  275. device_id: "{{ device_id }}"
  276. else:
  277. alias: "Wait for app event, with timeout"
  278. wait_for_trigger:
  279. - platform: event
  280. event_type:
  281. - mobile_app_notification_action
  282. - mobile_app_notification_cleared
  283. event_data:
  284. device_id: "{{ device_id }}"
  285. - platform: event
  286. event_type: 'context_notification_clear'
  287. event_data:
  288. context: "{{ context.id }}"
  289. timeout: >-
  290. {{ update_timeout - now().timestamp() }}
  291. - if: "{{ wait.trigger is none }}"
  292. then:
  293. alias: "Reached timeout, ending."
  294. stop: "Reached timeout, ending."
  295. - alias: "Check if the notification was cleared (completed) by another device."
  296. if: "{{ wait.trigger.event.event_type == 'context_notification_clear' }}"
  297. then:
  298. - alias: "Clear notification on other devices, task was completed"
  299. service: "{{ input_notify_device }}"
  300. data:
  301. message: "clear_notification"
  302. data:
  303. tag: "{{ update_data.data.tag }}"
  304. - alias: "And give a 20 seconds notification on other devices that it was cleared."
  305. service: "{{ input_notify_device }}"
  306. data:
  307. title: "Notification completed"
  308. message: >-
  309. {{ wait.trigger.event.data.user.split(' ')[0] }} completed the notification «{{
  310. (update_data.title if 'title' else update_data.message)[0:20]
  311. }}...»
  312. data:
  313. color: "#688840"
  314. timeout: 20
  315. notification_icon: >-
  316. {{ update_data.data.notification_icon if 'data' in update_data and 'notification_icon' in update_data.data else 'mdi:checkbox-marked-circle-plus-outline' }}
  317. - alias: "Notification cleared by another user, ending."
  318. stop: "Notification cleared by another user, ending."
  319. - alias: "Check that the notification is within this context"
  320. condition: >-
  321. {% set in_ctx = namespace(bool=false) %}
  322. {% for key in wait.trigger.event.data.keys() if key.startswith('action') and key.endswith('key') %}
  323. {% if wait.trigger.event.data[key].startswith(ctx) %}
  324. {% set in_ctx.bool = true %}
  325. {% endif %}
  326. {% endfor %}
  327. {{ in_ctx.bool }}
  328. - if: "{{ wait.trigger.event.event_type.endswith('_cleared') }}"
  329. then:
  330. alias: "User cleared notification, stop listening for events."
  331. stop: "User cleared notification, stop listening for events."
  332. - service: system_log.write
  333. data:
  334. level: warning
  335. message: >-
  336. Context: {{ ctx }}\n
  337. Event-type: {{ wait.trigger.event.event_type }}\n
  338. Event-action: {{ wait.trigger.event.data.action }}
  339. - if: "{{ wait.trigger.event.data.action.endswith('_DISMISS_ALL') }}"
  340. then:
  341. event: context_notification_clear
  342. event_data:
  343. context: "{{ context.id }}"
  344. user: '{{ states.person|selectattr("attributes.user_id", "==", wait.trigger.event.context.user_id)|map(attribute="attributes.friendly_name")|first }}'
  345. - variables:
  346. action: >-
  347. {{ action_handlers[ wait.trigger.event.data.action ]|default(false) }}
  348. - service: system_log.write
  349. data:
  350. level: warning
  351. message: "Action: {{ action }}"
  352. - alias: "Check if the action is associated with a script"
  353. if: "{{ not action }}"
  354. then:
  355. - stop: "Action is not associated with any scripts, ending."
  356. - if: "{{ action is string or 'variables' not in action }}"
  357. then:
  358. service: script.turn_on
  359. target:
  360. entity_id: "{{ script }}"
  361. else:
  362. service: script.turn_on
  363. target:
  364. entity_id: "{{ action.script }}"
  365. data:
  366. variables: "{{ action.variables }}"
  367. - alias: Send message to device
  368. service: "{{ input_notify_device }}"
  369. data: >-
  370. {{ update_data }}
  371. # @ignore: Incorrect type. Expected "object"