Skip to content

Instantly share code, notes, and snippets.

@alexdetsch
Last active April 18, 2026 15:05
Show Gist options
  • Select an option

  • Save alexdetsch/42d4c07d906eceed5d088231bb87dd7d to your computer and use it in GitHub Desktop.

Select an option

Save alexdetsch/42d4c07d906eceed5d088231bb87dd7d to your computer and use it in GitHub Desktop.
Homeassistant Blueprint Device Progress Notification
blueprint:
name: "Device Progress Notification"
description: >
Displays a persistent Android notification with progress bar, percentage,
remaining time, and estimated completion time for any device that reports
operation state and progress. Supports both remaining-time countdown
(e.g. Bambu Lab) and end-time timestamp (e.g. HomeConnect) as time source.
Create one automation per device. (v1.4.0)
source_url: https://gist.github.com/alexdetsch/42d4c07d906eceed5d088231bb87dd7d
domain: automation
input:
device_name:
name: Device Name
description: "Display name for the notification (e.g. 'Washing Machine', '3D Printer')"
default: "Device"
selector:
text:
operation_state_entity:
name: Operation State Entity
description: "Entity reporting the device's operational status (e.g. sensor.washer_operation_state, sensor.bambu_print_status)"
selector:
entity:
domain: sensor
running_state_value:
name: Running State Value
description: >
The state value that indicates the device is actively running.
HomeConnect: "Run" · Bambu Lab: "running" · Others: check your integration.
default: "Run"
selector:
text:
progress_entity:
name: Progress Entity
description: "Entity reporting program progress as a percentage (0–100)"
selector:
entity:
domain: sensor
time_source:
name: Time Source
description: >
How the device reports timing information.
"Remaining time": a countdown value (e.g. Bambu Lab).
"End time": a timestamp when the program finishes (e.g. HomeConnect).
default: "remaining"
selector:
select:
options:
- label: "Remaining time"
value: "remaining"
- label: "End time (timestamp)"
value: "end_time"
remaining_time_entity:
name: Remaining Time Entity
description: "Entity reporting the remaining program duration (only used when time source is 'Remaining time')"
default: sensor.none
selector:
entity:
domain: sensor
remaining_time_unit:
name: Remaining Time Unit
description: "Unit of the remaining time entity (only used when time source is 'Remaining time')"
default: "seconds"
selector:
select:
options:
- label: "Seconds"
value: "seconds"
- label: "Minutes"
value: "minutes"
- label: "Hours"
value: "hours"
end_time_entity:
name: End Time Entity
description: "Entity reporting the program's finish time as a timestamp (only used when time source is 'End time')"
default: sensor.none
selector:
entity:
domain: sensor
notify_service:
name: Notification Service
description: >
The mobile_app notify service for your Android device
(e.g. notify.mobile_app_pixel_8)
default: notify.mobile_app_smartphone
selector:
text:
notification_channel:
name: Notification Channel
description: "Android notification channel (controls priority and sound behavior)"
default: "device_progress"
selector:
text:
icon_running:
name: Icon (running)
description: "MDI icon shown while the device is running"
default: "mdi:progress-clock"
selector:
icon:
icon_finished:
name: Icon (finished)
description: "MDI icon shown when the program completes"
default: "mdi:check-circle"
selector:
icon:
variables:
device_name: !input device_name
progress_entity: !input progress_entity
time_source: !input time_source
remaining_time_entity: !input remaining_time_entity
remaining_time_unit: !input remaining_time_unit
end_time_entity: !input end_time_entity
notify_service: !input notify_service
notification_channel: !input notification_channel
running_state_value: !input running_state_value
icon_running: !input icon_running
icon_finished: !input icon_finished
notification_tag: >-
device_progress_{{ states[progress_entity].object_id | default('device') }}
remaining_sec: >-
{% if time_source == 'end_time' %}
{% set end_ts = states(end_time_entity) %}
{% if end_ts not in ['unknown', 'unavailable', ''] %}
{{ [((as_timestamp(end_ts) - as_timestamp(now())) | int), 0] | max }}
{% else %}
0
{% endif %}
{% else %}
{% set raw = states(remaining_time_entity) | float(0) %}
{% if remaining_time_unit == 'hours' %}
{{ (raw * 3600) | int }}
{% elif remaining_time_unit == 'minutes' %}
{{ (raw * 60) | int }}
{% else %}
{{ raw | int }}
{% endif %}
{% endif %}
trigger:
- id: "started"
platform: state
entity_id: !input operation_state_entity
to: !input running_state_value
- id: "update"
platform: time_pattern
minutes: "/1"
- id: "finished"
platform: state
entity_id: !input operation_state_entity
from: !input running_state_value
condition: []
action:
- choose:
# ── Device finished ──
- conditions:
- condition: trigger
id: "finished"
sequence:
- action: "{{ notify_service }}"
data:
title: "{{ device_name }} – Done! ✅"
message: "The program has finished."
data:
tag: "{{ notification_tag }}"
channel: "{{ notification_channel }}"
importance: high
persistent: false
timeout: 300
notification_icon: "{{ icon_finished }}"
color: "#4CAF50"
# ── Periodic update (only while running) ──
- conditions:
- condition: trigger
id: "update"
- condition: state
entity_id: !input operation_state_entity
state: !input running_state_value
sequence:
- action: "{{ notify_service }}"
data:
title: >-
{{ device_name }} – {{ states(progress_entity) | int(0) }}%
message: >-
{% set rsec = remaining_sec | int(0) %}
{% set hours = (rsec // 3600) %}
{% set minutes = ((rsec % 3600) // 60) %}
{% if time_source == 'end_time' %}
{% set end_time = as_timestamp(states(end_time_entity)) | timestamp_custom('%H:%M') %}
{% else %}
{% set end_time = (now() + timedelta(seconds=rsec)).strftime('%H:%M') %}
{% endif %}
{% if hours > 0 %}
Remaining: {{ hours }}h {{ minutes }}min · Done ~{{ end_time }}
{% else %}
Remaining: {{ minutes }} min · Done ~{{ end_time }}
{% endif %}
data:
tag: "{{ notification_tag }}"
channel: "{{ notification_channel }}"
importance: low
persistent: true
sticky: true
ongoing: true
notification_icon: "{{ icon_running }}"
color: "#2196F3"
alert_once: true
progress: "{{ states(progress_entity) | int(0) }}"
progress_max: 100
# ── Device started ──
- conditions:
- condition: trigger
id: "started"
sequence:
- action: "{{ notify_service }}"
data:
title: >-
{{ device_name }} – Started
message: >-
{% set rsec = remaining_sec | int(0) %}
{% set hours = (rsec // 3600) %}
{% set minutes = ((rsec % 3600) // 60) %}
{% if time_source == 'end_time' %}
{% set end_time = as_timestamp(states(end_time_entity)) | timestamp_custom('%H:%M') %}
{% else %}
{% set end_time = (now() + timedelta(seconds=rsec)).strftime('%H:%M') %}
{% endif %}
{% if hours > 0 %}
Estimated duration: {{ hours }}h {{ minutes }}min · Done ~{{ end_time }}
{% else %}
Estimated duration: {{ minutes }} min · Done ~{{ end_time }}
{% endif %}
data:
tag: "{{ notification_tag }}"
channel: "{{ notification_channel }}"
importance: default
persistent: true
sticky: true
ongoing: true
notification_icon: "{{ icon_running }}"
color: "#2196F3"
progress: 0
progress_max: 100
mode: restart
max_exceeded: silent
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment