瀏覽代碼

Re-wrote script, to listen on fewer events and therefore made script simpler - reducing parallel sequences.

Joachim M. Giæver 1 年之前
父節點
當前提交
6049169bf7
共有 1 個文件被更改,包括 303 次插入296 次删除
  1. 303 296
      script/notify_user.yaml

+ 303 - 296
script/notify_user.yaml

@@ -15,107 +15,101 @@ blueprint:
 
 
     Here's an example of a «happy birthday» script that will notify every device
     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
     and let each device action on it (send an sms or dismiss it) without affecting
-    other users:
+    other users. 
 
 
+    Example usage:
     ```
     ```
     script:
     script:
-      birthday_wishes:
-        mode: parallel
-        sequence:
-          - variables:
-              blang: "{{ lang if lang is defined else none }}"
-              bname: "{{ name|lower|title }}"
-              bage: "{{ age|string + {1: 'st', 2: 'nd', 3: 'rd'}.get(4 if 10 <= age|int % 100 < 20 else age|int % 10, 'th') }}"
-          - service: "{{ notify_device }}"
-            data:
-              message: command_activity
-              data:
-                intent_action: "android.intent.action.SENDTO"
-                intent_uri: >-
-                  sms:{{ number }}?body={{-
-                    "🎼 «Hurra for deg som fyller ditt år, ja - deg vil jeg gratulere» 🎶 🎉 \n\n"
-                  }}{%- if blang == 'no_nb' -%}
-                    Gratulere med {{ age|default('') }}årsdagen, {{ bname }}! 🎂🎁 Håpe du får en fin dag 🥳
-                    {%- else -%}
-                    Happy {{ bage ~ ' ' if age else '' }}birthday, {{ bname }}! 🎂🎁 Hope'll get a beautiful day 🥳
-                  {%- endif -%}
+      notify_device:
+        alias: Notify device
+        fields:
+          notify_device:
+            name: "Device notify service"
+            description: "Device service, e.g notify.mobile_app_x"
+            selector:
+              object:
+          data:
+            name: Notification data
+            description: "Notification data, same as for notify-services"
+            selector:
+              object:
+          action_scripts:
+            name: Action scripts
+            description: >-
+              See blueprint
+            selector:
+              object:
+            default: {}
+          timeout:
+            name: "Timeout"
+            description: >-
+              Timeout before clearing event and stop waiting. 0 equals to never. 
+              Respects «Days» too in YAML mode.
+            selector:
+              duration:
+        max: 200
+        use_blueprint:
+          path: notify_user.yaml
+          input:
+            notify_device: "{{ notify_device }}"
+            index: "{{ index if index is defined else 0 }}"
+            timeout: "{{ timeout if timeout is defined else 0 }}"
+            data: "{{ data }}"
+            action_scripts: "{{ action_scripts if action_scripts is defined else {} }}"
+    ```
 
 
-    automation:
-      - alias: "Birthday: Notify on birthday!"
-        trigger: 
-          - platform: calendar
-            event: start
-            entity_id: calendar.ical_birthdays
-            offset:
-              hours: 10
-        action: 
-          - repeat:
-              for_each: "{{ birthdays }}"
-              sequence:
-                # Set variables here
-                - repeat:
-                    for_each: "{{ notify_devices }}"
-                    sequence:
-                      service: script.turn_on
-                      target:
-                        entity_id: script.notify_device
-                      data:
-                        variables:
-                          notify_device: "{{ repeat.item }}"
-                          index: "{{ index }}-{{ repeat.index }}"
-                          timeout:
-                            hours: 14
-                          data:
-                              title: "It is {{ name }}'s birthday. 🎁"
-                              message: >-
-                                {% if age is not false %}
-                                  {{ name }} is turning {{ age }} today.
-                                {% else %}
-                                  {{ name }} is getting one year older today.
-                                {% endif %}
-                                Send your congratulations 🎉 to let him/her
-                                know that you appreciate them. 🥳
-                              data:
-                                actions:
-                                  - action: SEND_SMS
-                                    title: Send SMS
-                                  - action: DISMISS
-                                    title: Dismiss
-                          action_scripts:
-                            SEND_SMS:
-                              script: script.birthday_wishes
-                              variables:
-                                lang: "{{ lang }}"
-                                name: "{{ name }}"
-                                age: "{{ age }}"
-                                notify_device: "{{ repeat.item }}"
-                                number: "{{ phones|join(',') }}"
+    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:
   input:
     notify_device:
     notify_device:
-      name: "Devices to notify"
+      name: "Device to notify"
       description: >-
       description: >-
         The name of the notify service, e.g service.mobile_app_x
         The name of the notify service, e.g service.mobile_app_x
       selector:
       selector:
-        object:        
-    index:
-      name: Device index
-      description: >-
-        If called several times within same context, increase this for each.
-      default:
-        seconds: 0
-      selector:
-        number:
-          min: 1
-          max: 1000
-          step: 1
+        object:
     timeout:
     timeout:
       name: "Timeout"
       name: "Timeout"
       description: >-
       description: >-
-        Timeout before notifications dissapear. 
+        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:
       selector:
         duration:
         duration:
-          # enable_days: true
+          # enable_days: true (HA fire an error on this.)
     data:
     data:
       name: "Message data"
       name: "Message data"
       description: "Equal to data field in the notify service"
       description: "Equal to data field in the notify service"
@@ -148,7 +142,7 @@ blueprint:
       selector:
       selector:
         object:
         object:
       default: {}
       default: {}
-    
+
     tts:
     tts:
       name: "TTS"
       name: "TTS"
       description: "Not used yet"
       description: "Not used yet"
@@ -160,149 +154,190 @@ mode: parallel
 
 
 variables:
 variables:
   input_notify_device: !input notify_device
   input_notify_device: !input notify_device
-  index: !input index
-  ctx: "{{ this.context.id + '_' + input_notify_device ~ '_' ~ index }}" # 
-  
   input_timeout: !input timeout
   input_timeout: !input timeout
-  update_timeout: >-
-    {% 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 %}
-    {{ 0 if seconds == 0 else (now().timestamp() + seconds) }}
-
   input_data: !input data
   input_data: !input data
-  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 '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 (
-          timeout != 0 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 timeout != 0 %}
-        {% 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 %}
-    {% 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_scripts: !input action_scripts
   action_scripts: !input action_scripts
-  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) }}
 
 
-sequence: 
-  # - service: system_log.write
-  #   data:
-  #     level: warning
-  #     message: >-
-  #       Action handlers: {{ action_handlers }}
-  #       Timeout: {{ update_timeout }} seconds
-  #       Data: {{ update_data }}
-  
-  - alias: "Parallize event listeners and notification"
+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:
     parallel:
-    - alias: "Listen for event if actions are given"
-      if: "{{ action_handlers|length > 0 }}"
-      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
-                else:
-                  alias: "Wait for app event, with timeout"
-                  wait_for_trigger:
-                    - platform: event
-                      event_type: 
-                        - mobile_app_notification_action
-                        - mobile_app_notification_cleared
-                  timeout: >-
-                    {{ update_timeout - now().timestamp() }}
+      - sequence: []
+
+      - 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 }}"
+                  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."
 
 
-              - if: "{{ wait.trigger is none }}"
-                then:
-                  alias: "Reached timeout, stopping."
-                  stop: "Reached timeout, stopping."
-              
-              - variables: 
-                  in_ctx: >-
+                - 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 the notification «{{
+                            (update_data.title if 'title' else update_data.message)[0:20]
+                          }}...»
+                        data:
+                          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) %}
                     {% set in_ctx = namespace(bool=false) %}
                     {% for key in wait.trigger.event.data.keys() if key.startswith('action') and key.endswith('key') %}
                     {% for key in wait.trigger.event.data.keys() if key.startswith('action') and key.endswith('key') %}
                       {% if wait.trigger.event.data[key].startswith(ctx) %}
                       {% if wait.trigger.event.data[key].startswith(ctx) %}
@@ -311,83 +346,55 @@ sequence:
                     {% endfor %}
                     {% endfor %}
                     {{ in_ctx.bool }}
                     {{ in_ctx.bool }}
 
 
-              - alias: "Check if context matches or restart from top"
-                condition: "{{ in_ctx }}"
+                - 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.event_type.endswith('_cleared') or
-                    wait.trigger.event.data.action not in action_handlers.keys()
-                  }}
-                then:
-                  - alias: "Cleared event within user context"
-                    event: custom_mobile_app_notification_action
-                    event_data:
-                      context: "{{ ctx }}"
-                      action: "cleared"
-                  - stop: "User cleared notification or choose an invalid option, stop listening for events."
+                - service: system_log.write
+                  data:
+                    level: warning
+                    message: >-
+                      Context: {{ ctx }}\n
+                      Event-type: {{ wait.trigger.event.event_type }}\n
+                      Event-action: {{ wait.trigger.event.data.action }}
 
 
-              - event: custom_mobile_app_notification_action
-                event_data:
-                  context: "{{ ctx }}"
-                  action: "{{ wait.trigger.event.data.action }}"
-              - stop: "User acted within context, stop listening for further 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 }}'
 
 
-    - alias: "Listen for forwarded events"
-      if: "{{ action_handlers|length > 0 or update_timeout != 0 }}"
-      then:
-        - if: "{{ update_timeout == 0 }}"
-          then:
-            alias: "Wait for context event, without timeout"
-            wait_for_trigger:
-              - platform: event
-                event_type: custom_mobile_app_notification_action
-                event_data: 
-                  context: "{{ ctx }}"
-          else:
-            alias: "Wait for context event, with timeout"
-            wait_for_trigger:
-              - platform: event
-                event_type: custom_mobile_app_notification_action
-                event_data: 
-                  context: "{{ ctx }}"
-            timeout: >-
-              {{ update_timeout - now().timestamp() }}
+                - variables:
+                    action: >-
+                      {{ action_handlers[ wait.trigger.event.data.action ]|default(false) }}
 
 
-        - alias: "Check if timed out and remove notification"
-          if: "{{ wait.trigger == none }}"
-          then:
-            alias: "Clear notification, timeout reached."
-            service: "{{ input_notify_device }}"
-            data:
-              message: "clear_notification"
-              data:
-                tag: "{{ data.data.tag }}"
-          else:
-            - variables:
-                action: >-
-                  {% set action = wait.trigger.event.data.action %}
-                  {{ action_handlers[action] if action in action_handlers else false }}
+                - service: system_log.write
+                  data:
+                    level: warning
+                    message: "Action: {{ action }}"
 
 
-            - alias: "Action is known or stop executing"
-              condition: "{{ action is not 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 }}"
+                - 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 }}"
 
 
-    - alias: Send message to device
-      service: "{{ input_notify_device }}"
-      
-      data: >- 
-        {{ update_data }}
-      # @ignore: Incorrect type. Expected "object"
+        data: >-
+          {{ update_data }}
+        # @ignore: Incorrect type. Expected "object"