Help creating a template sensor to overcome Google Cast issue

I wouldn’t use a template sensor because it has a limit of 255 characters. I’d just calculate it on the fly inside the service. I’d also entertain the idea of using != ‘playing’. This may be unwanted if you don’t want to deal with it when paused or stopped. idle may be the way to go.

I’d also skip doing the media_player.none stuff by adding a condition. It would be a little long winded but you should never have to update it and no errors should get produced.

- condition: template
  value_template: >-
      {% set media_players = states.media_player 
          | selectattr('entity_id', 'in', state_attr('media_player.casa_inteira', 'entity_id')) 
          | selectattr('state','!=','playing') 
          | map(attribute='entity_id') 
          | join(', ') %}
      {{ media_players | length > 0 }}
- service: media_player.play_media
  data_template:
    entity_id: >-
      {{ states.media_player 
          | selectattr('entity_id', 'in', state_attr('media_player.casa_inteira', 'entity_id')) 
          | selectattr('state','!=','playing') 
          | map(attribute='entity_id') 
          | join(', ') }}
    media_content_id: 'http://hassio.local:8123/local/audio/tts/sistema_reiniciado.mp3'
    media_content_type: 'audio/mp3'
1 Like

Hello my friend! Thanks for passing by, I really appreciate it.

The idea of using a template_sensor is that I would not have to use the same code over and over again, since I have about 20 scripts that involves a TTS announcement using the media_player.play_media service.

I’ve calculated that if all media players are selected on the output text, no more than 140 characters are used so I think the 255 limit would not be a problem, I believe.

Also, using a condition would halt the rest of the current running script, since many of them have other services calls after the media_player.play_media service.

Another important thing is that the code should always use the “all speakers group” called media_player.casa_inteira if all cast devices are idle, to make sure there are no audio sync problems when the media is played on all devices at the same time.

The use of != 'playing' sounds awesome! I really like it too.

I am still not sure what to do if there are no speakers available or media_player.casa_inteira is playing at the moment, resulting something like media_player.none. I don’t care if there are errors messages on the log about the lack of an entity_id on that service call if the script does not halt of anything bad happens…

Thanks a lot @petro!

Hahaha no worries my friend! It happens to the best of us :slight_smile:

Thanks for your code, it really helped on this project.

1 Like

@petro, I was thinking: is it possible to create a script just for that usage? A condition could be use to check if there are any speakers available (like you suggested before) and just pass the data part to the script? I’ve read somewhere that variables could be passed to another script when the service is called. Thanks.

Go to the Services page and enter:

  • Service: media_player.play_media
  • Entity: media_player.none

Click CALL SERVICE and check the log. There will be no WARNING or ERROR message concerning the non-existent entity.

Maybe a future version of Home Assistant will complain but it doesn’t in 0.91.

1 Like

Hello! Thanks for your suggestion.

I believe that @petro idea of using a script with a condition would be a better solution, since we can pass variables to the script, like this:

- service: script.ghm_play_media
  data_template:
    media_content_id: 'http://hassio.local:8123/local/audio/tts/sistema_reiniciado.mp3'

So it can call the script.ghm_play_media and pass the variable media_content_id or any TTS service data to a script like this:

ghm_play_media:
  sequence:
    - condition: template
      value_template: >-
          {% set media_players = states.media_player 
              | selectattr('entity_id', 'in', state_attr('media_player.casa_inteira', 'entity_id')) 
              | selectattr('state','!=','playing') 
              | map(attribute='entity_id') 
              | join(', ') %}
          {{ media_players | length > 0 }}
    - service: media_player.play_media
      data_template:
        entity_id: >-
          {{ states.media_player 
              | selectattr('entity_id', 'in', state_attr('media_player.casa_inteira', 'entity_id')) 
              | selectattr('state','!=','playing') 
              | map(attribute='entity_id') 
              | join(', ') }}
        media_content_type: 'audio/mp3'

This idea sounds great since if there are no speakers available, the condition would be met and the service would not be called at all.

I am still not sure if this would be solved:

Another important thing is that the code should always use the “all speakers group” called media_player.casa_inteira if all cast devices are idle , to make sure there are no audio sync problems when the media is played on all devices at the same time.

That would be very important as well.

Thanks!

Hello guys!

So here is the solution I’ve found

It is not the best way to do it, but it works:

## Scripts ##

ghm_play_media:
  sequence:
    - condition: template
      value_template: "{{ not is_state('media_player.casa_inteira' , 'playing')}}"
    - service: media_player.volume_set
      data_template:
        entity_id: >-
            {%- set players = ['media_player.banheiro_social', 'media_player.banheiro_da_suite', 'media_player.cozinha', 'media_player.quarto', 'media_player.sala', 'media_player.suite'] %}
            {{ states.media_player | selectattr('state','!=','playing') | selectattr('entity_id', 'in', players) | map(attribute='entity_id') | join(', ') }}
        volume_level: '{{volume}}'
    - service: media_player.play_media
      data_template:
        entity_id: >-
          {%- if is_state('input_boolean.boa_noite', 'on') %}
              {%- set players = ['media_player.banheiro_social', 'media_player.cozinha', 'media_player.quarto', 'media_player.sala'] %}
              {{ states.media_player | selectattr('state','!=','playing') | selectattr('entity_id', 'in', players) | map(attribute='entity_id') | join(', ') }}
          {%- else %}
              {%- if states.media_player.banheiro_social.state != 'playing' and
                    states.media_player.banheiro_suite.state != 'playing' and
                    states.media_player.cozinha.state != 'playing' and
                    states.media_player.quarto.state != 'playing' and
                    states.media_player.sala.state != 'playing' and
                    states.media_player.suite.state != 'playing' %}
                  media_player.casa_inteira
              {%- else %}
                  {%- set players = ['media_player.banheiro_social', 'media_player.banheiro_da_suite', 'media_player.cozinha', 'media_player.quarto', 'media_player.sala', 'media_player.suite'] %}
                  {{ states.media_player | selectattr('state','!=','playing') | selectattr('entity_id', 'in', players) | map(attribute='entity_id') | join(', ') }}
              {%- endif %}
          {%- endif %}
        media_content_type: 'audio/mp3'
        media_content_id: '{{media}}'

ghm_tts:
  sequence:
    - condition: template
      value_template: "{{ not is_state('media_player.casa_inteira' , 'playing')}}"
    - service: media_player.volume_set
      data_template:
        entity_id: >-
            {%- set players = ['media_player.banheiro_social', 'media_player.banheiro_da_suite', 'media_player.cozinha', 'media_player.quarto', 'media_player.sala', 'media_player.suite'] %}
            {{ states.media_player | selectattr('state','!=','playing') | selectattr('entity_id', 'in', players) | map(attribute='entity_id') | join(', ') }}
        volume_level: '{{volume}}'
    - service: tts.amazon_polly_say
      data_template:
        entity_id: >-
          {%- if is_state('input_boolean.boa_noite', 'on') %}
              {%- set players = ['media_player.banheiro_social', 'media_player.cozinha', 'media_player.quarto', 'media_player.sala'] %}
              {{ states.media_player | selectattr('state','!=','playing') | selectattr('entity_id', 'in', players) | map(attribute='entity_id') | join(', ') }}
          {%- else %}
              {%- if states.media_player.banheiro_social.state != 'playing' and
                    states.media_player.banheiro_suite.state != 'playing' and
                    states.media_player.cozinha.state != 'playing' and
                    states.media_player.quarto.state != 'playing' and
                    states.media_player.sala.state != 'playing' and
                    states.media_player.suite.state != 'playing' %}
                  media_player.casa_inteira
              {%- else %}
                  {%- set players = ['media_player.banheiro_social', 'media_player.banheiro_da_suite', 'media_player.cozinha', 'media_player.quarto', 'media_player.sala', 'media_player.suite'] %}
                  {{ states.media_player | selectattr('state','!=','playing') | selectattr('entity_id', 'in', players) | map(attribute='entity_id') | join(', ') }}
              {%- endif %}
          {%- endif %}
        message: '{{tts}}'

## Tests ##

ghm_play_media_test:
  sequence:
    - service: script.ghm_play_media
      data_template:
        media: 'http://hassio.local:8123/local/audio/tts/some_audio.mp3'
        volume: 0.5

ghm_tts_test:
  sequence:
    - service: script.ghm_tts
      data_template:
        tts: '<speak>Say something with Polly or Google format</speak>'
        volume: 0.5

It checks if all speakers group is playing something, if so, script does not start at all. If it is not playing, get only entity_id of devices not playing and pass the variables needed.

Also, I’ve added an “if” so it can determinate if someone has started the “good night” mode on the house (is_state('input_boolean.boa_noite', 'on')), so it only plays the media on devices outside the main bedroom.

Another cool feature is to pass the volume needed on each announcement/media play.

I will set this as solved.

Thanks @petro and @123 for all the help!

2 Likes

Hey @Schneider. Awesome thread! I’m trying to do the same thing with my Google Homes by only sending HA notifications to media players that aren’t in state media_content_type: music (to avoid walking with a limp sometime real soon :flushed:).

However, I seem to be having issues with my ‘all google home groups’. I’ve set up groups both in the Google Home app and also in HA using the groups.yaml. All group entities are visible but when I play a song on one of my Google Homes, the device itself says ‘playing’ but the group status is off. Am I missing something?

You can see from the image below that the media player media_player.kitchen_home is playing but that the group above that it’s in is not and I don’t think your script will work without that functioning?

The group above is one created inside HA, the one below is the Google Home app.

Cracked it. Was going about it the wrong way. Final simplified code below. Edit defined media players and media player group (from Google Home App) which in my case is the media_player.upstairs_homes.

ghm_tts:
  sequence:
    - service: media_player.volume_set
      data_template:
        entity_id: >-
            {%- set players = ['media_player.kitchen_home', 'media_player.lounge_home'] %}
            {{ states.media_player | selectattr('state','!=','playing') | selectattr('entity_id', 'in', players) | map(attribute='entity_id') | join(', ') }}
        volume_level: '{{volume}}'
    - service: tts.google_say
      data_template:
        entity_id: >-
              {%- if states.media_player.kitchen_home.state != 'playing' and
              states.media_player.lounge_home.state != 'playing' %}
              media_player.upstairs_homes
              {%- else %}
              {%- set players = ['media_player.kitchen_home', 'media_player.lounge_home'] %}
              {{ states.media_player | selectattr('state','!=','playing') | selectattr('entity_id', 'in', players) | map(attribute='entity_id') | join(', ') }}
              {%- endif %}
        message: '{{tts}}'

## Tests ##
ghm_tts_test:
  sequence:
    - service: script.ghm_tts
      data_template:
        tts: "This is a notification that should not of played on speakers that are playing music"
        volume: 0.5
1 Like

Hello sir!

I am really sorry but did not see your previous message.

I am glad you’ve cracked it and made it even better, congratulations!

Have a great day!

Here’s how to create a script that doesn’t require modification should you ever add more media_players.

Create a group containing the two media_players on the main floor:

group:
  players_main:
    name: Media Players Main Floor
    entities:
      - media_player.kitchen_home
      - media_player.lounge_home

Now you can use the group in the script and refer to its members using the expand function.

ghm_tts:
  sequence:
    - service: media_player.volume_set
      data_template:
        entity_id: >-
            {{ expand('group.players_main') | selectattr('state','!=','playing') | map(attribute='entity_id') | join(', ') }}
        volume_level: '{{volume}}'

    - service: tts.google_say
      data_template:
        entity_id: >-
              {% set players = expand('group.players_main') | list %}
              {% set total = players | count %}
              {% set not_playing = players | selectattr('state','!=','playing') | list %}
              {% if not_playing | count == total %}
                media_player.upstairs_homes
              {% else %}
                {{ not_playing | map(attribute='entity_id') | join(', ') }}
        message: '{{tts}}'

Optional:

If you plan to add media_players upstairs then make a group to represent them (group.players_upstairs) and modify the template as follows:

    - service: tts.google_say
      data_template:
        entity_id: >-
              {% set players = expand('group.players_main') | list %}
              {% set total = players | count %}
              {% set not_playing = players | selectattr('state','!=','playing') | list %}
              {% if not_playing | count == total %}
                {% set floor = 'group.players_upstairs') %}
              {% else %}
                {% set floor = 'group.players_main') %}
              {% endif %}
              {{ expand(floor) | selectattr('state','!=','playing') | map(attribute='entity_id') | join(', ') }}
        message: '{{tts}}'

EDIT
Corrected typos. Replaced 'state','!=','players' with 'state','!=','playing'

little typo, should be ‘playing’ for select attr

Thanks! I was testing the templates using existing lights and then modified, all too quickly, for use with media_players (of which I have none for testing purposes).

Yah, I figured. I do the same thing and always add the same kind of typos

Amazing!

Two of the best template masters on the community collaborating on the same post haha

Great job guys.

… and since this thread is about your media_players, here’s how your scripts appear when they use groups and the expand function. They are reduced in size, easier to read, and easier to maintain.

Groups

  players_all:
    name: Media Players All
    entities:
      - media_player.banheiro_social
      - media_player.banheiro_da_suite
      - media_player.cozinha
      - media_player.quarto
      - media_player.sala
      - media_player.suite

  players_main:
    name: Media Players Main
    entities:
      - media_player.banheiro_social
      - media_player.cozinha
      - media_player.quarto
      - media_player.sala

Scripts

ghm_play_media:
  sequence:
    - condition: template
      value_template: "{{ not is_state('media_player.casa_inteira' , 'playing')}}"

    - service: media_player.volume_set
      data_template:
        entity_id: >-
          {{ expand('group.players_all') | selectattr('state','!=','playing') | map(attribute='entity_id') | join(', ') }}
        volume_level: '{{volume}}'

    - service: media_player.play_media
      data_template:
        entity_id: >-
          {% set players = expand('group.players_all') | list %}
          {% set not_playing = players | selectattr('state','!=','playing') | list %}
          {% if is_state('input_boolean.boa_noite', 'on') %}
            {{ expand('group.players_main')  | selectattr('state','!=','playing') | map(attribute='entity_id') | join(', ') }}
          {% else %}
              {% if not_playing | count == players | count %}
                  media_player.casa_inteira
              {% else %}
                {{ not_playing | map(attribute='entity_id') | join(', ') }}
              {% endif %}
          {% endif %}
        media_content_type: 'audio/mp3'
        media_content_id: '{{media}}'

ghm_tts:
  sequence:
    - condition: template
      value_template: "{{ not is_state('media_player.casa_inteira' , 'playing')}}"

    - service: media_player.volume_set
      data_template:
        entity_id: >-
          {{ expand('group.players_all') | selectattr('state','!=','playing') | map(attribute='entity_id') | join(', ') }}
        volume_level: '{{volume}}'

    - service: tts.amazon_polly_say
      data_template:
        entity_id: >-
          {% set players = expand('group.players_all') | list %}
          {% set not_playing = players | selectattr('state','!=','playing') | list %}
          {% if is_state('input_boolean.boa_noite', 'on') %}
            {{ expand('group.players_main')  | selectattr('state','!=','playing') | map(attribute='entity_id') | join(', ') }}
          {% else %}
              {% if not_playing | count == players | count %}
                  media_player.casa_inteira
              {% else %}
                {{ not_playing | map(attribute='entity_id') | join(', ') }}
              {% endif %}
          {% endif %}
        message: '{{tts}}'
4 Likes

Thank you very much!

I was about to try this solution this weekend and you’ve helped me a great deal!

I’ll let you know if it works :slight_smile:

Thanks again!

Hello guys!

I was only able to deploy this solution as of today :slight_smile:

It works beautifully! Great job!

Thanks a lot again.

1 Like

This is a very nice piece of work. I am working on implementing right now.
I assume the service call for above script’s would be:

 - service: script.ghm_play_media
   data_template:    
     media: 'http://path to file'
     volume: 5   

And the call for tts would be:

 - service: script.ghm_tts
   data_template:    
     tts: 'make me smile'
     volume: 5   

My question is can the volume be templated as well? I usually don’t mess with the volume of my speakers (I have google minis) but if the volume is accidentally turned all the way down, they can’t hear anything. I was thinking an option where the script sets the volume ONLY if current is lower than requested. So I send volume 5 and if the volume is currently at 3, it bumps it to 5, but if it’s at 7, it leaves it alone. Is that possible??
As it is I will be commenting out the volume setting, but this would be nice option in this new awesome tool.
I plan on sending this out as YouTube video in the near future assuming there are no objections. This problem is everywhere out there and I would like to spread the word. I will of course site this article and pull people here for further information.
Thanks for the great work!
https://www.WhatAreWeFixing.Today/

As we are now over a year since this thread,
Did any one come up with a “out of the box” solution for that? blueprint or add-ons of any kind?