notify_user.yaml 14 KB

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