Script to resume radio (TuneIn) and Spotify after TTS on Google Home speakers

I’ve been posting several versions of a script to resume audio on Google Home after a TTS has been sent in this topic: TTS to media_player and resume stream
Unfortunately Google Home speakers don’t support the snapshots like Sonos speakers do.

However, I have done some updates since then, and now I think it finally deserves to be shown of in this Share your Projects section.

Recent changes
22 November

  • BREAKING: removed the need for HA group entities for the speaker groups, the entites have to be defined in the script variables now.
  • Added a variable delay_volume_restore to set the delay after the silent mp3. In some cases the delay could be not enough, and the volume can not be retreived
  • Added a variable default_volume_level for a default volume, in case the retreived volume is not a valid value (e.g. if the silent mp3 was not playing yet)
  • Added a variable debug which will write the information in the other viariables to the HA log so it can be used to debug the script (you don’t need to change your log level for this, the information can also be retreived from the trace)
  • In case a single entity was playing, and this entity was belonging to a group, it would not resume if the TTS was sent to the group, and speaker_group_split was set to false. This is fixed now.
  • Some additional comments are added to the variables
  • For previous examples I translated the names to English, I stopped doing that, they are now in my native language (Dutch)
  • removed old changelog because of character limit
  • added variable to provide tts_service

This script supports:

  • Resuming of TuneIn, Spotify and generic streams after TTS message to a Google cast device
  • Resuming an entire speaker group after a TTS has been sent to a single group member
  • Resuming of individual group members after a TTS has been sent to a speaker group (TTS will be sent to individual group members because sending to a Google Home speaker group does not work properly)

Prerequisites:

  1. For Spotify you need to have the Spotify integration installed, and Spotcast (available on HACS)
  2. Google Nest Hub speakers can be entered under the variable players_screen. This will make sure the photo display is turned on again after the TTS in case nothing was already playing.
  3. Frequently used audio streams (e.g. TuneIn streams) can be specified under frequent_radio. Enter the part after http(s):// and before the next /. This is because the app name TuneIn Free is no longer shown after audio has been resumed, and ensures resuming audio if the script is used a second time.
  4. The media_player entities from the Spotify integration need to be specified under the variable spotify_media_players. The entity names should be formatted like media_player.spotify_{{ spotcast user }}. For the primary Spotcast user you can use whatever you want as spotcast account.
  5. If you use speaker groups in the Google Home app, you can enter them under the variable speaker_groups. If you use them, you’ll need to complete this variable, and add the group members in there as well (see the script below for an example)
  6. The primary Spotcast user needs to be specified under primary_spotcast (see above comment).
  7. To determine the Spotify account, the source in the Spotify media_players is used. This is compared to the friendly name of the Goolge Home media_player. Therefor the Google Home media players in HA need to have the exact same name as they have in the Google Home app (this is also already a requirement for Spotcast to work with entity_id’s)
  8. I’m using Google Cloud TTS, check the TTS variable to make sure this matches your setup. It could be that your TTS service also requires service data like language, in such cases you need to add them in the service call (lines 265 - 269 in the script).
  9. For the restore_volume_all variable to work, you need to provide the location of a silent MP3 (of at least 2 seconds)

Known limitations:

  • It is possible to create speaker groups on the fly from the Google Home app, e.g. if you are playing something from Spotify on your Kitchen speaker, you can add your Living Room speaker in the Google Home app, without them belonging to a speaker group. The script won’t recognize these groups created on the fly. The cast integration won’t recognize these devices as playing anymore, so they won’t be resumed.
  • When Spotify switches to a new song, the Spotify Media Player will shortly not show as playing. When at that moment the script is started, the stream will not be resumed afterwards. Maybe an addional binary_sensor with a short off_delay could help here (but that would require additional configuration to let the script work)

How to start the script
In case you want your script/automation to resume with the next action immediately

  - alias: "TTS voor Nest Hub Kitchen"
    service: script.turn_on 
    target:
      entity_id: script.google_home_say
    data:
      variables:
        tts_message: "Hello, hello! This is a test!"
        tts_target: media_player.google_kitchen
        tts_volume: 35
        volume_non_playing: 25

In case you want to have your script/automation wait for the TTS script to be completed

  - alias: "TTS voor Nest Hub Kitchen"
    service: script.google_home_say
    data:
      tts_message: "Hello, hello! This is a test!"
      tts_target: 
        - media_player.google_kitchen
        - media_player.google_living
        - media_player.upstairs_group
      tts_volume: 35
      restore_volume_all: True
      speaker_group_split: False

The script can also be started from the GUI, both in YAML mode and full GUI mode. The variables tts_message and tts_target are required. tts_volume is optional, in case it is not set the current volume will be used. In full GUI mode you can only select one enttiy_id as limitation of the the entity selector. In YAML mode, or if called in a service call in an automation or script, you can enter multiple entity_ids.

And finally the script itself:

google_home_say:
  alias: "Algemeen | TTS voor Google Home"
  description: Script for TTS messages to Google Home speakers
  icon: mdi:cast-audio
  mode: parallel
  fields:
    tts_message:
      description: "Message to be uses as TTS for Google Home."
      example: "Hello, this is a test message."
      required: true
      selector:
        text:
    tts_target:
      description: "The target Google Home."
      example: media_player.kitchen_hub
      required: true
      selector:
        entity:
          integration: cast
          domain: media_player
    tts_volume:
      description: "Volume for TTS message (in %)."
      example: 25
      required: false
      selector:
        number:
          min: 0
          max: 100
          step: 5
          mode: slider
          unit_of_measurement: "%"
    restore_volume_all:
      description: "Restore volume of all TTS targets, will cause a short delay in sending the TTS"
      example: true
      required: false
      selector:
        boolean:
    volume_non_playing:
      description: "Default volume to restore non playing devces to (in %). Only used in case tts_volume is set, and restore_volume_all is not set to True"
      example: 25
      required: false
      selector:
        number:
          min: 0
          max: 100
          step: 5
          mode: slider
          unit_of_measurement: "%"
    speaker_group_split:
      description: "Set to True to split Google Home speaker groups in separate entities when sending the TTS"
      example: true
      required: false
      selector:
        boolean:
  variables:
    # Where [LIST] is mentioned, make sure you enter a list, and not a comma separated string
    tts_service: tts.google_cloud_say # the tts service, if addional service data is required, edit in the script (line 288-292)
    players_screen: # [LIST] Cast devices (Nest Hub (Max)) with a screen
      - media_player.keuken_hub
      - media_player.slaapkamer_hub
    frequent_radio: # [LIST] frequently uses streams (only the part between http(s):// and the first / after that)
      - icecast.omroep.nl # NPO radio 2 & 3FM (NL)
      - playerservices.streamtheworld.com # Radio Veronica (NL)
    spotify_media_players: # [LIST] media players from the spotify integration, should be named like "media_player.spotify_[spotcast account]"
      - media_player.spotify_pepijn
      - media_player.spotify_martijn
      - media_player.spotify_marleen
      - media_player.spotify_floris
    speaker_groups: # [DICT/LIST] speaker groups, also list the entities belonging to the group
      media_player.huis_groep:
        - media_player.keuken_hub
        - media_player.slaapkamer_hub
        - media_player.henk_mini
        - media_player.woonkamer_mini
        - media_player.klaas_mini
      media_player.boven_groep:
        - media_player.henk_mini
        - media_player.woonkamer_mini
        - media_player.klaas_mini
      media_player.beneden_groep:
        - media_player.keuken_hub
        - media_player.slaapkamer_hub
    primary_spotcast: "pepijn" # the spotify account which is used as primary spotcast account
    silent_mp3: "media-source://media_source/local/misc/10-seconds-of-silence.mp3" # url to the silent mp3 used for volume level restore
    default_volume_restore: True # set to True if you want to restore volume of non playing entities by default
    delay_volume_restore: 3 # the delay which is used after playing the silent mp3 to make sure it is actually playing and the volume can be restored
    default_volume_level: 0.25 # the default volume level to use in case the silent mp3 was not playing yet (delay too short)
    default_group_split: False # set to True in case the TTS should be sent to the individual entities of a speaker group instead of the group itself
    debug: False # set to True in case you want to write the variables used in the script to the logbook for debugging
  sequence:
    - alias: "Convert input to lists if not provided as such"
      variables:
        tts_target_list: >
          {% if voice_tts_target is defined %}
            {{ [ voice_tts_target ] }}
          {% else %}
            {% set tts_list = tts_target.replace(' ' , '').split(',') if tts_target is string else tts_target %}
            {% set speaker_group_list = speaker_groups.keys() | list if speaker_groups is defined else [] %}
            {% set tts_target_group =  tts_list | select('in', speaker_group_list) | list %}
            {% set tts_target_single = tts_list | reject('in', speaker_group_list) | list %}
            {% set ns = namespace(single = []) %}
            {% for group in speaker_groups %}
              {% set ns.single = ns.single + speaker_groups[group] %}
            {% endfor %}
            {% set tts_group_entities = ns.single | unique | list %}
            {% set single_not_in_group = tts_target_single | reject('in', tts_group_entities) | list %}
            {% if speaker_group_split | default(False) %}
              {{ tts_group_entities + single_not_in_group }}
            {% else %}
              {{ tts_target_group + single_not_in_group }}
            {% endif %}
          {% endif %}
        speaker_group_list: "{{ speaker_groups.keys() | list if speaker_groups is defined else [] }}"
    - alias: "Deterimine which entities are playing and should be resumed"
      variables:
        players_to_resume: >
          {# determine which media_players are playing and store attributes#}
            {% 
              set all_players_playing = states.media_player 
                                          | selectattr('state', 'eq', 'playing') 
                                          | map(attribute='entity_id') 
                                          | list  
            %}
          {# determine which Google Home speakers groups are playing #}
            {% 
              set all_speaker_groups_playing =  voice_groups | default(
                                                all_players_playing 
                                                  | select('in', speaker_group_list) 
                                                  | list )
            %}
          {# determine which of the tts_targets are playing  #}
            {% set tts_playing = 
              [ voice_tts_target ] if 
                ( 
                  voice_tts_target is defined
                  and voice_media_content != 'no music'
                )
              else tts_target_list | select('in', all_players_playing) | list 
            %}
          {# determine which groups should be resumed becaue a single tts target is part of that group #}
            {% set ns = namespace(groups = []) %}
              {% for group in all_speaker_groups_playing %}
                {% for entity in speaker_groups[group] if entity in tts_target_list %}
                  {% set ns.groups = ns.groups + [group] %}
                {% endfor %}
              {% endfor %}
            {% set speaker_groups_resume = ns.groups | unique | list %}
          {# determine which single entities are in a group which is going to be resumed #}
            {% set ns = namespace(reject = []) %}
            {% for group in speaker_groups_resume %}
              {% set ns.reject = ns.reject + speaker_groups[group] %}
            {% endfor %}
            {% set reject_list = ns.reject %}
          {# determine which single playing entities are in a group which is a tts_target #}
            {% set tts_target_group = tts_target_list | select('in', speaker_group_list) | list %}
            {% set ns = namespace(single_resume = []) %}
            {% for group in tts_target_group %}
              {% set ns.single_resume = ns.single_resume + speaker_groups[group] | select('in', all_players_playing) | list %}
            {% endfor %}
            {% set single_resume_list = ns.single_resume %}
          {# combine all the above to a list of players which should be resumed #}
          {% set players_resume = (tts_playing + speaker_groups_resume + single_resume_list) | reject('in', reject_list) | unique | list %}
          {% set entities = expand(players_resume) %}
          {% set ns = namespace(info=[]) %}
          {% for entity in entities %}
            {% set ns.info = ns.info +  [ 
                                          dict(
                                            entity_id = entity.entity_id, 
                                            media_content_id = entity.attributes.media_content_id | default('no media_content'),
                                            media_title = entity.attributes.media_title | default('no title'),
                                            media_artist = entity.attributes.media_artist | default('no artist'),
                                            media_content_type = entity.attributes.media_content_type | default('no type'),
                                            app_name = entity.attributes.app_name | default('no app'),
                                            entity_picture = entity.attributes.entity_picture | default('no pic'),
                                            volume_level = entity.attributes.volume_level | round(2) 
                                            )
                                        ] 
            %}
          {% endfor %}
          {{ ns.info }}
    - alias: "Create lists with attributes of players to resume"
      variables:
        tts_target_not_playing: >
          {% set players_resume_entity = players_to_resume | map(attribute='entity_id') | list %}
          {% set ns = namespace(members = []) %}
          {% for entity in players_resume_entity %}
            {% if entity in speaker_group_list %}
              {% set ns.members = ns.members + state_attr(entity | replace('media_player', 'group'), 'entity_id') | list %}
            {% endif %}
          {% endfor %}
          {% set players_resume_all = players_resume_entity + ns.members %}
          {{ tts_target_list | reject('in', players_resume_all) | list }}
        spotify_players_info: >
          {% 
            set spotify_player_list = states.media_player 
                                        | selectattr('state', 'eq', 'playing')
                                        | selectattr('entity_id', 'search', 'media_player.spotify') 
                                        | map(attribute='entity_id') 
                                        | list
          %}
          {% set entities = expand(spotify_player_list) %}
          {% set ns = namespace(info=[]) %}
          {% for entity in entities %}
            {% set ns.info = ns.info + [ dict(entity_id=entity.entity_id, source=entity.attributes.source) ] %}
          {% endfor %}
          {{ ns.info }}
    - variables:
        volume_restore_required: >
          {{
            restore_volume_all | default(default_volume_restore)
            and tts_target_not_playing | count > 0
            and tts_volume is defined
          }}
    - alias: "Play silent MP3 if volume of non playing targets should be restored"
      choose:
        - conditions: "{{ volume_restore_required }}"
          sequence:
            - alias: "Play silent MP3"
              service: media_player.play_media
              target:
                entity_id: "{{ tts_target_not_playing }}"
              data:
                media_content_id: "{{ silent_mp3 }}"
                media_content_type: "audio/mp3"
            - delay: 3
    - variables:
        volume_old_not_playing: >
          {% set entities = expand(tts_target_not_playing) %}
          {% set ns = namespace(info=[]) %}
          {% for entity in entities %}
            {% set ns.info = ns.info +  [
                                        dict(
                                          entity_id = entity.entity_id, 
                                          volume_level = entity.attributes.volume_level | default(default_volume_level) | round(2)
                                          ) 
                                        ] 
            %}
          {% endfor %}
          {{ ns.info }}
        screen_not_playing: >
          {{ tts_target_not_playing | select('in', players_screen) | list }}
    - alias: "Debug log"
      choose:
        - conditions: "{{ debug }}"
          sequence:
            - alias: "Debug info to log"
              service: system_log.write
              data:
                level: info
                message: |
                  tts_message = {{ tts_message }}
                  tts_volume = {{ tts_volume | default('undefined') }}
                  restore_volume_all = {{ restore_volume_all | default('undefined') }}
                  volume_not_playing = {{ volume_not_playing | default('undefined') }}
                  speaker_group_split = {{ speaker_group_split | default('undefined') }}
                  silent_mp3 = {{ silent_mp3 if silent_mp3 | default('undefined') }}
                  default_volume_restore = {{ default_volume_restore | default('undefined') }}
                  default_group_split = {{ default_group_split | default('undefined') }}
                  tts_target_list = {{ tts_target_list | default('undefined') }}
                  players_screen = {{ players_screen | default('undefined') }}
                  frequent_radio = {{ frequent_radio | default('undefined') }} 
                  speaker_groups = {{ speaker_groups | default('undefined') }}
                  speaker_group_list = {{ speaker_group_list | default('undefined') }}
                  players_to_resume = {{ players_to_resume | default('undefined') }}
                  tts_target_not_playing = {{ tts_target_not_playing | default('undefined') }}
                  spotify_players_info = {{ spotify_players_info | default('undefined') }}
                  volume_old_not_playing = {{ volume_old_not_playing | default('undefined') }}
                  screen_not_playing = {{ screen_not_playing | default('undefined') }}
    - alias: "Set volume to TTS volume if set"
      choose:
        - conditions:
            - alias: "TTS Volume set?"
              condition: template
              value_template: "{{ tts_volume is defined }}"
          sequence:
            - alias: "Apply TTS volume"
              service: media_player.volume_set
              target:
                entity_id: "{{ tts_target_list }}"
              data:
                volume_level: "{{ tts_volume if tts_volume < 1 else tts_volume / 100 }}"
    - alias: "Send TTS message"
      service: "{{ tts_service }}"
      data:
        entity_id: "{{ tts_target_list }}"
        message: "{{ tts_message }}"
    - alias: "Short delay if required"
      choose:
        - conditions:
            - alias: "Delay required"
              condition: template
              value_template: >
                {{
                  players_to_resume | count > 0
                  or volume_restore_required
                  or screen_not_playing | count > 0
                }}
          sequence:
            - delay: 4
    - alias: "Anything to resume?"
      choose:
        - conditions:
            - alias: "Anything playing"
              condition: template
              value_template: "{{ players_to_resume | count > 0 }}"
          sequence:
            - alias: "Resume players"
              repeat:
                count: "{{ players_to_resume | count }}"
                sequence:
                  - alias: "Variables for specific media_player"
                    variables:
                      spotify: >
                        {{ ( voice_app_name | default(players_to_resume[repeat.index-1].app_name ) == 'Spotify') }}
                      radio: >
                        {{ 
                          voice_app_name | default(players_to_resume[repeat.index-1].app_name == 'TuneIn Free')
                          or (
                                  ( voice_media_content | default(players_to_resume[repeat.index-1].media_content_id)).split('//')[1]
                                    | default('no/tunein', true)
                                ).split('/')[0]
                            in frequent_radio
                        }}
                      spotcast_account: >
                        {% if voice_spotcast is defined and voice_spotcast != 'no spotify' %}
                          {{ voice_spotcast }}
                        {% else %}
                          {% set mp_names = players_to_resume | map(attribute='friendly_name') | list %}
                          {% if players_to_resume[repeat.index - 1].app_name != 'Spotify' %}
                            Not Spotify
                          {% elif spotify_players_info | count == 1 %}
                            {{ spotify_players_info[0].entity_id.split('_')[2] }}
                          {% elif players_to_resume[repeat.index -1].entity_id in speaker_group_list %}
                            {% set ha_group = players_to_resume[repeat.index -1].entity_id | replace('media_player', 'group') %}
                            {% set members = state_attr(ha_group, 'entity_id') %}
                            {% set list_check = [ players_to_resume[repeat.index -1].entity_id ] + [] if members == None else members | list %}
                            {% set names_check = expand(list_check) | map(attribute='name') | list %}
                            {{ spotify_players_info
                                | selectattr('source', 'in', names_check) 
                                | map(attribute='entity_id') 
                                | join 
                                | replace('media_player.spotify_', '')
                            }}
                          {% else %}
                            {{ 
                              (
                                spotify_players_info 
                                  | selectattr('source', 'eq', players_to_resume[repeat.index -1].friendly_name) 
                                  | map(attribute='entity_id') 
                                  | join
                              ).split('_')[2]
                              if players_to_resume[repeat.index - 1].friendly_name in (spotify_players_info | map(attribute='source') | list)
                              else primary_spotcast 
                            }}
                          {% endif %}
                        {% endif %}
                  - alias: "Wait until TTS message is complete"
                    wait_template: "{{ states(players_to_resume[repeat.index -1].entity_id) in ['idle', 'off'] }}"
                  - alias: "Set volume to old volume in case TTS volume was set"
                    choose:
                      - conditions:
                          - alias: "TTS Volume set?"
                            condition: template
                            value_template: "{{ tts_volume is defined }}"
                        sequence:
                          - alias: "Set volume back to old state"
                            service: media_player.volume_set
                            target:
                              entity_id: "{{ players_to_resume[repeat.index -1].entity_id }}"
                            data:
                              volume_level: "{{ players_to_resume[repeat.index -1].volume_level | float(default=0.25) | round(2) }}"
                  - alias: "Resume playing"
                    choose:
                      - alias: "Spotify?"
                        conditions: "{{ spotify }}"
                        sequence:
                          - alias: "Primary spotcast account?"
                            choose:
                              - conditions: "{{ spotcast_account == primary_spotcast }}"
                                sequence:
                                  - alias: "Resume spotify with primary spotcast account"
                                    service: spotcast.start
                                    data:
                                      entity_id: "{{ players_to_resume[repeat.index -1].entity_id }}"
                                      force_playback: true
                            default:
                              - alias: "Resume spotify with specific account"
                                service: spotcast.start
                                data:
                                  entity_id: "{{ players_to_resume[repeat.index -1].entity_id }}"
                                  account: "{{ spotcast_account }}"
                                  force_playback: true
                      - alias: "Radio?"
                        conditions: "{{ radio }}"
                        sequence:
                          - alias: "Resume radio"
                            service: media_player.play_media
                            target:
                              entity_id: "{{ players_to_resume[repeat.index -1].entity_id }}"
                            data:
                              media_content_id: >
                                {{ voice_media_content | default(players_to_resume[repeat.index -1].media_content_id) }}
                              media_content_type: "music"
                              extra:
                                title: >
                                  {% if voice_media_artist is defined %}
                                    {{ voice_media_artist }}
                                  {% elif players_to_resume[repeat.index -1].media_artist != 'no artist' %}
                                    {{ players_to_resume[repeat.index -1].media_artist }}
                                  {% else %}
                                    {{ players_to_resume[repeat.index -1].media_title }}
                                  {% endif %}
                                thumb: "{{ voice_media_picture | default(players_to_resume[repeat.index -1].entity_picture) }}"
    - alias: "TTS sent to non playing devices?"
      condition: template
      value_template: "{{ tts_target_not_playing | count > 0 }}"
    - alias: "Set volume non playing entites back if required"
      choose:
        - conditions: "{{ volume_non_playing is defined or volume_restore_required }}"
          sequence:
            - alias: "Resume players"
              repeat:
                count: "{{ volume_old_not_playing | count }}"
                sequence:
                  - alias: "Wait until TTS message is complete"
                    wait_template: "{{ states(volume_old_not_playing[repeat.index -1].entity_id) in ['idle', 'off'] }}"
                  - alias: "Set volume back to old state"
                    service: media_player.volume_set
                    target:
                      entity_id: "{{ volume_old_not_playing[repeat.index -1].entity_id }}"
                    data:
                      volume_level: >
                        {% if volume_restore_required %}
                          {{ volume_old_not_playing[repeat.index -1].volume_level | float(default=0.25) | round(2) }}
                        {% else %}
                          {{ volume_non_playing if volume_non_playing < 1 else volume_non_playing / 100 }}
                        {% endif %}
    - alias: "Google Home with screen?"
      choose:
        - conditions: "{{ screen_not_playing | count > 0 }}"
          sequence:
            - alias: "Resume players"
              repeat:
                count: "{{ screen_not_playing | count }}"
                sequence:
                  - alias: "Wait until TTS message is complete"
                    wait_template: "{{ states(screen_not_playing[repeat.index -1]) in ['idle', 'off'] }}"
                  - alias: "Turn Google Home on to return to idle mode (photo display)"
                    service: media_player.turn_on
                    target:
                      entity_id: "{{ screen_not_playing | select('in', players_screen) | list }}"

I’m not a template expert, and had a lot of help from the nice people on the Home Assistant Discord. I’m sure a lot of improvements are possible, so if you see something, please let me know.

7 Likes

If you’re interested, you can replace this:

        tts_target_list: >
          {# convert tts_target input to list if it is not already #}
          {{ 
            ( tts_target | replace(' ' , '')).split(',') | list 
            if tts_target is string else tts_target 
          }}

with this slightly shorter version:

        tts_target_list: >
          {# convert tts_target input to list if it is not already #}
          {{ 
            (tts_target).replace(' ', '').split(',') 
            if tts_target is string else tts_target 
          }}
1 Like

For my Lovelace Dashboard I have made a card to easily send a TTS message.

For this I’ve used some helpers.

  • input_text.tts_message for the text to be sent
  • 'input_boolean.tts_volume` to set if you want to change the volume for the TTS, or use the current volume
  • input_number.tts.volume for the volume setting (value from 0 to 100 using increments of 5)
  • an input_boolean for each Google Cast media player, with the entity_id as the player. So e.g. input_boolean.google_kitchen for media_player.google_kitchen
  • input_boolean.restore_volume_all to set the option to send the silent MP3 so the volume of all players can be restored.
  • input_number.volume_non_playing to set the volume fo non playing devices in case input_boolean.restore_volume_all is off.
  • A group called group.tts_google_home with the device input_booleans as group members.

The input_booleans have been added to a group called group.tts_google_home

I created a script which uses this input to call the other script from post 1
It will reset the helpers to some defaults settings after the TTS has been sent (volume to 25%, all device toggles to off etc)

google_say_dashboard:
  alias: "TTS Lovelace Dashboard"
  description: Script to send messages using Lovelace dashboard
  icon: mdi:cast-audio
  mode: single
  sequence:
    - alias: "Change volume while casting?"
      choose:
        - conditions:
            - alias: "Input boolean on?"
              condition: state
              entity_id: input_boolean.tts_volume
              state: "on"
          sequence:
            - service: script.turn_on
              target:
                entity_id: script.google_home_say
              data:
                variables:
                  tts_message: "{{ states('input_text.tts_message') }}"
                  tts_volume: "{{ states('input_number.tts_volume') | float / 100 | round(2) }}"
                  tts_target: >
                    {{ 
                        expand('group.tts_google_home') 
                            | selectattr('state', 'eq', 'on') 
                            | map(attribute='entity_id') 
                            | list
                            | replace('input_boolean', 'media_player')
                    }}
                  restore_volume_all: "{{ is_state('input_boolean.restore_volume_all', 'on') }}"
                  volume_non_playing: "{{ states('input_number.volume_non_playing') | float / 100 | round(2) }}"
      default:
        - service: script.turn_on
          target:
            entity_id: script.google_home_say
          data:
            variables:
              tts_message: "{{ states('input_text.tts_message') }}"
              tts_target: >
                {{ 
                    expand('group.tts_google_home') 
                        | selectattr('state', 'eq', 'on') 
                        | map(attribute='entity_id') 
                        | list
                        | replace('input_boolean', 'media_player')
                }}
    - service: input_number.set_value
      target:
        entity_id: input_number.tts_volume
      data:
        value: 25
    - service: input_text.set_value
      target:
        entity_id: input_text.tts_message
      data:
        value: ""
    - service: homeassistant.turn_off
      target:
        entity_id: group.tts_google_home

I don’t have a very fancy Lovelace dashboard, so I made a simple card to use the above:

  - type: vertical-stack
    cards: 
      - type: entities
        title: Google Home TTS
        entities:
          - entity: input_text.tts_message
            name: Message
          - entity: input_boolean.tts_volume
            name: Change Volume?
      - type: conditional
        conditions:
        - entity: input_boolean.tts_volume
          state_not: "off"
        card:
          type: entities
          entities:
            - entity: input_number.tts_volume
              name: Volume for TTS message
            - entity: input_boolean.restore_volume_all
              name: Restore Volume Non Playing?
      - type: conditional
        conditions:
        - entity: input_boolean.restore_volume_all
          state: "off"
        - entity: input_boolean.tts_volume
          state_not: "off"
        card:
          type: entities
          entities:
            - entity: input_number.volume_non_playing
              name: Volume for non playing devices
      - type: entities
        title: "Send to: "
        entities:
          - entity: input_boolean.house_group
            name: Entire house (group)
          - entity: input_boolean.first_floor_group
            name: First floor (group)
          - entity: input_boolean.ground_floor_group
            name: Ground Floor (group)
          - entity: input_boolean.google_second_floor
            name: Second Floor
          - entity: input_boolean.google_bedroom
            name: Master Bedroom
          - entity: input_boolean.google_jack
            name: Bedroom Jack
          - entity: input_boolean.google_jones
            name: Bedroom Jones
          - entity: input_boolean.google_kitchen
            name: Kitchen
          - entity: input_boolean.google_living_room
            name: Living Room
      - type: entities
        entities:
          - entity: script.google_say_dashboard
            name: Send Message

This will then look like this:

1 Like

If you wish, you can reduce that to this:

        tts_target: >
          {{ 
              expand('group.tts_google_home') 
                  | selectattr('state', 'eq', 'on') 
                  | map(attribute='entity_id') 
                  | list
                  | replace('input_boolean', 'media_player')
          }}
1 Like

Hi TheFes, I’m trying my best to get this to work with “YouTube Music”. Its like the connection gets knocked from my mobile casting and never comes back. I updated your code to include YouTube Music in the conditions. Would you be able to point my in the right direction?

@onl1kairi
I’ve been trying to use YouTube Music as well for a short period of time. However, there is no service like Spotcast for YouTube music.

You can use the media_extractor integration (Media Extractor - Home Assistant) to resume the last track which was playing (using the media_content_id). However, it will then only play that track, and will not resume your playlist, or radio station (or whatever that is called in YouTube Music)

There is a YouTube Music integration available on HACS, but it works different as Spotcast does (see GitHub - KoljaWindeler/ytube_music_player: YouTube music player for homeassistant). That service does have a resume function though, but that only works if you started the stream using this integration (as far as I could figure out).
I tried to install it again to test it out again, but could not immediately get the token required to connect it, and my boss expects me to work right now :wink:

This all made me decide to stick with Spotify :slight_smile:

1 Like

Get this when i copy and paste to script:
Message malformed: extra keys not allowed @ data[‘google_home_say’]

Amazing job here !
Congrats and very useful !
Using both scripts (post 1 & 3) !
Love the work.
Appreciated & thanks to @TheFes !

Did you past it directly into configuration.yaml?
If so, you need add a line at the top with script: and intend everything two spaces.

However, new installations normally come with a scripts.yaml where you can paste the script as it is.

Edit:
I’ve read it again now, and I think you are trying to paste it in the YAML editor of the GUI (from configuration → scripts).
In that case you have to remove the top line (google_home_say:) and remove the first two spaces of all the lines.
Howver, I would recommend to paste it into scripts.yaml instead of in the GUI YAML editor.

1 Like

Yep tried to put in the gui. But as I understand cutting and pasting in script.yaml is okay? No need to delete lines?

Yep, you can paste it directly into scripts.yaml

You will of course have to amend the variables to your own situation

Thanks, the script is in the system. No time yet to make it work and probably have many question to get it to work.

@TheFes, thanks for this script! It works really well. However, I do have some weird behavior with it. If I am playing music on two separate google homes (one mini and one 1st gen hub), the volumes at which they were playing gets switched after the TTS message. Any idea why this is happening?

Another small issue I noticed is that after the TTS message, the app_name changes from TuneIn Free to Default Media Receiver, thereby blacking out the screen on the hub, no longer showing the radio station information. Do you know how to solve this?

Unfortunately I did not find a way to get cast TuneIn. What the script does is just send the URL using the default media player. Maybe it could be done using Assistant Relay, but that is not maintained anymore. I’m from the Netherlands, and Dutch was not supported, so I did not invest a lot of time in it.

The volume issue is a bit strange indeed. I will have a look into that.

Thank you for reply. No worries about TuneIn as this is not a big issue. It works, and that is the most important thing :smile:

I just noticed that it also switches the volumes of the two devices if both are not playing.

Made some updates in the way the attributes before TTS are stored, which should prevent things to be mixed up.

Hey TheFes,

It’s not working. At first, it was crashing on your notify debug so I’ve removed it. Now nothing happens. There are no errors associated.

Setting a TTS volume causes the music I’m listening to to change to that volume but no TTS is produced.

Whoops, the notify shouldn’t be in there. It relies on a text notify service to be set up.
I am working on a changed version with an optional debug info write to the log.
For now it should work without the notifiy part (as I changed it to now)

It doesn’t. Nothing happens and no error is produced.

Nevermind, you’re using Google Cloud.

I’ll try to convert this to google_say.