I’ve created an automation that synchronizes the volume levels for all Sonos speakers in the same sonos_group. This is done by checking the sonos_group members of the speaker that triggers the automation. To simulate the trigger, I start my template in the editor with:
{% set trigger = namespace(entity_id="media_player.<speaker>") %}
Now I want to exclude the speaker (Move or Roam) that is not on its charging dock. So I create an “reject array” like this:
{% set undocked = namespace(entities=[]) %}
{% for state in state_attr(trigger.entity_id,"sonos_group") if is_state("binary_sensor."+state.split(".")[1]+"_power", "off") %}
{% set undocked.entities = undocked.entities + [ state ] %}
{%- endfor %}
It’s a bit creative, but since it’s not an attribute, I cannot think of an easier way. It however works like a charm and can be validated by entering {{ undocked.entities }}.
Now I want to reject the entities in “undocked.entities” by doing this:
And you guessed it, this doesn’t work and I can’t figure out why…
If I test it manually by "media_player.<speaker that’s in array>" in undocked.entities and it works like expected. It’s probably something small, but I’m probably overlooking it. Anyone any idea?
Edit: I solved it like the post below shows. @123 (Taras) nevertheless explained why the rejectattr didn’t work.
Solved it by making the filter an ‘if not’. Now I don’t have to do the reject anymore. It however remains a question why the rejectattr didn’t work.
{% for state in state_attr(trigger.entity_id,"sonos_group") if not is_state("binary_sensor."+state.split(".")[1]+"_power", "off") %}
{% set undocked.entities = undocked.entities + [ state ] %}
{%- endfor %}
Use expand() to convert the list of media_player entity_ids (in sonos_group) to a generator object containing each media_player’s complete information. Then rejectattr is able to do its work properly.
it stripped away all other information and the only thing remaining is the value of the entity_id key. You have the rejectattr filter looking for a key named entity_id but the map filter discarded all keys.
That’s what I think I tried more or less. Your reject('in', ...) is new to me, so for sure it wasn’t exactly the same. I think my code was something like:
Absolutely correct. I winged that second template and simply copy-pasted the first one without giving any thought about expand. I think you made it as concise as it’s going to get.
Just for fun, and only if you have the time, see if this produces the same results as your first template that creates a “reject array”.
Honestly, it’s no better than the for-loop you’re using (arguably longer and more complex) but, simply as an exercise for myself, I wanted to see if I could do it without a for-loop.
Gets the entity_ids of the sonos group
Finds each entity_id’s device_id
Finds the entity_id of all entities associated with each device_id
Selects only the entity_ids ending with _power
Joins the result to produce a single list of entities (they should be binary_sensors)
Expands each entity_id
Selects only the entity_ids that are off
Collapses it to a list of (media_player) entity_ids
NOTE
I don’t have any Sonos Roam devices so I couldn’t confirm it works.
It works perfectly
Interesting, also for future code. It keeps surprising (in a positive way).
Good to see search supports regex too.
Since I need a list of entities multiple times in one automation (choose condition and the actual “action”) I made it a variable that has all entities I “need”:
variables:
target_ids: >-
{% set target = namespace(entities=[]) %} {% for state in
state_attr(trigger.entity_id,"sonos_group") if not
is_state("binary_sensor."+state.split(".")[1]+"_power", "off") %}
{% set target.entities = target.entities + [ state ] %}
{%- endfor %} {{ target.entities }}
It’s a good practice; many of my automations employ variables. Just keep in mind (to avoid frustration later) that a variable cannot store a generator object. In other words, the result of expand(whatever) cannot be stored directly in a variable.
Good to know indeed
Inspired by your code, I’ve found the way back from power to the media_player. Needed to sync the volume once it’s back on it’s dock again
{{ device_entities(device_id(trigger.entity_id))
| select("search","^media_player") | list
}}
The trigger of course is the change to on (equals “charging”)