Skip to content

Instantly share code, notes, and snippets.

@EarMaster
Last active May 3, 2026 08:05
Show Gist options
  • Select an option

  • Save EarMaster/56700e0b9ce03b13f8070072f1e6b37e to your computer and use it in GitHub Desktop.

Select an option

Save EarMaster/56700e0b9ce03b13f8070072f1e6b37e to your computer and use it in GitHub Desktop.
Weather-based boiler mode recommendation for Home Assistant. Analyzes the upcoming forecast and triggers a customizable action (notification, light, etc.) when your heating mode should change. No automatic switching — you stay in control.

Heating Mode Recommendation Blueprint for Home Assistant

A Home Assistant automation blueprint that analyzes the upcoming weather forecast and suggests when to manually switch your boiler between Heat, Hot Water, and Off (solar-only) mode — so you stop forgetting to do it yourself.

Designed for setups where the boiler mode must be changed physically (e.g. by pressing a button on the unit), but you still want smart, weather-aware reminders without switching multiple times a week.

Import Blueprint

How it works

Every morning at a configurable time, the blueprint fetches a daily weather forecast (up to 7 days) and evaluates three conditions:

Recommendation Condition
Heat Avg. daily high < 13 °C or overnight low < 5 °C
Hot Water Neither Heat nor Off conditions are met
Off Avg. daily high ≥ 18 °C and overnight low ≥ 10 °C and ≥ 3 sunny days (solar reliable)

All thresholds are configurable via the UI — no YAML editing required.

If the recommendation differs from the currently tracked boiler mode, your custom action fires. If they already match, nothing happens.

Vacation mode (optional)

When vacation mode is active, the recommendation is always Off — unless the overnight low drops below a configurable frost protection threshold for at least a configurable number of days, in which case Heat is recommended to protect pipes from freezing.

Requirements

  • An input_select helper that tracks the current physical boiler state (3 options matching the labels configured in the blueprint)
  • A weather integration that provides daily forecasts with temperature and templow fields

Recommended weather integrations (no API key required):

  • Met.no – built into Home Assistant, works out of the box
  • Open-Meteo – 7-day horizon, available via HACS

Setup

  1. Add the blueprint to Home Assistant
  2. Create (or reuse) an input_select helper with options matching the mode labels:
input_select:
  heating_mode:
    name: "Boiler Mode"
    options:
      - "Off"
      - "Hot Water"
      - "Heat"
    icon: mdi:thermometer-auto
  1. Optionally create an input_boolean helper for vacation mode:
input_boolean:
  vacation:
    name: "Vacation Mode"
    icon: mdi:beach
  1. Update the helper manually whenever you physically switch the boiler — this keeps the recommendation logic accurate
  2. Create an automation from the blueprint and configure your action

Action

The blueprint accepts any Home Assistant action — notification, light color, TTS, persistent notification, etc. The following template variables are available inside your action:

Variable Example value
{{ recommendation }} Heat
{{ current_mode }} Hot Water
{{ reason }} Avg. high: 9.4 °C | Overnight low: 3.1 °C | ☀️ 2/5 sunny days
{{ vacation_mode }} true

Example: Actionable mobile notification

- action: notify.mobile_app_your_phone
  data:
    title: "🔥 Boiler mode change suggested"
    message: >
      Switch to {{ recommendation }}
      (currently: {{ current_mode }}) — {{ reason }}
    data:
      actions:
        - action: "HEATING_CONFIRM"
          title: "✅ Done, I switched it"
        - action: "HEATING_LATER"
          title: "⏰ Remind me later"

To update the tracked mode when you confirm, add a companion automation:

trigger:
  - platform: event
    event_type: mobile_app_notification_action
    event_data:
      action: HEATING_CONFIRM
action:
  - action: input_select.select_option
    target:
      entity_id: input_select.heating_mode
    data:
      option: "Heat"  # Replace with the option value you switched to

Configuration reference

Input Default Description
Weather Entity Any weather.* entity with daily forecasts
Heating Mode (input_select) Tracks the current physical boiler state
Label – Heat Heat Must match the input_select option exactly
Label – Hot Water Hot Water Must match the input_select option exactly
Label – Off Off Must match the input_select option exactly
Daily Evaluation Time 07:30 When the forecast is checked each day
Forecast Horizon 5 days How many days to average (1–7)
Heat – Max. avg. daily high 13 °C Below this → Heat recommended
Heat – Max. overnight low 5 °C Below this → Heat recommended regardless of highs
Off – Min. avg. daily high 18 °C Must exceed this for Off to be recommended
Off – Min. overnight low 10 °C Must exceed this for Off to be recommended
Off – Minimum sunny days 3 days Minimum sunny/partly cloudy days for solar reliability
Vacation Mode Switch (optional) input_boolean that forces Off when active
Vacation – Frost protection threshold -10 °C Overnight low below this triggers Heat despite vacation mode
Vacation – Frost protection min. days 2 days How many days must breach the frost threshold to override vacation mode
Reason Template (optional) Custom template for {{ reason }}; supports avg_high, min_low, sunny_count, _days, vacation_mode, frost_day_count
Action on Mismatch Any HA action; has access to recommendation, current_mode, reason, vacation_mode
blueprint:
name: "Heating Mode Recommendation (Weather Forecast)"
description: >
Calculates a daily heating mode recommendation based on the weather
forecast and triggers a fully customizable action when the recommendation
differs from the currently tracked mode.
**Available template variables in the action:**
- `{{ recommendation }}` – e.g. "Heat", "Hot Water", "Off"
- `{{ current_mode }}` – value currently stored in the input_select
- `{{ reason }}` – short human-readable summary with temperature stats
- `{{ vacation_mode }}` – true if vacation mode is active
domain: automation
author: "Nico Wiedemann <nico.wiedemann@gmail.com>"
input:
# ── Core entities ────────────────────────────────────────────────────────
weather_entity:
name: Weather Entity
description: >
The weather forecast entity (e.g. `weather.forecast_home`).
Must provide daily forecasts with `temperature` and `templow` fields.
Recommended: Met.no (built-in, no API key) or Open-Meteo (7-day horizon,
no API key, available via HACS).
selector:
entity:
domain: weather
heating_mode_entity:
name: Heating Mode (input_select)
description: >
The input_select helper that tracks the current physical state of the
boiler. Its options must exactly match the three mode labels configured
below.
selector:
entity:
domain: input_select
# ── Mode labels ──────────────────────────────────────────────────────────
value_heat:
name: Label – "Heat"
description: >
Exact option string in the input_select that represents full heating
mode (space heating + domestic hot water).
default: "Heat"
selector:
text: {}
value_hot_water:
name: Label – "Hot Water"
description: >
Exact option string in the input_select that represents domestic
hot-water-only mode (boiler active, no space heating).
default: "Hot Water"
selector:
text: {}
value_off:
name: Label – "Off"
description: >
Exact option string in the input_select that represents the off / solar-only
mode (boiler off, solar thermal handles domestic hot water).
default: "Off"
selector:
text: {}
# ── Schedule ─────────────────────────────────────────────────────────────
evaluation_time:
name: Daily Evaluation Time
description: Time of day at which the recommendation is recalculated.
default: "07:30:00"
selector:
time: {}
forecast_days:
name: Forecast Horizon
description: >
Number of upcoming days included in the calculation.
Longer horizons reduce switching frequency; 4–7 days recommended.
default: 5
selector:
number:
min: 1
max: 7
step: 1
unit_of_measurement: days
mode: slider
# ── Thresholds: Heat ─────────────────────────────────────────────────────
threshold_heat_high:
name: "Heat – Max. avg. daily high"
description: >
If the average daily high across the forecast period falls **below**
this value, "Heat" is recommended.
default: 13
selector:
number:
min: 0
max: 20
step: 0.5
unit_of_measurement: °C
mode: slider
threshold_heat_low:
name: "Heat – Max. overnight low"
description: >
If the coldest overnight low across the forecast period falls **below**
this value, "Heat" is recommended regardless of the daily high.
default: 5
selector:
number:
min: -10
max: 15
step: 0.5
unit_of_measurement: °C
mode: slider
# ── Thresholds: Off (solar-only) ─────────────────────────────────────────
threshold_off_high:
name: "Off – Min. avg. daily high"
description: >
Average daily high must reach **at least** this value before
"Off" can be recommended.
default: 18
selector:
number:
min: 10
max: 35
step: 0.5
unit_of_measurement: °C
mode: slider
threshold_off_low:
name: "Off – Min. overnight low"
description: >
Coldest overnight low must be **at least** this value before "Off"
is recommended (prevents cold mornings without heating backup).
default: 10
selector:
number:
min: 0
max: 20
step: 0.5
unit_of_measurement: °C
mode: slider
min_sunny_days:
name: "Off – Minimum sunny days"
description: >
Minimum number of forecast days classified as sunny, partly cloudy,
or windy for solar thermal to be considered reliable enough to
recommend "Off".
default: 3
selector:
number:
min: 1
max: 7
step: 1
unit_of_measurement: days
mode: slider
# ── Vacation mode (optional) ─────────────────────────────────────────────
vacation_entity:
name: Vacation Mode Switch (optional)
description: >
A boolean helper (e.g. `input_boolean.vacation`) that, when turned on,
forces the recommendation to "Off" — unless frost protection kicks in.
When this switch is turned on, the recommendation is evaluated
immediately instead of waiting for the next scheduled evaluation time.
Leave empty to disable this feature.
default: ""
selector:
entity:
domain: input_boolean
frost_threshold:
name: "Vacation – Frost protection threshold"
description: >
If vacation mode is active and the overnight low on at least the
configured number of days drops **below** this value, "Heat" is
recommended instead of "Off" to protect pipes from freezing.
default: -10
selector:
number:
min: -20
max: 5
step: 0.5
unit_of_measurement: °C
mode: slider
frost_days:
name: "Vacation – Frost protection min. days"
description: >
Minimum number of forecast days with an overnight low below the frost
threshold required to override vacation mode and recommend "Heat".
default: 2
selector:
number:
min: 1
max: 7
step: 1
unit_of_measurement: days
mode: slider
# ── Reason template (optional) ───────────────────────────────────────────
reason_template:
name: Reason Template (optional)
description: >
Customizes the `{{ reason }}` variable available in your action.
Useful for localization or a different format.
Available sub-variables: `{{ avg_high }}`, `{{ min_low }}`,
`{{ sunny_count }}`, `{{ _days }}`, `{{ vacation_mode }}`,
`{{ frost_day_count }}`.
Leave empty to use the default English format.
default: ""
selector:
template: {}
# ── Action ───────────────────────────────────────────────────────────────
notify_action:
name: Action on Mismatch
description: >
Executed when the recommendation differs from the current mode.
The following variables are available inside your action templates:
`{{ recommendation }}` – the suggested mode label
`{{ current_mode }}` – the mode currently set on the boiler
`{{ reason }}` – a short stats summary (avg high, overnight low, sunny days)
`{{ vacation_mode }}` – true if vacation mode is currently active
selector:
action: {}
# ── Triggers ─────────────────────────────────────────────────────────────────
trigger:
- platform: time
at: !input evaluation_time
- platform: homeassistant
event: start
# Fire immediately when vacation mode is turned changed (only if entity is configured)
- platform: state
entity_id: !input vacation_entity
# ── Actions ──────────────────────────────────────────────────────────────────
action:
# 1. Fetch the daily weather forecast
- action: weather.get_forecasts
target:
entity_id: !input weather_entity
data:
type: daily
response_variable: forecast_raw
# 2a. Input variables and raw forecast slice
- variables:
_weather_id: !input weather_entity
_mode_id: !input heating_mode_entity
_days: !input forecast_days
_val_heat: !input value_heat
_val_hw: !input value_hot_water
_val_off: !input value_off
_thr_h_high: !input threshold_heat_high
_thr_h_low: !input threshold_heat_low
_thr_off_high: !input threshold_off_high
_thr_off_low: !input threshold_off_low
_min_sunny: !input min_sunny_days
_vacation_entity: !input vacation_entity
_frost_threshold: !input frost_threshold
_frost_days: !input frost_days
forecasts: >
{{ forecast_raw[_weather_id].forecast[:_days] }}
# 2b. Derived calculations (references variables from 2a)
- variables:
temps_high: >
{{ forecasts | map(attribute='temperature') | list }}
temps_low: >
{% set lows = forecasts | selectattr('templow', 'defined')
| map(attribute='templow') | list %}
{{ lows if lows | length == forecasts | length
else forecasts | map(attribute='temperature') | list }}
avg_high: >
{{ (temps_high | sum) / (temps_high | length) }}
min_low: >
{{ temps_low | min }}
sunny_count: >
{{ forecasts
| map(attribute='condition')
| select('in', ['sunny', 'partlycloudy', 'windy'])
| list | length }}
# Number of days where overnight low drops below the frost threshold
frost_day_count: >
{{ temps_low | select('lt', _frost_threshold) | list | length }}
# True if vacation entity is configured and currently active
vacation_mode: >
{{ _vacation_entity != "" and states(_vacation_entity) == "on" }}
# 2c. Decision logic (references all variables from 2a and 2b)
# _reason_template is loaded here so its sub-variables are already defined
- variables:
_reason_template: !input reason_template
recommendation: >
{% if vacation_mode %}
{% if frost_day_count >= _frost_days %}
{{ _val_heat }}
{% else %}
{{ _val_off }}
{% endif %}
{% elif avg_high < _thr_h_high or min_low < _thr_h_low %}
{{ _val_heat }}
{% elif avg_high >= _thr_off_high
and min_low >= _thr_off_low
and sunny_count >= _min_sunny %}
{{ _val_off }}
{% else %}
{{ _val_hw }}
{% endif %}
current_mode: "{{ states(_mode_id) }}"
reason: >
{% if _reason_template | trim != "" %}
{{ _reason_template }}
{% else %}
{% if vacation_mode %}
🏖️ Vacation mode |
{% if frost_day_count >= _frost_days %}
❄️ Frost warning: {{ frost_day_count }} days below {{ _frost_threshold }} °C |
{% endif %}
{% endif %}
Avg. high: {{ avg_high | round(1) }} °C |
Overnight low: {{ min_low | round(1) }} °C |
☀️ {{ sunny_count }}/{{ _days }} sunny days
{% endif %}
# 3. Abort if the recommendation already matches the current boiler mode
- condition: template
value_template: "{{ recommendation | trim != current_mode | trim }}"
# 4. Run the user-defined action with recommendation/current_mode/reason in scope
- choose: []
default: !input notify_action
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment