|
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 |