Omitech

Piloter une pompe à chaleur Daikin avec une sonde de température et la météo

Par Domotique 11 min de lecture

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.

Main dirigeant une télécommande réglée sur 21 °C vers une climatisation murale
Photo de Jakub Zerdzicki

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 à corrigerCorrection de la consigne
1 °C ou plus2 crans, soit 1 °C
entre 0,3 et 1 °C1 cran, soit 0,5 °C
moins de 0,3 °Caucune

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.

aucune correction+1+0,50-0,5-1-1,5-1-0,300,311,5Correction de la consigne selon l'écart à corrigerÉcart à corriger (°C) : positif quand la pièce manque de chaleurCorrection (°C)

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_error laisse 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é climate qui expose temperature et current_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é.