diff --git a/README.md b/README.md index b231d89..64d5cd4 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,4 @@ Repository for blueprints that is polished enough for public sharing. | Name | Domain | Version | Import Blueprint | Github Link | | --- | --- | --- | --- | --- | -| 🔔 Notifications | Script | 1.6.1 | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fgithub.com%2Fsamuelthng%2Ft-house-blueprints%2Fblob%2Fmain%2Fnotifications.yaml) | [🔗](https://github.com/samuelthng/t-house-blueprints/blob/main/notifications.yaml) | +| 🔔 Notifications | Script | 2.0 | [![Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.](https://my.home-assistant.io/badges/blueprint_import.svg)](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https%3A%2F%2Fgithub.com%2Fsamuelthng%2Ft-house-blueprints%2Fblob%2Fmain%2Fnotifications.yaml) | [🔗](https://github.com/samuelthng/t-house-blueprints/blob/main/notifications.yaml) | diff --git a/notifications.yaml b/notifications.yaml index 8be729f..ea32ddc 100644 --- a/notifications.yaml +++ b/notifications.yaml @@ -1,14 +1,15 @@ +mode: restart blueprint: - name: 🔔 Notifications - domain: script + name: 🔔 Notifications (Version 2) source_url: https://github.com/samuelthng/t-house-blueprints/blob/main/notifications.yaml + homeassistant: + min_version: 2023.11.0 + domain: script + author: samuelthng/t-house-blueprints description: >-
-

🔔 Notifications

-

 Version 1.6.1🔗 Github Link

- - Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled. - +

🔔 Notifications

+ Version 2 | 🔗 Github Link | 💬 Community Post

@@ -28,6 +29,8 @@ blueprint: > 🔗 Custom Notification Link + > 📜 Flexible [script fields](https://www.home-assistant.io/integrations/script/#fields) + > 🍎 Supports iOS > 🤖 Supports Android @@ -39,13 +42,29 @@ blueprint:
Expand/collapse changelog - ### Version 1.6.1 - *26 Oct 2023* - - Fixed: Camera Snapshots not showing - - ### Version 1.6 - *26 Oct 2023* - - Added: Show/Hide options toggle - - Added: URI action options and iOS specific action options - - Added: Third option + ### Version 2 - *12 Dec 2023* + - Fixed: iOS showing `Failed to load attachment` + - Refactor: Build payload in templates + - Deprecated: `📲 Notification Strategy` due to refactor. + - Deprecated: `📲 Service to notify` due to refactor. + - Added: Support for [fields](https://www.home-assistant.io/integrations/script/#fields) + - Added: `🏷️ Title` field + - Added: `💬 Message` field + - Added: `🏷️ Subtitle` field + - Added: `📸 Attachment: Camera Entity: Camera Entity` field + - Added: Options show/hide fields + - Added: `⌛️ Enable Timeout` and `⌛️ Enable Timeout Action(s)` fields. + - Added: [`🚘 Show on Android Auto`](https://companion.home-assistant.io/docs/notifications/notifications-basic?_highlight=car_ui#android-auto-visibility) option. + - Added: `⌛️ Enable Timeout Action(s)` option and field. + + > Notes: + > - When enabling boolean fields with value of false, be sure to toggle them to true once, and then to false. + > There seems to be a bug where the boolean value is not passed if it's enabled and value is false without the toggle. + > - If you rely on `📲 Service to notify`, do not upgrade, `Version 1.6.1` will be the last version supporting that feature. + + ### Version 1.6.1 - *26 Oct 2023* [🔗](https://community.home-assistant.io/t/notifications-actionable-mobile-notifications-script-with-optional-timeout-feature-and-camera-snapshots-works-with-ios-android/551552/66) + + ### Version 1.6 - *26 Oct 2023* [🔗](https://community.home-assistant.io/t/notifications-actionable-mobile-notifications-script-with-optional-timeout-feature-and-camera-snapshots-works-with-ios-android/551552/62) ### Version 1.5 - *11 Oct 2023* [🔗](https://community.home-assistant.io/t/notifications-actionable-mobile-notifications-script-with-optional-timeout-feature-and-camera-snapshots-works-with-ios-android/551552#version-14-7-jul-2023-8) @@ -108,21 +127,6 @@ blueprint: ############################## # Input: Notification Strategy ############################## - notify_type: - name: "📲 Notification Strategy" - description: >- - Pick a notification strategy.

- 👍🏻 `Device` is the preferred and default option.
- ⚠️ Ensure that you have filled the required corresponding fields - default: "device" - selector: - select: - mode: dropdown - options: - - label: Device (Preferred) - value: device - - label: Notify Service - value: notify_service notify_device: name: "📲 Device to notify" description: >- @@ -134,16 +138,6 @@ blueprint: device: filter: - integration: mobile_app - notify_service_name: - name: "📲 Service to notify" - description: >- - Service must be exposed by the Home Assistant [mobile app integration](https://companion.home-assistant.io/docs/notifications/notifications-basic).

- ❔ Only used with `📲 Notification Strategy: Notify Service`
- ⚠️ Use this option only if `Device` option does not show your devices. - default: "" - selector: - text: - prefix: "notify.mobile_app_" ############################## # Input: Notification Content @@ -199,7 +193,7 @@ blueprint: confirm_text: name: "1️⃣ Option 1 - Title" description: "Title to show on the first option." - default: "Confirm" + default: "Option 1" selector: text: @@ -241,7 +235,7 @@ blueprint: confirm_action: name: "1️⃣ Option 1 - Action(s)" - description: "Action(s) to run when this option is selected.\n\n❔ Used with `Mode: Action(s)` option\n❔ Script stops after actions are run\n❔ Leave empty to do nothing\n`Optional`" + description: "Action(s) to run when this option is selected.\n\n❔ Used with `Mode: Action(s)` option\n❔ Script stops after actions are run\n❔ Leave empty to do nothing\n\n`Optional`" default: [] selector: action: @@ -339,7 +333,7 @@ blueprint: dismiss_text: name: "2️⃣ Option 2 - Title" description: "Title to show on the first option." - default: "Confirm" + default: "Option 2" selector: text: @@ -381,7 +375,7 @@ blueprint: dismiss_action: name: "2️⃣ Option 2 - Action(s)" - description: "Action(s) to run when this option is selected.\n\n❔ Used with `Mode: Action(s)` option\n❔ Script stops after actions are run\n❔ Leave empty to do nothing\n`Optional`" + description: "Action(s) to run when this option is selected.\n\n❔ Used with `Mode: Action(s)` option\n❔ Script stops after actions are run\n❔ Leave empty to do nothing\n\n`Optional`" default: [] selector: action: @@ -521,7 +515,7 @@ blueprint: option_three_action: name: "3️⃣ Option 3 - Action(s)" - description: "Action(s) to run when this option is selected.\n\n❔ Used with `Mode: Action(s)` option\n❔ Script stops after actions are run\n❔ Leave empty to do nothing\n`Optional`" + description: "Action(s) to run when this option is selected.\n\n❔ Used with `Mode: Action(s)` option\n❔ Script stops after actions are run\n❔ Leave empty to do nothing\n\n`Optional`" default: [] selector: action: @@ -627,6 +621,12 @@ blueprint: selector: duration: enable_day: false + run_timeout_actions: + name: "⌛️ Enable Timeout Action(s)" + description: "Run timeout actions upon a timeout event.

👍🏻 Recommended to enable.
⚠️ If disabled, script will timeout without executing any timeout actions." + default: true # Default `true` for backward compatibility. + selector: + boolean: timeout_action: name: "⌛️ Timeout Action(s)" description: "Action to run when notification response is timed out. \n\n❔ Leave empty if no actions should be run \n\n⚠️ Android Users: Note, URI options do not stop the timeout countdown. This action could be run after pressing the URI option.\n❔ Android Users: Workaround for the above, you may disable `⌛️ Run timeout action(s) when notification cleared` and manually clear the notification after selecting your URI option. \n\n`Optional`" @@ -699,9 +699,15 @@ blueprint: ############################## # Input: Misc Inputs ############################## + car_ui: + name: "🚘 Show on Android Auto" + description: "Display notification on Android Auto interface. \n\n`🤖 Android Only`" + default: false + selector: + boolean: tag: name: "🔖 Tag" - description: "Used to uniquely identify the notification. \n\nUse tag if you are using this script with other notification services. \nLeave empty otherwise. \n\nExample: `my_awesome_notification_for_device_X` \n\n👍🏻 Recommended to leave empty\n`Optional`" + description: "Used to uniquely identify the notification. \n\nUse tag if you are using this script with other notification services. \nLeave empty otherwise. \n\nExample: `my_awesome_notification_for_device_X` \n\n👍🏻 Recommended to leave empty\n\n`Optional`" default: "" selector: text: @@ -761,7 +767,141 @@ blueprint: - label: "Secret: Always hide content on the lockscreen." value: secret -mode: restart +############################## +# Fields +############################## +fields: + field_notify_device: + name: "📲 Device to notify" + description: Override device to be notified. + default: !input notify_device + selector: + device: + filter: + - integration: mobile_app + field_title: + name: "🏷️ Title" + description: "Override title of the notification." + example: !input title + default: !input title + selector: + text: + field_message: + name: "💬 Message" + description: "Override message body to display on the notification." + example: !input message + default: !input message + selector: + text: + field_subtitle: + name: "🏷️ Subtitle" + description: "Override subtitle of the notification." + example: !input subtitle + default: !input subtitle + selector: + text: + field_attachment_type: + name: "📸 Attachment Type" + description: "Override the Attachment Type of the notification." + example: !input attachment_type + default: !input attachment_type + selector: + select: + options: + - label: "None" + value: "none" + - label: "Camera" + value: "camera_entity" + field_attachment_camera_entity: + name: "📸 Attachment: Camera Entity" + description: "Override camera entity if your script has attachment type set to Camera." + example: !input attachment_camera_entity + default: !input attachment_camera_entity + selector: + entity: + filter: + - domain: "camera" + + field_option_one_enabled: + name: "1️⃣ Option 1" + description: "Override showing or hiding this option." + example: !input confirm_enabled + selector: + boolean: + # [Unsupported due to `choose.sequence`] - Can't pass variables to choose.sequence. + # field_option_one_action: + # name: "1️⃣ Option 1 - Action(s)" + # description: "Override actions for this option." + # example: >- + # - alias: This is an example action + # service: homeassistant.check_config + # default: !input confirm_action + # selector: + # action: + field_option_two_enabled: + name: "2️⃣ Option 2" + description: "Override showing or hiding this option." + example: !input dismiss_enabled + selector: + boolean: + # [Unsupported due to `choose.sequence`] - Can't pass variables to choose.sequence. + # field_option_two_action: + # name: "1️⃣ Option 2 - Action(s)" + # description: "Override actions for this option." + # example: >- + # - alias: This is an example action + # service: homeassistant.check_config + # default: !input dismiss_action + # selector: + # action: + field_option_three_enabled: + name: "3️⃣ Option 3" + description: "Override showing or hiding this option." + example: !input dismiss_enabled + selector: + boolean: + # [Unsupported due to `choose.sequence`] - Can't pass variables to choose.sequence. + # field_option_three_action: + # name: "1️⃣ Option 3 - Action(s)" + # description: "Override actions for this option." + # example: >- + # - alias: This is an example action + # service: homeassistant.check_config + # default: !input option_three_action + # selector: + # action: + field_enable_timeout: + name: "⌛️ Enable Timeout" + description: "Override timeout feature." + example: !input enable_timeout + selector: + boolean: + + field_timeout: + name: "⌛️ Timeout Duration" + description: "Override timeout duration" + default: !input timeout + selector: + duration: + enable_day: false + + field_run_timeout_actions: + name: "⌛️ Enable Timeout Action(s)" + description: "Override running timeout actions" + example: !input run_timeout_actions + selector: + boolean: + + # [Unsupported due to `choose.sequence`] - Can't pass variables to choose.sequence. + # field_timeout_action: + # name: "⌛️ Timeout Action(s)" + # description: "Override timeout action." + # example: >- + # - alias: This is an example action + # service: homeassistant.check_config + # default: !input timeout_action + # selector: + # action: sequence: ############################## @@ -769,6 +909,16 @@ sequence: ############################## - alias: "Setup variables" variables: + script_title: !input title + title: "{{ iif(field_title is defined, field_title, script_title) }}" + script_message: !input message + message: "{{ iif(field_message is defined and (field_message|length), field_message, script_message) }}" + script_subtitle: !input subtitle + subtitle: "{{ iif(field_subtitle is defined, field_subtitle, script_subtitle) }}" + interruption_level: !input interruption_level + notification_link: !input notification_link + visibility: !input visibility + importance: !input importance custom_tag: !input tag # No need for manual definition anymore, treat existing definitions as custom tags. first_option: "{{ 'FIRST_' ~ context.id }}" second_option: "{{ 'SECOND_' ~ context.id }}" @@ -776,14 +926,21 @@ sequence: enable_timeout: !input enable_timeout clear_on_timeout: !input clear_on_timeout persist: !input persist - timeout: !input timeout # Load timeout into variable for use with templates. + script_timeout: !input timeout # Load timeout into variable for use with templates. + timeout: "{{ iif(field_timeout is defined, field_timeout, script_timeout) }}" timeout_seconds: "{{ (timeout.hours * 60 + timeout.minutes) * 60 + timeout.seconds }}" + script_run_timeout_actions: !input run_timeout_actions + run_timeout_actions: "{{ iif(field_run_timeout_actions is defined, field_run_timeout_actions, script_run_timeout_actions) }}" + car_ui: !input car_ui + icon: !input icon enable_icon_color: !input enable_icon_color icon_color_selector: !input icon_color icon_color_hex: '{{ "#{:02x}{:02x}{:02x}".format(icon_color_selector[0], icon_color_selector[1], icon_color_selector[2]) }}' swipe_away_as_timeout: !input swipe_away_as_timeout - attachment_type: !input attachment_type - attachment_camera_entity: !input attachment_camera_entity + script_attachment_type: !input attachment_type + attachment_type: "{{ iif(field_attachment_type is defined, field_attachment_type, script_attachment_type) }}" + script_attachment_camera_entity: !input attachment_camera_entity + attachment_camera_entity: "{{ iif(field_attachment_camera_entity is defined, field_attachment_camera_entity, script_attachment_camera_entity) }}" camera_image_url: >- {% if (attachment_camera_entity|length) %} {% if (state_attr(attachment_camera_entity,'entity_picture')|length) %} @@ -799,19 +956,17 @@ sequence: 'camera_entity': iif(camera_image_url | length, camera_image_url) }.get(attachment_type) }} - notify_type: !input notify_type - notify_device: !input notify_device - notify_service_name: !input notify_service_name + script_notify_device: !input notify_device + notify_device: "{{ iif(field_notify_device is defined, field_notify_device, script_notify_device) }}" notify_service: >- - {% if notify_type == 'device' %} {{ "notify.mobile_app_{id}".format(id=device_attr(notify_device,'name')|slugify) }} - {% elif notify_type == 'notify_service' %} - {{ "notify.mobile_app_{id}".format(id=notify_service_name|slugify) }} - {%- endif %} - option_one_enabled: !input confirm_enabled - option_one_title: !input confirm_text + script_option_one_enabled: !input confirm_enabled + option_one_enabled: "{{ iif(field_option_one_enabled is defined, field_option_one_enabled, script_option_one_enabled) }}" + script_option_one_title: !input confirm_text + option_one_title: "{{ iif(field_option_one_text is defined, field_option_one_text, script_option_one_title) }}" option_one_mode: !input confirm_option_mode - option_one_actions: !input confirm_action + script_option_one_actions: !input confirm_action + option_one_actions: "{{ iif(field_option_one_action is defined, field_option_one_action, script_option_one_actions) }}" option_one_uri: !input confirm_uri option_one_icon_value: !input confirm_icon option_one_icon: >- @@ -839,10 +994,12 @@ sequence: 'authenticationRequired': option_one_auth_required, "{}".format('uri' if option_one_uri|length else ''): option_one_uri, } }} - option_two_enabled: !input dismiss_enabled + script_option_two_enabled: !input dismiss_enabled + option_two_enabled: "{{ iif(field_option_two_enabled is defined, field_option_two_enabled, script_option_two_enabled) }}" option_two_title: !input dismiss_text option_two_mode: !input dismiss_option_mode - option_two_actions: !input dismiss_action + script_option_two_actions: !input dismiss_action + option_two_actions: "{{ iif(field_option_two_action is defined, field_option_two_action, script_option_two_actions) }}" option_two_uri: !input dismiss_uri option_two_icon_value: !input dismiss_icon option_two_icon: >- @@ -870,10 +1027,12 @@ sequence: 'authenticationRequired': option_two_auth_required, "{}".format('uri' if option_two_uri|length else ''): option_two_uri, }}} - option_three_enabled: !input option_three_enabled + script_option_three_enabled: !input option_three_enabled + option_three_enabled: "{{ iif(field_option_three_enabled is defined, field_option_three_enabled, script_option_three_enabled) }}" option_three_title: !input option_three_text option_three_mode: !input option_three_option_mode - option_three_actions: !input option_three_action + script_option_three_actions: !input option_three_action + option_three_actions: "{{ iif(field_option_three_action is defined, field_option_three_action, script_option_three_actions) }}" option_three_uri: !input option_three_uri option_three_icon_value: !input option_three_icon option_three_icon: >- @@ -904,6 +1063,37 @@ sequence: options: >- {{ [option_one, option_two, option_three] | selectattr('enabled') | list }} tag: "{{ iif(custom_tag|length, tag, this.entity_id ~ '-' ~ context.id) }}" + # Build OS specific notification payloads. + notification_data: >- + {% set apple_device = 'APPLE' in (device_attr(notify_device, "manufacturer")|upper) %} + {% set payload = namespace(data={ 'apple_device': apple_device }) %} + {% if payload.data.apple_device %} + {# iOS #} + {% set push = namespace(d={}) %} + {% set payload.data = dict(payload.data, **{ 'subtitle': subtitle }) %} + {% set push.d = dict(push.d, **{ 'interruption-level': interruption_level }) %} + {% if notification_link|length %}{% set payload.data = dict(payload.data, **{ 'url': notification_link }) %}{% endif %} + {% if attachment_type == 'camera_entity' %}{% set payload.data = dict(payload.data, **{ 'entity_id': attachment_camera_entity }) %}{% endif %} + {% set payload.data = dict(payload.data, **{ 'push': push.d }) %} + {% else %} + {# Android #} + {% set payload.data = dict(payload.data, **{ 'subject': subtitle }) %} + {% set payload.data = dict(payload.data, **{ 'visibility': visibility }) %} + {% set payload.data = dict(payload.data, **{ 'importance': importance }) %} + {% if notification_link|length %}{% set payload.data = dict(payload.data, **{ 'clickAction': notification_link }) %}{% endif %} + {% if attachment_type == 'camera_entity' and (media_url|length) %}{% set payload.data = dict(payload.data, **{ 'image': media_url }) %}{% endif %} + {% if icon|length %}{% set payload.data = dict(payload.data, **{ 'notification_icon': icon }) %}{% endif %} + {% if enable_icon_color %}{% set payload.data = dict(payload.data, **{ 'color': icon_color_hex }) %}{% endif %} + {% if enable_timeout and clear_on_timeout %}{% set payload.data = dict(payload.data, **{ 'timeout': timeout_seconds }) %}{% endif %} + {% if channel|length %}{% set payload.data = dict(payload.data, **{ 'channel': channel }) %}{% endif %} + {% if persist %}{% set payload.data = dict(payload.data, **{ 'persistent': persist }) %}{% endif %} + {% if car_ui %}{% set payload.data = dict(payload.data, **{ 'car_ui': car_ui }) %}{% endif %} + {% endif %} + {# Common #} + {% set payload.data = dict(payload.data, **{ 'actions': options }) %} + {% set payload.data = dict(payload.data, **{ 'tag': tag }) %} + {% if group|length %}{% set payload.data = dict(payload.data, **{ 'group': group }) %}{% endif %} + {{ payload.data }} ############################## # Send Notification @@ -911,30 +1101,9 @@ sequence: - alias: "Send notification" service: "{{ notify_service }}" data: - title: !input title - message: !input message - data: - subtitle: !input subtitle # iOS/macOS - subject: !input subtitle # Android - actions: "{{ options }}" - visbility: !input visibility - tag: "{{ tag }}" - group: "{{ iif(group|length, group, ) }}" - persistent: !input persist - channel: !input channel - importance: !input importance - push: - interruption-level: !input interruption_level - notification_icon: !input icon - color: "{{ iif(enable_icon_color, icon_color_hex, )}}" - clickAction: !input notification_link - url: !input notification_link - timeout: "{{ iif(enable_timeout and clear_on_timeout, timeout_seconds, )}}" # Set notification timeout if timeout feature is enabled and notification should be cleared. - # [Attachments] - entity_id: "{{ iif(attachment_type == 'camera_entity' and 'camera.' in attachment_camera_entity, attachment_camera_entity,) }}" # - Camera Stream (iOS) - image: "{{ media_url }}" - video: "{{ media_url }}" - audio: "{{ media_url }}" + title: "{{ title }}" + message: "{{ message }}" + data: "{{ notification_data }}" ############################## # Evaluate Response @@ -962,7 +1131,7 @@ sequence: event_type: mobile_app_notification_cleared event_data: tag: "{{ tag }}" - timeout: !input timeout + timeout: "{{ timeout }}" continue_on_timeout: true else: - alias: "Awaiting response indefinitely…" @@ -989,33 +1158,33 @@ sequence: ############################## - choose: - alias: "Trigger: Timeout" - conditions: "{{ wait.trigger == none }}" - sequence: !input timeout_action + conditions: "{{ run_timeout_actions and wait.trigger == none }}" + sequence: !input timeout_action # "{{ timeout_actions }}" - alias: "Trigger: Notification Cleared" - conditions: "{{ swipe_away_as_timeout and wait.trigger.event.event_type == 'mobile_app_notification_cleared' }}" - sequence: !input timeout_action + conditions: "{{ run_timeout_actions and swipe_away_as_timeout and wait.trigger.event.event_type == 'mobile_app_notification_cleared' }}" + sequence: !input timeout_action # "{{ timeout_actions }}" - alias: "Trigger: first_option" conditions: "{{ wait.trigger.event.data.action == option_one['action'] }}" - sequence: !input confirm_action + sequence: !input confirm_action # "{{ option_one_actions }}" - alias: "Trigger: second_option" conditions: "{{ wait.trigger.event.data.action == option_two['action'] }}" - sequence: !input dismiss_action - - alias: "Trigger: second_option" + sequence: !input dismiss_action # "{{ option_two_actions }}" + - alias: "Trigger: third_option" conditions: "{{ wait.trigger.event.data.action == option_three['action'] }}" - sequence: !input option_three_action + sequence: !input option_three_action # "{{ option_three_actions }}" ############################## # Compatibility: iOS Clear Notification ############################## - - if: - - alias: "Should send clear notification command? (for iOS/macOS)" + - alias: "Send clear notification commands" + if: + - alias: "Should send clear notification command? (for iOS/macOS)?" condition: template - value_template: "{{ enable_timeout and clear_on_timeout }}" + value_template: "{{ enable_timeout and clear_on_timeout and notification_data.apple_device }}" then: - - alias: "Send clear notification command" - domain: mobile_app - type: notify - device_id: !input notify_device - message: "clear_notification" + - alias: "Send clear notification command to iOS device" + service: "{{ notify_service }}" data: - tag: "{{ tag }}" + message: "clear_notification" + data: + tag: "{{ tag }}"