Data_template - Need help formatting this

{% 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.

yes, that’s my common practice also.
The reason I want the safeguard in the template sensor also, is just for cosmetic reasons: don’t want a sensor with an empty state…

btw, not sure if this is needed in your case, but in my setting I need the |lower pipe:

{% set ns.speakers = ns.speakers ~ d ~ 'media_player.googlehome_' ~ rooms[i]|lower %}

resulting in this full template sensor:

      media_players_active:
        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
        entity_id:
          - input_boolean.player_woonkamer
          - input_boolean.player_hall
          - input_boolean.player_master_bedroom
          - input_boolean.player_hobbykamer
          - input_boolean.player_office
          - input_boolean.player_dorm_marte
        value_template: >
          {% set rooms = states.input_boolean
            |selectattr('entity_id','in',state_attr('group.activate_media_players','entity_id'))
            |selectattr('state','eq','on') |map(attribute='name')|list %}
          {% 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]|lower %}
            {% endif %}
          {% endfor %}
          {% if ns.speakers|length|int == 0 %} None
          {% else %}
          {{ ns.speakers }}
          {% endif %}

and a little customization (the icons could/should theoretically be set in the template sensor, but experience tells me they react quicker when set in customize…)

homeassistant:
  customize_glob:
    input_boolean.player_*:
      templates:
        icon: >
          if (state === 'on') return 'mdi:play';
          return 'mdi:stop';
        icon_color: >
          if (state === 'on') return 'rgb(255, 255, 0)';
          return 'grey';

  customize:
    sensor.media_players_active:
      templates:
        icon: >
          if (state === 'None') return 'mdi:volume-off';
          return 'mdi:radio';
        icon_color: >
          if (state === 'None') return 'grey';
          return 'rgb(255, 255, 0)';

using this play script:

  play_radio:
    alias: Play radio
    sequence:
      - condition: template
        value_template: >
          {{not is_state('sensor.media_players_active','None')}}
      - service: media_player.volume_set
        data_template:
          entity_id: >
            {{states('sensor.media_players_active')}}
          volume_level: >
            {{ states('input_number.radio_volume')|float }}
      - service: media_player.play_media
        data_template:
          entity_id: >
            {{states('sensor.media_players_active')}}
          media_content_id: >
            {{states('sensor.radio_station')}}
          media_content_type: 'music'

(could also have used the group in the condition, by for now chose to keep it at the sensor itself)

1 Like

If your entity names include uppercase characters (which, in retrospect, is often true) then you can avoid having to use lower by simply making a small modification.

Change map(attribute='name') to map(attribute='object_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 %}

The entity’s object_id is always in lowercase.


EDIT
I’ve updated the template in my previous post to include this modification.

so true, which in this case is the way to proceed.
see: State objects - Home Assistant

I always use name so I get the friendly_name in the template, which was the reason I used it in my posted example, for creating a list presented in the frontend.

Should have changed it in the template sensor, which main purpose is creating the comma separated list for the script…
sorry. glad you spotted that.

reason I didn’t use it here though is that my booleans start with player_ and then the room name (already have booleans for these rooms…might have to rename them … )

or use:

{% set rooms = states.input_boolean
            |selectattr('entity_id','in',state_attr('group.activate_media_players','entity_id'))
            |selectattr('state','eq','on') |map(attribute='object_id')|list|replace('player_','') %}

which in our language would be called: being more Roman (catholic) than the Pope :wink:

Here’s a PR that I think you’ll like:
Template: Expand method to expand groups, and closest as filter #23691

Here’s its pending documentation change and an example from it:

{{ ['device_tracker.paulus', 'group.child_trackers'] | expand 
  | selectattr("attributes.battery", 'defined')
  | join(', ', attribute="attributes.battery") }}

yes, I am indeed familiar with that, and the author of the PR. We are at this moment discussing the drop of automatic creation of the all_groups, which I am unhappy to see, since I use these groups rather frequently.

Ive suggested to create templatable groups to be able to template an all group, and to create dynamic groups as I tried couple of posts above in our current thread.
see https://github.com/home-assistant/architecture/issues/177#issuecomment-491502085 for more info on that architecture discussion and here: https://github.com/home-assistant/home-assistant/pull/23789#issuecomment-491173254