Data_template - Need help formatting this

nice!
since I own a couple of extra media-players, I was thinking of a way to create a group dynamically with all the ‘on’ players, and have that in a template, so I could use the group in the entity_id.

Would that be possible?

I have 1 player always on, have a ‘broadcast’ group with all players, and this would be a 3d group, with selected players, made dynamically. Could be done in python I guess, but maybe we don’t need that?

1 Like

I’m guessing the guy I helped did it wrong then. I always thought it worked but I gave up on the guy when he claimed it didn’t. He must have had a typo. Oh well.

1 Like

Truly one of the more challenging aspects of assisting others is to second-guess the reported results in the event the suggestion was applied incorrectly. So not only predict the expected correct but also incorrect outcomes and what may have caused them. Add the fact I’m prone to making erroneous suggestions (case in point: in this very thread) and it all leads to a long session of head-scratching and confusion. Which is why it’s always helpful to have a fresh pairs of eyes available.

I’m reminded of a cartoon from my childhood where the character misreads a giant sign above a building. The first word is “Brain” but the character refers to it as “rain”. When informed of the error, he replies “The ‘B’ was so big that I didn’t see it!” :slight_smile:

2 Likes

That was my original plan and it would make the code so much cleaner and easier to maintain that way. Quite refreshing knowing there’s others out there with similar needs and (think alike) :slight_smile:

Hopefully someone could shed some light on this in the near future as to whether this is easy to achieve…

Neat a.F!
But for documentation purposes
How would you go on this for-loop?

{% set rooms = ['den', 'family', 'master', 'guest', 'bathroom', 'living', 'garage', 'workshop'] %}
{% set ns = namespace(speakers = 'media_player.kitchen') %}
{% for i in range(rooms | length) %}
  {% if states('input_boolean.' ~ rooms[i]) == 'on' %}
    {% set ns.speakers = ns.speakers ~ ', media_player.' ~ rooms[i] %}
  {% endif %}
{% endfor %}
{{ ns.speakers }}

The assumption made in the template is that each room in the rooms list has a corresponding media.player and input_boolean bearing the same name. Example:

media_player.den
input_boolean.den

media player.family
input_boolean.family

etc
  • The template creates a global string variable called speakers and initializes it to media_player.kitchen. This is the default media_player (and needs no corresponding input_boolean).

  • It proceeds to iterate through each item in the rooms list.

  • If a room’s input_boolean is on, it appends the room’s media_player to speakers.

  • After iterating through the list, it reports the contents of speakers.

Here it is in action. I created several input_booleans and set den, master, and bathroom to on.

If the name of each room’s input_boolean was not identical to the room’s name (like input_boolean.den_speaker) then the template would need a small modification. The input_boolean names can be stored in a separate list or even in a different data structure, such as a dictionary. All this to say, the input_boolean’s name doesn’t have to be identical to the room’s name … but it makes the template simpler.


EDIT
Typo. Replaced ', media.player.' with ', media_player.'

1 Like

nice! check for media.player… should be media_player :wink:

now, how to get that list as content for a group?

group:
  media_players_on:
    name: Media players on
    control: hidden
    entities:
      - >
        {{states('sensor.media_players_on')}}

Are there any examples of a group that dynamically defines itself? If there are, I have not seen them.

well not sure, that’s why I asked. But maybe it isn’t necessary.

Ive entered the template in my setup to see what happens. added the entity_id’s in the sensor, so the state changes on each input_boolean state change.

was wondering of we can use an empty list to initiate the template too? dev-template doesn’t object.

but won’t make the group correctly either. this doesn’t work, because it also adds the ‘,’ with 1 entity_id only:

      {% set rooms = ['woonkamer','hall','master_bedroom','hobbykamer','office','dorm_marte'] %}
      {% set ns = namespace(speakers = '') %}
      {% for i in range(rooms | length) %}
        {% if states('input_boolean.player_' ~ rooms[i]) == 'on' %}
        {% set ns.speakers = ns.speakers ~ ' media_player.googlehome_' ~ rooms[i] + ',' %}
        {% endif %}
      {% endfor %}
      {{ ns.speakers }}

SO need to test for only 1 entity_id (to not add the comma) and need a safegard against the empty list, and consequently a none for the sensor…

it does complain against my group…;-((

Testing configuration at /config
Failed config
  group: 
    - Invalid config for [group]: Entity ID {{states('sensor.media_players_on')}}
 is an invalid entity id for dictionary value @ data['group']['media_players_on']['entities']. Got ["{{states('sensor.media_players_on')}}\n"]. (See /config/configuration.yaml, line 57). Please check the docs at https://home-assistant.io/components/group/

Correct. The leading comma should be omitted for the first entity. You can do that by checking if the ns.speakers’s length is non-zero.


EDIT
Corrected description of how to check when to include/exclude a leading comma for building the list.

yes I had been experimenting with that same thought, but it wouldn’t get me past the full loop counter of 6…

Please help me creating that template?

This version creates a list that initially contains no default entry. It determines whether to include/exclude a leading comma based on if the list’s length is non-zero.

{% set rooms = ['kitchen', 'den', 'family', 'master', 'guest', 'bathroom', 'living', 'garage', 'workshop'] %}
{% set ns = namespace(speakers = '') %}
{% for i in range(rooms | length) %}
  {% if states('input_boolean.' ~ rooms[i]) == 'on' %}
    {% set d = ', ' if ns.speakers | length > 0 else '' %}
    {% set ns.speakers = ns.speakers ~ d ~ 'media_player.' ~ rooms[i] %}
  {% endif %}
{% endfor %}
{{ ns.speakers }}

yess! thanks.

only need a safeguard for an empty list…
not sure how to do that best. can test for length == 0 maybe, and give it value Not set or None maybe?
Then I can use a condition for the sensor not being None to run the action in the automation/script…

The ‘safeguard’ is to set a default media_player as was done in the original design.

An alternate ‘safeguard’ is to completely avoid executing the action if all input_booleans are off (i.e. with a condition).

added it t the template for now:

          {% set rooms = ['woonkamer','hall','master_bedroom','hobbykamer','office','dorm_marte'] %}
          {% set ns = namespace(speakers = '') %}
          {% for i in range(rooms | length) %}
            {% if states('input_boolean.player_' ~ rooms[i]) == 'on' %}
            {% set d = ', ' if ns.speakers | length > 0 else '' %}
            {% set ns.speakers = ns.speakers ~ d ~ 'media_player.googlehome_' ~ rooms[i] %}
            {% endif %}
          {% endfor %}
          {% if is_state('group.activate_media_players','off') %} None activated
          {% else %}
          {{ ns.speakers }}
          {% endif %}

using the group with the input_booleans…

can’t use ns.speakers | length == 0 as a test, because it simply returns empty… would be the most direct test of course, now have to rely in the group I made.

Oddly enough this already works, even without any automation at all…

If I turn on the group with the booleans, or even a singel boolean, they start to play immediately… haven’t got a clue why…

I like this and might incorporate it into my system so I started playing with it. This seems to work (in the template editor) without needing a group.

{% set rooms = ['kitchen','dining_room','pool_room','sitting_room'] %}
{% set ns = namespace(sonos = 'None selected') %}
{% for i in range(rooms | length) %}
  {% if states('input_boolean.media_player_' ~ rooms[i]) == 'on' %}
    {% set d = ', ' if ns.sonos| length > 0 and ns.sonos != 'None selected' else '' %}
    {% if ns.sonos == 'None selected' %}
      {% set ns.sonos = '' %}
    {% endif %}
    {% set ns.sonos = ns.sonos ~ d ~ 'media_player.' ~ rooms[i] %}
  {% endif %}
{% endfor %}

{{ ns.sonos }}

well, that works, but should have thought of my old |int trick:

          {% set rooms = ['woonkamer','hall','master_bedroom','hobbykamer','office','dorm_marte'] %}
          {% set ns = namespace(speakers = '') %}
          {% for i in range(rooms | length) %}
            {% if states('input_boolean.player_' ~ rooms[i]) == 'on' %}
            {% set d = ', ' if ns.speakers | length > 0 else '' %}
            {% set ns.speakers = ns.speakers ~ d ~ 'media_player.googlehome_' ~ rooms[i] %}
            {% endif %}
          {% endfor %}
          {% if ns.speakers|length|int == 0 %} None
          {% else %}
          {{ ns.speakers }}
          {% endif %}

|int makes 0 out of nothing :wink:

43
04

cool.

also, since we’re templating niceties:

        value_template: >
          {{ states.input_boolean|selectattr('entity_id','in',state_attr('group.activate_media_players','entity_id'))
            |selectattr('state','eq','on') |map(attribute='name')|join(', ') }}

        value_template: >
          {{ states.input_boolean|selectattr('entity_id','in',state_attr('group.activate_media_players','entity_id'))
            |selectattr('state','eq','on')|list|length }}

have been playing with the for loop to count the ‘on’ booleans, but didn’t get it right, and using the ns.speaker|length won’t work since it is the string of all spelled media players, so doesn’t count the items, but the characters. :wink:

using the above:

        friendly_name_template: >
          {% set count = states.input_boolean
            |selectattr('entity_id','in',state_attr('group.activate_media_players','entity_id'))
            |selectattr('state','eq','on')|list|length %} 
          {% set player = 'player' if count in [0,1] else 'players' %}
          {% set number = 'No' if count == 0 else count %} 
          {{number}} Media {{player}} active

58
14
12

Now how the get the counter in the main template loop…

1 Like

Here’s how to implement the ‘alternate safeguard’ approach I proposed in my previous post.

Advantages:

  • The rooms are defined in a group as opposed to within the data_template.
  • The service is not evaluated if no input_booleans are enabled.

Create a group containing the input_booleans representing each room’s speaker (media_player). This will be used by the condition and the service’s data_template.

  rooms:
    name: Rooms
    entities:
      - input_boolean.kitchen
      - input_boolean.den
      - input_boolean.family
      - input_boolean.master
      - input_boolean.guest
      - input_boolean.bathroom

In your script or in your automation’s condition or in its action, add a condition to test if group.rooms is on. If it’s off, none of the input_booleans is enabled so there’s no need to proceed with the service.

  - condition: template
    value_template: "{{states('group.rooms') == 'on'}}"

Now the data_template can benefit from the availability of group.rooms. All it needs to do is convert it into a list containing just the room names. I borrowed the template @Mariusthvdb created.

  - service: media_player.play_media
    data_template:
      entity_id: >-
        {% set rooms = states.input_boolean | 
           selectattr('entity_id','in',state_attr('group.rooms','entity_id')) | 
           selectattr('state','eq','on') | 
           map(attribute='object_id') | list %}
        {% set ns = namespace(speakers = '') %}
        {% for i in range(rooms | length) %}
          {% if states('input_boolean.' ~ rooms[i]) == 'on' %}
            {% set d = ', ' if ns.speakers | length > 0 else '' %}
            {% set ns.speakers = ns.speakers ~ d ~ 'media_player.' ~ rooms[i] %}
          {% endif %}
        {% endfor %}
        {{ ns.speakers }}
      media_content_id: http://url.com/audio.mp3
      media_content_type: audio/mp3

EDIT
Changed map(attribute='name') to map(attribute='object_id')

yes!
was doing the same at this very moment :wink:
minds alike, cool

this has a huge benefit: only need to add the boolean to the group, and all other templates follow automatically. No more need to edit those when adding or taking out players

Perhaps the seed of the idea was planted yesterday and germinated overnight. :sunflower:

The reason I prefer using a condition is because it’s inherently more efficient. If there are no input_booleans selected, it omits running the service entirely.