Données, calculs et index météorologiques sous Home Assistant

N'ayant pas encore de station météo digne de ce nom, j'utilise trois sources distinctes pour connaitre quelques données météorologiques chez nous.

Capteur de température Aqara dans un boitier pour le prétéger des imtempéries de l'extérieur
Mon capteur de température Aqara dans son boitier prêt à affronter l'extérieur

ℹ️ J'essaye d'utiliser, en priorité, des sources de données locales aux données dans le cloud.

Il existe un add-on sur HACS, Thermal Comfort, permettant d'obtenir quelques éléments présentés dans la suite de l'article, mais j'ai préféré le faire moi-même pour mieux comprendre chaque donnée, calcul ou index.

Le point de rosée

Le point de rosée est également appelé « température de rosée ». Il s’agit tout simplement de la température en degrés en dessous de laquelle la rosée se dépose de façon naturelle à l’extérieur.

template:
  - sensor:
      # https://en.wikipedia.org/wiki/Dew_point#Calculating_the_dew_point
      name: "Dew Point"
      unit_of_measurement: "°C"
      device_class: temperature
      state_class: measurement
      state: >-
        {% set h = states('sensor.garden_sensors_humidity') | float(0.0) %} {## ⚠️ À modifier ##}
        {% set t = states('sensor.garden_sensors_temperature') | float(0.0) %} {## ⚠️ À modifier ##}

        {% set b = 17.62 %}
        {% set c = 243.12 %}
        {% set l = ((h / 100) | log) + ((b * t) / (c + t)) %}

        {{ ((c * l) / (b - l)) | round(2) }}

L'humidité absolue

L'humidité absolue décrit la quantité totale d'eau contenue dans un certain volume d'air (g/mᵌ).

template:
  - sensor:
      # https://pon.fr/dzvents-alerte-givre-et-calcul-humidite-absolue/
      name: "Absolute Humidity"
      unit_of_measurement: "g/m³"
      device_class: humidity
      state_class: measurement 
      state: >-
        {% set h = states('sensor.garden_sensors_humidity') | float(0.0) %} {## ⚠️ À modifier ##}
        {% set t = states('sensor.garden_sensors_temperature') | float(0.0) %} {## ⚠️ À modifier ##}

        {{ ((6.112 * ((17.67 * t) / (243.5 + t) | log) * h * 2.1674) / (t + 273.15)) | round(2) }}

Le point de givrage

Le point de givre est la température en dessous de laquelle l’air n’a plus assez d’énergie pour maintenir l’eau qu’il contient sous forme de vapeur, elle se solidifie pour se transformer en cristaux.

Indice de risque :

  • 0 : Aucun risque de givrage
  • 1 : Givre peu probable malgré la température
  • 2 : Givre probable malgré la température
  • 3 : Forte probabilité de givre
template:
  - sensor:
      # https://pon.fr/dzvents-alerte-givre-et-calcul-humidite-absolue/
      name: "Frost Point"
      unit_of_measurement: "°C"
      device_class: temperature
      state_class: measurement
      state: >-
        {% set t = (states('sensor.garden_sensors_temperature') | float(0.0)) + 273.15 %} {## ⚠️ À modifier ##}
        {% set r = (states('sensor.dew_point') | float(0.0)) + 273.15 %}

        {{ ((r + (2671.02 / ((2954.61 / t) + (2.193665 * (t | log)) - 13.3448)) - t) - 273.15) | round(2) }}
      attributes:
        risk: >-
          {% if is_number(states('sensor.garden_sensors_temperature')) and is_number(states('sensor.frost_point')) and is_number(states('sensor.absolute_humidity')) %} {## ⚠️ À modifier ##}
            {% set f = states('sensor.frost_point') | float %}
            {% set a = states('sensor.absolute_humidity') | float %}
            {% set t = states('sensor.garden_sensors_temperature') | float %} {## ⚠️ À modifier ##}

            {% if t <= 1 and f <= 0 %}
              {% if a <= 2.8 %}
                1
              {% else %}
                3
              {% endif %}
            {% elif t <= 4 and f <= 0.5 and a > 2.8 %}
              2
            {% endif %}
          {% else %}
            0
          {% endif %}

L'indice de chaleur

L'indice de chaleur indique une température ressentie par le corps humain à l'ombre suivant l'humidité relative et la température de l'air. L'indice de chaleur n'est valable que pour des valeurs égalent ou dépassant une température de l'air de 27°C, un point de rosée de 12°C et une humidité relative de 40%.

Indice de risque :

  • 0 : Aucun inconfort
  • 1 : Inconfort
  • 2 : Extrême inconfort
  • 3 : Danger
  • 4 : Danger extrême
template:
  - sensor:
      # https://en.wikipedia.org/wiki/Heat_index
      name: "Heat Index"
      unit_of_measurement: "°C"
      device_class: temperature
      state_class: measurement
      state: >-
        {% set t = states('sensor.garden_sensors_temperature') | float(0.0) %} {## ⚠️ À modifier ##}
        {% set r = states('sensor.garden_sensors_humidity') | float(0.0) %} {## ⚠️ À modifier ##}

        {% set p1 = -8.78469475556 %}
        {% set p2 = 1.61139411 * t %}
        {% set p3 = 2.33854883889 * r %}
        {% set p4 = -0.14611605 * t * r %}
        {% set p5 = -0.012308094 * t * t %}
        {% set p6 = -0.0164248277778 * r * r %}
        {% set p7 = (2.211732e-3) * t * t * r %}
        {% set p8 = (7.2546e-4) * t * r * r %}
        {% set p9 = (3.582e-6) * t * t * r * r %}

        {{ (p1 + p2 + p3 + p4 + p5 + p6 + p7 + p8 + p9) | round(0) }}
      attributes:
        risk: >-
          {% if (states('sensor.garden_sensors_temperature') | float(0.0)) >= 27 and (states('sensor.garden_sensors_humidity') | float(0.0)) >= 40 and (states('sensor.dew_point') | float(0.0)) >= 12 %} {## ⚠️ À modifier ##}
            {% set i = states('sensor.heat_index') | float(0.0) %}

            {% if i >= 54 %}
              4
            {% elif i >= 41 %}
              3
            {% elif i >= 32 %}
              2
            {% elif i >= 27 %}
              1
            {% else %}
              0
            {% endif %}
          {% else %}
            0
          {% endif %}
      icon: >-
        {% set r = state_attr('sensor.heat_index', 'risk') | int(0) %}

        {% if r == 4 %}
          mdi:wifi-strength-4-alert
        {% elif r == 3 %}
          mdi:wifi-strength-3
        {% elif r == 2 %}
          mdi:wifi-strength-2
        {% elif r == 1 %}
          mdi:wifi-strength-1
        {% else %}
          mdi:wifi-strength-off-outline
        {% endif %}

L’indice humidex

L’indice humidex est un indice, prenant en compte la température de l'air et le point de rosée, pour mesurer le confort ressenti par un humain par rapport à la chaleur.

template:
  - sensor:
      # https://fr.wikipedia.org/wiki/Indice_humidex
      name: "Humidex"
      state: >-
        {% set t = states('sensor.garden_sensors_temperature') | float(0.0) %} {## ⚠️ À modifier ##}
        {% set r = states('sensor.dew_point') | float(0.0) %}

        {% set a = 5417.7530*((1 / 273.16) - (1 / (273.15 + r))) %}

        {{ (t + 0.55555 * (6.11 * (e ** a) - 10)) | round(0) }}
      attributes:
        risk: >-
          {% set h = states('sensor.humidex') | float(0.0) %}

          {% if h >= 54 %}
            4
          {% elif h >= 45 %}
            3
          {% elif h >= 40 %}
            2
          {% elif h >= 30 %}
            1
          {% else %}
            0
          {% endif %}
      icon: >-
        {% set r = state_attr('sensor.humidex', 'risk') | int(0) %}

        {% if r == 4 %}
          mdi:wifi-strength-4-alert
        {% elif r == 3 %}
          mdi:wifi-strength-3
        {% elif r == 2 %}
          mdi:wifi-strength-2
        {% elif r == 1 %}
          mdi:wifi-strength-1
        {% else %}
          mdi:wifi-strength-off-outline
        {% endif %}

Bonus

Anticiper les grosses chaleurs

Pour anticiper les périodes de chaleur, j'ai besoin de connaitre la température maximale actuelle et celles des 3 prochains jours. Pour cela, je m'appuie sur les données du capteur de température Aqara du jardin et des données de Météo-France.

⚠️ N'oubliez pas de remplacer les entités sensor.garden_sensors_temperature et weather.xxx par les vôtres !

template:
  - sensor:
      name: "Outside - Temperature max - Next days"
      unit_of_measurement: "°C"
      device_class: temperature
      state_class: measurement
      state: >-
        {% set sensors = [
          states('sensor.garden_sensors_temperature') | float(0.0),
          state_attr('weather.xxx', 'forecast')[0].temperature | float(0.0),
          state_attr('weather.xxx', 'forecast')[1].temperature | float(0.0),
          state_attr('weather.xxx', 'forecast')[2].temperature | float(0.0),
          state_attr('weather.xxx', 'forecast')[3].temperature | float(0.0),
        ] %}

        {{ sensors | max }}

On ajoute un input_number pour régler notre seuil limite pour les fortes chaleurs.

input_number:
  outside_heatwave:
    name: Seuil - Forte chaleur
    min: "0"
    max: "50"
    step: "1"
    unit_of_measurement: "°C"

Et enfin un binary_sensor pour savoir si dans les trois prochains jours ou si actuellement on est dans une période de fortes chaleurs :

template:
  - binary_sensor:
      name: "Outside - Is heatwave"
      state: "{{ states('sensor.outside_temperature_max_next_days') >= states('input_number.outside_heatwave') }}"
      device_class: heat

Ensoleillement

Pour déterminer si le soleil tape sur la façade, j'utilise le capteur de luminosité Xiaomi YTC4043GL.

Capteur de liminosité Xiaomi situé sous le coffre du volet roulant pour le protéger de la pluie

⚠️ Le Xiaomi YTC4043GL n'est pas étanche, il faut le protéger, au maximum, de la pluie !

Le capteur me donne la luminosité à un instant T et non pas une moyenne ce qui peut être contraignant lorsque un nuage passe devant le soleil et fait chuter la luminosité. Pour cela nous allons utiliser l'intégration statistics pour calculer la moyenne sur les 15 dernières minutes et un sensor pour prendre la valeur du capteur quand la moyenne calculée est unavailable :

⚠️ N'oubliez pas de modifier outside_south_illuminance_lux par votre capteur de luminosité

sensor:
  - platform: statistics
    name: "Outside - Illuminance (lux) - South - Stats"
    entity_id: sensor.outside_south_illuminance_lux
    state_characteristic: average_linear
    max_age: "00:15"
    sampling_size: 500
    precision: 0

template:
  - sensor:
      - name: "Outside - Illuminance (lux) - South"
        unit_of_measurement: "lx"
        device_class: illuminance
        state_class: measurement
        state: "{{ states('sensor.outside_illuminance_lux_south_stats') if is_number(states('sensor.outside_illuminance_lux_south_stats')) else states('sensor.outside_south_illuminance_lux') }}"

Pour éviter qu'au seuil mesuré, le booléen oscille entre true / false, on va utiliser le principe du cycle d'hystérésis :

  • Si la valeur dépasse (seuil + marge) lux, on considère qu'il y a ensoleillement (booléen à true)
  • Si la valeur est inférieure à (seuil - marge) lux, on considère qu'il n'y a plus d'ensoleillement (booléen à false)
  • Entre (seuil - marge) et (seuil + marge) lux, on garde la valeur précédente

Nous allons utiliser deux input_number pour les seuils, une automatisation pour gérer le cycle d'hystérésis et un input_boolean pour stocker le résultat :

⚠️ Le seuil haut (seuil + marge) doit toujours être supérieur au seuil bas (seuil - marge). Par simplification, il n'y a aucune vérification dans l'automatisation !

input_boolean:
  sunny_south:
    name: Ensoleillement - Sud

input_number:
  sunny_south_low_threshold:
    name: Ensoleillement - Sud - Seuil bas
    min: "0"
    max: "83000"
    step: "1"
    unit_of_measurement: "lux"
  sunny_south_high_threshold:
    name: Ensoleillement - Sud - Seuil haut
    min: "0"
    max: "83000"
    step: "1"
    unit_of_measurement: "lux"

automation:
  - description: Sunny - South - Threshold
    alias: sunny_south_threshold
    id: d5637a4f-08b6-4ff2-88ba-77b7626a0513
    mode: restart
    trigger:
      - platform: state
        entity_id: input_boolean.sunny_south
      - platform: state
        entity_id: sensor.outside_illuminance_lux_south
    action:
      - choose:
          - conditions:
              - condition: template
                value_template: "{{ not is_state('input_boolean.sunny_south', 'on') }}"
              - condition: numeric_state
                entity_id: sensor.outside_illuminance_lux_south
                above: input_number.sunny_south_high_threshold
            sequence:
              - service: input_boolean.turn_on
                target:
                  entity_id: input_boolean.sunny_south
          - conditions:
              - condition: template
                value_template: "{{ not is_state('input_boolean.sunny_south', 'off') }}"
              - condition: numeric_state
                entity_id: sensor.outside_illuminance_lux_south
                below: input_number.sunny_south_low_threshold
            sequence:
              - service: input_boolean.turn_off
                target:
                  entity_id: input_boolean.sunny_south

Lovelace

Affichage des champs sur le dashboard Lovelace
L'affichage permettant un contrôle visuel rapide

J'utilise trois cards sous lovelace pour réaliser cette interface :

type: vertical-stack
title: Jardin
cards:
  - type: entities
    entities:
      - entity: sensor.garden_sensors_temperature
        type: custom:multiple-entity-row
        name: Informations
        state_header: Température
        secondary_info: last-changed
        entities:
          - sensor.garden_sensors_humidity
      - entity: sensor.outside_illuminance_lux_south
        type: custom:multiple-entity-row
        name: Luminosité
        icon: mdi:brightness-5
        state_header: Sud
        secondary_info: last-changed
        entities:
          - entity: sensor.outside_illuminance_lux_east
            name: Est
      - entity: input_boolean.sunny_south
        type: custom:multiple-entity-row
        name: Ensoleillement
        icon: mdi:weather-sunny
        state_header: Sud
        secondary_info: last-changed
        entities:
          - entity: input_boolean.sunny_east
            name: Est
      - entity: sensor.frost_point
        attribute: risk
        type: custom:multiple-entity-row
        name: Point de givrage
        state_header: Risque
        secondary_info: last-changed
      - entity: binary_sensor.outside_is_heatwave
        type: custom:multiple-entity-row
        name: Forte chaleur
        secondary_info: last-changed
      - entity: sensor.humidex
        attribute: risk
        type: custom:multiple-entity-row
        name: Humidex
        state_header: Risque
        secondary_info: last-changed
      - entity: sensor.heat_index
        attribute: risk
        type: custom:multiple-entity-row
        name: Indice de chaleur
        state_header: Risque
        secondary_info: last-changed
      - type: custom:fold-entity-row
        head:
          label: PARAMÉTRAGE
          type: section
        entities:
          - entity: input_number.garden_heatwave
            type: custom:numberbox-card
            icon: mdi:thermometer
            icon_plus: mdi:chevron-up
            icon_minus: mdi:chevron-down
          - entity: input_number.sunny_east_high_threshold
            type: custom:numberbox-card
            icon: mdi:eye-plus
            icon_plus: mdi:chevron-up
            icon_minus: mdi:chevron-down
          - entity: input_number.sunny_east_low_threshold
            type: custom:numberbox-card
            icon: mdi:eye-minus
            icon_plus: mdi:chevron-up
            icon_minus: mdi:chevron-down
          - entity: input_number.sunny_south_high_threshold
            type: custom:numberbox-card
            icon: mdi:eye-plus
            icon_plus: mdi:chevron-up
            icon_minus: mdi:chevron-down
          - entity: input_number.sunny_south_low_threshold
            type: custom:numberbox-card
            icon: mdi:eye-minus
            icon_plus: mdi:chevron-up
            icon_minus: mdi:chevron-down

Conclusion

Cet article est un peu long car j'avais besoin de vous présenter les différentes données, calculs ou index météorologiques que j'utilise. Ils nous resserviront dans le prochain article sur la gestion des volets.

Vous pouvez aller plus loin en ajoutant, par exemple, des alertes sur les différents risques (givre, chaleur).