Better way to get the number of entities who have an attribute "x" containing a given string?

I’d like a sensor that tells me how many state.plant sensors have the word “moisture” in their “problem” attribute.

E.g:

 states.plant.lettuce.problem = 'moisture low, conductivity low'
 states.plant.pepper.problem = ''
 states.plant.carrots.problem = 'conductivity low'
...
 states.sensor.problem_count = 1

Currently, I have this:

dehydrated_plants:
      friendly_name: "Plants needing water"
      value_template: >-
         {% set count = 0 %}
         {% for plant in states.plant if 'moisture low' in plant.attributes.problem %}
         {% set count = count+1 %}
         {% endfor %}
         {{count}}

This works, but I feel like there must be a more elegant template syntax to accomplish this.

Any ideas?

Actually, that will only produce a result on startup and never update itself again (until the next restart). The reason is because the template contains no identifiable entities.

On startup, Home Assistant examines the template and assigns a “listener” to each identifiable entity. The listener detects when the entity changes state and causes the template the be re-evaluated (refreshed/updated).

If it cannot find entities, it assigns no listeners, evaluates the template once to produce a result, and then never evaluates the template again (until the next restart).

A common way to mitigate the issue is to specify an entity that changes its state periodically. For example, sensor.time changes state every minute and sensor.date changes state at the beginning of every day (moments after midnight). So the following Template Sensor would be evaluated once a day (and this assumes you have already configured sensor.date):

dehydrated_plants:
      friendly_name: "Plants needing water"
      entity_id: sensor.date
      value_template: >-
         {{ etc ...

The second issue is that the variable count will not report the correct value. That’s because variables inside a for-loop don’t retain their value outside the for-loop.

You can prove it to yourself by copy-pasting the following into the Template Editor:

{% set count = 0 %}
{%- for count in range(0,5) -%}
  {%- set count = count+1 -%}
  Inside the for-loop: {{ count }}
{% endfor %}
 
Outside the for-loop: {{count}}

You have to use a namespace to create a variable that retains its value after exiting the for-loop.

{% set ns = namespace(count = 0) %}
{%- for count in range(0,5) -%}
 {%- set ns.count = ns.count+1 -%}
 Inside the for-loop: {{ ns.count }}
{% endfor %}
 
Outside the for-loop: {{ns.count}}

If we combine everything I’ve described above, we get the following:

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

Finally, to answer your original question, using a for-loop to calculate the total is the only way because there is no Jinja2 filter in Home Assistant that allows you to select entities based on a free-form sub-string match (i.e. match for sub-string ‘cat’ in string ‘bats cats hats’).

To be precise, there is a filter to match a sub-string but only if it is at the beginning (startswith) or end (endswith) of the string. So if you know for certain that ‘moisture low’ is always at the beginning of string then I might be able to create a simpler template (maybe). It’s not a filter but a string method so, nope.


EDIT

This is a bit of a kludge but if ‘moisture low’ is always at the beginning of the value in the problem attribute, you can try this (paste it into the Template Editor; you will obviously need some plant sensors to be reporting ‘moisture low’ to get meaningful results):

{{ states.plant
  | selectattr('attributes.problem', 'ge', 'moisture low')
  | selectattr('attributes.problem', 'le', 'moisture lowz')
  | list | count }}
3 Likes

Actually, that will only produce a result on startup and never update itself again (until the next restart). The reason is because the template contains no identifiable entities.

Correct. I have automations that call homeassistant.update_entity to force the update when certain sensors update.

But it’s good to know that the reason why it doesn’t update is because of lack of identifiable entities! That was a problem in other sensors of mine. Adding entity_id: sensor.date is certainly cleaner, so I’ll play around with that.

The second issue is that the variable count will not report the correct value. That’s because variables inside a for-loop don’t retain their value outside the for-loop.

Ah! Yes, you’re right. I’m too used to languages with nested scoping support.

Thank you for the help and the corrected template. Glad (and annoyed) to hear that the for loop approach is the best you can do.