Bonjour,
Je pose ça là pour une gestion du chauffage avec des radiateur électrique.
contexte :
- maison principalement chauffé au bois avec des radiateur électrique (1000w)
- relais zigbee de type interrupteur pour chaque radiateur :
Ça chauffe à 1000w ou ça chauffe pas
- capteur de température dans chaque pièce
- gestion du chauffage par calendrier
L’IA a fait pas mal le taf de codage
et Bonne année !
Description
Ce blueprint avancé pour Home Assistant permet de gérer le chauffage multi-pièces de manière intelligente et économe. Il combine :
- Calendrier : applique automatiquement un préréglage (eco, confort, etc.) en fonction des événements.
- Géorepérage : bascule en mode absence si toutes (ou certaines) personnes sont éloignées du domicile.
- Forçage global : possibilité de forcer un préréglage pour toute la maison pendant une durée définie.
- Forçage par pièce : chaque pièce peut être forcée individuellement.
- Limiteur de créneaux (max_slots) : limite le nombre de pièces qui chauffent simultanément pour éviter les surcharges.
- Priorité : définit quelles pièces sont servies en premier si la capacité est limitée.
- Fallback : applique un préréglage de repli si une pièce demande à chauffer mais n’a pas de créneau disponible.
- Logbook Debug : option pour suivre les décisions dans le journal.
Idéal pour optimiser la consommation énergétique tout en gardant le confort.
Prérequis
- Home Assistant ≥ 2023.x
- Entités nécessaires :
input_booleanpour activer/désactiver l’automatisationcalendarpour les événements (message → préréglage)personpour le géorepérageclimatepour les radiateurs/piècestimerpour la durée du forçage globalinput_booleanpar pièce pour le forçage individuel
Guide d’installation et d’utilisation
Importer le Blueprint
- Télécharge le fichier YAML (ou copie-colle le code dans l’éditeur de blueprint).
- Sauvegarde.
Configurer les Inputs
Lors de la création de l’automatisation à partir du blueprint :
- Mode chauffage :
input_booleanmaître pour activer/désactiver. - Calendrier : entité
calendarqui contient les événements (ex. “confort”, “eco”). - Personnes : liste des entités
personpour le géorepérage. - Seuil de distance : en km (ex. 150 km).
- Règle d’absence : ON = toutes les personnes doivent être loin, OFF = au moins une suffit.
- Forçage global :
input_boolean+timer+ durée (en minutes). - Préréglage global forcé : eco, confort, etc.
- Pièces (climate) : liste des entités climate à piloter.
- Ordre de priorité : facultatif, sinon ordre des pièces.
- Forçage par pièce : liste d’
input_boolean(même ordre que les pièces). - Préréglages “chauffage” : ex. home, comfort.
- Fallback : préréglage appliqué si pas de créneau (ex. eco).
- Mapping calendrier → préréglage : personnalise selon tes événements.
- Préréglage par défaut : si message inconnu.
- Max slots : nombre max de pièces chauffant simultanément (hors forcées).
- Debug logbook : active pour voir les décisions dans le journal.
Logique de fonctionnement
- Si forçage global ON → applique le préréglage global forcé.
- Sinon, si toutes/une personne est loin → applique “away”.
- Sinon → applique le préréglage du calendrier.
- Les pièces forcées sont toujours servies.
- Les autres pièces sont limitées par
max_slotset priorisées. - Si une pièce demande à chauffer mais n’a pas de slot → applique le fallback.
Exemple d’usage
- Événement calendrier “confort” → toute la maison passe en confort.
- Si tout le monde est à plus de 150 km → passe en “away”.
- Forçage global ON pendant 4h → tout en “eco”.
- Radiateur du salon forcé → chauffe même si slots pleins.
Informations système (compatibilité vérifiée)
version: core-2025.12.5
installation_type: Home Assistant OS
docker: true | arch: amd64 | OS: Linux 6.12.51-haos
python_version: 3.13.9
timezone: Europe/Paris
Supervisor: supervisor-2025.12.3 | Host OS: Home Assistant OS 16.3
Add-ons: File editor, Studio Code Server, ESPHome, Zigbee2MQTT, Mosquitto, AdGuard, Music Assistant...
Cloud: Nabu Casa actif (remote OK)
Testé sur Home Assistant OS, architecture x86_64, avec HACS et intégrations courantes.
blueprint:
name: "Chauffage — Calendrier + Géorepérage + Forçage Global/Par Pièce + Limiteur de créneaux + Priorité (Anonymisé)"
description: >
Applique un préréglage global déterminé par un calendrier et des règles de géorepérage. Gère un
forçage global (avec minuterie) et un forçage par pièce. Limite le nombre de pièces non forcées
qui chauffent simultanément (max_slots). Une pièce déjà en chauffe et en dessous de sa consigne
termine son cycle avant d’être rétrogradée (sauf si forcée). Un ordre de priorité contrôle quelles
pièces obtiennent des créneaux de chauffe en premier.
domain: automation
input:
mode_switch:
name: "Mode chauffage (activer/désactiver l’automatisation)"
description: "Interrupteur maître. Quand OFF, le blueprint n’exécute aucune action."
selector:
entity:
domain: input_boolean
calendar_entity:
name: "Calendrier pour le préréglage (message → préréglage)"
description: "Événement du calendrier dont le champ 'message' est mappé vers un préréglage via 'preset_mapping'."
selector:
entity:
domain: calendar
persons:
name: "Personnes suivies pour le géorepérage"
description: "Liste des entités 'person' utilisées pour calculer la distance à la maison (zone.home)."
selector:
entity:
multiple: true
domain: person
distance_threshold:
name: "Seuil de distance du géorepérage (km)"
description: "Distance (en km) au-delà de laquelle une personne est considérée 'loin' de la maison."
default: 150
selector:
number:
min: 10
max: 1000
unit_of_measurement: km
mode: slider
enforce_all_far:
name: "Règle d’absence"
description: "ON : TOUTES les personnes doivent être au-delà du seuil. OFF : AU MOINS UNE suffit."
default: true
selector:
boolean: {}
global_force_boolean:
name: "Forçage global (input_boolean)"
description: "Interrupteur pour forcer un préréglage global pendant une durée définie."
selector:
entity:
domain: input_boolean
global_force_timer:
name: "Minuterie du forçage global"
description: "Minuterie associée au forçage global. Démarre/annule automatiquement."
selector:
entity:
domain: timer
global_force_minutes:
name: "Durée du forçage global (minutes)"
description: "Durée appliquée lorsque le forçage global passe à ON."
default: 240
selector:
number:
min: 5
max: 1440
unit_of_measurement: min
mode: slider
global_force_preset:
name: "Préréglage pendant le forçage global"
description: "Préréglage appliqué quand le forçage global est actif."
default: eco
selector:
select:
options:
- eco
- home
- comfort
- away
- sleep
climates:
name: "Pièces (entités climate)"
description: "Liste des entités climate à piloter (une par pièce ou par radiateur)."
selector:
entity:
multiple: true
domain: climate
priority_order:
name: "Ordre de priorité (mêmes entités que 'Pièces', de la plus prioritaire à la moins prioritaire)"
description: "Facultatif. Si vide, l’ordre suit la liste des 'Pièces'. Sert à attribuer les créneaux de chauffe en premier."
selector:
entity:
multiple: true
domain: climate
default: []
per_radiator_force_booleans:
name: "Forçages par pièce (input_booleans ; même ordre que 'Pièces')"
description: "Liste d’input_booleans 1:1 avec 'Pièces'. Quand ON pour une pièce, elle est forcée en chauffe."
selector:
entity:
multiple: true
domain: input_boolean
default: []
heating_presets:
name: "Préréglages considérés comme 'chauffage'"
description: "Toute pièce demandant l’un de ces préréglages est considérée en demande de chauffe."
default:
- home
- comfort
selector:
select:
multiple: true
options:
- home
- comfort
- presence
fallback_non_heating:
name: "Préréglage de repli si une pièce demande à chauffer sans créneau disponible"
description: "Préréglage à appliquer aux pièces en demande mais sans slot disponible (non forcées)."
default: eco
selector:
select:
options:
- eco
- away
- sleep
preset_mapping:
name: "Mapping message de calendrier → préréglage (objet)"
description: "Dictionnaire qui convertit le champ 'message' de l’événement calendrier en préréglage cible."
default:
eco: eco
confort: comfort
comfort: comfort
home: home
presence: home
away: away
absent: away
nuit: sleep
sleep: sleep
selector:
object: {}
default_preset:
name: "Préréglage par défaut si le message du calendrier est inconnu"
description: "Utilisé quand 'message' ne correspond à aucune clé du mapping."
default: eco
selector:
select:
options:
- eco
- away
- home
- comfort
- sleep
max_slots:
name: "Nombre max. de pièces non forcées qui chauffent simultanément"
description: "Capacité en parallèle pour les pièces non forcées (les pièces forcées ne comptent pas dans ce quota)."
default: 2
selector:
number:
min: 1
max: 10
mode: slider
debug_logbook:
name: "Activer les logs de débogage (Logbook)"
description: "Si ON, écrit un résumé des listes calculées dans le Logbook."
default: true
selector:
boolean: {}
mode: restart
# ---- Déclencheurs (ici, !input est nécessaire) ----
trigger:
- platform: calendar
entity_id: !input calendar_entity
event: start
id: cal_start
- platform: calendar
entity_id: !input calendar_entity
event: end
id: cal_end
- platform: state
entity_id: !input mode_switch
to: "on"
id: mode_on
- platform: state
entity_id: !input persons
id: presence_change
- platform: homeassistant
event: start
id: hass_start
- platform: state
entity_id: !input global_force_boolean
to: "on"
id: force_on
- platform: state
entity_id: !input global_force_boolean
to: "off"
id: force_off
- platform: event
event_type: timer.finished
event_data:
entity_id: !input global_force_timer
id: timer_finished
- platform: state
entity_id: !input per_radiator_force_booleans
id: per_room_force_change
# Réallocation des créneaux au début/fin d’un cycle
- platform: state
entity_id: !input climates
attribute: hvac_action
id: hvac_action_change
# ---- Condition (ici aussi, !input est requis) ----
condition:
- condition: state
entity_id: !input mode_switch
state: "on"
# ---- Liaisons d'inputs vers variables "id" (pour éviter !input dans les templates) ----
variables:
calendar_entity_id: !input calendar_entity
mode_switch_id: !input mode_switch
persons_ids: !input persons
distance_threshold: !input distance_threshold
enforce_all_far: !input enforce_all_far
global_force_boolean_id: !input global_force_boolean
global_force_timer_id: !input global_force_timer
global_force_minutes_val: !input global_force_minutes
global_force_preset_val: !input global_force_preset
climates_ids: !input climates
priority_order_in: !input priority_order
force_booleans_ids: !input per_radiator_force_booleans
heating_presets: !input heating_presets
fallback_non_heating: !input fallback_non_heating
preset_mapping: !input preset_mapping
default_preset: !input default_preset
max_slots: !input max_slots
debug_logbook: !input debug_logbook
# Calendrier → préréglage (utilise la variable calendar_entity_id, pas !input)
cal_msg: >-
{{ state_attr(calendar_entity_id, 'message') | default('', true) | lower | trim }}
cal_preset: >-
{% set p = preset_mapping.get(cal_msg) %}
{{ p if p is not none else default_preset }}
action:
# ===== État global / géorepérage =====
- variables:
distances: >-
{% set res = [] %}
{% for p in persons_ids %}
{% set res = res + [distance(p, 'zone.home') | float(0)] %}
{% endfor %}
{{ res }}
both_far: >-
{% set cnt_far = 0 %}
{% for d in distances %}
{% if d > distance_threshold %}
{% set cnt_far = cnt_far + 1 %}
{% endif %}
{% endfor %}
{% if enforce_all_far %}
{{ cnt_far == (distances | count) }}
{% else %}
{{ cnt_far > 0 }}
{% endif %}
global_force_on: "{{ is_state(global_force_boolean_id, 'on') }}"
target_preset_global: >-
{% if global_force_on %}
{{ global_force_preset_val }}
{% elif both_far %}
{{ 'away' }}
{% else %}
{{ cal_preset }}
{% endif %}
# ===== Gestion du timer de forçage global (durée en minutes) =====
- choose:
- conditions:
- condition: trigger
id: force_on
sequence:
- service: timer.start
target:
entity_id: "{{ global_force_timer_id }}"
data:
duration: >-
{% set m = (global_force_minutes_val | int) %}
{{ '%02d:%02d:00' | format((m // 60) | int, (m % 60) | int) }}
- conditions:
- condition: or
conditions:
- condition: trigger
id: force_off
- condition: trigger
id: timer_finished
sequence:
- service: input_boolean.turn_off
target:
entity_id: "{{ global_force_boolean_id }}"
- service: timer.cancel
target:
entity_id: "{{ global_force_timer_id }}"
# ===== Construction des listes : forcées / à terminer / en demande =====
- variables:
forced_list: >-
{% set res = [] %}
{% for cid in climates_ids %}
{% set idx = loop.index0 %}
{% set b = force_booleans_ids[idx] if (force_booleans_ids | count > idx) else none %}
{% if b is not none and is_state(b, 'on') %}
{% set res = res + [cid] %}
{% endif %}
{% endfor %}
{{ res }}
must_finish_list: >-
{% set res = [] %}
{% for cid in climates_ids %}
{% set hv = state_attr(cid, 'hvac_action') | default('unknown') %}
{% set cur = state_attr(cid, 'current_temperature') | float(0) %}
{% set tgt = state_attr(cid, 'temperature') | float(0) %}
{% set heating = (hv == 'heating') or (hv == 'unknown' and cur < tgt) %}
{% if heating and (cur < tgt) %}
{% set res = res + [cid] %}
{% endif %}
{% endfor %}
{{ res }}
wants_list: >-
{% set res = [] %}
{% for cid in climates_ids %}
{% set req = 'home' if (cid in forced_list) else target_preset_global %}
{% if req in heating_presets %}
{% set res = res + [cid] %}
{% endif %}
{% endfor %}
{{ res }}
selected_init: >-
{% set s = forced_list %}
{% for cid in must_finish_list %}
{% if cid not in s %}
{% set s = s + [cid] %}
{% endif %}
{% endfor %}
{{ s }}
capacity: >-
{% set count_non_forced = 0 %}
{% for cid in selected_init %}
{% if cid not in forced_list %}
{% set count_non_forced = count_non_forced + 1 %}
{% endif %}
{% endfor %}
{% set cap = max_slots - count_non_forced %}
{{ 0 if cap < 0 else cap }}
priority: >-
{% set prio = priority_order_in %}
{% for cid in climates_ids %}
{% if cid not in prio %}
{% set prio = prio + [cid] %}
{% endif %}
{% endfor %}
{{ prio }}
selected_final: >-
{% set selected = selected_init %}
{% set cap = capacity | int %}
{% for cid in priority %}
{% if cap > 0 and (cid not in forced_list) and (cid in wants_list) and (cid not in selected) %}
{% set selected = selected + [cid] %}
{% set cap = cap - 1 %}
{% endif %}
{% endfor %}
{{ selected }}
# ===== Application des préréglages finaux par pièce =====
- repeat:
# NOTE : si votre version de HA n’accepte pas le template ici, remettez : for_each: !input climates
for_each: "{{ climates_ids }}"
sequence:
- variables:
cid: "{{ repeat.item }}"
heat: "{{ cid in selected_final }}"
req: "{{ 'home' if (cid in forced_list) else target_preset_global }}"
final_preset: >-
{% if heat %}
{{ req }}
{% else %}
{% if req in heating_presets %}
{{ fallback_non_heating }}
{% else %}
{{ req }}
{% endif %}
{% endif %}
- service: climate.set_preset_mode
target:
entity_id: "{{ cid }}"
data:
preset_mode: "{{ final_preset }}"
# ===== Journal de débogage optionnel =====
- choose:
- conditions:
- condition: template
value_template: "{{ debug_logbook }}"
sequence:
- service: logbook.log
data:
name: "Chauffage (Blueprint)"
message: >-
cal={{ cal_preset }} | target_global={{ target_preset_global }}
| distances={{ distances | map('round', 1) | list }} km
| forced={{ forced_list }}
| must_finish={{ must_finish_list }}
| wants={{ wants_list }}
| selected_final={{ selected_final }}
entity_id: "{{ climates_ids | first }}"