123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- blueprint:
- domain: script
- name: Notify user
- description: >-
- A script blueprint that takes context into consideration where you
- dont have to think about the parallelizm.
- However, you should take into account that this script might live
- «forever», if you're not cautious. It will spawn into three services,
- where 2 of them might live until a script reload or a HA restart.
- However 2, this script is perfect if you know what you're doing and dont
- want to deal with the hassle of «contexting». E.g if you want to push the
- same thing to multiple devices, and let them do actions based on the data.
- Here's an example of a «happy birthday» script that will notify every device
- and let each device action on it (send an sms or dismiss it) without affecting
- other users.
- Example usage
- ``` # (create a script with this )
- script:
- notify_device:
- alias: Notify device
- fields:
- [add fields from blueprint here]
- max: 200
- use_blueprint:
- path: notify_user.yaml
- input:
- notify_device: "{{ notify_device }}"
- timeout: "{{ timeout if timeout is defined else 0 }}"
- data: "{{ data }}"
- action_scripts: "{{ action_scripts if action_scripts is defined else {} }}"
- ```
- and call it:
- ```
- service: script.turn_on
- target:
- entity_id: script.notify_device
- data:
- variables:
- notify_device: "{{ notify.mobile_app_device }}"
- data: (data as a normal "notify"-service)
- message: "..."
- data:
- actions:
- - action: DO_IT_NOW_DISMISS_ALL #Ending action with DISMISS_NOW will remove it from other devices.
- title: Do it now
- action_scripts:
- DO_IT_NOW_DISMISS_ALL:
- script: "script.my_script" #script to run if user clicks "Do it now"
- variables:
- var1: "Variables my script need"
- ```
- Feel free to use it within a loop too:
- ```
- repeat:
- for_each: >-
- - device_1
- - device_2
- sequence:
- - service: script.turn_on
- [...]
- data:
- variables:
- notify_device: "{{ repeat.item }}"
- ```
- input:
- notify_device:
- name: "Device to notify"
- description: >-
- The name of the notify service, e.g service.mobile_app_x
- selector:
- object:
- timeout:
- name: "Timeout"
- description: >-
- Timeout before clearing notification and stop waiting. 0 equals to never.
- Respects «Days» too in YAML mode. Timeout set here will override `data.data.timeout`
- and visa-versa.
- selector:
- duration:
- # enable_days: true (HA fire an error on this.)
- data:
- name: "Message data"
- description: "Equal to data field in the notify service"
- selector:
- object:
- # Scripts to run based on `data->actions` set in notify-service. E.g:
- # ```
- # data:
- # actions:
- # - action: ACTION_ANCHOR
- # title: This test
- # ```
- # with matching:
- # ```
- # action_scripts:
- # ACTION_ANCHOR: script...
- # ```
- # The format for `ACTION_ANCHOR` can be just a string, referencing a script,
- # or an object with the following format:
- # ```
- # ACTION_ANCHOR:
- # script: script..
- # variables:
- # var1: ....
- # ```
- action_scripts:
- name: Action scripts
- description: >-
- Read `action_scripts` comments within blueprint.
- selector:
- object:
- default: {}
- tts:
- name: "TTS"
- description: "Not used yet"
- selector:
- boolean:
- default: "{{ false }}"
- mode: parallel
- variables:
- input_notify_device: !input notify_device
- input_timeout: !input timeout
- input_data: !input data
- action_scripts: !input action_scripts
- sequence:
- - variables:
- device_name: "{{ input_notify_device.replace('notify.mobile_app_', '') }}"
- ctx: "{{ context.id ~ '_' ~ device_name }}"
- device_id: >-
- {{ dict.from_keys(device_attr(device_id('device_tracker.' ~ device_name), 'identifiers')).mobile_app }}
- update_timeout: >-
- {% if 'data' in input_data and 'timeout' in input_data.data %}
- {{ input_data.data.timeout|int }}
- {% else %}
- {% set seconds = 0 %}
- {% if input_timeout is iterable %}
- {% if 'seconds' in input_timeout %}
- {% set seconds = seconds + input_timeout.seconds|int %}
- {% endif %}
- {% if 'minutes' in input_timeout %}
- {% set seconds = seconds + (input_timeout.minutes|int * 60 * 60) %}
- {% endif %}
- {% if 'hours' in input_timeout %}
- {% set seconds = seconds + ((input_timeout.hours|int) * 60 * 60) %}
- {% endif %}
- {% if 'days' in input_timeout %}
- {% set seconds = seconds + ((input_timeout.days|int) * 60 * 60 * 24) %}
- {% endif %}
- {% elif input_timeout is not iterable %}
- {% set seconds = input_timeout|int %}
- {% endif %}
- {{ false if seconds <= 0 else (now().timestamp() + seconds) }}
- {% endif %}
- update_data: >-
- {% set data_data = (input_data.data.items() | list) if 'data' in input_data else [] %}
- {% if 'data' in input_data %}
- {% if 'actions' in input_data.data %}
- {% set action = namespace(entities=[]) %}
- {% for a in input_data.data.actions %}
- {% set update = {"action": ctx ~ '_' ~ a.action}.items() | list %}
- {% set current = a.items() | list | rejectattr(
- '0', 'eq', update | map(attribute='0') | list
- ) | list %}
- {% set action.entities = action.entities + [
- dict.from_keys(current + update)
- ] %}
- {% endfor %}
- {% set actions = { "actions": action.entities }.items() | list %}
- {% set data_data = dict.from_keys(data_data | rejectattr(
- '0', 'eq', actions | map(attribute='0') | list
- ) | list + actions).items() | list
- %}
- {% endif %}
- {% if 'timeout' not in input_data and update_timeout is not false %}
- {% set add = { "timeout": update_timeout|int }.items() | list %}
- {% set data_data = dict.from_keys(data_data | rejectattr(
- '0', 'eq', add | map(attribute='0') | list
- ) | list + add).items() | list
- %}
- {% endif %}
- {% if 'group' not in input_data.data %}
- {% set add = { "group": "default-group" }.items() | list %}
- {% set data_data = dict.from_keys(data_data | rejectattr(
- '0', 'eq', add | map(attribute='0') | list
- ) | list + add).items() | list
- %}
- {% endif %}
- {% if (
- (
- 'alert_once' in input_data.data or
- 'actions' in input_data.data or
- 'persistent' in input_data.data
- ) and 'tag' not in input_data.data
- ) or (
- update_timeout is not false and 'tag' not in input_data.data
- )
- %}
- {% set add = { "tag": "tag_" + ctx }.items() | list %}
- {% set data_data = dict.from_keys(data_data | rejectattr(
- '0', 'eq', add | map(attribute='0') | list
- ) | list + add).items() | list
- %}
- {% endif %}
- {% else %}
- {% if update_timeout is not false %}
- {% set add = { "tag": "tag_" + ctx, 'timeout': update_timeout|int }.items() | list %}
- {% set data_data = dict.from_keys(data_data | rejectattr(
- '0', 'eq', add | map(attribute='0') | list
- ) | list + add).items() | list
- %}
- {% endif %}
- {% endif %}
- {% if data_data|length != 0 %}
- {% set data_data = {"data": dict.from_keys(data_data)}.items() | list %}
- {{ dict.from_keys((input_data.items() | list | rejectattr(
- '0', 'eq', data_data | map(attribute='0') | list
- ) | list) + data_data) }}
- {% else %}
- {{ input_data }}
- {% endif %}
- action_handlers: >-
- {% set actions = namespace(handlers=[]) %}
- {% for ask in action_scripts.keys() %}
- {% set askc = ctx ~ '_' ~ ask %}
- {% for action in update_data.data.actions if askc == action.action %}
- {% set actions.handlers = actions.handlers + [(
- askc, action_scripts[ask]
- )] %}
- {% endfor %}
- {% endfor %}
- {{ dict.from_keys(actions.handlers) }}
- - alias: "Parallize event and action listeners"
- parallel:
- - alias: "Listen for event if actions are given"
- if: "{{ action_handlers|length > 0 or update_timeout is not false }}"
- then:
- - alias: "Loop for events until criterias are met"
- repeat:
- while: "{{ (update_timeout - now().timestamp() > 0) or update_timeout == 0 }}"
- sequence:
- - if: "{{ update_timeout == 0 }}"
- then:
- alias: "Wait for app event, without timeout"
- wait_for_trigger:
- - platform: event
- event_type:
- - mobile_app_notification_action
- - mobile_app_notification_cleared
- event_data:
- device_id: "{{ device_id }}"
- - platform: event
- event_type: 'context_notification_clear'
- event_data:
- context: "{{ context.id }}"
- else:
- alias: "Wait for app event, with timeout"
- wait_for_trigger:
- - platform: event
- event_type:
- - mobile_app_notification_action
- - mobile_app_notification_cleared
- event_data:
- device_id: "{{ device_id }}"
- - platform: event
- event_type: 'context_notification_clear'
- event_data:
- context: "{{ context.id }}"
- timeout: >-
- {{ update_timeout - now().timestamp() }}
- - if: "{{ wait.trigger is none }}"
- then:
- alias: "Reached timeout, ending."
- stop: "Reached timeout, ending."
- - alias: "Check if the notification was cleared (completed) by another device."
- if: "{{ wait.trigger.event.event_type == 'context_notification_clear' }}"
- then:
- - alias: "Clear notification on other devices, task was completed"
- service: "{{ input_notify_device }}"
- data:
- message: "clear_notification"
- data:
- tag: "{{ update_data.data.tag }}"
- - alias: "And give a 20 seconds notification on other devices that it was cleared."
- service: "{{ input_notify_device }}"
- data:
- title: "Notification completed"
- message: >-
- {{ wait.trigger.event.data.user.split(' ')[0] }} completed «{{
- (update_data.title if 'title' else update_data.message)[0:20]
- }}...» ✔
- data:
- visibility: public
- importance: low
- color: "#688840"
- timeout: 20
- notification_icon: >-
- {{ update_data.data.notification_icon if 'data' in update_data and 'notification_icon' in update_data.data else 'mdi:checkbox-marked-circle-plus-outline' }}
- - alias: "Notification cleared by another user, ending."
- stop: "Notification cleared by another user, ending."
- - alias: "Check that the notification is within this context"
- condition: >-
- {% set in_ctx = namespace(bool=false) %}
- {% for key in wait.trigger.event.data.keys() if key.startswith('action') and key.endswith('key') %}
- {% if wait.trigger.event.data[key].startswith(ctx) %}
- {% set in_ctx.bool = true %}
- {% endif %}
- {% endfor %}
- {{ in_ctx.bool }}
- - if: "{{ wait.trigger.event.event_type.endswith('_cleared') }}"
- then:
- alias: "User cleared notification, stop listening for events."
- stop: "User cleared notification, stop listening for events."
- - if: "{{ wait.trigger.event.data.action.endswith('_DISMISS_ALL') }}"
- then:
- event: context_notification_clear
- event_data:
- context: "{{ context.id }}"
- user: '{{ states.person|selectattr("attributes.user_id", "==", wait.trigger.event.context.user_id)|map(attribute="attributes.friendly_name")|first }}'
- - variables:
- action: >-
- {{ action_handlers[ wait.trigger.event.data.action ]|default(false) }}
- - alias: "Check if the action is associated with a script"
- if: "{{ not action }}"
- then:
- - stop: "Action is not associated with any scripts, ending."
- - if: "{{ action is string or 'variables' not in action }}"
- then:
- service: script.turn_on
- target:
- entity_id: "{{ script }}"
- else:
- service: script.turn_on
- target:
- entity_id: "{{ action.script }}"
- data:
- variables: "{{ action.variables }}"
- - alias: Send message to device
- service: "{{ input_notify_device }}"
- # @ignore: Incorrect type. Expected "object"
- data: "{{ update_data }}"
|