Automation Trigger: list of devices

Hi,

I would like to create an automation to get a notification if one of my sensors have a battery level below x percent. However I don’t wont to update a group or the list manually, but want a dynamic list, like below:

- alias: Battery state
  trigger:
   - platform: numeric_state
     entity_id: sensor.*_battery_level
     below: '50'

Does anybody know how to achieve this? If this can be done, how would I then get the entity name in my notification to an IOS device?

2 Likes

I’d suggest a template sensor. Play around in the template editor — something like this should give a list of entities that you’re looking for, provided the battery level is the sensor state and not an attribute:

{% for sensor in states.sensor %}
  {{ sensor.entity_id if "_battery_level" in sensor.entity_id and sensor.state|int < 50 }}
{% endfor %}

Then you need to work out a way for the template to return True if there is an entity in the list.

Need to sort out:

Recommendation: use AppDaemon instead. It’s ideal for this sort of more complex logic, but is a bit of a learning curve.

Alternatively, see this topic.

1 Like

I agree with @Troon. However, a template like this is not easy. So, here you go:

binary_sensor:
- platform: template
  sensors:
    battery_alert:
      value_template: >
        {%- set ns = namespace(below=[]) %}
        {%- for s in states.sensor %}
          {%- if s.entity_id.endswith('battery_level') and s.state|int < 50 %}
            {%- set ns.below = ns.below + [ s ] %}
          {%- endif %}
        {%- endfor %}
        {{ ns.below | count > 0 }} 

Then for your automation…

- alias:  Alert when batteries are below 50
  trigger:
  - platform: state
    entity_id: binary_sensor.battery_alert
    to: 'on'
  action:
  - service: notify.yourdevice
    data_template:
      message: >
        {%- set ns = namespace(below=[]) %}
        {%- for s in states.sensor %}
          {%- if s.entity_id.endswith('battery_level') and s.state|int < 50 %}
            {%- set ns.below = ns.below + [ s.attributes.friendly_name ] %}
          {%- endif %}
        {%- endfor %}
        {%- if ns.below | count == 1 %}
          {{ ns.below[0] }} is low!
        {%- elif ns.below | count == 2 %}
          {{ ns.below | join(' and ') }} are low!
        {%- else %}
          {{ ns.below[:-1] | join(', ') }}, and {{ ns.below[-1] }} are low!
        {%- endif %}

DISCLAIMER: This is untested, we may need to make changes.

2 Likes

smallest of typos:

{%- if sensor.entity_id.endswith('battery_level')

should be

{%- if s.entity_id.endswith('battery_level')

working fine. thanks!

suggestion: add the template of the notification message to the binary_sensor in an attributes_template, that way you always have the list available. And you can simply reference that in the automation notification :wink:

{%- set ns = namespace(below=[]) %}
{%- for s in states.sensor %}
  {%- if 'battery_level' in s.entity_id and s.state|int < 50 %}
    {%- set ns.below = ns.below + [ s.attributes.friendly_name + ' (' + s.state + '%)'] %}
  {%- endif %}
{%- endfor %}
{%- if ns.below | count == 1 %}
  {{ ns.below[0] }} is low!
{%- elif ns.below | count == 2 %}
  {{ ns.below | join(' and ') }} are low!
{%- else %}
  {{ ns.below[:-1] | join(', ') }}, and {{ ns.below[-1] }} are low!
{%- endif %}

to make it a bit more flexible in the selection of entities (I use other named battery sensors, so can easily add/adapt to the naming like this.

The suggested binary_sensor has no identifiable entities to monitor to it will only be evaluated on startup.

I believe it may have been petro’s intention to include a time-based entity so that the binary_sensor is periodically refreshed.

If we include sensor.date

    battery_alert:
      entity_id: sensor.date
      value_template: >

then the binary_sensor will be evaluated once a day, moments after midnight.

If we do that, then the automation, instead of being triggered by the binary_sensor, should check its state at a desired time of the day (say, in the morning) otherwise, if there’s a sensor with a low battery, the automation will generate a notification at midnight which might be intrusive and undesirable.

well, happy to report is does evaluate correctly, even without having to set an entity_id

made this little test setup



and it works just fine: if I slide the level up or down, the binary goes on/off, and when on, the notification is sent.
(had to slide down to 0, because I have a 2% phone laying around :wink:

pony downside here is when already on (being unsafe) it wont retrigger if another battery goes below the alert level.

1 Like

Please explain how that’s possible. Where are the identifiable entities within the binary_sensor’s template?

PS
I tested the suggested binary_sensor, using three MQTT Sensors reporting battery level. Publishing a payload value of less than 50 to their state_topic results in no state change for the binary_sensor. That’s an expected result because the binary_sensor’s template contains no identifiable entities (so no listeners can be created to monitor the binary_sensor).

Yeah, you’ll need to add a list of batteries to entity_id or provide a 1 time shot like @123 is saying. From what I remember, expand() on groups now adds the entity_id’s found to the ‘entity_id’ list. So in theory, you could just create a group of battery devices and use this template:

value_template: >
  {%- for s in expand('group.batteries') %}
  ... etc...

It is the slider…

Unfortunately, not yet. It was introduced in 0.110 but, after testing it, I discovered it failed to work. I reported it as an issue and the correction is pending. I don’t know if it will make it into 0.113.

Yes and that makes the following statement misleading given that the discussion is about the OP’s requirements and petro’s binary_sensor:

happy to report is does evaluate correctly, even without having to set an entity_id

Undoubtedly, your template includes the input_number. That’s fine and good but including the entities within the template is not how the OP wishes to have the sensor work.

I’ve combined the suggestions provided by Petro and Mariusthvdb then added a few of my own ideas to create the following sensor and automation. I’ve tested them and confirmed they work.

This Template Sensor’s state reports the number of sensors with low batteries. The names of the corresponding sensors are reported in its battery_low attribute and are stripped of the words “battery level” in order to make them more compact.

The Template Sensor is refreshed by sensor.date (in other words, once a day at midnight). If that is insufficient, there are other ways to make it refresh more frequently.

sensor:
- platform: template
  sensors:
    battery_alert:
      entity_id: sensor.date
      value_template: >
        {% set ns = namespace(below=[]) %}
        {% for s in states.sensor 
              if s.entity_id.endswith('battery_level') and s.state != 'unknown' and s.state|int < 50 %}
          {% set ns.below = ns.below + [ s ] %}
        {% endfor %}
        {{ ns.below | count }} 
      attribute_templates:
        battery_low: >
          {% set ns = namespace(below=[]) %}
          {% for s in states.sensor 
                if 'battery_level' in s.entity_id and s.state != 'unknown' and s.state|int < 50 %}
            {% set ns.below = ns.below + [ s.name[:-14] ~ ' (' ~ s.state ~ '%)'] %}
          {% endfor %}
          {{ ns.below | join(', ') }}

Here is a sample automation that triggers at 08:30. If sensor.battery_level is greater than 0 it proceeds to produce a notification containing the string in the battery_low attribute. It uses the quantity of low-battery sensors to determine if the message should use the word “is” or “are”.

Be advised that, if there are any low batteries, this automation will produce a notification every morning. The advantage is that if the following day an additional sensor battery becomes low, the notification will include it as well. You will be reminded every morning until the batteries are finally replaced (which, depending on your mood, can be perceived to be either an advantage or disadvantage).

- alias:  Report when batteries are low
  trigger:
    platform: time
    at: '08:30:00'
  condition:
    condition: template
    value_template: "{{states('sensor.battery_alert')|int > 0}}"
  action:
    service: notify.notify
    data_template:
      title: 'Low Battery Alert'
      message: >
        {% set phrase = 's are ' if states('sensor.battery_alert')|int > 1 else ' is ' %}
        The following sensor{{ phrase }} low: {{ state_attr('sensor.battery_alert', 'battery_low') }}

If you prefer to be notified the instant a battery becomes low, the following automation will do that. However, because of the way a Template Trigger works, it will only notify you once, the moment the battery_alert sensor reports more than 0. It won’t trigger again if a second or third sensor’s battery becomes low. Before it can trigger again, the battery_alert sensor must first return to 0.

- alias:  Instantly report when a battery is low
  trigger:
    platform: template
    value_template: "{{states('sensor.battery_alert')|int > 0}}"
  action:
    service: notify.notify
    data_template:
      title: 'Low Battery Alert'
      message: >
        {% set phrase = 's are ' if states('sensor.battery_alert')|int > 1 else ' is ' %}
        The following sensor{{ phrase }} low: {{ state_attr('sensor.battery_alert', 'battery_low') }}

Here’s an example of the output after changing:
service: notify.notify
to:
service: persistent_notification.create

Screenshot from 2020-07-17 12-41-20

2 Likes

I translated:

to:

binary_sensor:
  - platform: template
    sensors:
      battery_alert:
        friendly_name: Battery alert
        device_class: safety
        value_template: >
          {%- set ns = namespace(below=[]) %}
          {%- for s in states.sensor %}
            {%- if ('battery_level' in s.entity_id or 'motion_sensor_battery' in s.entity_id)
                and s.state|int < states('input_number.battery_alert_level')|int %}
              {%- set ns.below = ns.below + [ s ] %}
            {%- endif %}
          {%- endfor %}
            {{ns.below|count > 0}}
        attribute_templates:
          Below: >
            {%- set ns = namespace(below=[]) %}
            {%- for s in states.sensor %}
              {%- if ('battery_level' in s.entity_id or 'motion_sensor_battery' in s.entity_id)
                  and s.state|int < states('input_number.battery_alert_level')|int %}
                {%- set ns.below = ns.below + [s.attributes.friendly_name + ' (' + s.state + '%)'] %}
              {%- endif %}
            {%- endfor %}
            {%- if ns.below|count == 0 %} All batteries are fine
            {%- elif ns.below|count == 1 %}
              {{ ns.below[0] }} is low!
            {%- elif ns.below|count == 2 %}
              {{ ns.below|join(' and ') }} are low!
            {%- else %}
              {{ns.below[:-1]|join(', ')}}, and {{ns.below[-1]}} are low!
            {%- endif %}

input_number:
  battery_alert_level:
    icon: mdi:battery-alert
    name: Battery alert level
    min: 0
    max: 100
    step: 5
    unit_of_measurement: '%'

Didn’t want to mislead at all…but yeah, was mistaken about the reason for the binary_sensor to update. realized the the moment I wrote the post. sorry for that.

yes, too bad we can’t create that group dynamically. Or could your appdeamon app to create the all-groups use

          {%- set ns = namespace(below=[]) %}
          {%- for s in states.sensor %}
            {%- if ('battery_level' in s.entity_id or 'motion_sensor_battery' in s.entity_id)
                 %}
              {%- set ns.below = ns.below + [ s.entity_id ] %}
            {%- endif %}
          {%- endfor %}
            {{ns.below}}

or maybe even better (though this also adds the comma to the last somehow):

          {%- for s in states.sensor %}
            {%- if ('battery_level' in s.entity_id or 'motion_sensor_battery' in s.entity_id)
                 %}
              {{ s.entity_id }}{% if not loop.last %}, {% endif %}
            {%- endif %}
          {%- endfor %}

to create the group, and then the binary_sensor could use that group to expand…

Would there happen to be an easy way to exclude any entities with iPhone, iPad, or iPod in the entity_id?

I personally recommend using the Battery Status package from @NotoriousBDG.

Found here: https://raw.githubusercontent.com/notoriousbdg/Home-AssistantConfig/master/packages/battery_alert.yaml

It’s been around for a while but it still works fairly well with a few minor changes.

Here is my battery alert package based on that code:

################################################
## Customize
################################################
homeassistant:
  customize:
    ################################################
    ## Disabled Battery Sensors
    ################################################
    sensor.mbr_motion_battery_level:
      battery_alert_disabled: true
    ################################################
    ## Node Anchors
    ################################################
    package.node_anchors:
      customize: &customize
        package: 'battery_alert'

    ################################################
    ## Group
    ################################################

    group.battery_status:
      <<: *customize
      friendly_name: "Battery Status"
      icon: mdi:battery-charging
      control: hidden

    ################################################
    ## Automation
    ################################################
    automation.battery_persistent_notification:
      <<: *customize
      friendly_name: "Battery Persistent Notification"
      icon: mdi:comment-alert-outline

    automation.battery_persistent_notification_clear:
      <<: *customize
      friendly_name: "Battery Persistent Notification Clear"
      icon: mdi:comment-remove-outline

    automation.battery_notification_default:
      <<: *customize
      friendly_name: "Battery Notification Default Format"
      icon: mdi:comment-alert-outline

    automation.battery_notification_slack:
      <<: *customize
      friendly_name: "Battery Notification Slack Format"
      icon: mdi:comment-alert-outline


################################################
## Input Boolean
################################################
input_boolean:
  low_batteries:
    name: Low Batteries
    icon: mdi:battery-alert

################################################
## Input Number
################################################
input_number:
  battery_alert_threshold_max:
    name: "Max Alert Threshold"
    icon: mdi:arrow-collapse-up
    mode: slider
    min: -1
    max: 100

  battery_alert_threshold_min:
    name: "Min Alert Threshold"
    icon: mdi:arrow-collapse-down
    mode: slider
    min: -1
    max: 100

################################################
## Input Select
################################################
input_select:
  notifier_format:
    name: Notifier Format
    options:
     - Default
     - Slack
    icon: mdi:comment-question-outline

################################################
## Input Text
################################################
input_text:
  notifier_name:
    name: Notifier Name
    mode: text

################################################
## Automation
################################################
automation:
- alias: battery_test_script_notification
  initial_state: 'on'
  trigger:
    - platform: time_pattern
      minutes: '/1'
      seconds: 00
  action:
    - service: script.battery_check

- alias: battery_persistent_notification
  initial_state: 'on'
  trigger:
    - platform: time_pattern
      minutes: '/15'
      seconds: 00
    - platform: state
      entity_id:
        - input_number.battery_alert_threshold_min
        - input_number.battery_alert_threshold_max
  action:
    - condition: template
      value_template: &low_battery_check >
        {% macro battery_level() %}
        {% for entity_id in states.group.battery_status.attributes.entity_id if (
          not is_state_attr(entity_id, 'battery_alert_disabled', true)
          and states(entity_id) is not none
          and (
            (
              (
                states(entity_id) is number
                or states(entity_id) | length == states(entity_id)| int | string | length
                or states(entity_id) | length == states(entity_id)| float | string | length
              )
              and states(entity_id) | int < states.input_number.battery_alert_threshold_max.state | int
              and states(entity_id) | int > states.input_number.battery_alert_threshold_min.state | int
            )
            or states(entity_id) | lower == 'low'
            or states(entity_id) | lower == 'unknown'
            or states(entity_id) | lower == 'unavailable'
          )
        ) -%}
          {{ state_attr(entity_id, "friendly_name") }} ({{ states(entity_id) }})
        {% endfor -%}
        {% endmacro %}
        {{ battery_level() | trim != "" }}
    - service: input_boolean.turn_on
      data:
        entity_id: input_boolean.low_batteries
    - service: persistent_notification.create
      data_template:
        title: "Low Battery Levels"
        notification_id: low_battery_alert
        message: &message >
          {% macro battery_level() %}
          {% for entity_id in states.group.battery_status.attributes.entity_id if (
            not is_state_attr(entity_id, 'battery_alert_disabled', true)
            and states(entity_id) is not none
            and (
              (
                (
                  states(entity_id) is number
                  or states(entity_id) | length == states(entity_id)| int | string | length
                  or states(entity_id) | length == states(entity_id)| float | string | length
                )
                and states(entity_id) | int < states.input_number.battery_alert_threshold_max.state | int
                and states(entity_id) | int > states.input_number.battery_alert_threshold_min.state | int
              )
              or states(entity_id) | lower == 'low'
              or states(entity_id) | lower == 'unknown'
              or states(entity_id) | lower == 'unavailable'
            )
          ) -%}
            {{ state_attr(entity_id, "friendly_name") }} ({{ states(entity_id) }})
          {% endfor -%}
          {% endmacro %}
          {{ battery_level() }}

- alias: battery_persistent_notification_clear
  initial_state: 'on'
  trigger:
    - platform: time_pattern
      minutes: '/15'
      seconds: 00
    - platform: state
      entity_id:
        - input_number.battery_alert_threshold_min
        - input_number.battery_alert_threshold_max
  action:
    - condition: template
      value_template: &low_battery_clear >
        {% macro battery_level() %}
        {% for entity_id in states.group.battery_status.attributes.entity_id if (
          not is_state_attr(entity_id, 'battery_alert_disabled', true)
          and states(entity_id) is not none
          and (
            (
              (
                states(entity_id) is number
                or states(entity_id) | length == states(entity_id)| int | string | length
                or states(entity_id) | length == states(entity_id)| float | string | length
              )
              and states(entity_id) | int < states.input_number.battery_alert_threshold_max.state | int
              and states(entity_id) | int > states.input_number.battery_alert_threshold_min.state | int
            )
            or states(entity_id) | lower == 'low'
            or states(entity_id) | lower == 'unknown'
            or states(entity_id) | lower == 'unavailable'
          )
        ) -%}
          {{ state_attr(entity_id, "friendly_name") }} ({{ states(entity_id) }})
        {% endfor -%}
        {% endmacro %}
        {{ battery_level() | trim == "" }}
    - service: input_boolean.turn_off
      data:
        entity_id: input_boolean.low_batteries
    - service: persistent_notification.dismiss
      data:
        notification_id: low_battery_alert

- alias: battery_notification_default
  initial_state: 'on'
  trigger:
    - platform: time
      at: '10:00:00'
    - platform: time
      at: '18:00:00'
    - platform: state
      entity_id:
        - input_number.battery_alert_threshold_min
        - input_number.battery_alert_threshold_max
  action:
    - condition: template
      value_template: *low_battery_check
    - condition: template
      value_template: "{{ states.input_select.notifier_format.state == 'Default' }}"
    - service_template: >
        {% if "notify." in states.input_text.notifier_name.state %}
          {{ states.input_text.notifier_name.state }}
        {% else %}
          notify.notify
        {% endif %}
      data_template:
        title: "Low Battery Levels"
        message: *message

- alias: battery_notification_slack
  initial_state: 'on'
  trigger:
    - platform: time
      at: '10:00:00'
    - platform: time
      at: '18:00:00'
    - platform: state
      entity_id:
        - input_number.battery_alert_threshold_min
        - input_number.battery_alert_threshold_max
  action:
    - condition: template
      value_template: *low_battery_check
    - condition: template
      value_template: "{{ states.input_select.notifier_format.state == 'Slack' }}"
    - service_template: >
        {% if states.input_text.notifier_name.state != "" %}
          {{ states.input_text.notifier_name.state }}
        {% else %}
          notify.notify
        {% endif %}
      data_template:
        message: "Low Battery Levels"
        data:
          attachments:
          - color: '#52c0f2'
            title: "These devices have low battery levels"
            text: *message

- alias: update_battery_status_group_members
  initial_state: 'on'
  trigger:
    - platform: time_pattern
      minutes: '/1'
      seconds: 00
  action:
    - service: group.set
      data_template:
        object_id: "battery_status"
        entities: >-
          {%- for item in states.sensor if (
            not is_state_attr(item.entity_id, 'hidden', true)
            and (
              is_state_attr(item.entity_id, 'device_class', 'battery')
              or 'battery' in item.attributes.icon | lower
              or (item.entity_id | lower).endswith('_bat')
              or (item.name | lower).endswith('_bat')
              ) or (
                (
                  'battery' in item.entity_id | lower
                  or 'battery' in item.name | lower
                ) and (
                  item.attributes.icon is not defined
                ) and (
                  not is_state_attr(item.entity_id, 'battery_alert_disabled', true)
                )
              )
            )
          -%}
            {{ item.entity_id }}{% if not loop.last %}, {% endif %}
          {%- endfor -%}

sensor:
  platform: template
  sensors:
    my_phone_life360_battery_level:
      friendly_name: My Phone Battery Level from Life360
      value_template: "{{ state_attr('device_tracker.life360_me', 'battery') | round(0) }}"
      unit_of_measurement: "%"
      device_class: battery
    wife_phone_life360_battery_level:
      friendly_name: Wife's Phone Battery Level from Life360
      value_template: "{{ state_attr('device_tracker.life360_wife', 'battery') | round(0) }}"
      unit_of_measurement: "%"
      device_class: battery

it will create a pop up notification on a low battery (below the threshold that you can set in a card that you create from the entities from code above) and you can have it send a notification to your notification service if you want ( it has several built-in you can use).

Here is the notification and status card I use:

ex1

and here is the lovelace card I made to interact with the entities of the package:

ex2

Jayzus. That’s a boatload of code to monitor and report low battery levels.

Sensor batteries last several months (some even longer) so it seems like overkill to monitor their levels every minute. Even if you check once a day, you’ve got days, or even weeks, of advance notice before they go dead. :man_shrugging:

2 Likes

If you check them every minute, you won’t have long before they’re completely flat…

Mommy, are we there yet ?
Mommy, are we there yet ?
Mommy, are we there yet ?
Mommy, are we there yet ?
Mommy, are we there yet ?
Mommy, are we there yet ?

:rofl:

2 Likes

ok, so I am officially confused (again). Had thought to go another route, and create a base template sensor:

sensor:
  - platform: template
    sensors:
      low_battery_level:
        friendly_name: Low battery level
        value_template: >
          {%- set ns = namespace(below=[]) %}
          {%- for s in states.sensor %}
            {%- if ('battery_level' in s.entity_id or 'motion_sensor_battery' in s.entity_id)
                and s.state|int < states('input_number.battery_alert_level')|int %}
              {%- set ns.below = ns.below + [s] %}
            {%- endif %}
          {%- endfor %}
            {{ns.below|count}}
        attribute_templates:
          List: >
            {%- set ns = namespace(below=[]) %}
            {%- for s in states.sensor %}
              {%- if ('battery_level' in s.entity_id or 'motion_sensor_battery' in s.entity_id)
                  and s.state|int < states('input_number.battery_alert_level')|int %}
                {%- set ns.below = ns.below + [s.attributes.friendly_name] %}
              {%- endif %}
            {%- endfor %}
              {{ns.below}}

on which I would create the binary_sensor:

binary_sensor:
  - platform: template
    sensors:
      battery_alert:
        friendly_name: Battery alert
        device_class: safety
        value_template: >
          {{states('sensor.low_battery_level')|int > 0}}
        attribute_templates:
          Below: >
            {% set count_below = states('sensor.low_battery_level')|int %}
            {% set list_below = state_attr('sensor.low_battery_level','List') %}
            {%- if count_below == 0 %} All batteries are fine
            {%- elif count_below == 1 %}
              {{list_below[0]}} is low!
            {%- elif count_below == 2 %}
              {{ list_below|join(' and ') }} are low!
            {%- else %}
              {{list_below[:-1]}}, and {{list_below[-1]}} are low!
            {%- endif %}

this doesnt workout as I expected:

while, if I use the template in one:

all is alright.

Why is this? cant we iterate the template sensor state/attributes?