Last active
January 3, 2026 22:12
-
-
Save cameri/1793ac7b9efc0c6e4a58eb815cadd9da to your computer and use it in GitHub Desktop.
Home Assistant Blueprint: Stale sensor detection
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| blueprint: | |
| name: Stale "Last seen" Sensor Report | |
| description: > | |
| Checks timestamp sensors ending in " Last seen". Lists those older than the threshold | |
| or marked as "unavailable", stripping the suffix and adding the days ago. | |
| domain: automation | |
| input: | |
| days_threshold: | |
| name: Days Offline Threshold | |
| description: Sensors older than this many days will be reported. | |
| default: 7 | |
| selector: | |
| number: | |
| min: 1 | |
| max: 30 | |
| unit_of_measurement: 'days' | |
| mode: slider | |
| step: 1 | |
| time: | |
| name: Time to test on | |
| default: '08:00:00' | |
| selector: | |
| time: {} | |
| exclude: | |
| name: Excluded Entities | |
| default: {entity_id: []} | |
| selector: | |
| target: | |
| entity: | |
| device_class: timestamp | |
| actions: | |
| name: Actions | |
| description: '{{sensors}} will be replaced with the list (e.g., "Sensor Name (7d)").' | |
| selector: | |
| action: {} | |
| variables: | |
| days_threshold: !input 'days_threshold' | |
| exclude: !input 'exclude' | |
| threshold_seconds: "{{ days_threshold | int * 86400 }}" | |
| sensors: >- | |
| {% set result = namespace(sensors=[]) %} | |
| {% set timestamp_sensors = states.sensor | |
| | selectattr('attributes.device_class', 'defined') | |
| | selectattr('attributes.device_class', 'eq', 'timestamp') | |
| | list %} | |
| {% for state in timestamp_sensors %} | |
| {% if state.name.endswith(' Last seen') and not state.entity_id in exclude.entity_id %} | |
| {% set clean_name = state.name | replace(' Last seen', '') %} | |
| {% if state.state == 'unavailable' or state.state == 'unknown' %} | |
| {# Handle unavailable sensors separately #} | |
| {% set result.sensors = result.sensors + [clean_name ~ ' (unknown)'] %} | |
| {% else %} | |
| {# Calculate time difference for valid timestamps #} | |
| {% set last_seen_ts = as_timestamp(state.state, 0) %} | |
| {% set diff_seconds = as_timestamp(now()) - last_seen_ts %} | |
| {% if diff_seconds > threshold_seconds %} | |
| {% set days_ago = (diff_seconds / 86400) | int %} | |
| {% set result.sensors = result.sensors + [clean_name ~ ' (' ~ days_ago ~ 'd)'] %} | |
| {% endif %} | |
| {% endif %} | |
| {% endif %} | |
| {% endfor %} | |
| {{ result.sensors | join('\n') }} | |
| trigger: | |
| - platform: time | |
| at: !input 'time' | |
| condition: | |
| - "{{ sensors != '' }}" | |
| action: | |
| - choose: [] | |
| default: !input 'actions' | |
| mode: single |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment