I’m not sure if this warrants a new WTH post or not, but personally, I wish this post was titled:
“WTH, why do I need a complex template to get the number of entities with some attribute value?”
For example, in order to count the number of plant sensors reporting low moisture, I have to do this:
dehydrated_plants:
friendly_name: "Plants needing water"
entity_id: sensor.date
value_template: >-
{% set ns = namespace(count = 0) %}
{% for plant in states.plant if 'moisture low' in plant.attributes.problem %}
{% set ns.count = ns.count+1 %}
{% endfor %}
{{ns.count}}
I posted about this here .
This is a serious “WTH” to me, and is not limited to persons, or device trackers.
123
(Taras)
September 1, 2020, 7:17pm
22
Post pseudo-code to demonstrate how you envision it would achieve the same result but with less code.
I’ve voted for this, as it would be nice to have something better than “zoning…” as the state for a zone, and not need to use a template at all, but a template to get the number of people at home can actually be relatively simple like this:
{{ states.person | selectattr("state","equalto","home") | list | count}}
petro
(Petro)
September 1, 2020, 8:07pm
24
that template is only complicated because you have to check to see if ‘moisture low’ is in plant.attributes.problem. If we mad a built in test called ‘contains’, that would shorten the template to
{{ states.plant | selectattr('attributes.problem', 'contains', 'moisture low') | list | count }}
2 Likes
Sure, I’ll take a stab at it.
I realize HA is limited by what JINJA allows, so the solutions I see are either a new count
sensor:
sensor:
- platform: count
name: "Dehydrated Plants"
items_template: {{ states.plant }} # Required
filter_template: {{ 'moisture low' in item.attribute }} # Optional
# Or more generic "math" or "aggregate" sensor
sensor:
- platform: math
name: "Dehydrated Plants"
operation: count # sum, concat, avg, min, max, etc
items_template: {{ states.plant }} # Required
filter_template: {{ 'moisture low' in item.attribute }} # Optional
Or create a new JINJA test called “contains”
dehydrated_plants:
friendly_name: "Plants needing water"
entity_id: sensor.date
value_template: >-
{{ states.plant | map(attribute='attributes') | map(attribute='problem') | select('contains','moisture low, conductivity low') | list | count }}
EDIT Or what Petro said . His template syntax is much prettier too.
123
(Taras)
September 1, 2020, 8:13pm
27
Ansible has that.
I recall trying to solve this in Jinja2 and every tantalizing lead led me to … an Ansible example!
https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#test-if-a-list-contains-a-value
So if any developer wants kudos, please add a contains
test.
petro
(Petro)
September 1, 2020, 8:17pm
28
I also want a new select attr that casts…
{{ states | select_attr_cast('state', 'int', '<', 47) }}
123
(Taras)
September 1, 2020, 8:21pm
29
Yes, that’s a sonvagun I encountered recently where I wanted to select all entities whose state value (a number) is less than a threshold. Sounds simple until one realizes state value are strings and have to be cast to integer before performing the test.
If I use this it gets all values below a threshold but I lose visibility of which entities have these values.
{{ states.sensor
| selectattr('attributes.device_class', 'eq', 'temperature')
| map(attribute='state')
| map('int')
| select('lt', 24)
| list
}}
I believe iterating through the entities with a for-loop is the only way to get what I want (list of entity_ids).
{% set ns = namespace(t=[]) %}
{% for s in states.sensor
| selectattr('attributes.device_class', 'eq', 'temperature')
if s.state|int < 24 %}
{% set ns.t = ns.t + [s.entity_id] %}
{% endfor %}
{{ ns.t }}
The more I think about it, the more I wonder if making map/filter/reduce operations for collections would have more universal value?
Something like this:
sensor:
- platform: aggregator
type: filter
name: "Sensors That Count Things"
collection: {{ states.sensor }}
operation: {{ 'counter_' in item.entity_id }}
# returns [<sensor.counter_foo ...>, <sensor.counter_bar ...>]
- platform: aggregator
type: map
name: "Current Counter Sensor Values"
collection: {{ states.sensor.sensors_that_count_things }}
operation: {{ item.state }}
# returns [10, 0, 27, 5] (those sensor's states)
- platform: aggregator
type: reduce
name: "Sum of all counter sensors"
collection: {{ states.sensor.sensors_that_count_things }}
operation: {{ prev + item.state }}
# returns "42" (the sum of those states)
- platform: aggregator
type: map
name: "New Sensor Counts"
collection: {{ states.sensor.sensors_that_count_things }}
operation: {{ item.state + 1 }}
# returns [11, 1, 34, 6] (original states incremented by one)
Kinda funky. But would effectively allow users to create their own jinja “select” tests (like “contains”) on the fly