Edit May 14th, 2019: The latest code and instructions are available on GitHub at:
Original post:
I have many battery powered devices that have various methods to track battery level. Some of my devices have a separate entity that just represents battery while most of them have a battery_level attribute. To make it even more complex, I have some devices that report an 0-100 integer and others that use strings (Low, Medium, Full, etc.).
I’ve been wanting a way to identify any batteries that need my attention with very low admin overhead. I know that templates can be used to create separate battery entities for each device that has a battery, but it’s way to easy to forget to create or update them. I’ve been able to cobble together an alert that so far seems to be working to identify batteries that need attention with no additional configuration required beyond this single alert. The alert below nags me twice daily when any device has battery level < %40 or “Low”.
I don’t like all of the repetition in the conditions, but that’s the only way I know of (so far) that works. If anyone has suggestions to optimize this alert, I’d be happy to hear them.
Expand to see the Battery Alert details...
- alias: 'Battery Alert'
trigger:
- platform: time
at: '10:00:00'
- platform: time
at: '18:00:00'
condition:
condition: or
conditions:
- condition: template
value_template: >-
{%- set threshold = 40 -%}
{%- for item in states.light if (item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) or ("battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown")) -%}
{%- if loop.first -%}
{{ true }}
{%- endif -%}
{%- endfor -%}
- condition: template
value_template: >-
{%- set threshold = 40 -%}
{%- for item in states.switch if (item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) or ("battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown")) -%}
{%- if loop.first -%}
{{ true }}
{%- endif -%}
{%- endfor -%}
- condition: template
value_template: >-
{%- set threshold = 40 -%}
{%- for item in states.sensor if (item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) or ("battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown")) -%}
{%- if loop.first -%}
{{ true }}
{%- endif -%}
{%- endfor -%}
- condition: template
value_template: >-
{%- set threshold = 40 -%}
{%- for item in states.zwave if (item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) or ("battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown")) -%}
{%- if loop.first -%}
{{ true }}
{%- endif -%}
{%- endfor -%}
- condition: template
value_template: >-
{%- set threshold = 40 -%}
{%- for item in states.lock if (item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) or ("battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown")) -%}
{%- if loop.first -%}
{{ true }}
{%- endif -%}
{%- endfor -%}
action:
- service: notify.slack_notify
data_template:
message: >-
{%- set threshold = 40 -%}
{%- set domains = [states.light, states.switch, states.sensor, states.zwave, states.lock ] -%}
{%- for domain in domains -%}
{%- if loop.first -%}
The following devices have low battery levels:
{%- endif -%}
{%- for item in domain if (item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) or ("battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown")) -%}
{%- if (item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) %}
{{ item.name }} ({{ item.attributes['battery_level'] }}),
{% endif -%}
{%- if "battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown") -%}
{{ item.name }} ({{ item.state }}),
{% endif -%}
{%- endfor -%}
{%- endfor -%}
You could iterate through all devices of a domain, here’s an example for Z-Wave. Poster mentions it not working correctly, but after a restart it did work as expected.
My code above already does that for zwave, light, switch, sensor, and lock domains. It would be easy to add more, but I stopped there because those domains cover all my battery powered devices.
The issue I had with looping in the condition like you’re suggesting is I couldn’t get it to return a single true on the first match. The part I’m missing to make this method work is that I can’t figure out how to break out of the first loop when the second loop finds the first match. Breaking out of the second loop is easy with loop.first, but I don’t know how to break out of the first loop when that happens. No matter what I tried, the best I could get it to return was “true”, “truetrue”, “truetruetrue”, etc., which doesn’t work for conditions.
That’s what I tried first, and it definitely would have been the easiest option. My issue with that method is you can’t get to the battery_level attribute when you loop like that. Nearly every other attribute is available, except for battery_level. That’s the main reason why I ended up looping through the domains instead.
You can remove all of the duplication by using a template sensor to track all of the entities with a low battery:
sensor:
- platform: template
sensors:
low_battery:
friendly_name: "Low Battery"
value_template: >
{%- set threshold = 40 -%}
{%- set domains = ['light', 'switch', 'sensor', 'zwave', 'lock'] -%}
{%- for domain in domains -%}
{%- for item in states[domain] if ((item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) or ("battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown"))) -%}
{{ item.attributes.friendly_name }}{%- if not loop.last %}, {% endif -%}
{%- endfor -%}
{%- endfor -%}
Okay, this is absolutely beautiful. I tried playing around with it to see if I could figure it out on my own with no luck. So, how would one extract the friendly name for the sensor that falls below the threshold? Also, how often would this poll checking for battery levels?
I updated my post. For friendly names, just use item.attributes.friendly_name instead of item.name.
I honestly don’t know how often it’ll run, probably every single event loop (i.e. a lot). I don’t know how to reduce that without either specifying every entity to track in the template sensor or duplicating the template in both the condition and action. Maybe we can get the template sensor updated to allow a config option that specifies how often it should update.
In this example, input_boolean.update_low_battery will toggle states every 30 minutes, which then triggers sensor.low_battery to update. You could also set it up to update just before your alert automation runs as well if you wanted. That way you only update the sensor right before the automation runs to check for low battery.
@tboyce1, thanks so much! That’s definitely a lot cleaner and I like having that persistent notification. I made a few (mostly cosmetic) tweaks to what you sent. The trigger on your “Update Low Battery” automation needs a seconds: 00 attribute to keep it from running every second when the minutes match. I changed the condition on the automation to prevent empty alerts when there are no batteries. Finally, I added the current battery level to the notification.
I’ve got a few more ideas that can make this better, but I wanted to share the latest code that’s working for me.
input_boolean:
battery_status_update:
name: Battery Status Update
sensor:
- platform: template
sensors:
battery_status:
friendly_name: "Battery Status"
entity_id:
- input_boolean.battery_status_update
value_template: >
{%- set threshold = 40 -%}
{%- set domains = ['light', 'switch', 'sensor', 'zwave', 'lock'] -%}
{%- for domain in domains -%}
{%- for item in states[domain] if ((item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) or ("battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown"))) -%}
{%- if (item.attributes.battery_level is defined and item.attributes['battery_level'] | int < threshold) -%}
{{ item.name }} ({{ item.attributes['battery_level'] }}){%- if not loop.last %}, {% endif -%}{% endif -%}
{%- if "battery" in item.name | lower and ((item.state | int < threshold and item.state|int != 0) or item.state | lower == "low" or item.state | lower == "unknown") -%}
{{ item.name }} ({{ item.state }}){%- if not loop.last %}, {% endif -%} {% endif -%}
{%- endfor -%}
{%- endfor -%}
automation:
- alias: Battery Status Update
trigger:
- platform: time
minutes: '/5'
seconds: 00
action:
- alias: Battery Status Update
service: input_boolean.toggle
data:
entity_id: input_boolean.battery_status_update
- alias: 'Battery Alert'
trigger:
- platform: time
at: '10:00:00'
- platform: time
at: '18:00:00'
condition:
condition: template
value_template: "{% if states('sensor.battery_status') %}{{ true }}{% else %}{{ false }}{% endif %}"
action:
- service: persistent_notification.create
data_template:
title: Low Battery levels
message: "{{ states('sensor.battery_status') }}"
notification_id: low-battery-alert
- service: notify.slack_notify
data_template:
message: >-
These devices have low battery levels: {{ states('sensor.battery_status') }}
I took the last change from @NotoriousBDG and converted it into a package that can pretty much just be dropped in and will work. Just update the notification to your preference.
Node Anchors is used to simplify your configurations and avoid repetitions of settings. So instead of repeting all settings for each entity you just add an anchor to the setting. Example:
################################################
## With Node Anchors and Merge Key Tags
################################################
homeassistant:
customize:
package.node_anchors: # This is just a dummy entry
customize: &customize # Also a dummy entry that allows us to define the node anchor
package: 'philips_hue'
light.back_porch:
<<: *customize # This merges the keys/values from "&customize"
friendly_name: "Back Porch"
light.chandelier:
<<: *customize
friendly_name: "Chandelier"
light.dining_room:
<<: *customize
friendly_name: "Dining Room"
light.entry_lamp:
<<: *customize
friendly_name: "Entry Lamp"
light.front_porch:
<<: *customize
friendly_name: "Front Porch"