Skip to content

Instantly share code, notes, and snippets.

@hansharhoff
Last active January 29, 2026 20:48
Show Gist options
  • Select an option

  • Save hansharhoff/0c8f5e6b94b7e26d27f8d1675fd38b23 to your computer and use it in GitHub Desktop.

Select an option

Save hansharhoff/0c8f5e6b94b7e26d27f8d1675fd38b23 to your computer and use it in GitHub Desktop.
boost heat for X hours on Danfoss ally
blueprint:
name: Danfoss Ally - Manual heat boost (boost temp + idle temp)
description: >
Press an input_button to set selected Danfoss Ally thermostats to Manual + Boost temperature for X hours.
If pressed again while active/paused, extends remaining time by +extend hours.
When timer ends/cancels, sets Manual + Idle temperature.
Also enforces that thermostats stay in Manual if preset is changed externally.
domain: automation
input:
activation_button:
name: Activation input_button
selector:
entity:
domain: input_button
thermostats:
name: Danfoss Ally thermostats (climate entities)
selector:
entity:
domain: climate
multiple: true
boost_timer:
name: Timer helper
selector:
entity:
domain: timer
boost_hours:
name: Boost duration (hours)
default: 2
selector:
number:
min: 0.25
max: 24
step: 0.25
unit_of_measurement: h
mode: box
extend_hours:
name: Extend by (hours) when pressed again
default: 1
selector:
number:
min: 0.25
max: 12
step: 0.25
unit_of_measurement: h
mode: box
boost_temperature:
name: Boost temperature (°C)
default: 23
selector:
number:
min: 5
max: 30
step: 0.5
unit_of_measurement: "°C"
mode: box
idle_temperature:
name: Idle temperature (°C)
default: 18
selector:
number:
min: 5
max: 30
step: 0.5
unit_of_measurement: "°C"
mode: box
mode: queued
max: 10
trigger:
# Correct way to trigger on an input_button press: its state changes (timestamp)
- platform: state
entity_id: !input activation_button
id: activate
# Enforce Manual if anything changes preset_mode away from Manual
- platform: state
entity_id: !input thermostats
attribute: preset_mode
id: enforce_manual
# Restore idle temp when timer stops
- platform: state
entity_id: !input boost_timer
from: "active"
to: "idle"
id: stop
- platform: state
entity_id: !input boost_timer
from: "paused"
to: "idle"
id: stop
# Also handle explicit finished/cancelled events (some setups emit these reliably)
- platform: event
event_type: timer.finished
event_data:
entity_id: !input boost_timer
id: stop
- platform: event
event_type: timer.cancelled
event_data:
entity_id: !input boost_timer
id: stop
variables:
timer_entity: !input boost_timer
thermostats: !input thermostats
boost_hours: !input boost_hours
extend_hours: !input extend_hours
boost_temperature: !input boost_temperature
idle_temperature: !input idle_temperature
manual_preset: "Manual"
action:
# =========================================================
# ACTIVATE: start or extend timer, apply boost temp
# =========================================================
- if:
- condition: template
value_template: "{{ trigger.id == 'activate' and trigger.from_state is not none }}"
then:
# Extend if active/paused, else start fresh
- if:
- condition: template
value_template: "{{ is_state(timer_entity, 'active') or is_state(timer_entity, 'paused') }}"
then:
- service: timer.start
target:
entity_id: !input boost_timer
data:
duration: >
{% set finish = state_attr(timer_entity, 'finishes_at') %}
{% if finish %}
{% set remaining = as_timestamp(finish) - as_timestamp(now()) %}
{% else %}
{% set rem = state_attr(timer_entity, 'remaining') %}
{% set remaining = (as_timedelta(rem).total_seconds() if rem else 0) %}
{% endif %}
{% if remaining < 0 %}{% set remaining = 0 %}{% endif %}
{% set sec = (remaining + (extend_hours | float * 3600)) | int %}
{{ '%02d:%02d:%02d'|format(sec//3600, (sec%3600)//60, sec%60) }}
else:
- service: timer.start
target:
entity_id: !input boost_timer
data:
duration: >
{% set sec = (boost_hours | float * 3600) | int %}
{{ '%02d:%02d:%02d'|format(sec//3600, (sec%3600)//60, sec%60) }}
# Apply boost: Manual + boost temp
- service: climate.set_preset_mode
target:
entity_id: !input thermostats
data:
preset_mode: "{{ manual_preset }}"
- service: climate.set_temperature
target:
entity_id: !input thermostats
data:
temperature: !input boost_temperature
# =========================================================
# STOP: timer ended/cancelled -> set idle temp (still Manual)
# =========================================================
- if:
- condition: template
value_template: "{{ trigger.id == 'stop' and is_state(timer_entity, 'idle') }}"
then:
- service: climate.set_preset_mode
target:
entity_id: !input thermostats
data:
preset_mode: "{{ manual_preset }}"
- service: climate.set_temperature
target:
entity_id: !input thermostats
data:
temperature: !input idle_temperature
# =========================================================
# ENFORCE: keep Manual always; set temp based on timer state
# =========================================================
- if:
- condition: template
value_template: >
{{ trigger.id == 'enforce_manual'
and trigger.to_state is not none
and trigger.to_state.attributes.get('preset_mode') != manual_preset }}
then:
- service: climate.set_preset_mode
target:
entity_id: "{{ trigger.entity_id }}"
data:
preset_mode: "{{ manual_preset }}"
- if:
- condition: template
value_template: "{{ is_state(timer_entity, 'active') or is_state(timer_entity, 'paused') }}"
then:
- service: climate.set_temperature
target:
entity_id: "{{ trigger.entity_id }}"
data:
temperature: !input boost_temperature
else:
- service: climate.set_temperature
target:
entity_id: "{{ trigger.entity_id }}"
data:
temperature: !input idle_temperature
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment