Last active
January 29, 2026 20:48
-
-
Save hansharhoff/0c8f5e6b94b7e26d27f8d1675fd38b23 to your computer and use it in GitHub Desktop.
boost heat for X hours on Danfoss ally
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: 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