notify_user.yaml 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. blueprint:
  2. domain: script
  3. name: Notify user
  4. description: >-
  5. Notify a user
  6. input:
  7. notify_device:
  8. name: "Devices to notify"
  9. description: >-
  10. The name of the notify service, e.g service.mobile_app_x
  11. selector:
  12. object:
  13. index:
  14. name: Device index
  15. description: >-
  16. If called several times within same context, increase this for each.
  17. default:
  18. seconds: 0
  19. selector:
  20. number:
  21. min: 1
  22. max: 1000
  23. step: 1
  24. timeout:
  25. name: "Timeout"
  26. description: >-
  27. Timeout before notifications dissapear.
  28. selector:
  29. duration:
  30. # enable_days: true
  31. data:
  32. name: "Message data"
  33. description: "Equal to data field in the notify service"
  34. selector:
  35. object:
  36. # Scripts to run based on `data->actions` set in notify-service. E.g:
  37. # ```
  38. # data:
  39. # actions:
  40. # - action: ACTION_ANCHOR
  41. # title: This test
  42. # ```
  43. # with matching:
  44. # ```
  45. # action_scripts:
  46. # ACTION_ANCHOR: script...
  47. # ```
  48. # The format for `ACTION_ANCHOR` can be just a string, referencing a script,
  49. # or an object with the following format:
  50. # ```
  51. # ACTION_ANCHOR:
  52. # script: script..
  53. # variables:
  54. # var1: ....
  55. # ```
  56. action_scripts:
  57. name: Action scripts
  58. description: >-
  59. Read `action_scripts` comments within blueprint.
  60. selector:
  61. object:
  62. default: {}
  63. tts:
  64. name: "TTS"
  65. description: "Not used yet"
  66. selector:
  67. boolean:
  68. default: "{{ false }}"
  69. mode: parallel
  70. variables:
  71. input_notify_device: !input notify_device
  72. index: !input index
  73. ctx: "{{ this.context.id + '_' + input_notify_device ~ '_' ~ index }}" #
  74. input_timeout: !input timeout
  75. update_timeout: >-
  76. {% set seconds = 0 %}
  77. {% if input_timeout is iterable %}
  78. {% if 'seconds' in input_timeout %}
  79. {% set seconds = seconds + input_timeout.seconds|int %}
  80. {% endif %}
  81. {% if 'minutes' in input_timeout %}
  82. {% set seconds = seconds + (input_timeout.minutes|int * 60 * 60) %}
  83. {% endif %}
  84. {% if 'hours' in input_timeout %}
  85. {% set seconds = seconds + ((input_timeout.hours|int) * 60 * 60) %}
  86. {% endif %}
  87. {% if 'days' in input_timeout %}
  88. {% set seconds = seconds + ((input_timeout.days|int) * 60 * 60 * 24) %}
  89. {% endif %}
  90. {% elif input_timeout is not iterable %}
  91. {% set seconds = input_timeout|int %}
  92. {% endif %}
  93. {{ 0 if seconds == 0 else (now().timestamp() + seconds) }}
  94. input_data: !input data
  95. update_data: >-
  96. {% set data_data = (input_data.data.items() | list) if 'data' in input_data else [] %}
  97. {% if 'data' in input_data %}
  98. {% if 'actions' in input_data.data %}
  99. {% set action = namespace(entities=[]) %}
  100. {% for a in input_data.data.actions %}
  101. {% set update = {"action": ctx ~ '_' ~ a.action}.items() | list %}
  102. {% set current = a.items() | list | rejectattr(
  103. '0', 'eq', update | map(attribute='0') | list
  104. ) | list %}
  105. {% set action.entities = action.entities + [
  106. dict.from_keys(current + update)
  107. ] %}
  108. {% endfor %}
  109. {% set actions = { "actions": action.entities }.items() | list %}
  110. {% set data_data = dict.from_keys(data_data | rejectattr(
  111. '0', 'eq', actions | map(attribute='0') | list
  112. ) | list + actions).items() | list
  113. %}
  114. {% endif %}
  115. {% if 'group' not in input_data.data %}
  116. {% set add = { "group": "default-group" }.items() | list %}
  117. {% set data_data = dict.from_keys(data_data | rejectattr(
  118. '0', 'eq', add | map(attribute='0') | list
  119. ) | list + add).items() | list
  120. %}
  121. {% endif %}
  122. {% if (
  123. (
  124. 'alert_once' in input_data.data or
  125. 'actions' in input_data.data or
  126. 'persistent' in input_data.data
  127. ) and 'tag' not in input_data.data
  128. ) or (
  129. timeout != 0 and 'tag' not in input_data.data
  130. )
  131. %}
  132. {% set add = { "tag": "tag_" + ctx }.items() | list %}
  133. {% set data_data = dict.from_keys(data_data | rejectattr(
  134. '0', 'eq', add | map(attribute='0') | list
  135. ) | list + add).items() | list
  136. %}
  137. {% endif %}
  138. {% else %}
  139. {% if timeout != 0 %}
  140. {% set add = { "tag": "tag_" + ctx }.items() | list %}
  141. {% set data_data = dict.from_keys(data_data | rejectattr(
  142. '0', 'eq', add | map(attribute='0') | list
  143. ) | list + add).items() | list
  144. %}
  145. {% endif %}
  146. {% endif %}
  147. {% if data_data|length != 0 %}
  148. {% set data_data = {"data": dict.from_keys(data_data)}.items() | list %}
  149. {{ dict.from_keys((input_data.items() | list | rejectattr(
  150. '0', 'eq', data_data | map(attribute='0') | list
  151. ) | list) + data_data) }}
  152. {% else %}
  153. {{ input_data }}
  154. {% endif %}
  155. action_scripts: !input action_scripts
  156. action_handlers: >-
  157. {% set actions = namespace(handlers=[]) %}
  158. {% for ask in action_scripts.keys() %}
  159. {% set askc = ctx ~ '_' ~ ask %}
  160. {% for action in update_data.data.actions if askc == action.action %}
  161. {% set actions.handlers = actions.handlers + [(
  162. askc, action_scripts[ask]
  163. )] %}
  164. {% endfor %}
  165. {% endfor %}
  166. {{ dict.from_keys(actions.handlers) }}
  167. sequence:
  168. # - service: system_log.write
  169. # data:
  170. # level: warning
  171. # message: >-
  172. # Action handlers: {{ action_handlers }}
  173. # Timeout: {{ update_timeout }} seconds
  174. # Data: {{ update_data }}
  175. - alias: "Parallize event listeners and notification"
  176. parallel:
  177. - alias: "Listen for event if actions are given"
  178. if: "{{ action_handlers|length > 0 }}"
  179. then:
  180. - alias: "Loop for events until criterias are met"
  181. repeat:
  182. while: "{{ (update_timeout - now().timestamp() > 0) or update_timeout == 0 }}"
  183. sequence:
  184. - if: "{{ update_timeout == 0 }}"
  185. then:
  186. alias: "Wait for app event, without timeout"
  187. wait_for_trigger:
  188. - platform: event
  189. event_type:
  190. - mobile_app_notification_action
  191. - mobile_app_notification_cleared
  192. else:
  193. alias: "Wait for app event, with timeout"
  194. wait_for_trigger:
  195. - platform: event
  196. event_type:
  197. - mobile_app_notification_action
  198. - mobile_app_notification_cleared
  199. timeout: >-
  200. {{ update_timeout - now().timestamp() }}
  201. - if: "{{ wait.trigger is none }}"
  202. then:
  203. alias: "Reached timeout, stopping."
  204. stop: "Reached timeout, stopping."
  205. - variables:
  206. in_ctx: >-
  207. {% set in_ctx = namespace(bool=false) %}
  208. {% for key in wait.trigger.event.data.keys() if key.startswith('action') and key.endswith('key') %}
  209. {% if wait.trigger.event.data[key].startswith(ctx) %}
  210. {% set in_ctx.bool = true %}
  211. {% endif %}
  212. {% endfor %}
  213. {{ in_ctx.bool }}
  214. - alias: "Check if context matches or restart from top"
  215. condition: "{{ in_ctx }}"
  216. - if: >-
  217. {{
  218. wait.trigger.event.event_type.endswith('_cleared') or
  219. wait.trigger.event.data.action not in action_handlers.keys()
  220. }}
  221. then:
  222. - alias: "Cleared event within user context"
  223. event: custom_mobile_app_notification_action
  224. event_data:
  225. context: "{{ ctx }}"
  226. action: "cleared"
  227. - stop: "User cleared notification or choose an invalid option, stop listening for events."
  228. - event: custom_mobile_app_notification_action
  229. event_data:
  230. context: "{{ ctx }}"
  231. action: "{{ wait.trigger.event.data.action }}"
  232. - stop: "User acted within context, stop listening for further events."
  233. - alias: "Listen for forwarded events"
  234. if: "{{ action_handlers|length > 0 or update_timeout != 0 }}"
  235. then:
  236. - if: "{{ update_timeout == 0 }}"
  237. then:
  238. alias: "Wait for context event, without timeout"
  239. wait_for_trigger:
  240. - platform: event
  241. event_type: custom_mobile_app_notification_action
  242. event_data:
  243. context: "{{ ctx }}"
  244. else:
  245. alias: "Wait for context event, with timeout"
  246. wait_for_trigger:
  247. - platform: event
  248. event_type: custom_mobile_app_notification_action
  249. event_data:
  250. context: "{{ ctx }}"
  251. timeout: >-
  252. {{ update_timeout - now().timestamp() }}
  253. - alias: "Check if timed out and remove notification"
  254. if: "{{ wait.trigger == none }}"
  255. then:
  256. alias: "Clear notification, timeout reached."
  257. service: "{{ input_notify_device }}"
  258. data:
  259. message: "clear_notification"
  260. data:
  261. tag: "{{ data.data.tag }}"
  262. else:
  263. - variables:
  264. action: >-
  265. {% set action = wait.trigger.event.data.action %}
  266. {{ action_handlers[action] if action in action_handlers else false }}
  267. - alias: "Action is known or stop executing"
  268. condition: "{{ action is not false }}"
  269. - if: "{{ action is string or 'variables' not in action }}"
  270. then:
  271. service: script.turn_on
  272. target:
  273. entity_id: "{{ script }}"
  274. else:
  275. service: script.turn_on
  276. target:
  277. entity_id: "{{ action.script }}"
  278. data:
  279. variables: "{{ action.variables }}"
  280. - alias: Send message to device
  281. service: "{{ input_notify_device }}"
  282. data: >-
  283. {{ update_data }}
  284. # @ignore: Incorrect type. Expected "object"