Piloter une pompe à chaleur Daikin avec une sonde de température et la météo
Une sonde dans la pièce, les prévisions de Météo-France et un gain qui s'apprend tout seul : un blueprint qui ajuste la consigne de mes pompes à chaleur Daikin heure par heure.

Après le déménagement, nous avons remplacé l’antique chaudière à gaz par des pompes à chaleur air-air Daikin. Elles chauffent bien, mais elles règlent la température d’après leur propre sonde, intégrée à l’unité intérieure. Cette sonde mesure l’air à hauteur de l’appareil, si bien que la consigne affichée et la température réelle de la pièce ne correspondent jamais tout à fait.
J’avais déjà une sonde dans chacune de ces pièces. Il ne restait plus qu’à laisser Home Assistant corriger la consigne de la pompe à chaleur pour que ce soit la sonde de la pièce qui commande.
Tant qu’à faire, j’en ai profité pour lui faire anticiper la météo, et chauffer un peu plus durant les heures creuses.
Le matériel
- Les pompes à chaleur sont pilotées par le cloud Daikin, via l’application Onecta et son intégration dans Home Assistant. C’est une entorse à ma règle du tout local, et elle a un prix : l’API Onecta est limitée à 200 appels par jour. L’intégration expose le nombre d’appels restants dans un capteur, dont on va se servir. Je prévois déjà de remplacer les modules Wi-Fi par des modules Faikout, qui se pilotent en local.
- Les sondes de pièce sont des IKEA ALPSTUGA, des capteurs de qualité d’air en Matter-over-Thread qui mesurent aussi la température.
- La température extérieure vient de ma station météo Ecowitt WS90, celle installée au fond du jardin.
- Les prévisions horaires viennent de l’intégration Météo-France.
- La période tarifaire vient du compteur Linky, lu par la télé-information.
Le principe
Toutes les heures, l’automatisation compare la température de la pièce à la cible, et adapte la consigne de la pompe à chaleur. Elle corrige un peu, attend une heure, puis recommence. C’est lent, et ça convient à une maison qui met des heures à gagner ou perdre un degré.
Le cran dépend de l’écart à corriger :
| Écart à corriger | Correction de la consigne |
|---|---|
| 1 °C ou plus | 2 crans, soit 1 °C |
| entre 0,3 et 1 °C | 1 cran, soit 0,5 °C |
| moins de 0,3 °C | aucune |
Le cran est celui de la pompe à chaleur (target_temp_step), soit 0,5 °C sur les miennes. La consigne reste bornée entre la cible moins 3 °C et la cible plus 5 °C, sans jamais sortir des limites de l’appareil.
Deux détails rendent la boucle plus réactive :
- Quand on change la température cible, la consigne de la pompe à chaleur bouge immédiatement de la même valeur. Par exemple, passer la cible de 20 à 21 °C monte la consigne d’un degré tout de suite, sans attendre que la boucle s’en rende compte.
- Quand il faut chauffer davantage, le calcul part du plus haut entre la consigne actuelle et la température mesurée par l’unité moins 1 °C. Une consigne restée très en dessous de ce que mesure l’unité pourrait monter de plusieurs crans sans que le compresseur redémarre.
Anticiper la météo
Notre maison réagit lentement aux changements de température. Si le soleil se lève sur une matinée froide et que la température extérieure va prendre 5 °C dans les trois heures, la pièce va se réchauffer toute seule : continuer à pousser le chauffage maintenant, c’est avoir trop chaud dans deux heures. À l’inverse, une chute de température annoncée pour la soirée mérite qu’on anticipe un peu.
L’automatisation récupère donc les prévisions horaires de Météo-France et calcule la variation de température extérieure attendue sur les trois prochaines heures. L’écart à corriger devient :
écart = cible - température de la pièce - gain × variation prévue sur 3 h
Un exemple, avec une température cible de 20 °C et une pièce à 19,6 °C : l’écart vaut 0,4 °C, et la consigne monte d’un cran. Si Météo-France annonce 4 °C de plus dans trois heures et que le gain vaut 0,1, l’écart tombe à 0 et la consigne ne bouge pas.
Un gain qui s’apprend
Reste à trouver le bon gain, et il n’a aucune raison d’être le même d’une pièce à l’autre. Une pièce bien isolée, ou exposée plein nord, ne réagit pas comme une pièce en plein soleil. Plutôt que de le chercher à la main, je le laisse s’ajuster tout seul.
Chaque heure, l’automatisation regarde ce qui s’est réellement passé : la variation de température extérieure mesurée par la station météo sur les trois dernières heures, et l’écart entre la pièce et la cible. Elle corrige le gain ainsi :
nouveau gain = gain + 0,002 × (pièce - cible) × variation mesurée sur 3 h
Si la température extérieure a augmenté et que la pièce a dépassé la cible, l’anticipation était trop faible : le gain augmente. C’est aussi le cas quand la température extérieure a diminué et que la pièce est restée sous la cible. Dans les deux autres cas, l’anticipation était trop forte et le gain diminue.
Le facteur 0,002 garde l’apprentissage lent, pour que quelques heures atypiques ne le dérèglent pas. Le gain reste borné entre 0 et 0,5, et il n’est pas modifié quand la consigne a atteint l’une de ses limites : la pompe à chaleur ne pouvait alors pas faire mieux, et l’écart mesuré ne reflète plus la qualité de l’anticipation.
Le gain démarre à 0. Les premiers jours, la boucle se contente donc de suivre la sonde de la pièce, et l’anticipation arrive progressivement.
Profiter des heures creuses
Dernier ajout : pendant les heures creuses, la cible prend 0,5 °C de plus. La pompe à chaleur stocke un peu de chaleur dans les murs quand l’électricité coûte le moins cher, et la maison la restitue en heures pleines.
La période tarifaire vient d’un capteur template qui lit la télé-information du Linky. Les plages horaires de mon contrat y sont aussi écrites en dur, en secours au cas où la télé-information ne répondrait plus. Elles sont bien sûr à adapter aux vôtres.
Ménager le quota Onecta
L’automatisation tourne une fois par heure et n’envoie une commande que si la consigne change. Elle reste donc loin des 200 appels quotidiens, mais l’intégration consomme elle aussi le quota à chaque rafraîchissement des états.
Par sécurité, elle ne fait plus rien quand il reste moins de 20 appels, et une seconde automatisation me prévient quand ce seuil est franchi. La pompe à chaleur garde alors sa dernière consigne jusqu’à la remise à zéro du quota.
La configuration
Les entrées
Chaque pièce a besoin de deux entrées numériques : la cible, que l’on règle soi-même, et le gain, que l’automatisation fait évoluer.
input_number:
climate_bureau_target:
name: Bureau - Consigne
min: 10
max: 30
step: 0.5
unit_of_measurement: °C
icon: mdi:thermostat
climate_bureau_weather_gain:
name: Bureau - Gain météo
min: 0
max: 0.5
step: 0.001
mode: box
icon: mdi:weather-partly-cloudy
Les capteurs
La variation de température extérieure mesurée sur trois heures est un capteur statistics, commun à toutes les pièces :
sensor:
- platform: statistics
name: Jardin - Variation température 3 h
unique_id: '6cea841f-0c56-4cad-a08a-e813e4996655'
entity_id: sensor.jardin_station_meteo_shelly_ecowitt_ws90_temperature
state_characteristic: change
max_age:
hours: 3
sampling_size: 500
keep_last_sample: true
Et la période tarifaire, HC ou HP :
template:
- sensor:
- name: Période tarifaire
default_entity_id: sensor.periode_tarifaire
unique_id: '49d1ec8c-b186-413f-bffb-4a5cdf0d99ce'
icon: mdi:cash-clock
state: >-
{# Heures creuses de mon contrat, en minutes depuis minuit #}
{% set m = now().hour * 60 + now().minute %}
{% set schedule_hc = 875 <= m <= 993 or m >= 1415 or m <= 333 %}
{{ 'HC' if schedule_hc or is_state('sensor.jardin_teleinfo_periode_tarifaire_en_cours', 'HC') else 'HP' }}
Le blueprint
Toute la logique tient dans un blueprint. La version ci-dessous peut avoir pris du retard, la plus récente est sur mon GitHub (cliquez ici pour l’importer sur votre Home Assistant).
blueprint:
name: Régulation thermique
description: >-
Keeps a room at the target temperature by nudging the heat pump
setpoint from the room sensor, anticipating the outdoor temperature
change of the next 3 hours from the hourly forecast. The anticipation
gain learns from past outdoor changes. Only acts while the unit is in
heat mode. Optionally raises the target during off-peak hours to store
heat in the walls.
domain: automation
input:
climate_entity:
name: Climatisation
selector:
entity:
domain: climate
target_entity:
name: Consigne
selector:
entity:
domain: input_number
temperature_sensor:
name: Sonde de la pièce
selector:
entity:
domain: sensor
device_class: temperature
weather_entity:
name: Météo (prévisions horaires)
selector:
entity:
domain: weather
outdoor_change_sensor:
name: Variation de la température extérieure sur 3 h
selector:
entity:
domain: sensor
weather_gain_entity:
name: Gain météo (appris)
selector:
entity:
domain: input_number
rate_limit_sensor:
name: Quota Onecta restant (facultatif)
default: ''
selector:
entity:
domain: sensor
tariff_sensor:
name: Période tarifaire
selector:
entity:
domain: sensor
off_peak_boost:
name: Surchauffe en heures creuses
default: 0
selector:
number:
min: 0
max: 2
step: 0.5
unit_of_measurement: °C
variables:
climate_entity: !input climate_entity
target_entity: !input target_entity
temperature_sensor: !input temperature_sensor
weather_entity: !input weather_entity
outdoor_change_sensor: !input outdoor_change_sensor
weather_gain_entity: !input weather_gain_entity
rate_limit_sensor: !input rate_limit_sensor
tariff_sensor: !input tariff_sensor
off_peak_boost: !input off_peak_boost
target: >-
{{ states(target_entity) | float
+ (off_peak_boost if is_state(tariff_sensor, 'HC') else 0) }}
room: "{{ states(temperature_sensor) | float(none) }}"
current: "{{ state_attr(climate_entity, 'temperature') | float(none) }}"
low: "{{ [target - 3, state_attr(climate_entity, 'min_temp') | float(0)] | max }}"
high: "{{ [target + 5, state_attr(climate_entity, 'max_temp') | float(99)] | min }}"
triggers:
- trigger: homeassistant
event: start
- trigger: event
event_type: automation_reloaded
- trigger: time_pattern
hours: /1
id: tick
- trigger: state
entity_id: !input target_entity
id: target
- trigger: state
entity_id: !input tariff_sensor
to:
- HC
- HP
id: tariff
conditions:
- condition: template
value_template: >-
{{ is_state(climate_entity, 'heat')
and room is number
and current is number
and (not rate_limit_sensor
or states(rate_limit_sensor) | int(0) >= 20) }}
actions:
- action: weather.get_forecasts
target:
entity_id: "{{ weather_entity }}"
data:
type: hourly
response_variable: forecast
continue_on_error: true
- variables:
gain: "{{ states(weather_gain_entity) | float(0) }}"
hourly: >-
{{ forecast[weather_entity].forecast
if forecast is defined and weather_entity in forecast else [] }}
outlook: >-
{{ hourly[3].temperature - hourly[0].temperature
if hourly | count > 3 else 0 }}
error: "{{ target - room - gain * outlook }}"
setpoint: >-
{% set step = state_attr(climate_entity, 'target_temp_step') or 0.5 %}
{% set base = [current, state_attr(climate_entity, 'current_temperature')
| float(0) - 1] | max
if error > 0 else current %}
{% if trigger.id == 'target' %}
{% set t = current + trigger.to_state.state | float(0)
- trigger.from_state.state
| float(trigger.to_state.state | float(0)) %}
{% elif error | abs >= 1 %}
{% set t = base + (2 * step if error > 0 else -2 * step) %}
{% elif error | abs >= 0.3 %}
{% set t = base + (step if error > 0 else -step) %}
{% else %}
{% set t = current %}
{% endif %}
{{ ([low, (t / step) | round * step, high] | sort)[1] }}
new_gain: >-
{{ ([0, gain + 0.002 * (room - target)
* states(outdoor_change_sensor) | float(0), 0.5] | sort)[1]
| round(3) }}
- if:
- condition: template
value_template: >-
{{ trigger.id == 'tick' and low < current < high
and new_gain != gain }}
then:
- action: input_number.set_value
target:
entity_id: "{{ weather_gain_entity }}"
data:
value: "{{ new_gain }}"
- condition: template
value_template: "{{ setpoint != current }}"
- action: climate.set_temperature
target:
entity_id: "{{ climate_entity }}"
data:
temperature: "{{ setpoint }}"
mode: single
Quelques remarques sur ce code :
- L’automatisation ne fait rien tant que la pompe à chaleur n’est pas en mode chauffage. L’été, elle ne touche pas à la climatisation.
- Si Météo-France ne répond pas,
continue_on_errorlaisse la boucle continuer sans anticipation plutôt que de s’arrêter. - Le gain n’est corrigé que sur le déclenchement horaire : un changement de cible ou de période tarifaire ne fausse pas l’apprentissage.
- Le quota Onecta est facultatif : sans lui, le blueprint fonctionne avec n’importe quelle entité
climatequi exposetemperatureetcurrent_temperature.
L’automatisation
Une fois le blueprint importé, il ne reste plus qu’à créer une automatisation par pièce, depuis l’interface ou en YAML :
- id: climate_bureau
alias: Bureau - Régulation thermique
use_blueprint:
# Chemin du blueprint sous blueprints/automation/
path: comfort/smart_climate.yaml
input:
climate_entity: climate.bureau_room_temperature
target_entity: input_number.climate_bureau_target
temperature_sensor: sensor.bureau_capteur_de_qualite_d_air_alpstuga_temperature
weather_entity: weather.maison
outdoor_change_sensor: sensor.jardin_variation_temperature_3_h
weather_gain_entity: input_number.climate_bureau_weather_gain
rate_limit_sensor: sensor.bureau_gateway_ratelimit_restant_aujourd_hui
tariff_sensor: sensor.periode_tarifaire
off_peak_boost: 0.5
Et l’alerte sur le quota :
- id: onecta_rate_limit
alias: Daikin - Quota Onecta presque épuisé
triggers:
- trigger: numeric_state
entity_id: sensor.bureau_gateway_ratelimit_restant_aujourd_hui
below: 20
actions:
- action: notify.notify
data:
title: 🚨 Daikin - Quota Onecta presque épuisé
message: >-
Il reste {{ trigger.to_state.state }} appels Onecta aujourd'hui.
La régulation thermique est suspendue jusqu'à la remise à zéro du quota.
mode: single
Chez moi, la notification passe par un script qui envoie le message via Apprise et réessaie en cas d’échec. Un simple notify.notify fait également l’affaire.
Conclusion
Je voulais une régulation simple, capable de s’adapter aux différentes sondes que j’ai à la maison. La boucle tourne depuis début novembre, ce qui est encore court pour juger des résultats. Pour l’instant, les retours à la maison sont unanimes : le confort est là. Le gain a besoin de quelques jours de météo variée pour trouver sa valeur, et l’hiver ne fait que commencer.
Je ferai le point en fin de saison, avec les courbes de température et de consommation. Il me restera aussi à adapter le blueprint à la climatisation pour l’été.