WTH, why do I need a complex template the get the number of persons home

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.

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}}

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.

Ha, jinx :wink:

1 Like

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.

I also want a new select attr that casts…

{{ states | select_attr_cast('state', 'int', '<', 47) }} 

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