I’m not really jinja expert really, so there may be a better way than below.
For the first scenario, just check if loop.first & loop.last
For the second scenario, do what we were doing before but add that last flavor text with another loop.last check
For the last, check the loop.length vs the length of the collection.
{% for entity in states.group.entrypoints.attributes.entity_id if states(entity, 'open') %}
{% if loop.first and loop.last %}
{{ states[entity.split('.')[0]][entity.split('.')[1]].name }} is open
{% elif loop.length == length(states.group.entrypoints.attributes.entity_id) %}
All doors and windows are open
{% break %}
{% else %}
{% if loop.last %}, and {% elif loop.first %}{% else %}, {% endif %}
{{ states[entity.split('.')[0]][entity.split('.')[1]].name }}
{{ "are open" if loop.last }}
{% endif %}
{% endfor %}
One note, is that in the developer tab {% break %} and length() don’t work, I do not know if it works elsewhere but according to the jinja2 documentation it should exist.
The length function can easily be replaced by a magic number (in other words, 10), but the break would be messy to replace. You’d have to do something like check loop.length in every case and add a check for loop.first when printing “All…” to prevent it from printing multiple times.
Thanks for your help. From there, I manage to make it happen with this piece of code…
{% for entity in states.group.entrypoints.attributes.entity_id if is_state(entity, 'open') %}
{%- if loop.first and loop.last %}
{{ states[entity.split('.')[0]][entity.split('.')[1]].name }} is open
{% elif loop.length == states.group.occupants.attributes.entity_id|length %}
{{ "All doors and windows are open" if loop.last }}
{% else %}
{%- if loop.first %}{% elif loop.last %} & {% else %}, {% endif -%}
{{ states[entity.split('.')[0]][entity.split('.')[1]].name }}{{ " are open" if loop.last }}
{%- endif -%}
{% else %}
All doors and windows are close
{% endfor %}
entrypoints_open:
friendly_name: 'Which entry points are open?'
value_template: >-
{% for entity in states.group.entrypoints.attributes.entity_id if is_state(entity, 'open') %}
{%- if loop.first and loop.last %}
{{ states[entity.split('.')[0]][entity.split('.')[1]].name }} is open
{% elif loop.length == states.group.occupants.attributes.entity_id|length %}
{{ "All doors and windows are open" if loop.last }}
{% else %}
{%- if loop.first %}{% elif loop.last %} & {% else %}, {% endif -%}
{{ states[entity.split('.')[0]][entity.split('.')[1]].name }}{{ " are open" if loop.last }}
{%- endif -%}
{% else %}
All doors and windows are close
{% endfor %}
entity_id:
- sensor.front_door
- sensor.living_room_window
- sensor.back_door
- sensor.kitchen_window
Basically, the entity_id follows the same entity_id in your group.
Just for the sake of it being a challenge, I wrote a one-liner that lists all entities of a group (group.entrypoints) that currently have a specific state (open):
so cool, use this for seeing which of my motion sensors is sensing motion… and lights , and people home… the use is endless! You should create a component out of this… just fill in the group, and the filter as properties and your off. wow.
Thanks @dale3h and @petro, these are some nice one-liners!
While doing a non-scientific speed comparison in the template editor, the first one feels a tad sluggish however as 2 dictionaries go through 2 type changes to lists. The second one is already a bit faster and to the point, but it still has to filter through the whole list of states.
When using this as a template sensor, since the sensor will only be updated based on changes to the group and not for each member entities, we still need to list these entities inside an entity_id property so that the sensor gets updated as we want. So we have a list of entities in the group, and we need to copy that list for the sensor. This somewhat defeats the purpose of automagically detecting which entity has changed state by just pointing to the group. Bummer.
Since we need to list the entities anyway, instead of scanning the whole list of states and mapping them against the members of the group, it becomes much faster to simply list the group’s entities and then scan for their state. No need for an entity_id property either, as the parser can already detect the entities named within the template.
This would then result in a very fast template sensor for group.entrypoints that would get updated whenever one of its entities’ state has changed:
The time difference on these should be negligible on just about any system. The delay you are seeing in template is because of how templates work.
Templates only update when an entity inside the equation used in a template updates. Because you are only using 1 entity (group.entrypoints), the template will only update when it see’s changes in the group. Not when changes occur to the groups entities.
So in your method, you list out all the states. That will be instant because the template is scanning for changes in all the underlying entities.
I’m with you on the mechanics of template parsing!
The delay I perceive when testing on a Raspberry Pi is 0.5 to 1 seconds when using the entity_id sensor property and scanning the group with a sensor setup as follows:
…versus almost instant by just scanning the entities. No biggie for most sensors, scripts or automations, but some time-sensitive implementations would benefit from the reduced delay (say, play a chime when opening a door).
{{ entities|selectattr('attributes.icon', 'equalto', 'mdi:door') }} or other more relevant attributes work, but the parser won’t update this template if the entity’s state hasn’t changed.
nice!
could you enhance this with a filter showing name, and another attributes, more specifically last_changed?
Im trying this template for finding all lights on, and their last_changed attribute…
Your best case scenario is to make 2 lists and iterate througth them. You can’t map 2 values.
{% set mystates = states|selectattr('entity_id', 'in', state_attr('group.all_lights_only', 'entity_id')) |selectattr('state', 'equalto', 'on') | list %}
{% set names = mystates | map(attribute='name') | list %}
{% set times = mystates | map(attribute='last_updated') | list %}
{% for i in range(mystates | length) %}
{{ names[i] }} updated at {{ times[i] }}
{% endfor %}
there really isn’t any way to shorten it without making custom filters. No clue how to make them inside the HA environment.
cool, working nicely!
made a small adjustment to make the timestamp more readable and in time with the local timezone, and tidy-up the listing, otherwise: into the cookbook it goes!
{% set mystates = states|selectattr('entity_id', 'in', state_attr('group.all_lights_only', 'entity_id')) |selectattr('state', 'equalto', 'on') | list %}
{% set names = mystates | map(attribute='name') | list %}
{% set times = mystates | map(attribute='last_updated') | list %}
{% for i in range(mystates | length) %}
{{ names[i] }} updated at {{ as_timestamp(times[i])|timestamp_custom("%d %b: %X") }}
{%- endfor %}