notify_user.yaml 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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. ```
  17. script:
  18. birthday_wishes:
  19. mode: parallel
  20. sequence:
  21. - variables:
  22. blang: "{{ lang if lang is defined else none }}"
  23. bname: "{{ name|lower|title }}"
  24. bage: "{{ age|string + {1: 'st', 2: 'nd', 3: 'rd'}.get(4 if 10 <= age|int % 100 < 20 else age|int % 10, 'th') }}"
  25. - service: "{{ notify_device }}"
  26. data:
  27. message: command_activity
  28. data:
  29. intent_action: "android.intent.action.SENDTO"
  30. intent_uri: >-
  31. sms:{{ number }}?body={{-
  32. "🎼 «Hurra for deg som fyller ditt år, ja - deg vil jeg gratulere» 🎶 🎉 \n\n"
  33. }}{%- if blang == 'no_nb' -%}
  34. Gratulere med {{ age|default('') }}årsdagen, {{ bname }}! 🎂🎁 Håpe du får en fin dag 🥳
  35. {%- else -%}
  36. Happy {{ bage ~ ' ' if age else '' }}birthday, {{ bname }}! 🎂🎁 Hope'll get a beautiful day 🥳
  37. {%- endif -%}
  38. automation:
  39. - alias: "Birthday: Notify on birthday!"
  40. trigger:
  41. - platform: calendar
  42. event: start
  43. entity_id: calendar.ical_birthdays
  44. offset:
  45. hours: 10
  46. action:
  47. - repeat:
  48. for_each: "{{ birthdays }}"
  49. sequence:
  50. # Set variables here
  51. - repeat:
  52. for_each: "{{ notify_devices }}"
  53. sequence:
  54. service: script.turn_on
  55. target:
  56. entity_id: script.notify_device
  57. data:
  58. variables:
  59. notify_device: "{{ repeat.item }}"
  60. index: "{{ index }}-{{ repeat.index }}"
  61. timeout:
  62. hours: 14
  63. data:
  64. title: "It is {{ name }}'s birthday. 🎁"
  65. message: >-
  66. {% if age is not false %}
  67. {{ name }} is turning {{ age }} today.
  68. {% else %}
  69. {{ name }} is getting one year older today.
  70. {% endif %}
  71. Send your congratulations 🎉 to let him/her
  72. know that you appreciate them. 🥳
  73. data:
  74. actions:
  75. - action: SEND_SMS
  76. title: Send SMS
  77. - action: DISMISS
  78. title: Dismiss
  79. action_scripts:
  80. SEND_SMS:
  81. script: script.birthday_wishes
  82. variables:
  83. lang: "{{ lang }}"
  84. name: "{{ name }}"
  85. age: "{{ age }}"
  86. notify_device: "{{ repeat.item }}"
  87. number: "{{ phones|join(',') }}"
  88. ```
  89. input:
  90. notify_device:
  91. name: "Devices to notify"
  92. description: >-
  93. The name of the notify service, e.g service.mobile_app_x
  94. selector:
  95. object:
  96. index:
  97. name: Device index
  98. description: >-
  99. If called several times within same context, increase this for each.
  100. default:
  101. seconds: 0
  102. selector:
  103. number:
  104. min: 1
  105. max: 1000
  106. step: 1
  107. timeout:
  108. name: "Timeout"
  109. description: >-
  110. Timeout before notifications dissapear.
  111. selector:
  112. duration:
  113. # enable_days: true
  114. data:
  115. name: "Message data"
  116. description: "Equal to data field in the notify service"
  117. selector:
  118. object:
  119. # Scripts to run based on `data->actions` set in notify-service. E.g:
  120. # ```
  121. # data:
  122. # actions:
  123. # - action: ACTION_ANCHOR
  124. # title: This test
  125. # ```
  126. # with matching:
  127. # ```
  128. # action_scripts:
  129. # ACTION_ANCHOR: script...
  130. # ```
  131. # The format for `ACTION_ANCHOR` can be just a string, referencing a script,
  132. # or an object with the following format:
  133. # ```
  134. # ACTION_ANCHOR:
  135. # script: script..
  136. # variables:
  137. # var1: ....
  138. # ```
  139. action_scripts:
  140. name: Action scripts
  141. description: >-
  142. Read `action_scripts` comments within blueprint.
  143. selector:
  144. object:
  145. default: {}
  146. tts:
  147. name: "TTS"
  148. description: "Not used yet"
  149. selector:
  150. boolean:
  151. default: "{{ false }}"
  152. mode: parallel
  153. variables:
  154. input_notify_device: !input notify_device
  155. index: !input index
  156. ctx: "{{ this.context.id + '_' + input_notify_device ~ '_' ~ index }}" #
  157. input_timeout: !input timeout
  158. update_timeout: >-
  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. {{ 0 if seconds == 0 else (now().timestamp() + seconds) }}
  177. input_data: !input data
  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 'group' not in input_data.data %}
  199. {% set add = { "group": "default-group" }.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 (
  206. (
  207. 'alert_once' in input_data.data or
  208. 'actions' in input_data.data or
  209. 'persistent' in input_data.data
  210. ) and 'tag' not in input_data.data
  211. ) or (
  212. timeout != 0 and 'tag' not in input_data.data
  213. )
  214. %}
  215. {% set add = { "tag": "tag_" + ctx }.items() | list %}
  216. {% set data_data = dict.from_keys(data_data | rejectattr(
  217. '0', 'eq', add | map(attribute='0') | list
  218. ) | list + add).items() | list
  219. %}
  220. {% endif %}
  221. {% else %}
  222. {% if timeout != 0 %}
  223. {% set add = { "tag": "tag_" + ctx }.items() | list %}
  224. {% set data_data = dict.from_keys(data_data | rejectattr(
  225. '0', 'eq', add | map(attribute='0') | list
  226. ) | list + add).items() | list
  227. %}
  228. {% endif %}
  229. {% endif %}
  230. {% if data_data|length != 0 %}
  231. {% set data_data = {"data": dict.from_keys(data_data)}.items() | list %}
  232. {{ dict.from_keys((input_data.items() | list | rejectattr(
  233. '0', 'eq', data_data | map(attribute='0') | list
  234. ) | list) + data_data) }}
  235. {% else %}
  236. {{ input_data }}
  237. {% endif %}
  238. action_scripts: !input action_scripts
  239. action_handlers: >-
  240. {% set actions = namespace(handlers=[]) %}
  241. {% for ask in action_scripts.keys() %}
  242. {% set askc = ctx ~ '_' ~ ask %}
  243. {% for action in update_data.data.actions if askc == action.action %}
  244. {% set actions.handlers = actions.handlers + [(
  245. askc, action_scripts[ask]
  246. )] %}
  247. {% endfor %}
  248. {% endfor %}
  249. {{ dict.from_keys(actions.handlers) }}
  250. sequence:
  251. # - service: system_log.write
  252. # data:
  253. # level: warning
  254. # message: >-
  255. # Action handlers: {{ action_handlers }}
  256. # Timeout: {{ update_timeout }} seconds
  257. # Data: {{ update_data }}
  258. - alias: "Parallize event listeners and notification"
  259. parallel:
  260. - alias: "Listen for event if actions are given"
  261. if: "{{ action_handlers|length > 0 }}"
  262. then:
  263. - alias: "Loop for events until criterias are met"
  264. repeat:
  265. while: "{{ (update_timeout - now().timestamp() > 0) or update_timeout == 0 }}"
  266. sequence:
  267. - if: "{{ update_timeout == 0 }}"
  268. then:
  269. alias: "Wait for app event, without timeout"
  270. wait_for_trigger:
  271. - platform: event
  272. event_type:
  273. - mobile_app_notification_action
  274. - mobile_app_notification_cleared
  275. else:
  276. alias: "Wait for app event, with timeout"
  277. wait_for_trigger:
  278. - platform: event
  279. event_type:
  280. - mobile_app_notification_action
  281. - mobile_app_notification_cleared
  282. timeout: >-
  283. {{ update_timeout - now().timestamp() }}
  284. - if: "{{ wait.trigger is none }}"
  285. then:
  286. alias: "Reached timeout, stopping."
  287. stop: "Reached timeout, stopping."
  288. - variables:
  289. in_ctx: >-
  290. {% set in_ctx = namespace(bool=false) %}
  291. {% for key in wait.trigger.event.data.keys() if key.startswith('action') and key.endswith('key') %}
  292. {% if wait.trigger.event.data[key].startswith(ctx) %}
  293. {% set in_ctx.bool = true %}
  294. {% endif %}
  295. {% endfor %}
  296. {{ in_ctx.bool }}
  297. - alias: "Check if context matches or restart from top"
  298. condition: "{{ in_ctx }}"
  299. - if: >-
  300. {{
  301. wait.trigger.event.event_type.endswith('_cleared') or
  302. wait.trigger.event.data.action not in action_handlers.keys()
  303. }}
  304. then:
  305. - alias: "Cleared event within user context"
  306. event: custom_mobile_app_notification_action
  307. event_data:
  308. context: "{{ ctx }}"
  309. action: "cleared"
  310. - stop: "User cleared notification or choose an invalid option, stop listening for events."
  311. - event: custom_mobile_app_notification_action
  312. event_data:
  313. context: "{{ ctx }}"
  314. action: "{{ wait.trigger.event.data.action }}"
  315. - stop: "User acted within context, stop listening for further events."
  316. - alias: "Listen for forwarded events"
  317. if: "{{ action_handlers|length > 0 or update_timeout != 0 }}"
  318. then:
  319. - if: "{{ update_timeout == 0 }}"
  320. then:
  321. alias: "Wait for context event, without timeout"
  322. wait_for_trigger:
  323. - platform: event
  324. event_type: custom_mobile_app_notification_action
  325. event_data:
  326. context: "{{ ctx }}"
  327. else:
  328. alias: "Wait for context event, with timeout"
  329. wait_for_trigger:
  330. - platform: event
  331. event_type: custom_mobile_app_notification_action
  332. event_data:
  333. context: "{{ ctx }}"
  334. timeout: >-
  335. {{ update_timeout - now().timestamp() }}
  336. - alias: "Check if timed out and remove notification"
  337. if: "{{ wait.trigger == none }}"
  338. then:
  339. alias: "Clear notification, timeout reached."
  340. service: "{{ input_notify_device }}"
  341. data:
  342. message: "clear_notification"
  343. data:
  344. tag: "{{ data.data.tag }}"
  345. else:
  346. - variables:
  347. action: >-
  348. {% set action = wait.trigger.event.data.action %}
  349. {{ action_handlers[action] if action in action_handlers else false }}
  350. - alias: "Action is known or stop executing"
  351. condition: "{{ action is not false }}"
  352. - if: "{{ action is string or 'variables' not in action }}"
  353. then:
  354. service: script.turn_on
  355. target:
  356. entity_id: "{{ script }}"
  357. else:
  358. service: script.turn_on
  359. target:
  360. entity_id: "{{ action.script }}"
  361. data:
  362. variables: "{{ action.variables }}"
  363. - alias: Send message to device
  364. service: "{{ input_notify_device }}"
  365. data: >-
  366. {{ update_data }}
  367. # @ignore: Incorrect type. Expected "object"