Daily Low Battery Notification for All Devices
- Andrea Leandri
- 5 days ago
- 4 min read
Skill level: Beginner to Intermediate | Time to complete: 15-20 minutes
What you'll build: A Home Assistant automation that runs every morning at 09:00 and sends a single push notification listing every device whose battery has dropped below 20% - across all integrations, automatically, with no device list to maintain.
The Problem It Solves
Smart home devices run on batteries. Zigbee door sensors, remote controls, smoke detectors, motion sensors - they all drain quietly until one day a device stops responding and you spend an hour troubleshooting before realising it was a dead battery all along.
This automation catches that before it happens. Every morning at 09:00, if anything is running low, you get a single time-sensitive notification listing every affected device with its current level. If everything is fine, nothing happens - no unnecessary noise. And because it scans all sensor entities dynamically, you never need to update a device list when you add new hardware.
What You'll Need
Just Home Assistant and the Companion App for push notifications. No integrations, no add-ons, no configuration.yaml changes required.
How the Template Works
The automation uses a Jinja2 template that scans all sensor entities in HA automatically - one of the most useful patterns in Home Assistant:
{% set threshold = 20 %}
{% set ns = namespace(lines=[]) %}
{% for state in states.sensor
if state.state not in ['unavailable', 'unknown']
and state.state | int(-1) >= 0
and state.state | int(-1) < threshold
and (
state.attributes.device_class == 'battery'
or 'battery' in state.name | lower
or 'battery' in state.entity_id | lower
) %}
{% set ns.lines = ns.lines + ['- ' ~ state.name ~ ' (' ~ state.state ~ '%)'] %}
{% endfor %}
{{ ns.lines | join('\n') }}states.sensor iterates over every sensor entity in HA - the dynamic scan that makes the automation self-maintaining. state.state not in ['unavailable', 'unknown'] skips sensors that are offline. state.state | int(-1) >= 0 filters out non-numeric sensors. device_class == 'battery' is the clean way; the 'battery' in name/entity_id checks catch integrations that don't set device_class correctly. namespace(lines=[]) works around Jinja2's limitation of not allowing variable modification inside a for loop.
The result is a message like: Aqara Door Sensor Bedroom (14%) / IKEA Remote Control Living Room (8%) / Zigbee Motion Sensor Kitchen (19%)
The Automation
Settings -> Automations & Scenes -> Create Automation -> Edit in YAML:
alias: "Daily Battery Level Check"
description: >
Runs every day at 09:00 and sends a notification listing all devices
whose battery level has dropped below 20%.
triggers:
- at: "09:00:00"
trigger: time
conditions: []
actions:
- variables:
low_battery_message: |-
{% set threshold = 20 %}
{% set exclude = [
'Example Sensor To Exclude',
'Another Sensor To Exclude'
] %}
{% set ns = namespace(lines=[]) %}
{% for state in states.sensor
if state.state not in ['unavailable', 'unknown']
and state.state | int(-1) >= 0
and state.state | int(-1) < threshold
and state.name not in exclude
and (
state.attributes.device_class == 'battery'
or 'battery' in state.name | lower
or 'battery' in state.entity_id | lower
) %}
{% set ns.lines = ns.lines + [
'- ' ~ state.name ~ ' (' ~ state.state ~ '%)'
] %}
{% endfor %}
{{ ns.lines | join('\n') }}
# Only send if there is actually something to report
- condition: template
value_template: "{{ low_battery_message | trim | length > 0 }}"
- action: notify.mobile_app_iphone_XXXXXX # replace with your device
data:
title: "Low Battery Warning"
message: "{{ low_battery_message | trim }}"
data:
push:
interruption-level: time-sensitive
sound: default
mode: singleKey Details
The mobile device that notifies
Ensure that the notify.mobile_app_iphone_XXXXXX is replaced with your actual device where the companion app is installed.
The exclude list
Some sensors contain "battery" in their name but represent something different - a car starter battery state, a battery temperature reading, or a UPS runtime. Add their exact friendly names to the exclude list. Find the exact name in Developer Tools -> States.
The condition
The condition: template step with low_battery_message | trim | length > 0 is the silence gate. If all batteries are above 20%, the message is empty, the condition evaluates to false, and the automation stops without sending anything. This is what keeps it quiet on good days.
Time-sensitive notification
Interruption-level: time-sensitive means the notification breaks through iOS Focus modes including Do Not Disturb and Sleep - appropriate for a battery warning you might genuinely need to see. Change to active if you prefer it to respect Focus modes.
Preview the Template Without Waiting
To see every battery sensor HA can find right now, go to Developer Tools -> Template and paste the template with threshold = 100. This shows all battery sensors regardless of level - useful for auditing what will appear and what to add to the exclude list.
Customise It
Change threshold from 20 to any value you prefer - 15 for fewer alerts, 30 to get more warning time. Change the trigger time from 09:00 to whenever suits you. Add multiple notify actions for a shared household. Add a WhatsApp notification using the WhatsApp integration from the guide on this site.
Troubleshooting
No notification with low batteries: Test with threshold 100 in Developer Tools - Template. Check Traces to see if condition evaluated to false.
False positives in the list: Add those sensor names exactly to the exclude list.
Some battery sensors not appearing: They may not have device_class: battery set and lack battery in their name or entity ID. Check in Developer Tools -> States.
Notification doesn't break through Do Not Disturb: Confirm interruption-level: time-sensitive is set. Check iOS Settings -> Notifications -> Home Assistant.
Guide written by a Home Assistant enthusiast in Utrecht. Nothing ends a perfectly working automation like a dead battery you didn't know about.



Comments