I have been looking for a way to be able to select a few devices, a group of devices, something like that, then alert when they are not seen over a given period of time. Overall goal is to be able to tell when my zigbee2mqtt door sensor batteries die, for instance. I’ve been looking for a way to do this for QUITE a while now and found one blueprint that came close but it seems to basically just say “hey…devices are missing”. I couldn’t find a way to tell it which devices I wanted it to pay attention to, change the check in duration, etc.
Does anyone know a way to do this? Any ideas would be GREATLY appreciated.
I have built a Blueprint exactly for this purpose and other use cases (DS18B20 sensors vanishing, seasonal sensors exceptions, etc.)
Some battery powered devices just go from OK to zero in a matter of minutes so tracking battery level is not bulletproof.
The concept here is simply to assign label(s) to device entities that typically change through time (Temperature, Humidity, Signal, RSSI, Uptime, Memory, etc.) or manually identify entities that could vanish entirely (DS18B20/Tasmota sensors); the Blue print will notify you once the entity stops updating, goes offline or vanishes after a defined period of time.
I am using Pushover notifications but you can use another notify service.
I have been testing it for a few months and really like it so far.
I don’t have time to document my blueprint so haven’t posted it officially but feel free to try it, it could help with your use case. You’ll have to do manual installation.
Let me know if you have any questions or feedback.
blueprint:
name: Sensor Integrity Monitor (Stale, Offline or Vanished)
description: Alerts if sensors vanish, go offline, or stop updating. Supports separate thresholds for Stale vs Offline, perpetual seasonal windows, and robust Sun-based monitoring.
domain: automation
input:
check_interval:
name: Check Interval (Hours)
description: How often to run the check automatically.
default: 3
selector:
number:
min: 1
max: 24
unit_of_measurement: h
threshold_stale_mins:
name: Stale Threshold (Minutes)
description: Max time allowed since last state change for "Healthy" sensors.
default: 180
selector:
number:
min: 5
max: 1440
unit_of_measurement: min
threshold_offline_mins:
name: Offline Threshold (Minutes)
description: Grace period for sensors in "unavailable" or "unknown" state before alerting.
default: 30
selector:
number:
min: 0
max: 1440
unit_of_measurement: min
target_labels:
name: Labeled Entities
description: Entities with these labels will be monitored 24/7.
default: []
selector:
label:
multiple: true
vanishing_entities:
name: Vanishing Entities (Manual)
description: "Entities that might vanish entirely (e.g., Tasmota + DS18B20 sensors)."
default: []
selector:
entity:
multiple: true
# --- SUN-BASED DAYTIME MONITORING ---
daytime_entities:
name: "Daylight Only Entities"
description: Entities to monitor only between Sunrise and Sunset.
default: []
selector:
entity:
multiple: true
sunrise_offset:
name: "Sunrise Offset (Minutes)"
default: 30
selector:
number: { min: -120, max: 120, unit_of_measurement: min }
sunset_offset:
name: "Sunset Offset (Minutes)"
default: -30
selector:
number: { min: -120, max: 120, unit_of_measurement: min }
# --- PERPETUAL SEASONAL GROUPS ---
exclude_entities_a:
name: "Group A: Seasonal Entities"
default: []
selector:
entity: { multiple: true }
start_date_a:
name: "Group A: Start Monitoring On"
default: "2024-05-01"
selector: { date: {} }
end_date_a:
name: "Group A: Stop Monitoring On"
default: "2024-10-15"
selector: { date: {} }
exclude_entities_b:
name: "Group B: Seasonal Entities"
default: []
selector:
entity: { multiple: true }
start_date_b:
name: "Group B: Start Monitoring On"
default: "2024-11-01"
selector: { date: {} }
end_date_b:
name: "Group B: Stop Monitoring On"
default: "2024-03-15"
selector: { date: {} }
notify_service:
name: Notification Service
default: notify.pushover
selector:
text: { multiline: false }
mode: single
trigger:
- trigger: time_pattern
hours: "/1"
action:
- variables:
conf_interval: !input check_interval
input_start_a: !input start_date_a
input_end_a: !input end_date_a
input_start_b: !input start_date_b
input_end_b: !input end_date_b
is_manual: "{{ trigger.platform is none or trigger.platform != 'time_pattern' }}"
- condition: template
value_template: "{{ (now().hour % conf_interval | int) == 0 or is_manual }}"
- variables:
today: "{{ now().strftime('%j') | int }}"
# Seasonal Logic
start_a: "{{ as_timestamp(now().strftime('%Y-') ~ input_start_a[5:10], 0) | timestamp_custom('%j') | int }}"
end_a: "{{ as_timestamp(now().strftime('%Y-') ~ input_end_a[5:10], 0) | timestamp_custom('%j') | int }}"
should_monitor_a: "{% if start_a <= end_a %}{{ start_a <= today <= end_a }}{% else %}{{ today >= start_a or today <= end_a }}{% endif %}"
start_b: "{{ as_timestamp(now().strftime('%Y-') ~ input_start_b[5:10], 0) | timestamp_custom('%j') | int }}"
end_b: "{{ as_timestamp(now().strftime('%Y-') ~ input_end_b[5:10], 0) | timestamp_custom('%j') | int }}"
should_monitor_b: "{% if start_b <= end_b %}{{ start_b <= today <= end_b }}{% else %}{{ today >= start_b or today <= end_b }}{% endif %}"
# Sun-Based Logic
is_daylight: >
{% set sun = states.sun.sun %}
{% if sun %}
{{ (state_attr('sun.sun', 'elevation') | default(-10) > -5) }}
{% else %}
false
{% endif %}
# Assemble final list
entities_a: !input exclude_entities_a
entities_b: !input exclude_entities_b
ignored_seasonal: "{{ (entities_a if not should_monitor_a else []) + (entities_b if not should_monitor_b else []) }}"
day_entities: !input daytime_entities
ignored_daylight: "{{ day_entities if not is_daylight else [] }}"
all_exclusions: "{{ (ignored_seasonal + ignored_daylight) | unique | list }}"
# Master Monitor Queue
target_labels: !input target_labels
vanishing_entities: !input vanishing_entities
all_labeled: >
{% set ns = namespace(entities=[]) %}
{% for l in target_labels %}
{% set ns.entities = ns.entities + label_entities(l) %}
{% endfor %}
{{ ns.entities }}
all_to_monitor: >
{% set combined = (all_labeled + vanishing_entities + day_entities) | unique | list %}
{{ combined | reject('in', all_exclusions) | list }}
# Processing with Dual Thresholds
thresh_stale: !input threshold_stale_mins
thresh_offline: !input threshold_offline_mins
stale_entities: >
{% set ns = namespace(stale=[]) %}
{% for eid in all_to_monitor %}
{% set s = states[eid] %}
{% if s is none %}
{% set ns.stale = ns.stale + [ eid ~ " (VANISHED)" ] %}
{% else %}
{% set last_ts = as_timestamp(s.last_changed, 0) %}
{% set diff = (as_timestamp(now()) - last_ts) / 60 %}
{% set a = area_name(eid) %}
{% set name = state_attr(eid, 'friendly_name') or ( (a ~ ": " if a else "") ~ eid) %}
{% if s.state in ['unavailable', 'unknown'] %}
{% if diff >= thresh_offline %}
{% set ns.stale = ns.stale + [ name ~ " (OFFLINE)" ] %}
{% endif %}
{% else %}
{% if diff >= thresh_stale %}
{% set last_seen = s.last_changed | as_local %}
{% set timestamp = last_seen.strftime('%b %d, %I:%M %p') %}
{% set ns.stale = ns.stale + [ name ~ " (STALE @ " ~ timestamp ~ ")" ] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.stale }}
- if:
- condition: template
value_template: "{{ stale_entities | count > 0 }}"
then:
- service: !input notify_service
data:
title: "Sensor Integrity Alert"
message: "The following sensors require attention:\n{{ stale_entities | join('\n') }}"
data:
priority: 2
sound: siren
retry: 3600
expire: 86400