Looping through states in an automation?

Hello,

I’ve read the docs and some of the examples in the forum which aren’t quite the same.

I have a bunch of sensors with battery states, and want to trigger notifications when their batteries are running low. I have a lot of sensors and add to them frequently, so I don’t want to have to maintain a hard coded group.

The following template works really well (95% set for demonstration):

{% for state in states if state.entity_id.endswith("_power") and not state.entity_id.endswith("energy_power") -%}
{% if states(state.entity_id) | int < 95 %}
  {{ state.entity_id }} is running at {{ states(state.entity_id) }}%
{% endif %}
{%- endfor -%}

which looks like this:

image

What I would then like to do is trigger off a notification, every morning at 9am:

service: notify.mobile_app_pixel_2
data:
  title: 'Low battery: {{state.entity_id}}'
  message: '{{ state.entity_id }} is running at {{ states(state.entity_id) }}'

When I try to combine both into the Automation → Action → Edit as YAML GUI:

{% for state in states if state.entity_id.endswith("_power") and not state.entity_id.endswith("energy_power") -%}
{% if states(state.entity_id) | int < 95 %}
service: notify.mobile_app_pixel_2
data:
  title: 'Low battery: {{state.entity_id}}'
  message: '{{ state.entity_id }} is running at {{ states(state.entity_id) }}'
{% endif %}
{%- endfor -%}

it appears this isn’t the “HA way” to accomplish what I’m looking for.

I’d love to keep this automation inside YAML rather than use python scripts, but if that’s not possible thanks for letting me know.

Amadeus

Like this one? Only thing needed is to make the Template Sensor include the current battery level of each “low” device.

1 Like

Ah I was searching for posts about loops within automations, as opposed to loops within template sensors (or indeed the use case of checking for low batteries).

What a brilliant solution. I took some time to unpick it line by line and update some of the syntax to the “modern configuration”.

At 6am it will generate the count of low sensors as well as the text as per your example.

At 8am it will trigger notifications both to HA and to my phone as per your example.

template.yaml

- trigger:
    - platform: time_pattern
      hours: 6
      minutes: 0
  sensor:
    - name: "Low battery alert"
      state: >
        {%- set threshold = 40 -%}
        {%- set ns = namespace(count=0) -%}
        {%- for state in states if state.entity_id.endswith("_power") and not state.entity_id.endswith("energy_power") -%}
          {%- if states(state.entity_id) | int < threshold -%}
            {%- set ns.count = ns.count + 1 -%}
          {%- endif -%}
        {%- endfor -%}
        {{ ns.count }}
      unit_of_measurement: "sensors"
      attributes:
        message: >
          {%- set threshold = 40 -%}
          {% set ns = namespace(low=[]) %}
          {%- for state in states if state.entity_id.endswith("_power") and not state.entity_id.endswith("energy_power") -%}
            {%- if states(state.entity_id) | int < threshold -%}
              {% set ns.low = ns.low + [ state.name[:-6] ~ ' (' ~ state.state | int ~ '%)'] %}
            {%- endif -%}
          {%- endfor -%}
          {{ ns.low | join(', ') }}

automations.yml:

- id: '1642514516320'
  alias: Low battery warning
  description: Sends notification for low sensor batteries
  trigger:
  - platform: time
    at: 08:00
  condition:
  - condition: template
    value_template: '{{states(''sensor.low_battery_alert'')|int > 0}}'
  action:
  - service: notify.mobile_app_pixel_2
    data:
      title: Low battery alert
      message: '{% set phrase = ''s are '' if states(''sensor.low_battery_alert'')|int
        > 1 else '' is '' %} The following sensor{{ phrase }} low: {{ state_attr(''sensor.low_battery_alert'',
        ''message'') }}'
  - service: notify.persistent_notification
    data:
      title: Low battery alert
      message: '{% set phrase = ''s are '' if states(''sensor.low_battery_alert'')|int
        > 1 else '' is '' %} The following sensor{{ phrase }} low: {{ state_attr(''sensor.low_battery_alert'',
        ''message'') }}'
  mode: single

If you’re interested, I believe you can replace this for-loop:

      state: >
        {%- set threshold = 40 -%}
        {%- set ns = namespace(count=0) -%}
        {%- for state in states if state.entity_id.endswith("_power") and not state.entity_id.endswith("energy_power") -%}
          {%- if states(state.entity_id) | int < threshold -%}
            {%- set ns.count = ns.count + 1 -%}
          {%- endif -%}
        {%- endfor -%}
        {{ ns.count }}

with this:

      state: >
        {{ states | selectattr('entity_id', 'search', '^(?!.*energy).*_power$')
          | map(attribute='state') | map('int', 0) | select('<', 40) | list | count }}

You might want to copy-paste both into the Template Editor and compare their results to confirm they agree.

BTW, if the entities you wish to monitor are exclusively sensors, it would be best (for efficiency’s sake) to change the initial states in the template to states.sensor.

1 Like

Brilliant. Here is the final working code:

- trigger:
    - platform: time_pattern
      hours: 6
      minutes: 0
  sensor:
    - name: "Low battery alert"
      state: >
        {%- set threshold = 40 -%}
        {{ states.sensor | selectattr('entity_id', 'search', '^(?!.*energy).*_power$')
                  | map(attribute='state') | map('int', 0) | select('<', threshold) | list | count }}
      unit_of_measurement: "sensors"
      attributes:
        message: >
          {%- set threshold = 40 -%}
          {% set ns = namespace(low=[]) %}
          {%- for state in states.sensor if state.entity_id.endswith("_power") and not state.entity_id.endswith("energy_power") -%}
            {%- if states(state.entity_id) | int < threshold -%}
              {% set ns.low = ns.low + [ state.name[:-6] ~ ' (' ~ state.state | int ~ '%)'] %}
            {%- endif -%}
          {%- endfor -%}
          {{ ns.low | join(', ') }}

Obligatory reference to this meme :smiley:

1 Like