Templating Question (I can't think of a way to encapsulate it in one line)

In a given service, with a list of entities to act upon which has generated by a template, is it possible to have the target value for these entities set by a template based on upon their current value?

Specifically, I want to fade out (one or more of) my media players. I have a working script which is passed a list of media players to fade using the volume_down service (see below).

However there are a couple of drawbacks with this.

Firstly it seems the step used in volume_down is very small (and as far as I can tell not configurable).
Secondly, if two or more media players are at different volumes they will not reach zero volume at anything like the same time.

Looking at what I have below (which works albeit with the above drawbacks) I want to know if it is possible to change it use:

service: media_player.volume_set and have the volume_level calculated as the current volume for each entity less 10% (for example).

I can handle the looping, I just want to know if there is any way to refer in volume_level to the entity currently being acted upon from the list in 'entity_id`? (I suspect not)

As another (unlikely) alternative, is there a way to call a script (to do the fading, running in parallel mode) for each of the entities originally passed in as a list?

Of course all this would be moot (at least for Sonos users) if the group volume was exposed and could be changed!


Here is what I have now:

  media_player_fade_out:
    sequence:
      repeat:
        while:
          - condition: template
            value_template: >
              {% set ns = namespace(not_faded=False) %}
              {% for sonos in sonos_used %}
                {% if state_attr(sonos, 'volume_level') != 0 %}
                  {% set ns.not_faded = True %}
                {% endif %}
              {% endfor %}
              {{ ns.not_faded }}

        sequence:
          - service: media_player.volume_down
            data:
              entity_id: >
                {% set ns = namespace(entities='') %}
                {% for sonos in sonos_used if state_attr(sonos, 'volume_level') != 0 %}
                  {% set ns.entities = ns.entities ~ sonos %}
                  {% if not loop.last %}
                    {% set ns.entities = ns.entities ~ ', ' %}
                  {% endif %}
                {% endfor %}
                {% if ns.entities == '' %}
                  none
                {% else %}
                  {{ ns.entities }}
                {% endif %}

what’s sonos_used? A entity_id list?

Yes it is.
e.g.

- media_player.sonos_kitchen
- media_player.sonos_sitting_room

No clue what you’re asking here. I’m assuming you’re asking if you can do a single command to multiple entities with multiple values. Which is a no.

Anyways, as for your current script, you should move away from namespace if you can. These are functionally identical to what you currently have.

  media_player_fade_out:
    sequence:
      repeat:
        while:
          - condition: template
            value_template: >
              {{ expand(sonos_used) | selectattr('attributes.volume_level', '!=', 0) | list | length > 0 }}

        sequence:
          - service: media_player.volume_down
            target:
              entity_id: >
                {{ expand(sonos_used) | selectattr('attributes.volume_level', '!=', 0) | map(attribute='entity_id') | list }}
            data:

But you’ll have to loop each one individually from it’s current position if you want to use volue_set over volume_down. You should separate the script as well, so that you supply it a single list.

turn_down_for_what:
  variables:
    media_player_list: "{{ media_players | default([]) }}"
  sequence:
    - condition: template
      value_template: "{{ media_player_list | length != 0 }}"
    - repeat:
        count: "{{ media_player_list | length }}"
        sequence:
        - variables:
            current_entity_id: "{{ media_player_list[repeat.index - 1] }}"
            volume_level: >
              {% set current = state_attr(current_entity_id, 'volume_level') %}
              {% set next = current - 0.1 %}
              {{ next if next > 0 else 0 }}
        - service: media_player.volume_set
          target:
            entity_id: "{{ current_entity_id }}
          data:
            volume_level: "{{ volume_level }}"
  media_player_fade_out:
    sequence:
    - repeat:
        while:
          - condition: template
            value_template: "{{ expand(sonos_used) | selectattr('attributes.volume_level', '!=', 0) | list | length > 0 }}"
        sequence:
        - service: script.turn_down_for_what
          data:
            media_players: "{{ expand(sonos_used) | selectattr('attributes.volume_level', '!=', 0) | map(attribute='entity_id') | list }}"
1 Like

Sorry for the delay. Real life etc…
I don’t have time to digest this yet but thanks a lot.

(I hope I have my terminology right here…)

Is it possible to create a 2 dimensional list or ‘map’ (what I would call an array)?

I have the list of entity ids and want to create a ‘map’ like this of entity ids and volumes.

"media_player.sonos_kitchen": "0.22", "media_player.sonos_dining_room": "0.2"


I can get the volumes easily enough:

{% set sonos_used = 'media_player.sonos_kitchen', 'media_player.sonos_dining_room' %}
{% set volumes = expand(sonos_used) | selectattr('entity_id') | map(attribute='attributes.volume_level') | list %}

There’s a number of ways you can do this, but none of them are easy because jinja tries to forbit you from creating things like this.

The easiest would be using namespace

{% set used = 'media_player.sonos_kitchen', 'media_player.sonos_dining_room' %}
{% set ns = namespace(values=[]) %}
{% for e in expand(used) %}
  {% set ns.values = ns.values + [(e.entity_id, e.attributes.volume_level or 0)] %}
{% endfor %}
{% set mapper = dict(ns.values) %}
{{ mapper }}
1 Like

Just keep in mind that the previous template will treat media playsers that are off as a volume level of 0. If you want to filter out players that are off, just add | selectattr('state','eq','on') in the for loop line.

1 Like