Home Assistant & Versatile thermostat

J'ai souvent parlé ici de thermostats et de la planification de ceux ci. Si le modèle basic (climate:) a été amélioré au fil du temps, il manquait hélas toujours quelque chose, ce que le nouveau Versatile Thermostat vient combler. Je vais donc m'intéresser à celui ci afin de gérer mon climatiseur. Les intégrations de climatiseurs se calquent en général sur leur thermostat intégré, et de facto de la sonde qui y est intégré. Celle ci placée à l'intérieur de celui ci n'apporte pas la justesse d'une sonde externe qui elle sera placée à un emplacement plus idoine. On peut également imaginer une sonde virtuelle calculant la moyenne de plusieurs sondes physiques. C'est la principale utilité que je trouve à ce nouveau thermostat qui dans mon cas d'usage va commander le thermostat d'origine de mon climatiseur, mais VT peut également fonctionner de façon plus classique en commandant le switch ou le fil pilote d'un classique convecteur ou une vanne thermostatique. Et en plus c'est made in France !

Les plus du Versatile Thermostat :

  • La gestion d'une sonde externe (mais également de la température extérieure qui semble prise en compte dans son algorithme).
  • La gestion des ouvertures (encore que dans le cas d'un climatiseur je n'apprécie pas trop qu'il le passe à OFF plutôt qu'en ECO ou HG. Une évolution est en cours sur ce point).
  • La gestion de la présence et de l'occupation. Dans mon approche je gère déjà ça autrement avec proximity: et je ne vais pas m'en servir pour l'instant. Mais à terme certainement, ce qui me permettra de supprimer du code.
  • Le mode TPI (Time Proportional Interval). TPI est une fonction d'algorithme qui garantit que le dispositif s'allume le moins longtemps possible afin d'atteindre et de maintenir le bon au niveau de confort sélectionné en fonction de la sonde (très utile avec un climatiseur ou si on se contente de lui demander 21° il travaillera en fonction de sa sonde interne). Ce mode demande à faire évoluer les réglages après quelques semaines d'utilisation en fonction des résultats obtenus. De plus les climatiseurs se gèrent en général par paliers de 1°, avec VT on pourra utiliser des consignes intermédiaires.
  • La sécurisation, qui manque cruellement aux autres thermostats et peut conduire à des situations dangereuses et couteuses : si VT ne reçoit pas d'informations de la sonde dans un délais ajustable, il passe en mode sécurité et abaisse la température, voire coupe le convecteur.
  • La gestion des préréglages. Incomplet pour mon usage, mais cependant assez logique. 
  • Une carte Lovelace, qui est le fork de celle d'un autre thermostat. Louable et utile, mais j'aurais préféré que Jean-Marc fasse un fork de celle-ci qui semble laissée à l'abandon.
  • Le délestage, inutile dans mon cas, mais qui sera un gros plus quand on a que des convecteurs.

Les préréglages (preset)

Que ce soit manuellement ou avec une planification, il y a deux façons de gérer un appareil de chauffage, à la française ou on défini des modes (ECO/CONFORT/BOOST) et l'on s'en tient à ceux ci, soit on fait varier dynamiquement la température de consigne. Personnellement je fait varier la température de consigne avec la planification dont j'ai parlé plusieurs fois ici. C'est également ce qui est utilisé dans les pays nordiques ou les variation de température sont importantes.

L'objectif étant de mettre à disposition de l'utilisateur final une interface la plus claire possible, les préréglages disponibles dans VT peuvent m'être utiles. On peu par exemple imaginer l'utilisateur qui avoir besoin de booster le chauffage, il le fera alors avec le passage en mode BOOST depuis l'interface.

Se posent alors plusieurs questions :

  • Il faut désactiver la planification dynamique qui toutes les 5 minutes va réajuster le thermostat, et donc repasser le thermostat avec les paramètres planifiés.
  • Il faut également prévoir le mode de sortie du mode BOOST (que je considère comme une dérogation) :
    • Sortie manuelle immédiate : il faut un bouton
    • Sortie lors du prochain évènement (tranche horaire planifiée, couché, sortie, etc...) ou non : il faut pouvoir donner à l'utilisateur la possibilité de choisir : il faut un bouton !

Dans ma logique j'aimerais placer ces boutons dans la carte du thermostat. Mais on peut les placer ailleurs.

Sortie manuelle d'un preset

Dans mon cas je réactive simplement l'automation qui gère la planification.

L'idéal aurait été de disposer d'un preset AUTO dont je pourrais récupérer l'état et ainsi réactiver la planification. Il n'est pas possible d'utiliser le preset NONE/MANUAL car celui ci change d'état dès lors que l'on change la consigne.

Faute de disposer d'un preset AUTO (...), le seul preset que je peux détourner est donc celui du mode FROST (hors gel) dont je n'ai pas l'utilité. Je détecte l'action via l'état du VT et je réactive l'automation qui gère la planification (dans la même automation qui me permet de désactiver la planification lorsque je choisit ECO/COMFORT/BOOST), mais également les autres modes du climatiseur (FAN/DRY, etc.).

- id: 56dd275e-3f52-4d8gjk862-5eeft5708a82
  description: Comfort - AC - Mode Auto Versatile
  alias: "Comfort - AC - Mode Auto Versatile"
  mode: restart
  trigger:
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'off' }}"
      id: "off"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'dry' }}"
      id: "off"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'fan_only' }}"
      id: "off"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'heat_cool' }}"
      id: "off"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'heat' and state_attr('climate.ac_versatile', 'preset_mode') in ['boost', 'comfort', 'eco'] }}"
      id: "heat_boost_comfort_eco"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'heat' and state_attr('climate.ac_versatile', 'preset_mode') == 'frost' }}"
      id: "heat_frost"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'heat' and state_attr('climate.ac_versatile', 'preset_mode') == 'none' }}"
      id: "heat_none"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'cool' and state_attr('climate.ac_versatile', 'preset_mode') in ['boost', 'comfort', 'eco'] }}"
      id: "cool_boost_comfort_eco"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'cool' and state_attr('climate.ac_versatile', 'preset_mode') == 'frost' }}"
      id: "cool_frost"
    - platform: template
      value_template: "{{ state_attr('climate.ac_versatile', 'hvac_mode') == 'cool' and state_attr('climate.ac_versatile', 'preset_mode') == 'none' }}"
      id: "cool_none"
  action:
    - choose:
        - conditions: "{{ trigger.id in ['off', 'heat_boost_comfort_eco', 'cool_boost_comfort_eco'] }}" # Dérogation, on coupe le schedulle
          sequence:
            - service: automation.turn_off
              target:
                entity_id: automation.comfort_ac_immediate
              data:
                stop_actions: true                
    - choose:
        - conditions: "{{ trigger.id in ['heat_frost', 'cool_frost'] }}"
          sequence:
            - service: automation.turn_on
              target:
                entity_id: automation.comfort_ac_immediate
            - service: automation.trigger
              target:
                entity_id: automation.comfort_ac_immediate
              data:
                skip_condition: true

Sortie automatique d'un preset

Il s'agit ici de savoir si le prochain évènement (plage horaire, lever/coucher, géoloc, etc...) va repasser le thermostat dans le mode planifié ou pas.

Là je vais utiliser une automation déclenchée par mes différents triggers, automation qui sera ON ou OFF selon la volonté de l'utilisateur.

- id: 56efdfd5e-3f52-4ddd-a862-5e21f5708a82
  description: Comfort - AC - Restart
  alias: "Comfort - AC - Restart"
  mode: restart
  trigger:
    - platform: state
      entity_id:
        - binary_sensor.heating_ac_1
        - binary_sensor.heating_ac_2
        - binary_sensor.heating_ac_3
        - binary_sensor.heating_ac_4
        - binary_sensor.heating_ac_1_d
        - binary_sensor.heating_ac_2_d
        - binary_sensor.heating_ac_3_d
        - binary_sensor.heating_ac_4_d
        - input_boolean.to_away
        - binary_sensor.life_windows_and_doors_delayed
        - input_boolean.presence_ac
        - input_boolean.to_sleep # eco heat off cool
        - input_boolean.thermostats_ac_on_off
        - binary_sensor.lionel_geo # eco heat off cool
        - input_boolean.thermostats_away
        - input_select.comfort_ac
  condition:
    - condition: state
      entity_id: automation.comfort_ac_immediate
      state: "off"
    - condition: state
      entity_id: input_boolean.thermostats_ac_on_off
      state: "on"
  action:
    - service: automation.turn_on
      target:
        entity_id: automation.comfort_ac_immediate
    - service: automation.trigger
      target:
        entity_id: automation.comfort_ac_immediate
      data:
        skip_condition: true

Surveillance

S'agissant d'un "sur thermostat" il est intéressant de savoir ce qui se passe et si possible visuellement. Je vais donc créer deux sensor: / template: qui vont me permettre de suivre et comparer l'état de ces deux thermostats.

sensor:
- platform: template
  sensors:
    temp_up_th_ac: # Pour History Graph
      friendly_name: "TH AC"
      value_template: > 
        {% if is_state('climate.daikin', 'heat') %}
          Heat {{state_attr ('climate.daikin', 'temperature')}}°
        {% elif is_state('climate.daikin', 'cool') %}
          Cool {{state_attr ('climate.daikin', 'temperature')}}°
        {% else %}
          Off
        {% endif %}
    temp_up_th_ac_vt: # Pour History Graph
      friendly_name: "TH AC Versatile"
      value_template: > 
        {% if is_state('climate.ac_versatile', 'heat') %}
          Heat {{state_attr ('climate.ac_versatile', 'temperature')}}°
        {% elif is_state('climate.ac_versatile', 'cool') %}
          Cool {{state_attr ('climate.ac_versatile', 'temperature')}}°
        {% else %}
          Off
        {% endif %}

Visuellement ça me donne deux lignes, la première représente le vrai thermostat du climatiseur tandis que la seconde celui du VT. Et l'on voit bien que pour une consigne fixée ici à 22° le VT pousse parfois le climatiseur à 23°.

Quand je l'utilisait en direct je lui demandais généralement 23° pour obtenir 22° à la sonde. C'est plus que les recommandations étatiques me direz vous, mais il y a une raison ! Avoir 22° dans le hall / couloir me permet d'avoir de 19° à 21° dans les pièces adjacentes, dans lesquelles les convecteurs ne se déclenchent que très rarement, au point que cet hiver je les ai laissés tous OFF. Le seul cas ou ils sont maintenant utiles étant lorsque mes enfants sont présents et ferment les portes de leur chambre (il est bien connu que les ados s'isolent... Et encore le, PC gamer de mon fils doit lui assurer une part de chauffage...).

Economies

Ici on entre dans un domaine difficilement mesurable. En effet la consommation d'un climatiseur est fonction d'une multitude de paramètres, les horaires, le temps qu'il fait, le nombre d'occupants, etc... Il n'en reste pas moins que si je compare la consommation de mon climatiseur avant et après la mise e place du VT j'ai l'impression d'avoir réduit la conso de ± 10%. C'est subjectif et ça demande à être affiné. Par contre ce qui est certain c'est que le fait de ne quasiment plus activer les convecteurs constitue un gain énorme. Et conjugué à une ITE réalisée en 2022 et l'automatisation des volets roulants en fonction de l'ensoleillement, j'ai grandement gagné en confort et en cout. 

L'interface

Il y a longtemps que j'utilise la carte Simple Thermostat qui est très malléable et permet l'affichage d'une multitude d'informations.

Hélas cette carte ne semble plus maintenue et je n'ai pas les compétences pour reprendre le développement, Jean-Marc si tu me lis...

Conclusion

VT est un vrai plus pour le confort ! Je n'ai pas abordé ici la partie refroidissement du climatiseur et je le ferait cet été quand els conditions seront réunies.

En plus c'est made in France et vous pouvez communiquer facilement avec Jean-Marc qui en est l'auteur et se montre très disponible sur le forum HACF ou sur GitHub. Merci à lui !

Home Assistant, boost ponctuel du chauffage

Ceux qui avaient lu mes articles précédents consacrés à Schedy ont du remarquer que j'avais introduit un mode BOOST qui permettait de débrayer la planification pendant n minutes et ainsi forcer le thermostat d'une pièce pensant x minutes à une température choisie. Dans la pratique on s'aperçoit que ce mode n'a d'intérêt que dans la salle de bain si l'on déroge aux horaires habituel. Et c'est particulièrement mon cas car je n'ai pas d'horaires figés et je peux très bien avoir envie de me doucher à 3 heures du mat avant de me coucher après avoir végété sur mon canapé face au dernier épisode de la série du moment...

Ma salle de bain est donc toujours en ECO et j'ai créé une automation basée sur un timer: qui va faire le déroulé dès lors qu'il sera lancé. Je démarre le timer directement avec un bouton de télécommande via ControllerX, mais il est également possible de configurer ça dans la liste des triggers. La durée du boost est contenue dans un input_nuber: 

aqara_button:
  module: controllerx
  class: WXKG11LMRemoteLightController
  controller: 00:15:8d:00:01:e7:d5:24
  integration: 
    name: deconz
    listen_to: unique_id
  light: light.off
  mapping:
    1002:
      service: timer.finish
      entity_id: timer.shower    
    1004:
      service: timer.start
      data:
        duration: "{{ states('input_number.sdb_boost_time') | int * 60 }}"
        entity_id: timer.shower

Ensuite la liste des courses...

timer:
  shower:
    name: Boost SdB

input_number:
  sdb_boost_good:
    name: Boost Good
    min: 20
    max: 25
    step: 0.5
    unit_of_measurement: °C
  sdb_boost_max:
    name: Boost Max
    min: 23
    max: 28
    step: 0.5
    unit_of_measurement: °C
  sdb_boost_time:
    name: Boost Time
    min: 15
    max: 90
    step: 5

Et ensuite l'automation avec un choose: avec des actions conditionnées par les id: des trigger:, avec pour objectif de :

  • Au démarrage du timer on passe le thermostat du sèche serviette sur BOOST et on allume le radiateur soufflant et on l'annonce.
  • Si le timer est démarré et que la température de confort est atteinte on l'annonce sur une enceinte Sonos. A la douche !
  • Si la durée du timer est terminée ou que la température maximum est atteinte on éteint le radiateur soufflant et on repasse le sèche serviette en mode ECO.
- id : 'c88f056d-8bbc-40ff-a044-a3b1b733e3c8'
  alias: "RC : Boost SdB"
  description: "Boost SdB by chooser"
  trigger:
    - platform: numeric_state
      entity_id: sensor.rpi_mi_t_sdb
      above: input_number.sdb_boost_good
      id: "t_good"
    - platform: numeric_state
      entity_id: sensor.rpi_mi_t_sdb
      above: input_number.sdb_boost_max
      id: "tmax"
    - platform: event
      event_type: timer.finished
      event_data:
        entity_id: timer.shower
      id: "timer.finished"
    - platform: event
      event_type: timer.started
      event_data:
        entity_id: timer.shower
      id: "timer.started"
  condition: []
  action:
    - choose:
        - conditions: # Condition n°1
            - condition: trigger
              id: "timer.started"
          sequence:
            - service: climate.set_preset_mode
              data:
                preset_mode: boost
              target:
                entity_id: climate.thermostat_salle_de_bain
            - service: switch.turn_on
              entity_id: switch.plug_bw_01
            - service: tts.cloud_say
              entity_id: media_player.sonos_cloud_hall
              data_template:
                message: "Préparation de la salle de bain. Je vous dirait quand vous pourrez vous doucher !"
                cache: 'false'
        - conditions: # Condition n°2
            - condition: and
              conditions:
                - condition: trigger
                  id: "timer.started"
                - condition: trigger
                  id: "t_good" # On annonce que la température de confort+ est atteinte
          sequence:
            - service: tts.cloud_say
              entity_id: media_player.sonos_cloud_hall
              data_template:
                message: "La température de la salle de bain est de {{states('sensor.rpi_mi_t_sdb')}}°, vous pouvez vous doucher !"
                cache: 'false'
        - conditions: # Condition n°3
            - condition: or
              conditions:
                - condition: trigger
                  id: "timer.finished" # On arrete de chauffage à la fin du timer
                - condition: trigger
                  id: "t_max"          # On arrete le chauffage si la température max est atteinte.
          sequence:
            - service: switch.turn_off
              entity_id: switch.plug_bw_01
            - service: climate.set_preset_mode
              data:
                preset_mode: eco
              target:
                entity_id: climate.thermostat_salle_de_bain
            - service: tts.cloud_say
              entity_id: media_player.sonos_cloud_hall
              data_template:
                message: "Fin du chauffage de la salle de bain. A bientôt !"
                cache: 'false'
      default:

Rien d'extraordinaire et tout ça reste à affiner selon les besoin de chacun. On peut également ajouter une condition à cette automation pour qu'elle ne s'exécute qu'en hiver en se basant que l'intégration Season. En même temps les saisons ça ne veut plus dire grand chose, donc dans la pratique il vaudra mieux avoir quelque part un input_boolean: qui globalement autorisera ou non les fonctions liés au chauffage. Pour l'anecdote (vécue) ça évitera, que comme la semaine dernière (fin mai), la condition 2 n'annonce à la femme de ménage qu'elle peut se doucher...

A noter que si comme moi vous comptez remplacer Schedy par une automation du genre de ce dont j'ai parlé ici, il faudra prévoir de débrayer l'automatisme en le passant en manuel dans la condition 1 et en automatique dans la condition 3.

- service: input_select.set_options
  data:
    options: Automatique
  target:
    entity_id: input_select.comfort_sdb

Voilà !

 

Home Assistant, planification, encore...

J'ai souvent parlé de planification des convecteurs et du climatiseur. Depuis un an je tourne sur deux installations avec ce dont j'avais parlé ici en utilisant le moteur de planification Schedy. Schedy était très bien en son temps et il palliait à des lacunes de Home Assistant. Mais il y a quelques restrictions et son auteur ne le fait plus évoluer. En fait ça se comprend car on peut maintenant à peu près faire la même chose avec le mode choose: et quelques lignes de yaml, et ainsi supprimer une dépendance à un outil externe. Et plus c'est simple, moins il y a de chances de dysfonctionnements.

Je vais donc changer de moteur, par contre je vais réutiliser toutes les entités que j'avais crée pour Schedy, ce qui dans l'absolu en fait une solution réversible. Attention, cet article a été remanié, donc si vous vous étiez inspiré de la première version il sera judicieux de repartir à zéro...

Voici donc ce que j'ai fait pour le climatiseur, plus complexe qu'un simple radiateur car on va gérer les modes chauffage et refroidissement ainsi que le débrayage en manuel.

input_select:
  comfort_ac:
    name: Modes du climatiseur
    icon: mdi:form-select
    options:
      - Refroidissement
      - Chauffage
      - Manuel
      - "Off"

On va utiliser deux automations par pièce ou appareil, la première va gérer les multiples déclencheurs (trigger:), les modes (input_select:) et surtout les exclusions liées au mode de vie (absence de courte ou longue durée, sommeil, fenêtres ouvertes, etc...

Si aucune exclusion n'est validée et selon le mode en cours on lance la seconde automation dans le mode sélectionné. Si on est en mode Manuel, on ne fait rien, mais la seconde automation ne s'exécutera pas.

- id: 56ee275e-3f52-4d88-a862-5e21f5708a82
  description: Comfort - AC - Immediate
  alias: "Comfort - AC - Immediate"
  mode: restart
  trigger:
    - platform: state
      entity_id:
        - input_number.heating_ac_temperature_confort_1
        - binary_sensor.heating_ac_1
        - input_boolean.to_away
        - binary_sensor.life_windows_and_doors_delayed
        - input_boolean.to_sleep
        - binary_sensor.lionel_geo
        - input_select.comfort_ac
        - [...] ⚠️ A compléter 
  condition:
  action:
    - choose:
        - conditions: # 0 OFF
            - condition: or
              conditions:
                - "{{ is_state('binary_sensor.life_windows_and_doors_delayed', 'on') }}"
                - "{{ is_state('input_select.comfort_ac', 'Off') }}"
                - "{{ is_state('input_boolean.presence_ac', 'off') }}"
          sequence:
            - service: climate.set_hvac_mode
              data:
                hvac_mode: 'off'
              target:
                entity_id: climate.daikin

        - conditions: # 01 - COOL OFF
            - condition: or
              conditions:
                - "{{ is_state('input_boolean.thermostats_away', 'on') }}"
                - "{{ is_state('input_boolean.to_away', 'on') }}"
                - "{{ is_state('binary_sensor.lionel_geo', 'off') }}"
            - condition: 
                - "{{ is_state('input_select.comfort_ac', 'Refroidissement') }}"
          sequence:
            - service: climate.set_hvac_mode
              data:
                hvac_mode: 'off'
              target:
                entity_id: climate.daikin

        - conditions:  # 02 - COOL SLEEP
            - "{{ is_state('input_boolean.to_sleep', 'on') }}"
            - "{{ is_state('input_select.comfort_ac', 'Refroidissement') }}"
          sequence:
            - service: climate.set_hvac_mode
              data:
                hvac_mode: 'cool'
              target:
                entity_id: climate.daikin
            - service: climate.set_temperature
              data:
                temperature: "{{ states('input_number.heating_ac_temperature_eco') }}"
              target:
                entity_id: climate.daikin                

        - conditions:  # 03 - HEAT ECO
            - condition: or
              conditions:
                - "{{ is_state('input_boolean.to_away', 'on') }}"
                - "{{ is_state('input_boolean.to_sleep', 'on') }}"
                - "{{ is_state('binary_sensor.lionel_geo', 'off') }}" # input_boolean.presence_lionel_geo
            - condition: 
                - "{{ is_state('input_select.comfort_ac', 'Chauffage') }}"
          sequence:
            - service: climate.set_hvac_mode
              data:
                hvac_mode: 'heat'
              target:
                entity_id: climate.daikin
            - service: climate.set_temperature
              data:
                temperature: "{{ states('input_number.heating_ac_temperature_eco') }}"
              target:
                entity_id: climate.daikin

        - conditions: # 04 - HEAT AWAY
            - "{{ is_state('input_boolean.thermostats_away', 'on') }}"
            - "{{ is_state('input_select.comfort_ac', 'Chauffage') }}"
          sequence:
            - service: climate.set_hvac_mode
              data:
                hvac_mode: 'heat'
              target:
                entity_id: climate.daikin
            - service: climate.set_temperature
              data:
                temperature: "{{ states('input_number.heating_ac_temperature_away') }}"
              target:
                entity_id: climate.daikin

      default:
        - choose:
            - conditions: "{{ is_state('input_select.comfort_ac', 'Refroidissement') }}"
              sequence:
                - service: climate.set_hvac_mode
                  data:
                    hvac_mode: 'cool'
                  target:
                    entity_id: climate.daikin
                - service: automation.trigger
                  target:
                    entity_id: "automation.comfort_ac"

            - conditions: "{{ is_state('input_select.comfort_ac', 'Chauffage') }}"
              sequence:
                - service: climate.set_hvac_mode
                  data:
                    hvac_mode: 'heat'
                  target:
                    entity_id: climate.daikin
                - service: automation.trigger
                  target:
                    entity_id: "automation.comfort_ac"

La seconde automation sert à ajuster la température de consigne, elle est appelée par la première pour une action immédiate et se relancera également toutes les 10 minutes afin de rattraper une action non exécutée, un redémarrage ou un actionneur qui n'a pas fait son travail. Ca compense la replanification qui faisait la force de Schedy.

Qu'elle soit appelée par la première ou qu'elle s'exécute via le time_pattern: il ne se passera rien si toutes (and: par défaut) les conditions ne sont pas remplies (les contraintes gérées dans la première automation). A noter que je place les conditions après action: afin que ces conditions soient prises en compte même si cette automation est lancée manuellement.

Si aucune des conditions liées aux plages horaires actives n'est remplie (plages horaires définies par des binary_sensor: dans le précédent article), alors l'action par défaut passe le thermostat en mode ECO.

automation:
- id: 0ff7454c-4592-4479-b2ae-40eaf3043853
  description: Comfort - AC
  alias: "Comfort - AC"
  mode: restart
  trigger:
    - platform: time_pattern
      minutes: "/10"
  condition:
  action:
    - condition:
        - "{{ not states('input_select.comfort_ac') in ('Manuel', 'Off') }}"
        - "{{ is_state('binary_sensor.life_windows_and_doors_delayed', 'off') }}"
        - "{{ is_state('binary_sensor.lionel_geo', 'on') }}"
        - "{{ is_state('input_boolean.thermostats_away', 'off') }}"
        - "{{ is_state('input_boolean.presence_ac', 'on') }}"
        - "{{ is_state('input_boolean.to_away', 'off') }}"
        - "{{ is_state('input_boolean.to_sleep', 'off') }}"
    - choose:
        - conditions: "{{ is_state('binary_sensor.heating_ac_1', 'on') }}" # Plages horaires
          sequence:
            - service: climate.set_temperature
              data:
                temperature: "{{ states('input_number.heating_ac_temperature_confort_1') }}"
              target:
                entity_id: climate.daikin
        - conditions:  [...] ⚠️ A Compléter
      default:
        - choose:
            - conditions:
              sequence:
                - service: climate.set_temperature
                  data:
                    temperature: "{{ states('input_number.heating_ac_temperature_eco') }}"
                  target:
                    entity_id: climate.daikin

Et enfin l'automation qui gère le mode Manuel / Auto : Si on passe en manuel on désactive l'automation et j'ai fait le choix de passer le thermostat en off. Il est bien sur possible de le réactiver, mais il ne sera plus géré par la planification.

- id: 56dd275e-3f52-4d8ffa862-sejour
  description: Comfort - Sejour - Mode Auto
  alias: "Comfort - Sejour - Mode Auto"
  trigger:
    - platform: state
      entity_id: automation.comfort_sejour_immediate
      to: "on"
      id: "on"
    - platform: state
      entity_id: automation.comfort_sejour_immediate
      to: "off"
      id: "off"
  action:
    - choose:
        - conditions: "{{ trigger.id in ['on', 'xx'] }}"
          sequence:
            - service: automation.trigger
              target:
                entity_id: "automation.comfort_sejour_immediate"
    - choose:
        - conditions: "{{ trigger.id in ['off', 'xx'] }}"
          sequence:
            - service: climate.set_hvac_mode
              data:
                hvac_mode: "off"
              target:
                entity_id: climate.thermostat_sejour

Voilà, c'est finalement assez simple et il suffira ensuite de reproduire pour les autres pièces. Je vous conseille d'utiliser un fichier par pièce dans les packages. Les miens sont ici sur GitHub.

Un grand merci à Fabien, Mathieu et Philipp m'ont ouvert les yeux sur les possibilités offertes par choose: et leur patience quand je sèche sur les templates...

Bonus :

La carte avec tous les modes : (je sais c'est long, à dupliquer selon le nombre de convecteurs).

type: vertical-stack
cards:
  - type: custom:simple-thermostat
    entity: climate.thermostat_sejour
    layout:
      mode:
        icons: true
        headings: false
        names: true
      step: row
    hide:
      temperature: true
      state: true
    sensors:
      - entity: sensor.mi_sejour_temp
        icon: mdi:thermometer
      - entity: binary_sensor.state_openings_life
        icon: mdi:door
      - entity: sensor.sejour_puissance
        icon: mdi:lightning-bolt
    header:
      toggle:
        entity: automation.comfort_sejour_immediate
        name: Manuel/Auto
      name: Bureau
    control:
      - hvac
  - type: history-graph
    entities:
      - entity: switch.sw13_sejour_1
        name: ' '
    hours_to_show: 24
    refresh_interval: 0
  - type: conditional
    conditions:
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: entities
      entities:
        - entities:
            - entity: input_boolean.heating_sejour_enabled_1
              name: NUIT
              toggle: true
            - entity: input_boolean.heating_sejour_enabled_2
              name: MATIN
              toggle: true
            - entity: input_boolean.heating_sejour_enabled_3
              name: MIDI
              toggle: true
            - entity: input_boolean.heating_sejour_enabled_4
              name: SOIR
              toggle: true
          entity: climate.thermostat_sejour
          name: Semaine
          show_state: false
          toggle: false
          icon: mdi:calendar-range
          type: custom:multiple-entity-row
          state_color: true
        - entities:
            - entity: input_boolean.heating_sejour_enabled_1_d
              name: NUIT
              toggle: true
            - entity: input_boolean.heating_sejour_enabled_2_d
              name: MATIN
              toggle: true
            - entity: input_boolean.heating_sejour_enabled_3_d
              name: MIDI
              toggle: true
            - entity: input_boolean.heating_sejour_enabled_4_d
              name: SOIR
              toggle: true
          entity: climate.thermostat_sejour
          name: Week-End
          show_state: false
          toggle: false
          icon: mdi:calendar-range
          type: custom:multiple-entity-row
          state_color: true
        - entities:
            - entity: input_number.heating_sejour_temperature_eco
              type: custom:numberbox-card
              icon_plus: mdi:chevron-up
              icon_minus: mdi:chevron-down
            - entity: input_number.heating_sejour_temperature_away
              type: custom:numberbox-card
              icon_plus: mdi:chevron-up
              icon_minus: mdi:chevron-down
            - entity: binary_sensor.life_windows_and_doors_delayed
            - label: Présence dans la pièce
              type: section
            - entity: input_boolean.presence_sejour
              name: Présence
              show_state: true
            - entity: input_datetime.next_sejour_start
              type: custom:multiple-entity-row
              format: datetime
              name: Arrivée
            - entity: input_datetime.next_sejour_stop
              type: custom:multiple-entity-row
              format: datetime
              name: Départ
          head:
            label: OPTIONS >
            type: section
          padding: 30
          type: custom:fold-entity-row
      title: PLANIFICATION
      show_header_toggle: false
      state_color: true
  - type: conditional
    conditions:
      - entity: input_boolean.heating_sejour_enabled_1
        state: 'on'
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      title: Séjour / Semaine / Nuit
      cards:
        - entity: input_number.heating_sejour_temperature_confort_1
          type: custom:numberbox-card
          icon_plus: mdi:chevron-up
          icon_minus: mdi:chevron-down
          name: Température de confort
          icon: false
        - type: horizontal-stack
          cards:
            - entity: input_datetime.heating_sejour_start_1
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: false
              entities:
                - binary_sensor.heating_sejour_1
            - entity: input_datetime.heating_sejour_end_1
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
  - type: conditional
    conditions:
      - entity: input_boolean.heating_sejour_enabled_2
        state: 'on'
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      title: Séjour / Semaine / Matin
      cards:
        - entity: input_number.heating_sejour_temperature_confort_2
          type: custom:numberbox-card
          icon_plus: mdi:chevron-up
          icon_minus: mdi:chevron-down
          name: Température de confort
          icon: false
        - type: horizontal-stack
          cards:
            - entity: input_datetime.heating_sejour_start_2
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: false
              entities:
                - binary_sensor.heating_sejour_2
            - entity: input_datetime.heating_sejour_end_2
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
  - type: conditional
    conditions:
      - entity: input_boolean.heating_sejour_enabled_3
        state: 'on'
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      title: Séjour / Semaine / Midi
      cards:
        - entity: input_number.heating_sejour_temperature_confort_3
          type: custom:numberbox-card
          icon_plus: mdi:chevron-up
          icon_minus: mdi:chevron-down
          name: Température de confort
          icon: false
        - type: horizontal-stack
          cards:
            - entity: input_datetime.heating_sejour_start_3
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: false
              entities:
                - binary_sensor.heating_sejour_3
            - entity: input_datetime.heating_sejour_end_3
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
  - type: conditional
    conditions:
      - entity: input_boolean.heating_sejour_enabled_4
        state: 'on'
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      title: Séjour / Semaine / Soir
      cards:
        - entity: input_number.heating_sejour_temperature_confort_4
          type: custom:numberbox-card
          icon_plus: mdi:chevron-up
          icon_minus: mdi:chevron-down
          name: Température de confort
          icon: false
        - type: horizontal-stack
          cards:
            - entity: input_datetime.heating_sejour_start_4
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: false
              entities:
                - binary_sensor.heating_sejour_4
            - entity: input_datetime.heating_sejour_end_4
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
  - type: conditional
    conditions:
      - entity: input_boolean.heating_sejour_enabled_1_d
        state: 'on'
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      title: Séjour / Week-End / Nuit
      cards:
        - entity: input_number.heating_sejour_temperature_confort_1_d
          type: custom:numberbox-card
          icon_plus: mdi:chevron-up
          icon_minus: mdi:chevron-down
          name: Température de confort
          icon: false
        - type: horizontal-stack
          cards:
            - entity: input_datetime.heating_sejour_start_1_d
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: false
              entities:
                - binary_sensor.heating_sejour_1_d
            - entity: input_datetime.heating_sejour_end_1_d
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
  - type: conditional
    conditions:
      - entity: input_boolean.heating_sejour_enabled_2_d
        state: 'on'
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      title: Séjour / Week-End / Matin
      cards:
        - entity: input_number.heating_sejour_temperature_confort_2_d
          type: custom:numberbox-card
          icon_plus: mdi:chevron-up
          icon_minus: mdi:chevron-down
          name: Température de confort
          icon: false
        - type: horizontal-stack
          cards:
            - entity: input_datetime.heating_sejour_start_2_d
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: false
              entities:
                - binary_sensor.heating_sejour_2_d
            - entity: input_datetime.heating_sejour_end_2_d
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
  - type: conditional
    conditions:
      - entity: input_boolean.heating_sejour_enabled_3_d
        state: 'on'
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      title: Séjour / Week-End / Midi
      cards:
        - entity: input_number.heating_sejour_temperature_confort_3_d
          type: custom:numberbox-card
          icon_plus: mdi:chevron-up
          icon_minus: mdi:chevron-down
          name: Température de confort
          icon: false
        - type: horizontal-stack
          cards:
            - entity: input_datetime.heating_sejour_start_3_d
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: false
              entities:
                - binary_sensor.heating_sejour_3_d
            - entity: input_datetime.heating_sejour_end_3_d
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
  - type: conditional
    conditions:
      - entity: input_boolean.heating_sejour_enabled_4_d
        state: 'on'
      - entity: automation.comfort_sejour_immediate
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      title: Séjour / Week-End / Soir
      cards:
        - entity: input_number.heating_sejour_temperature_confort_4_d
          type: custom:numberbox-card
          icon_plus: mdi:chevron-up
          icon_minus: mdi:chevron-down
          name: Température de confort
          icon: false
        - type: horizontal-stack
          cards:
            - entity: input_datetime.heating_sejour_start_4_d
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: false
              entities:
                - binary_sensor.heating_sejour_4_d
            - entity: input_datetime.heating_sejour_end_4_d
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true

Home Assistant, timers & choose

Il y a bientôt deux ans, peu après mes débuts sous Home Assistant, j'ai mis en place un scénario de confort pour la douche. En gros la salle de bain est toujours en mode ECO et quand je souhaite prendre une douche j'appuie sur un bouton, ça lance le chauffage et un radiateur soufflant et ça l'éteint au bout d'un moment.

Aujourd'hui j'ai voulut l'améliorer afin de le lancer quand je dois me lever à une heure fixe, ce qui heureusement est plutôt rare. J'ai donc fait une automation qui se déclenche à partir d'un input_datetime: qui va chauffer la salle de bain, lancer la clim en mode chauffage via un script de dérogation de Schedy, éclairer quelques lampes et ouvrir quelques volets. Rien d'extraordinaire à ce stade et il faudrait y ajouter de la musique et allumer la bouilloire pour le thé....

automation:
- id: 'fab3sdfty-bgg6-4ddc-a23f-ee589300012c'
  alias: "RC : Réveil + douche"
  description: ''
  trigger:
    platform: time
    at: input_datetime.alarm_clock_date_time
  condition:
  action:
  - service: timer.start              # On lance le chauffage de la SdB
    data:
      duration: 00:25:30
    target:
      entity_id: timer.shower
  - service: input_boolean.turn_off   # On passe en mode jour (utile pour le chauffage)
    target:
      entity_id: input_boolean.to_sleep 
  - service: script.heating_ac_boost
  - delay: 00:10:00                   # On se prélasse encore un peu au lit...
  - service: cover.set_cover_position # On entreouvre le volet de la chambre
    data:
      position: 22
    target:
      entity_id:
        - cover.vr_lionel
  - service: cover.set_cover_position # On ouvre le volet de la cuisine
    data:
      position: 60
    target:
      entity_id:
        - cover.vr_cuisine      
  - service: light.turn_on            # On allume quelques lampes
    data:
      color_name: yellowgreen
    target:
      entity_id: light.groupe_d_ampoules_led

Là ou ça va devenir intéressant c'est que j'ai refondu mes automations de douche en une seule qui va utiliser un timer: et la fonction choose:. Je n'avais jamais utilisé de timer et encore moins le chooser, j'ai donc un peu galéré, mais voici le résultat, largement simplifié avec les trigger_id:, ce qui évite des templates...

automation:
- id : 'c88f056d-8bbc-40ff-a044-a3b1b733e3c8'
  alias: "RC : Boost SdB"
  description: "Boost SdB by chooser"
  trigger:
    - platform: numeric_state
      entity_id: sensor.rpi_mi_t_sdb
      above: input_number.sdb_boost_good
      id: "t_good"
    - platform: numeric_state
      entity_id: sensor.rpi_mi_t_sdb
      above: input_number.sdb_boost_max
      id: "tmax"
    - platform: event
      event_type: timer.finished
      event_data:
        entity_id: timer.shower
      id: "timer.finished"
    - platform: event
      event_type: timer.started
      event_data:
        entity_id: timer.shower
      id: "timer.started"
  condition: []
  action:
    - choose:
        - conditions:
            - condition: trigger
              id: "timer.started" # Mode ON sur la base su timer qui est lancé depuis une télécommande ou une autre automation
          sequence:
            - service: climate.set_preset_mode
              data:
                preset_mode: boost
              target:
                entity_id: climate.thermostat_salle_de_bain
            - service: switch.turn_on
              entity_id: switch.plug_bw_01
            - service: notify.slack_hass_canaletto
              data:
                message: "{{now().strftime('%d/%m/%Y, %H:%M')}} > BOOST | START | Salle de bain | Température : {{ states('sensor.rpi_mi_t_sdb') }}°"
            - service: tts.cloud_say
              entity_id: media_player.sonos_cloud_hall
              data_template:
                message: "Préparation de la salle de bain. Je vous dirait quand vous pourrez vous doucher !"
                cache: 'false'
        - conditions:
            - condition: trigger
              id: "t_good" # On annonce que la température de confort+ est atteinte
          sequence:
            - service: tts.cloud_say
              entity_id: media_player.sonos_cloud_hall
              data_template:
                message: "La température de la salle de bain est de {{states('sensor.rpi_mi_t_sdb')}}°, vous pouvez vous doucher !"
                cache: 'false'
            - service: notify.slack_hass_canaletto
              data:
                message: "{{now().strftime('%d/%m/%Y, %H:%M')}} > BOOST | READY | Salle de bain | Température : {{ states('sensor.rpi_mi_t_sdb') }}°"
        - conditions:
            - condition: or
              conditions:
                - condition: trigger
                  id: "timer.finished" # On arrete de chauffage à la fin du timer
                - condition: trigger
                  id: "t_max"          # On arrete le chauffage si la température max est atteinte.
          sequence:
            - service: switch.turn_off
              entity_id: switch.plug_bw_01
            - service: climate.set_preset_mode
              data:
                preset_mode: eco
              target:
                entity_id: climate.thermostat_salle_de_bain
            - service: notify.slack_hass_canaletto    
              data:
                message: "{{now().strftime('%d/%m/%Y, %H:%M')}} > BOOST | END | Salle de bain | Température : {{ states('sensor.rpi_mi_t_sdb') }}°"
            - service: tts.cloud_say
              entity_id: media_player.sonos_cloud_hall
              data_template:
                message: "Fin du chauffage de la salle de bain. A bientôt !"
                cache: 'false'
      default:

Le log dans Slack me sert au debug. Le TTS via l'intégration Sonos Cloud permet de baisser la musique et de la réactiver une fois l'annonce terminée.

On notera également que par défaut les conditions du chooser son and et qu'il est possible de les passer en or.

Si aucun trigger n'est utilisé on peut également définir une action par défaut. J'ai fait le choix de ne pas l'utiliser et d'activer ou désactiver le chauffage en lançant le timer pour une certaine durée, depuis un bouton (via ControlerX ou un BluePrint) ou l'automation du réveil : 

  - service: timer.start
    data:
      duration: 00:25:30
    target:
      entity_id: timer.shower

ou en forçant sa fin...

  - service: timer.finish
    target:
      entity_id: timer.shower

Et pour paramétrer une ligne sur Lovelace, à noter que j'ai oublié d'intégrer un template pour la durée dans les commandes ci dessus.

 

Home Assistant & Thermostats

Sous Home Assistant le thermostat de base (climate:) est plutôt "basic", et c'est le moins que l'on puisse dire ! Fort heureusement il y a des personnes géniales qui savent coder et se sont attelées à le modifier.

Simple Thermostat

Ce premier fork reprend simplement le thermostat de base en y ajoutant les "presets" que tout le monde attendait. En gros il y a deux façons d'utiliser un thermostat, soit on modifie dynamiquement la valeur de consigne avec des automations, le Scheduler ou Schedy et c'est ce que je fais, soit on joue la carte des presets à la française avec des modes ECO, CONFORT, Etc.. Et c'est ce que fait Simple Thermostat de façon très simple. Par exemple si on se positionne en BOOST et que l'on lui indique une consigne de 22°, il la gardera en mémoire. Ensuite on pourra rappeler le preset avec une automation

service: climate.set_preset_mode
target:
  entity_id: climate.thermostat_lionel
data:
  preset_mode: boost

Ou encore via l'interface :

EDIT 04/02/2022 : à partir de la version 2022-02 le thermostat générique de base évolue. Il est maintenant possible de définir des préréglages et de les rappeler via un service. Peut être pas aussi smart que Simple Thermostat, mais ça a le mérite d'être intégré dans le core et de ne pas faire appel à un composant externe qui peut toujours être délaissé par son développeur :

climate:
  - platform: generic_thermostat
    name: "Thermostat : Hall"
    heater: switch.sw03_hall
    target_sensor: sensor.rpi_mi_t_hall
    min_temp: 12
    max_temp: 24
    ac_mode: false
    cold_tolerance: 0.3
    hot_tolerance: 0
    min_cycle_duration:
      seconds: 360
    keep_alive:
      minutes: 3
    away_temp: 10
    comfort_temp: 20
    home_temp: 21
    sleep_temp: 18
    activity_temp: 19
    precision: 0.1

Smart Thermostat

On est ici face à un thermostat intelligent (j'ai pas non plus dit AI !) qui se base sur un contrôleur PID afin de déterminer la période pendant laquelle le convecteur devra être ON afin de profiter de l'inertie de celui ci. Le capteur cible mesure la température ambiante tandis que l'interrupteur contrôle un système de chauffage ON/OFF. Le contrôleur PID calcule la durée pendant laquelle le convecteur doit rester allumé pendant la période PWM pour atteindre le point de consigne, par exemple avec PWM réglé sur 15 minutes, si la sortie est de 100 %, le réchauffeur restera allumé pendant les 15 prochaines minutes. Si la sortie PID est de 33 %, le réchauffeur sera allumé pendant 5 minutes seulement.

Vous n'avez pas tout compris ? J'avoue que moi non plus. Mais le fait est que ça fonctionne, et d'ailleurs pour preuve en voici la formule :

Plus sérieusement, ce système sera plus particulièrement efficace avec des convecteurs (ou un autre système de chauffage) avec une forte inertie. Donc exit les convecteurs du genre grille pain...

Pour ceux qui aimerait en savoir plus je vous conseille la lecture du projet ou le mainteneur nous donne pas mal d'explications ainsi que quelques liens :

Ensuite voici la configuration type. On notera qu'ici l'idée des presets est reprise, mais qu'il faut les figer dans la configuration alors qu'ils sont dynamiques dans Simple Thermostat (Adrien si tu nous suit..).

climate:
  - platform: smart_thermostat
    name: "Thermostat : Lionel"
    heater: switch.sw01_lionel
    target_sensor: sensor.mi_lionel_temp
    min_temp: 10
    max_temp: 26
    ac_mode: False
    target_temp: 19
    keep_alive:
      seconds: 60
    precision: 0.1
    away_temp: 14
    eco_temp: 18
    boost_temp: 22
    comfort_temp: 20
    home_temp: 21
    sleep_temp: 17
    activity_temp: 20
    kp : 75
    ki : 0.001
    kd : 70000
    pwm : 00:15:00

Bien sur ces réglages sont à affiner en fonction du convecteur et de la pièce à chauffer. Pour cela il existe un mode AutoTune :

    autotune: "ciancone-marlin"

Dixit son auteur ce mode n'est pas encore tout à fait sur. J'y reviendrait donc plus tard au fil de mes tests.

En attendant on peu constater que là ou le convecteur aurait été ON jusqu'à attendre la température de consigne avec le thermostat standard (la première ligne), il y a eu plusieurs interruptions avec ce thermostat (la seconde ligne). Et  vous l'aurez compris, plus les interruptions sont importantes, moins la consommation le sera, et la facture d'autant plus réduite.

Multizone Thermostat

Il existe une autre variante qui intègre ces différents modes et bien qu'utilisable par zones indépendantes permet en plus d'ajuster la température voulue sur plusieurs zones adjacentes. Je vous laisse lire la description compète sur son Git, je l'ai quand à moi installé dans mon bureau afin d'essayer de déterminer que mode sera le plus adapté entre cout et confort. et je verrais dans quelques jours si le résultat est probant.

Il propose plusieurs modes de fonctionnement :

  • ON/OFF : proche du thermostat de base mais en prenant compte des valeurs d'hystérésis. Dans ce mode il peut également gérer les modes chauffage et refroidissement.
  • Proportionnel : PID avec possibilité de compensation météo et gestion de plusieurs valves. Les possibilité sont énormes mais la mise au point sera délicate.

Autres

Ici on va trouver une autre approche que je n'ai pas explorée.

Conclusion

Ces thermostats évolués sont très intéressants sur le papier et remplacent avantageusement le thermostat de base en fonction des besoins. Par contre ils restent encore complexes à mettre au point et il faudra peut être tout l'hiver pour en tirer des conclusions...

J'y reviendrais dans les prochains jours, mais ces deux approches constituent une avancée importante pour Home Assistant et j'espère que tout cela sera un jour intégré au Core.

Echanger

Voici deux fils ou échanger sur ce sujet :

 

Home Assistant & Schedy, encore...

L'hiver approche et on ouvre à nouveau ce sujet...

Pour mémoire, afin de planifier un peu finement pièce par pièce un chauffage électrique par convecteurs, on a principalement trois possibilités dans Home Assistant :

  • Faire une multitude de d'automations en YAML, j'ai fait lors de mes débuts avec Home Assistant et j'en ai parlé ici et .
  • Utiliser le Scheduler de Niels, j'en ai parlé ici et c'est une bonne solution GUI, mais il y a des lacunes (pas de replanification à la volée, pas de gestion ECO entre les plages).
  • Et enfin Schedy dont j'ai parlé plusieurs fois ( 1 | 2 ), qui lui ne dispose pas d'interface. On va donc s'en servir de moteur et lui construire une interface Lovelace. En fait j'ai géré ma clim avec tout l'été sur la base de ce que j'avais fait et c'est parfait. Je vais maintenant l'adapter afin d'en faire profiter mon frère, avec d'autres contraintes et le souhait qu'il soit le plus autonome possible.

Cahier des charges

On veut pouvoir gérer :

  • 4 plages d'horaires par jour pour 3 types de journées :
    1. Jour de semaine travaillé.
    2. Samedi matin travaillé.
    3. Dimanche, jours fériés et vacances à la maison...
  • Activation / désactivation possible de chacune des plages et faire en sorte qu'elles ne soient affichées que si elles sont actives. Le but étant de ne pas afficher les plages inutiles pour ne pas prêter à confusion. Par exemple, le dimanche si on chauffe le séjour de 10:00 à minuit, on a besoin que d'une seule plage.
  • Une température différente pour chacune des plages, en opposition à un classique ECO/CONFORT à la française. 
  • Une température ECO commune à toutes les périodes entre les plages; mais propre à chaque pièce et une température ABSCENT (HG).
  • La présence de l'occupant de la pièce (en entrant ses dates d'arrivée et de départ ou par géolocalisation, présence dans un rayon de...). Cette fonction ne sera utile que pour les chambres des enfants qui vont et viennent...
  • La planification des vacances de l'occupant principal, avec passage en mode week-end si présent.
  • Boost dynamique : température t pendant n minutes. Utile pour une dérogation temporaire, genre un occupant ne va pas travailler un lundi après-midi, il serait dommage qu'il se gèle...
  • Désactivation en cas d'ouverture d'une fenêtre pendant n minutes. Inutile de chauffer quand on aère les pièces...
  • Un mode télétravail, je ne l'ai pas encore vraiment défini, mais il s'agira probablement d'un script qui va apporter quelques dérogation sur le principe du boost.
  • Un mode dormir (utile pour passer en ECO les pièce de vie quand on va se coucher plus tôt que ce qui est programmé, s'insère dans une automation plus globale qui désactive également les éclairages).
  • Un mode absence temporaire (on ne passe pas le chauffage en mode ABSCENT HG comme une absence de longue durée, mais en mode ECO (binary sur géo loc ou via les fonction de l'alarme).

Philosophie

Toutes les plages sont gérés par des binary_sensor via différentes sources (input_datetime, input_number, input_boolean, et d'autres binary_sensor...) et Schedy ne fera qu'affecter la température de consigne correspondant à la plage au thermostat (climate:) quand le binary passera à ON. Il nous faut donc 1 binary par plage, ce qui fait 3 groupes de 4 = 12 pour une pièce. Ces binary constituent la partie centrale, ils risquent donc d'évoluer et je publierait les mise à jour sur GitHub. Ne cherchez pas à recopier ces bouts de code, il ne sont là que pour expliquer la philosophie et proviennent de plusieurs tests, vous trouverez mes sources sur GitHub. et ici pour un second projet plus complet. (je vais essayer de les tenir à jour).

Si pour gérer ces binary_sensor les variables que sont les input_boolean sont simple, il faut préparer les binary_sensor qui vont nous servir à déterminer le type de journée :

binary_sensor:
  - platform: template
    sensors:
      workday_saturday_working:
        friendly_name: "Samedi travaillé"
        value_template: >
          {% if now().isoweekday() in (6,) and states.binary_sensor.workday_sensor.state == 'on' and states.input_boolean.holidays_andre.state == 'off' %}
            true
          {%else%}
            false
          {% endif %}

      workday_weekday_working:
        friendly_name: "Jour de semaine travaillé"
        value_template: >
          {% if now().isoweekday() in (1,2,3,4,5,) and states.binary_sensor.workday_sensor.state == 'on' and states.input_boolean.holidays_andre.state == 'off'%}
            true
          {%else%}
            false
          {% endif %}

      workday_weekend_holidays:
        friendly_name: "Week-End, Vacances ou jour Férié"
        value_template: >
          {% if states.binary_sensor.workday_sensor.state == 'off' or states.input_boolean.holidays_andre.state == 'on'%}
            true
          {%else%}
            false
          {% endif %}

Mais à y réfléchir on peut envisager quelque chose de plus simple avec un template sensor :

template:
  - sensor:
      - name: "Day Type"
        state: >
          {% if now().isoweekday() in (6,) and states.binary_sensor.workday_sensor.state == 'on' and states.input_boolean.holidays_andre.state == 'off' %}
            saturday_working
          {% elif now().isoweekday() in (1,2,3,4,5,) and states.binary_sensor.workday_sensor.state == 'on' and states.input_boolean.holidays_andre.state == 'off'%}
            weekday_working
          {% elif states.binary_sensor.workday_sensor.state == 'off' or states.input_boolean.holidays_andre.state == 'on'%}
            weekend_and_holidays
          {% else %}
            failed
          {% endif %}

Ce qui va nous donner :

    binary_sensor:
      heating_antoine_2_s:
        entity_id: sensor.time    <<< IL FAUT SUPPRIMER CETTE LIGNE DESORMAIS OBSOLETE
        friendly_name: Mode Confort
        value_template: >
          {% set t = states('sensor.time') %}
          {% set start = states('input_datetime.heating_antoine_start_2_s') [0:5] %}
          {% set stop = states('input_datetime.heating_antoine_end_2_s') [0:5] %}
          {{ (start <= t < stop if start < stop else (start <= t or t < stop)) == true   # Slot horaire
            and states('sensor.day_type') == 'saturday_working'                          # Type de journée
            and states('input_boolean.presence_antoine') == 'on'                         # Présence de l'occupant
            and states('input_boolean.heating_antoine_enabled_2_s') == 'on'              # Activation de la plage    
          }}

Ce qui va se traduire dans Schedy par :

  - x: state("input_number.heating_antoine_temperature_confort_2_s") if (is_on("binary_sensor.heating_antoine_2_s")) else Next()

Et bien sur dans Schedy on va gérer d'autres contraintes plus globales :

  - x: "Break() if is_off('input_boolean.thermostats_on_off') else Next()"    # Etat général du chauffage
  - x: "Break() if is_on('binary_sensor.antoine_window_delayed') else Next()" # Fenêtre ouverte

Ainsi que le mode ECO si aucune plage confort n'est active : 

  - x: state("input_number.heating_antoine_temperature_eco") if (is_on("input_boolean.presence_antoine")) else Next()

Et ABSCENT si les contraintes générales son validées :

  - x: state("input_number.heating_antoine_temperature_away")

Et il ne faut pas oublier l'entête, avec deux types de thermostats. A noter que dans le cas du climatiseur on va gérer deux modes HVAC : off et heat_cool.

schedy_heating:
  module: hass_apps_loader
  class: SchedyApp

  actor_type: thermostat
  actor_templates:
    ac:
      # send_retry_interval: 15
      send_retries: 20
      supports_hvac_modes: true 
      # off_temp: 17
      # delta: 0
      hvac_mode_off: "off"
      hvac_mode_on: heat_cool  # J'ai demandé à pouvoir gérer ce mode via un input_select: mais le développeur ne semble pas très chaud...
      # max_temp: 95
      # min_temp: 45

    convecteur:
      send_retry_interval: 30
      send_retries: 10
      supports_hvac_modes: true

Exemples

Et voici la programmation d'une chambre avec un convecteur :

      schedule:
      - rules:
        - rules:
          # CONTRAINTES
          - x: "Break() if is_off('binary_sensor.marie_home') else Next()"          # Absence temporaire, on passe en ECO
          - x: "Break() if is_off('input_boolean.thermostats_on_off') else Next()"  # ON / OFF global du chauffage, on passe le thermostat à OFF
          - x: "Break() if is_on('input_boolean.thermostats_away') else Next()"     # Absence globale prolongée (on ferme la maison), on passe en consigne AWAY
          - x: "Break() if is_on('binary_sensor.marie_window_delayed') else Next()" # Contrainte ouverture de fenêtre, on passe en consigne AWAY
          # CONFORT SEMAINE
          - x: state("input_number.heating_marie_temperature_confort_1") if (is_on("binary_sensor.heating_marie_1")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_2") if (is_on("binary_sensor.heating_marie_2")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_3") if (is_on("binary_sensor.heating_marie_3")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_4") if (is_on("binary_sensor.heating_marie_4")) else Next()
          # CONFORT SAMEDI
          - x: state("input_number.heating_marie_temperature_confort_1_s") if (is_on("binary_sensor.heating_marie_1_s")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_2_s") if (is_on("binary_sensor.heating_marie_2_s")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_3_s") if (is_on("binary_sensor.heating_marie_3_s")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_4_s") if (is_on("binary_sensor.heating_marie_4_s")) else Next()
          # CONFORT DIMANCHE & FERIE
          - x: state("input_number.heating_marie_temperature_confort_1_d") if (is_on("binary_sensor.heating_marie_1_d")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_2_d") if (is_on("binary_sensor.heating_marie_2_d")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_3_d") if (is_on("binary_sensor.heating_marie_3_d")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_4_d") if (is_on("binary_sensor.heating_marie_4_d")) else Next()
          # ECO
          - x: state("input_number.heating_marie_temperature_eco") if (is_on("input_boolean.presence_marie")) else Next()
          - x: "Break(2)"
      # REGLES LIES AUX CONTRAINTES (Attention à l'ordre des règles)
      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.thermostats_on_off")) else Next()
      - x: state("input_number.heating_marie_temperature_away") if (is_on("input_boolean.thermostats_away")) or (is_on('binary_sensor.marie_window_delayed')) else Next()
      - x: state("input_number.heating_marie_temperature_eco") if (is_on("input_boolean.presence_marie")) and (is_off("binary_sensor.marie_home")) else Next()

La contrainte fenêtre ouverte est gérée par un binary_sensor: avec une temporisation :

binary_sensor:
  - platform: template
    sensors:
      marie_window_delayed:
        friendly_name: "Fenêtre (delayed)"
        device_class: window
        #window_room: bedroom
        delay_on: 
          seconds: 300
        delay_off:
          seconds: 300
        value_template: >-
          {{ is_state('binary_sensor.fenetre_marie', 'on') }}
        icon_template: >-
          {% if is_state('binary_sensor.fenetre_marie', 'on') %}
            mdi:window-open
          {% else %}
            mdi:window-closed
          {% endif %}  

Quant à l'absence temporaire on va la gérer avec l'intégration proximity: qui va facilement nous donner la distance entre la maison et ma fille, ainsi et surtout sa direction. L'idée est de passer en ECO lorsque ma fille est ici pour quelques jours mais qu'elle est de sortie. Il me faudra temporiser la sortie afin de ne pas passer en ECO trop rapidement (genre elle va chercher du pain...), mais également le retour afin que sa chambre soit chauffée lors du retour effectif... 

- id: '5e1b6bdd-dee7-4a63-b8cb-3b9d01719b2b'
  alias: GEO - Lionel en depart
  mode: single
  trigger:
  - platform: numeric_state
    entity_id:
      - proximity.lionel
    above: 3
  condition:
  - condition: and
    conditions:
    - condition: template
      value_template: '{{ trigger.to_state.attributes.dir_of_travel  == "away_from" }}'
  action:
  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.presence_lionel_geo
  - service: notify.slack_hass_canaletto
    data:
      message: "{{ states.sensor.date_time.state}} > Lionel s'éloigne de sa maison | Distance : {{ states.proximity.lionel.state }} Km."

Et le même en approche. On bascule un input_boolean: que l'on pourra basculer manuellement en cas de défaillance...

- id: 'a6bbcd6c-8c18-4ea5-8992-8dae0d63c24c'
  alias: GEO - Lionel en approche
  mode: single
  trigger:
  - platform: numeric_state
    entity_id:
      - proximity.lionel
    below: 3
  condition:
  - condition: and
    conditions:
    - condition: template
      value_template: '{{ trigger.to_state.attributes.dir_of_travel  == "towards" }}'
  action:
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.presence_lionel_geo
  - service: notify.slack_hass_canaletto
    data:
      message: "{{ states.sensor.date_time.state}} > Lionel s'approche de sa maison | Distance : {{ states.proximity.lionel.state }} Km."

Et pour plus d'élégance et pour ne pas afficher le boolean dans le Lovelace de l'utilisateur on en fait un binary (pour les test le boolean reste plus pratique que d'aller faire un tour en voiture...) :

binary_sensor:
  - platform: template
    sensors:
       lionel_geo:
         friendly_name: Lionel (Présence relative sur Géoloc)
         device_class: presence
         icon_template: >-
           {% if is_state('binary_sensor.antoine_away_from_home','on') %} mdi:home-account
           {% else %} mdi:home-outline
           {% endif %}
         value_template: >-
           {{ is_state('input_boolean.presence_lionel_geo', 'on') }}

Un autre exemple est celui du climatiseur. Dans mon cas il est dans un large couloir et arrose plus ou moins toutes les pièces. C'est bien sur à adapter à la région, l'installation et au mode de vie, mais dans mon cas on va interagir différemment selon la saison .Afin de ne pas tout dupliquer il sera nécessaire d'adapter les consignes en début d'été et en début d'hiver.

  • En été on va avoir des périodes de refroidissement et des périodes ou le climatiseur sera OFF.
  • En hiver on va avoir des périodes CONFORT et des périodes ECO. 

En fait Schedy est fait pour faire fonctionner les climatiseurs en mode Heat/Cool. Ca ne me convient pas car en mode demie saison parfois il se mets en route alors que c'est inutile. J'ai demandé au développeur de faire des modifications, mais ce n'est pas sa préoccupation du moment. J'ai donc dupliqué deux rooms pour lui en faisant de sorte que seule l'une puisse fonctionner. C'est un pis aller en attendant mieux. Ca génère une erreur dans le log, mais elle n'est qu'indicative.

    ac_heat:
      allow_manual_changes: true
      rescheduling_delay: 1
      actors:
        climate.daikin:
          send_retries: 20
          supports_hvac_modes: true
          hvac_mode_off: "off"
          hvac_mode_on: heat
      watched_entities:
      - input_number.heating_ac_temperature_confort_1
      - input_number.heating_ac_temperature_confort_2
      - input_number.heating_ac_temperature_confort_3
      - input_number.heating_ac_temperature_confort_4
      - input_number.heating_ac_temperature_confort_1_d
      - input_number.heating_ac_temperature_confort_2_d
      - input_number.heating_ac_temperature_confort_3_d
      - input_number.heating_ac_temperature_confort_4_d
      - input_number.heating_ac_temperature_eco
      - input_number.heating_ac_temperature_away
      - binary_sensor.heating_ac_1
      - binary_sensor.heating_ac_2
      - binary_sensor.heating_ac_3
      - binary_sensor.heating_ac_4
      - binary_sensor.heating_ac_1_d
      - binary_sensor.heating_ac_2_d
      - binary_sensor.heating_ac_3_d
      - binary_sensor.heating_ac_4_d
      - input_boolean.presence_ac
      - binary_sensor.life_windows_and_doors_delayed
      - input_boolean.heating_enabled
      - input_boolean.cooling_enabled
      - input_boolean.thermostats_ac_on_off

      schedule:
      - rules:
        - x: "Break() if is_off('input_boolean.thermostats_ac_on_off') else Next()"         # Etat général du chauffage
        - x: "Abort() if is_off('input_boolean.heating_enabled') else Next()"               # On stop toute planification si on n'est pas dans ce mode
        - rules:
          # CONTRAINTES
          - x: "Break() if is_off('input_boolean.presence_ac') else Next()"                   # Présence au sens callendrier actif
          - x: "Break() if is_on('binary_sensor.life_windows_and_doors_delayed') else Next()" # Fenêtre ouverte
          
          - rules:
            - x: "Break() if is_on('input_boolean.to_sleep') else Next()"                       # Je vais dormir...
            - x: "Break() if is_off('binary_sensor.lionel_geo') else Next()"                    # Je vais au ciné...
            # CONFORT SEMAINE
            - x: state("input_number.heating_ac_temperature_confort_1") if (is_on("binary_sensor.heating_ac_1")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_2") if (is_on("binary_sensor.heating_ac_2")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_3") if (is_on("binary_sensor.heating_ac_3")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_4") if (is_on("binary_sensor.heating_ac_4")) else Next()
            # CONFORT WEEK-END & FERIE
            - x: state("input_number.heating_ac_temperature_confort_1_d") if (is_on("binary_sensor.heating_ac_1_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_2_d") if (is_on("binary_sensor.heating_ac_2_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_3_d") if (is_on("binary_sensor.heating_ac_3_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_4_d") if (is_on("binary_sensor.heating_ac_4_d")) else Next()
            # ECO
            - x: state("input_number.heating_ac_temperature_eco") if (is_on("input_boolean.presence_ac")) and (is_on("input_boolean.heating_enabled")) else Next()
            - x: "Break(2)"
        # REGLES LIES AUX CONTRAINTES
        - x: state("input_number.heating_ac_temperature_eco") if (is_on("input_boolean.to_sleep")) and (is_on("input_boolean.heating_enabled")) else Next()
        - x: state("input_number.heating_ac_temperature_eco") if (is_off("binary_sensor.lionel_geo")) and (is_on("input_boolean.heating_enabled")) else Next()

      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.thermostats_ac_on_off")) else Next()
      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.presence_ac")) else Next()      
      - x: Mark(OFF, Mark.OVERLAY) if (is_on("binary_sensor.life_windows_and_doors_delayed")) else Next()


    ac_cool:
      allow_manual_changes: true
      rescheduling_delay: 1
      actors:
        climate.daikin:
          send_retries: 20
          supports_hvac_modes: true
          hvac_mode_off: "off"
          hvac_mode_on: cool
      watched_entities:
      - input_number.heating_ac_temperature_confort_1
      - input_number.heating_ac_temperature_confort_2
      - input_number.heating_ac_temperature_confort_3
      - input_number.heating_ac_temperature_confort_4
      - input_number.heating_ac_temperature_confort_1_d
      - input_number.heating_ac_temperature_confort_2_d
      - input_number.heating_ac_temperature_confort_3_d
      - input_number.heating_ac_temperature_confort_4_d
      - input_number.heating_ac_temperature_eco
      - input_number.heating_ac_temperature_away
      - binary_sensor.heating_ac_1
      - binary_sensor.heating_ac_2
      - binary_sensor.heating_ac_3
      - binary_sensor.heating_ac_4
      - binary_sensor.heating_ac_1_d
      - binary_sensor.heating_ac_2_d
      - binary_sensor.heating_ac_3_d
      - binary_sensor.heating_ac_4_d
      - input_boolean.presence_ac
      - binary_sensor.life_windows_and_doors_delayed
      - input_boolean.heating_enabled
      - input_boolean.cooling_enabled
      - input_boolean.thermostats_ac_on_off

      schedule:
      - rules:
        - x: "Break() if is_off('input_boolean.thermostats_ac_on_off') else Next()"
        - x: "Abort() if is_off('input_boolean.cooling_enabled') else Next()"
        - rules:
          # CONTRAINTES
          - x: "Break() if is_off('input_boolean.presence_ac') else Next()"
          - x: "Break() if is_on('binary_sensor.life_windows_and_doors_delayed') else Next()"
          
          - rules:
            - x: "Break() if is_on('input_boolean.to_sleep') else Next()"
            - x: "Break() if is_off('binary_sensor.lionel_geo') else Next()"
            # CONFORT SEMAINE
            - x: state("input_number.heating_ac_temperature_confort_1") if (is_on("binary_sensor.heating_ac_1")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_2") if (is_on("binary_sensor.heating_ac_2")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_3") if (is_on("binary_sensor.heating_ac_3")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_4") if (is_on("binary_sensor.heating_ac_4")) else Next()
            # CONFORT WEEK-END & FERIE
            - x: state("input_number.heating_ac_temperature_confort_1_d") if (is_on("binary_sensor.heating_ac_1_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_2_d") if (is_on("binary_sensor.heating_ac_2_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_3_d") if (is_on("binary_sensor.heating_ac_3_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_4_d") if (is_on("binary_sensor.heating_ac_4_d")) else Next()
            # ECO
            - x: Mark(OFF, Mark.OVERLAY) if (is_on("input_boolean.presence_ac")) and (is_on("input_boolean.cooling_enabled")) else Next()
            - x: "Break(2)"
        # REGLES LIES AUX CONTRAINTES
        - x: Mark(OFF, Mark.OVERLAY) if (is_on("input_boolean.to_sleep")) and (is_on("input_boolean.cooling_enabled")) else Next()
        - x: Mark(OFF, Mark.OVERLAY) if (is_off("binary_sensor.lionel_geo")) and (is_on("input_boolean.cooling_enabled")) else Next()

      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.thermostats_ac_on_off")) else Next()
      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.presence_ac")) else Next()      
      - x: Mark(OFF, Mark.OVERLAY) if (is_on("binary_sensor.life_windows_and_doors_delayed")) else Next()

Les deux modes du climatiseur sont gérés par deux input_boolean: et deux automations de façon à ce qu'un seul mode puisse être ON à un instant t.

  - id: 'd1bdf1ac-4d43-4f04-9b09-03e7a5e9ff59'
    alias: 'CLIMATE - AC Chaud'
    description: ''
    trigger:
      - platform: state
        entity_id: input_boolean.heating_enabled
        to: 'on'
      - platform: state
        entity_id: climate.daikin
        # attribute: hvac_mode
        to: 'heat'
    action:
    - service: input_boolean.turn_off
      target:
        entity_id: input_boolean.cooling_enabled
    - service: input_boolean.turn_on
      target:
        entity_id: input_boolean.heating_enabled
    - service: climate.set_hvac_mode
      target:
        entity_id: climate.daikin
      data:
        hvac_mode: heat

  - id: '8fbf1387-04db-44e8-a210-472e4ff80501'
    alias: 'CLIMATE - AC Froid'
    description: ''
    trigger:
      - platform: state
        entity_id: input_boolean.cooling_enabled
        to: 'on'
      - platform: state
        entity_id: climate.daikin
        # attribute: hvac_mode
        to: 'cool'
    action:
    - service: input_boolean.turn_off
      target:
        entity_id: input_boolean.heating_enabled
    - service: input_boolean.turn_on
      target:
        entity_id: input_boolean.cooling_enabled
    - service: climate.set_hvac_mode
      target:
        entity_id: climate.daikin
      data:
        hvac_mode: cool

On pourrait compléter ces deux automations avec pour chacune 12 input_number.set_value afin de d'adapter les consigne à la saison.... On pourrait

service: input_number.set_value
target:
  entity_id: input_number.heating_ac_temperature_confort_1
data:
  value: 23

Et tant qu'à y être on pourrait aussi déclencher tout ça avec un trigger basé sur la saison ou appliquer un condition...

  trigger:
    - platform: state
      entity_id: sensor.season
      to: summer  # A vérifier, je ne suis pas certain que le "to" passe

# Ou en condition....

  condition:
    - condition: state
      entity_id: sensor.season
      state: summer

Ne cherchez pas à recopier ces bouts de code, il ne sont là que pour expliquer la philosophie, vous trouverez mes sources sur GitHub. (je vais essayer de les tenir à jour).

L'interface

C'est là toute la difficulté, créer une interface simple pour un utilisateur lambda. Personnellement taper dans le YAML de Schedy me va bien, mais pour mon frère que je souhaite autonome dans sa maison il n'en est pas question.

Il ne faut pas que cette interface soit compliquée. J'ai donc choisit de masquer tout ce que n'est pas utile au quotidien. Pour y parvenir j'ai utilisé des cartes conditionnelles, l'intégration Fold-Entity et bien sur Multiple-Entity-Row que j'utilise régulièrement.. Un onglet sera dédié à chaque pièce, onglet dans lequel on trouve deux cartes Vertical Stack. Une première pour les fonctions et informations générales et la seconde ou vont se déplier les plages actives

Outre la visualisation du thermostat graphique, on va retrouver ici des informations telles une fenêtre ouverte ou la puissance de l'appareil :

Et au passage un sensor pour obtenir le graphe des consignes successives :

sensor:
- platform: template
  sensors:
    temp_up_th_ac: # Pour History Graph
      friendly_name: "TH AC"
      value_template: > 
        {% if is_state('climate.daikin', 'heat') %}
          Heat {{state_attr ('climate.daikin', 'temperature')}}°
        {% elif is_state('climate.daikin', 'cool') %}
          Cool {{state_attr ('climate.daikin', 'temperature')}}°
        {% else %}
          Off
        {% endif %}

Ensuite si on déroule les options, on retrouve les consignes ECO et ABSENCE, le retour Schedy n'est là que pour du debug, il nous indique l'information reçue par Schedy. Les 3 commutateurs suivant seront des contraintes générales que nous pourrons utiliser ultérieurement dans Schedy.

On va ensuite déplier le boost. On choisit ici la température et la durée. Un reset est prévu afin d'annuler un boost lancé malencontreusement.

Le boost est géré par un script qui va demander à Schedy de passer outre les planifications prévues pendant la durée choisie. De base j'ai demandé à Schedy de replanifier toute les minutes, en actionnant ce script on va mettre en pause la replanification interne.

script:
  heating_antoine_boost:
    alias: Boost
    icon: mdi:timer-outline
    sequence:
    - event: schedy_set_value
      event_data:
        app_name: schedy_heating
        room: chambre_antoine
        v: '{{ states.input_number.heating_antoine_temperature_boost.state }}'
        rescheduling_delay: '{{ states.input_number.heating_antoine_time_boost.state }}'

La carte suivante de notre stack est consacrée à la présence de l'occupant (avec ses dates d'arrivée et de départ cachées, sachant que ces dates, principalement liées aux chambres des enfants, pourront être gérées autrement (calendrier scolaire, géoloc sur approche, ...) ainsi que le choix des plages à activer pour chaque type de journée :

Ensuite on passe à la Vertical Stack des différentes plages. Il s'agit ici d'une suite de 12 cartes conditionnelles qui ne s'affichent que lorsqu'elle sont choisies dans la carte précédente. J'ai nommé ces plages NUIT, MATIN, MIDI et SOIR, mais c'est purement informel, 1, 2, 3, et 4 aurait pu faire l'affaire et rien n'empêche d'en créer plus ou moins... Si une plage n'est pas activée elle ne sera ni visible, ni fonctionnelle, et quand une plage passe en mode confort un indicateur est présent.

Attention : Je n'ai pas fait de contrôle pour prévenir une éventuelle superposition des plages. On part donc du principe qu'elles sont consécutives (plus pratique visuellement, mais sans conséquence), mais surtout qu'elles ne se superposent pas. Il doit être possible de gérer ça, mais ça me semble bien compliqué...

Déploiement

J'ai déjà expliqué comment installer Schedy dans les articles précédents.

Pour le reste j'ai mis le code sur GitHub. Il y a d'une part le fichier de configuration schedy_heating.yaml qui trouvera sa place dans le répertoire /config/appdaemon et la partie HA avec un fichier global et fichier propre à chaque pièce fait l'objet d'un fichier dans le répertoire /packages. On y trouvera donc heating_global.yaml et heating_antoine.yaml pour cette première pièce.

Pour créer une nouvelle pièce on va dupliquer heating_antoine.yaml en heating_cuisine.yaml par exemple, et ensuite faire un cherche et remplace (case sensitive) dans un éditeur afin de remplacer toutes occurrences antoine par cuisine et Antoine par Cuisine. Et vous l'aurez compris, même punition pour les cartes Lovelace.

Je vous conseille toutefois de bien fignoler et debugger une première pièce avant de déployer les suivantes. J'ai joué la carte du fonctionnel sans trop me préoccuper de l'esthétique, et chaque pièce va comporter des particularités. Inutile par exemple de déployer les fonctions Arrivée / Départ sur les pièces communes. On fait ici du sur mesure, ça sous entend également d'y passer un peu de temps....

Voilà ! Et j'espère ne pas y revenir....

 

Home Assistant & Planification, Schedy !

l'inconvénient du Scheduler que je vous avait présenté ici c'est qu'il ne fait pas de replanification et il se contente uniquement d'une action (avec ses contraintes) en début de plage, ce qui impose de devoir programmer la plage suivante. De fait si pour une raison quelconque l'appareil ou le thermostat n'a pas reçu l'ordre, c'est raté. De même il est impossible d'imposer une contrainte supplémentaire après le départ, la notion d'humidité utile pour la clim par exemple, ou encore l'arrivée d'une personne qui ne supporte pas la clim. Pour résumer le Scheduler c'est une action avec des contraintes en début de plage uniquement, là ou Schedy va savoir replanifier dynamiquement en fonction des nouvelles contraintes pendant le déroulement d'une plage. Et ça change tout !

Je vous passe l'installation, ça sous entend app_daemon et j'en ai déjà parlé ici. L'utilisation de Schedy est très simple. Tout se passe dans un seul fichier .yaml et si vous êtes le seul occupant de la demeure ce sera parfait car il suffit d'aller changer les valeur et de sauvegarder pour que les modifications soient prises en compte dynamiquement. Ce mode dynamique est le gros plus des applications app_daemon. Je vous la fait courte car la doc est très bien faite et vous y trouverez d'autres exemples, qui d'ailleurs ne s'appliquent pas uniquement au chauffage. Voici la partie principale du fichier de configuration de Schedy :

      schedule:
      - v: 20
        rules:
        - weekdays: 1-5
          rules:
          - rules:
            - x: "Next() if heating_mode() == 'Normal' else Break()"
            - { start: "06:00", end: "07:30" }
            - { start: "15:00", end: "22:30" }
          - rules:
            - x: "Next() if heating_mode() != 'Normal' else Break()"
            - { start: "08:00", end: "23:30" }
        - weekdays: 6-7
          rules:
          - { start: "08:00", end: "23:30" }

Si vous voulez changer un horaire, un mois ou une saison, il suffit le le faire ici.

Un GUI pour Schedy...

A la base Schedy ne dispose pas d'une interface et n'a pas été développé dans ce sens. Le produit est très stable et son auteur n'a plus trop le temps de le faire évoluer, pour l'heure il se contente de la maintenance.

L'idée ici n'est pas de fournir une planification plug & play pour l'administrateur, mais que celui-ci permette à un utilisateur lambda de modifier les plages et la température de garde associée à chacune d'elles. On va gérer ici 4 plages par thermostat (matin, midi, soir et nuit), sachant que l'on pourrait juste les numéroter et en créer plus ou moins. On dispose également d'une consigne pour la température qui sera appliquée en dehors de ces plages (et qui n'a rien à voir avec le mode hors gel qui lui se gère sur le thermostat.

On va se servir du mode mode "package" de Home Assistant, ce qui va nous permettre d'avoir dans un seul fichier .yaml tous les composants nécessaires pour accompagner notre thermostat. Pour ça il vous faudra faire une petite modification dans le fichier de configuration :

homeassistant:
  packages: !include_dir_named packages

Schedy n'a pas de GUI mais dispose toutefois d'une mince ouverture pour communiquer avec Lovelace et on va ainsi pouvoir lui adjoindre une interface minimale, mais sur mesure. On aurait pu penser à ce qu'il utilise des input_datetime: pour les horaires, mais non la seule possibilité réside dans des input_number: ou sensor: au travers desquels on va faire passer les températures de consigne et les heures de début et de fin des plages. Et pour les horaires (ça m'a pris du temps à comprendre) il faut lui donner l'heure sous la forme d'un nombre en minutes écoulées depuis minuit. Donc si on veut qu'une plage débute à 02:00 il faut lui donner 120. Ca ne s'invente pas !

Il va donc nous falloir traduire les input_datetime: qui vont permettre la saisie d'un horaire (à dupliquer par le nombre de plages à gérer) :

input_datetime:
  ac_start_1:
    has_date: false
    has_time: true
  ac_stop_1:
    has_date: false
    has_time: true

En sensor: afin de ne pas avoir à saisir le nombre de minutes... (Merçi @mathieu !) (à dupliquer par le nombre de plages à gérer) :

sensor:
  platform: template
  sensors:
    ac_start_1:
      friendly_name: "AC Start 1"
      icon_template: mdi:timer-sand
      value_template: "{{ (state_attr('input_datetime.ac_start_1', 'timestamp') / 60)|int }}"
    ac_stop_1:
      friendly_name: "AC Stop 1"
      icon_template: mdi:timer-sand
      value_template: "{{ (state_attr('input_datetime.ac_stop_1', 'timestamp') / 60)|int }}"

On va également créer des input_number: pour les températures de consigne (à dupliquer par le nombre de plages à gérer, sans oublier la température hors plages) :

input_number:
  ac_max_1_temp:
    name: Ac Temp 1
    min: 14
    max: 26
    step: 0.5
    unit_of_measurement: °C

On ajoute un input_boolean: pat plage, il va nous permettre d'activer ou désactiver la plage :

input_boolean:
  ac_1:
    name: AC 1
    icon: mdi:account-check

De façon plus globale on se servira également d'un binary_sensor:  sur lequel reposera l'activation du chauffage, un autre pour la climatisation (je pense que ça peut également être des input_boolean:). Au chapitre des contraintes j'ai déjà un input_boolean: qui me sert au mode absent. Il est également possible d'utiliser workday: dans les contraintes et ainsi définir des plages qui s'exécuteront soit les jours de travail, soit le week-end. Mais on peut s'en passer et gérer ça dans Schedy, l'avantage restant à workday: qui lui gère les jours fériés.

Voici la première partie du fichier de configuration de la partie Schedy :

schedy_heating:  # This is our app instance name.
  module: hass_apps_loader
  class: SchedyApp

  actor_type: thermostat

  expression_environment: |
    def time_between(start, end):
        current = time.hour * 60 + time.minute + 1
        if start >= end:
            return current >= start or current < end
        return current >= start and current < end
  
  schedule_prepend:
  - x: "Mark(OFF, Mark.OVERLAY) if not is_empty(filter_entities('binary_sensor', state='on', window_room=room_name)) else Next()"
  - x: "OFF if is_off('binary_sensor.heating_enabled') else Next()"

  watched_entities:
  - binary_sensor.heating_enabled

La première partie  concerne la déclaration de l'application app_daemon. Ensuite le type d'utilisation et l'environnement qui va nous permettre de récupérer les informations de planification depuis Lovelace. On trouve ensuite la gestion des ouvertures (Voir plus bas en détail) et du mode OFF du chauffage. La dernière ligne concerne la déclation des entités HA que nous utilisons dans cette partie, ici le binary_sensor: du ON/OFF.

La suite concerne les pièces avec leurs thermostats associés :

rooms:
    bureau:
      rescheduling_delay: 120
      actors:
        climate.thermostat_bureau:
       
      watched_entities:
      - binary_sensor.bureau_door_delayed
      - input_number.day_temperature
      - sensor.ac_start_1
      - sensor.ac_stop_1
      - input_boolean.thermostats_away        # Mode Absent qui pourrait également être dans les paramètres globaux
      - input_boolean.ac_1
      # - binary_sensor.workday_sensor

      schedule:
      - months: 1-4
        weekdays: 1-6
        rules:
        - x: state("input_number.day_temperature") if (state("input_boolean.ac_1") == "on") and (state("binary_sensor.bureau_door_delayed") == "off") and  (state("input_boolean.thermostats_away") == "off") and time_between(int(state("sensor.ac_start_1")), int(state("sensor.ac_stop_1"))) else Next()
      - v: 19

Outre le thermostat on déclare ici les différentes entités que l'on va utiliser.

La ligne importante est la plus longue à la fin. On commence par la température, l'activation ou nom de ce planning, la gestion de l'ouverture, le mode absent qu'il me faudra déplacer dans les paramètres globaux et l'heure de début et de fin. On remarque également que je n'ai ici pas utilisé workday: mais que j'ai choisit de laisser (pour l'exemple) les jours et mois ou cette planification peut s'exécuter (on peut également utiliser la saison). La dernière ligne concerne la température de consigne qui sera appliquée en dehors des plages. Il est également possible de la gérer depuis Lovelace avec l'input_number: idoine.

Les ouvertures

Curieusement l'auteur a prévu de gérer les ouvertures, mais il n'a pas intégré la notion de délai. Ainsi de base si on ouvre une porte ou une fenêtre on coupe le convecteur immédiatement, ce qui n'a pas de sens si on ne fait que rentrer ou sortir, alors que ça en aurait si on ouvre une fenêtre pour aérer une pièce plus longuement. D'ailleurs si ça n'a pas d'impact sur un convecteur, le résultat sera bien différent pour un climatiseur pour lequel les changements d'état on souvent une grande latence. On va donc devoir jouer des avec les templates et ainsi créer des retardateurs pour les ouvertures (et non je ne pousserait pas le vice jusqu'à aller gérer les delais dans Lovelace...) (la partie icon_template: est vraiment là pour l'exemple car il n'y aucun intérêt à afficher ça dans Lovelace) :

binary_sensor:
  - platform: template
    sensors:
      garage_door_delayed:
        friendly_name: "Delayed Garage Door"
        #window_room: bedroom
        delay_on: 
          seconds: 180
        delay_off:
          seconds: 360
        value_template: >-
          {{ is_state('binary_sensor.porte_garage', 'on') }}
        icon_template: >-
          {% if is_state('binary_sensor.porte_garage', 'on') %}
            mdi:door-open
          {% else %}
            mdi:door-closed
          {% endif %}

Le froid

Toute cette partie peut s'appliquer à des convecteurs ou climatiseurs en mode chauffage. Pour le mode froid le fonctionnement est un peu différent, on va gérer un seuil de déclenchement et une température de maintient, alors qu'en dehors des plage on éteindra le climatiseur (ici dans la dernière partie).

Epilogue...

Pour cet article, qui m'a pris un peu trop de temps, je me suis bien sur inspiré de mon expérience puisée dans le documentation de Schedy et le fil de discutions du forum Home Assistant. Mais j'ai également trouvé le GitHub d'un utilisateur ou il présente sa configuration. Je vous invite à aller y puiser des idées, et vous remarquerez qu'il a choisit une présentation un peu différente pour la saisie des plages horaires à l'aide de sliders. En ce qui me concerne j'ai préféré compacter au maximum cette partie, même si toutes les fantaisies sont possibles. Pensez à revenir car j'enrichirait cet article dès que j'aurais le temps de continuer.

Replay. La suite...

J'avais un peu laissé en plan cette idée mais mon climatiseur Daikin perd parfois le WI-FI, donc la notion de replanification de Schedy prend toute son importance. Entre temps @PYG a publié un script qui simplifie grandement la création des différentes entités nécessaires, script à lancer en SSH et que l'on pourra adapter à souhait, ce que j'ai fait ici pour mon climatiseur en mode chauffe, et qu'il me faudra adapter en mode froid (on gère les pièces en ligne 10 et le nombre de périodes en 12).

/bin/bash
test -d /config/packages || mkdir /config/packages
cd /config/packages
cat >heating_global.yaml<<EOF
input_boolean:
  heating_enabled:
    name: Heating Global
    icon: mdi:toggle-switch
EOF
for room in hall_ac
do
for period in {1..4}
do
cat >${room}_heating_period_${period}.yaml<<EOF
input_datetime:
  ${room}_heating_period_${period}_start:
    name: "Heating Period ${period} Start Time"
    has_date: false
    has_time: true
  ${room}_heating_period_${period}_end:
    name: "Heating Period ${period} End Time"
    has_date: false
    has_time: true
sensor:
  platform: template
  sensors:
    ${room}_heating_period_${period}_start:
      value_template: "{{ (state_attr('input_datetime.${room}_heating_period_${period}_start', 'timestamp') / 60)|int }}"
    ${room}_heating_period_${period}_end:
      value_template: "{{ (state_attr('input_datetime.${room}_heating_period_${period}_end', 'timestamp') / 60)|int }}"
input_number:
  ${room}_heating_period_${period}_temperature:
    name: Heating Period ${period} Temperature
    min: 18
    max: 25
    step: 1
    unit_of_measurement: °C
    icon: 'mdi:thermometer-lines'
input_boolean:
  ${room}_heating_period_${period}:
    name: Heating Period ${period} Enabled
    icon: mdi:toggle-switch
EOF
done
done
exit

Ensuite j'ai un peu modifié sa config Schedy afin de l'adapter à mon besoin...

schedy_heating:
  module: hass_apps_loader
  class: SchedyApp
  
  actor_type: thermostat
  
  actor_templates:
    default:
      send_retry_interval: 30
      send_retries: 10
      supports_hvac_modes: true
      off_temp: 18
  
  watched_entities:
  - input_boolean.homeoffice
  - binary_sensor.workday_sensor
  # - binary_sensor.holiday_sensor
  - input_boolean.heating_enabled
  
  expression_environment: |
    def homeoffice():
      return is_on("input_boolean.homeoffice")
    def workday():
      return is_on("binary_sensor.workday_sensor")
    # def holiday():
      # return is_on("binary_sensor.holiday_sensor")
    def time_between(start, end):
        start = int(state(start))
        end = int(state(end))
        current = time.hour * 60 + time.minute + 1
        if start >= end:
            return current >= start or current < end
        return current >= start and current < end

  schedule_prepend:
  - x: "14 if is_off('input_boolean.heating_enabled') else Next()"
  
  rooms:
    hall_ac:
      allow_manual_changes: true
      rescheduling_delay: 1
      actors:
        climate.daikin:
          template: default
      watched_entities:
      - input_number.hall_ac_heating_period_1_temperature
      - input_boolean.hall_ac_heating_period_1
      - sensor.hall_ac_heating_period_1_start
      - sensor.hall_ac_heating_period_1_end
      - input_number.hall_ac_heating_period_2_temperature
      - input_boolean.hall_ac_heating_period_2
      - sensor.hall_ac_heating_period_2_start
      - sensor.hall_ac_heating_period_2_end
      - input_number.hall_ac_heating_period_3_temperature
      - input_boolean.hall_ac_heating_period_3
      - sensor.hall_ac_heating_period_3_start
      - sensor.hall_ac_heating_period_3_end
      - input_number.hall_ac_heating_period_4_temperature
      - input_boolean.hall_ac_heating_period_4
      - sensor.hall_ac_heating_period_4_start
      - sensor.hall_ac_heating_period_4_end
      schedule:
      - rules:
        # not workday
        - rules:
          - x: "Break() if workday() else Next()"
          - x: >
              state("input_number.hall_ac_heating_period_1_temperature")
              if (is_on("input_boolean.hall_ac_heating_period_1")
              and time_between("sensor.hall_ac_heating_period_1_start", "sensor.hall_ac_heating_period_1_end"))
              else Next()
          - x: >
              state("input_number.hall_ac_heating_period_2_temperature")
              if (is_on("input_boolean.hall_ac_heating_period_2")
              and time_between("sensor.hall_ac_heating_period_2_start", "sensor.hall_ac_heating_period_2_end"))
              else Next()
          - x: "Break(2)"
        # workday
        - rules:
          - x: >
              state("input_number.hall_ac_heating_period_3_temperature")
              if (is_on("input_boolean.hall_ac_heating_period_3")
              and time_between("sensor.hall_ac_heating_period_3_start", "sensor.hall_ac_heating_period_3_end"))
              else Next()
          - x: >
              state("input_number.hall_ac_heating_period_4_temperature")
              if (is_on("input_boolean.hall_ac_heating_period_4")
              and time_between("sensor.hall_ac_heating_period_4_start", "sensor.hall_ac_heating_period_4_end"))
              else Next()
          - x: "Break(2)"
      # default
      - v: 18

Et j'ai créé la carte Lovelace qui va avec...

type: grid
square: true
cards:
  - type: vertical-stack
    cards:
      - type: entities
        entities:
          - entity: binary_sensor.heating_enabled
            name: Etat du chauffage
          - entity: input_boolean.heating_enabled
            name: Activation du chauffage
          - entity: binary_sensor.workday_sensor
            name: Jour de semaine
        theme: teal
        title: AC Daikin
      - type: entities
        entities:
          - entity: input_boolean.hall_ac_heating_period_1
          - entity: input_datetime.hall_ac_heating_period_1_start
          - entity: input_datetime.hall_ac_heating_period_1_end
          - entity: input_number.hall_ac_heating_period_1_temperature
          - entity: input_boolean.hall_ac_heating_period_2
          - entity: input_datetime.hall_ac_heating_period_2_start
          - entity: input_datetime.hall_ac_heating_period_2_end
          - entity: input_number.hall_ac_heating_period_2_temperature
        title: Week-End & Jours fériés
        show_header_toggle: false
        theme: teal
        state_color: true
      - type: entities
        entities:
          - entity: input_boolean.hall_ac_heating_period_3
          - entity: input_datetime.hall_ac_heating_period_3_start
          - entity: input_datetime.hall_ac_heating_period_3_end
          - entity: input_number.hall_ac_heating_period_3_temperature
          - entity: input_boolean.hall_ac_heating_period_4
          - entity: input_datetime.hall_ac_heating_period_4_start
          - entity: input_datetime.hall_ac_heating_period_4_end
          - entity: input_number.hall_ac_heating_period_4_temperature
        title: Semaine
        show_header_toggle: false
        theme: teal
        state_color: true
columns: 1

Et voilà !

 

 
 

 

 

Home Assistant & Planification, la suite !

Ce qui déroute souvent quand on débute avec Home Assistant c'est l'absence de planificateur intégré, alors même que la chose parrait évidente. Il y a tout de même plusieurs façons de planifier des évènements et on a déjà examiné ici plusieurs solutions :

  • En codant des des automation basées sur des input_time, etc... On peut obtenir quelque chose de très personnalisé. Mais il faut pas mal d'huile de coude.
  • Avec des agendas externes comme Google Agenda ou Microsoft 365, ou encore des agendas plus geeks... (ics, etc).

Il y a une autre solution qui bien que dépourvue d'interface est vraiment très puissante, mais un peu compliquée à appréhender. Je veux parler de Shedy. Ca impose d'installer AppDaemon et ensuite ça se passe en YAML, à ceci près que sous AppDaemon la prise en compte des modifications apportées au code YAML est dynamique, et ça c'est très sympa.  AppDaemon c'est un addon qui s'installe dans la section addon de HA et je m'en sert également pour ControlerX. Dans toutes ces solutions, seul Shedy sait faire de la replanification dynamique, c-a-d que toutes les x minutes il reconsidère l'état de tous les paramètres, là ou les autres se contentent de faire ON au début et OFF à la fin (ou augmenter la consigne et la baisser).

Depuis peu il y a aussi les BluePrint, ça va changer beaucoup de choses sous HA et surement permettre de proposer des planifications simples et prêtes à l'emploi.

On a peut être aussi des possibilités avec NodeRed, mais pour moi c'est non, vous le savez, je n'aime pas. Bon, blague à part, c'est bien et même intéressant, surtout pour ceux qui ne réussissent pas à entrer dans la logique yaml, ce qui se comprends très bien. Mais dans l'absolu ça n'a pas besoin de HA, à part pour l'interface, ah oui NR n'a pas d'interface utilisateur, juste une interface dev. Bon, après chacun son truc.

Et puis il y a le Scheduler dont je veux vous parler aujourd'hui et pour lequel je vous conseille de suivre cette conversation.

L'idée de son auteur est de créer un scheduler entièrement graphique, donc à l'opposé de Schedy. A vrai dire j'aurais préféré qu'il fasse une interface pour Schedy. Mais les développeurs sont ce qu'ils sont et lui est parti sur deux modules, un composant moteur et une carte d'interface. Du coup si l'interface est très réussie il reste encore des choses à voir coté motorisation pour arriver au niveau de Schedy, qui lui gère notamment la notion de re planification dynamique. Attention, ce n'est pas un reproche, Niels a fait un travail remarquable en très peu de temps, et comme c'est un perfectionniste, je ne doute pas qu'au fil des semaines cet outil, déjà totalement utilisable, gagnera en fonctionnalités.

Je vous passe les détails de l'installations décrits ici, et , en gros on installe le composant depuis HACS, on redémarre et on installe la carte. Ensuite on va créer une carte à laquelle on attribuera des objets à planifier avec des conditions. Par exemple, je veux que le thermostat de la cuisine passe à 21° tous les matins à 07:15 et qu'il repasse à 19.5° à 09:00 (avec plusieurs plages possibles dans la journée), mais à condition que nous soyons en semaine, que le commutateur qui autorise le chauffage soit à ON et que je ne soit pas en voyage, etc...

On va donc créer une carte avec les objets dont on a besoin que l'on peut choisir par type ou mieux individuellement. Par exemple un thermostat, et les input_bolean: Chauffage ON/OFF et Mode Absent ON/OFF : 

Je prends l'exemple des thermostats qui me préoccupait, mais on peut bien sur planifier tout ce qui est actionnable, directement, via des scripts ou des scènes.

Il est bien sur possible de faire ça par le code, ce qui permet en plus d'ajouter des options de présentation de la carte. Personnellement j'ai choisit d'utiliser une carte de type "Vertical Stack" "Panel Mode", ce qui sera bien plus agréable à utiliser pour planifier une journée. Ce mode n'est pas très adapté au mobile, mais c'est le genre de choses que je fais depuis mon PC.

type: vertical-stack
cards:
  - type: 'custom:scheduler-card'
    discover_existing: false
    display_options:
      primary_info:
        - '<b>{entity} / {name}</b>'
      secondary_info:
        - >-
          <b>Next</b> : {relative-time} ({days} {time}) | <b>Action</b> :
          {action} ({additional-tasks})
      icon: 'mdi:radiator'
    include:
      - climate.daikin
      - input_boolean.thermostats_ac_on_off
      - input_boolean.thermostats_away
      - input_boolean.thermostats_on_off
      - input_select.chauffage
    title: AC Daikin
  - type: 'custom:scheduler-card'
    discover_existing: false
    display_options:
      primary_info:
        - '<b>{entity} / {name}</b>'
        - '<b>Next</b> : {relative-time} | <b>Action</b> : {action}'
        - additional-tasks
      secondary_info:
        - '{days} {time}'
      icon: 'mdi:radiator'
    include:
      - climate.thermostat_lionel
      - input_boolean.thermostats_ac_on_off
      - input_boolean.thermostats_away
      - input_boolean.thermostats_on_off
      - input_select.chauffage
    title: Thermostat Chambre Lionel

J'utilise ici une carte de la pile pour chaque thermostat et dans chaque carte je vais pouvoir ajoute plusieurs planifications. Par exemple une pour la semaine et une pour les week-end et jours fériés. A noter que workday: est supporté, ce qui simplifie grandement la gestion des jours férié. A droite de chaque planification on a un interrupteur qui permet d'activer ou désactiver la planification, fonction également possible par une automation. On peut également la laisser activée et la soumettre à une condition pour peu qu'on ait associé l'objet idoine, un input_bolean: par exemple...

Quand on ajoute une entrée on choisit l'objet sur lequel on peut agir, ici un thermostat, on précise l'entité et le type d'action unique à exécuter (allumer, éteindre plus adapté à d'autres équipements, ou mieux de faire un schéma, ce qui sera pleinement adapté à un thermostat :

Par exemple ici j'ai choisit de chauffer le séjour, le week-end, de 08:00 à 22:30 à 21.5°. Sauf que si on laisse la chose en l'état à 22:30 le thermostat continuera à chauffer à 21.5°. La logique actuelle veut que l'on crée une plage suivante avec une autre température, mais également une précédente, contrairement à Schedy qui lui va considérer une température de base (Eco) et uniquement augmenter les plages définies pour revenir ensuite à sa température de base.

Le développeur me répond qu'il ne veut pas faire un planificateur trop spécifique au chauffage. Mais je pense l'avoir convaincu de changer sa logique, et pour chaque plage définie ainsi prévoir une action de début et une action de fin, ce qui me paraitrait un bon début.

Il n'y a pas ici d'options de replanification dynamique. Si Home Assistant redémarre, le thermostat conservera son état (à condition de ne pas utiliser l'option initial_hvac_mode:), par contre face à d'éventuelles coupures secteur, il faut utiliser des actionneurs capables de retrouver leur état avant coupure et de les configurer ainsi. C'est les cas des Shelly ou des cartes IPX800, mais certains actionneurs resteront muets après coupure et ne sont donc pas conseillés pour cette utilisation.

Avec le bouton Options en bas à droite on va pouvoir ajouter des conditions supplémentaires basées sur l'état des entités que l'on aura choisies, ici un input_bolean: qui conditionne le fonctionnement du chauffage. Donc si cette condition n'est pas remplie la planification ne s'exécutera pas, la validation se faisant en début de planification. 

J'ai par exemple un input_bolean: qui conditionne le chauffage des chambres de ma fille et mon fils qui vivent à l'autre bout de la France. Ce commutateur s'actionne soit manuellement, soit en en fonction de leur date d'arrivée que j'aurais saisie (et peut être un jour automatisée en fonction des billets TGV reçus... ou en fonction de leur localisation...).

Voilà pout un petit tour rapide. Alors vous allez me demander pourquoi utiliser une solution plutôt qu'une autre ? Il y a plusieurs questions à se poser, la plus évidente étant de savoir si vous êtes le seul à agir ou si vous voulez un système plus WAF accessible à tous les membre de la maison.

Par exemple j'ai monté un Home Assistant chez mon frère, ce n'est pas un geek et je n'avais pas l'intention d'intervenir chaque fois qu'il souhaite ajuter ses plages de températures. Au départ j'avais basé ses planifications sur Google Agenda, une solution qui fonctionne très bien, mais pas idéale coté expérience utilisateur, en effet cela nécessitait deux interfaces, celle de HA et celle de Google Agenda. De plus les jours fériés n'étaient pas pris en compte (il aurait fallut faire des automations plus complexes). J'ai donc remplacé tout ça par ce Scheduler et ça lui va très bien.

En ce qui me concerne j'ai le nez dans le code et j'avais dès le début fait pas mal d'automations pour gérer les différentes situations (présent, absent, j'ai du monde à diner, à coucher, des amis passent le week-end ici, etc...) et pour l'instant ça reste ainsi. Mais mon installation est également un labo et j'ai installé Schedy pour le tester en live, notamment au niveau des conditions et situations particulières. J'aimerais aussi pouvoir le personnaliser avec un minium de GUI. Je ne suis pas un bon exemple à suivre...

En conclusion je dirais que ce Scheduler est vraiment ce que tout le monde attendait et qu'il couvrira la majorité des besoins. Après il y avait un vague projet de scheduler intégré au core, mais pour l'heure ça reste une arlésienne.

 

Home Assistant & Notifications d'état

Avant, je planifiait mon chauffage avec des automations qui s'appuyaient sur des input_date, input_time pour actionner et input_number pour sélectionner la température de consigne souhaitée et je notifiait dans un journal sur Slack, ce qui me permet un debug facile. J'en avait largement parlé ici, mais maintenant il existe un vrai scheduler et je vais changer ma façon de faire. Sauf que ce scheduler, dont je vous parlerais bientôt, ne dispose pas d'une option de notification. Ca viendra peut être, et ce travail sera peut être à jeter, bien que le principe puisse être adapté à d'autre notifications.

Le scheduler pilotant dans mon cas les thermostats, je vais donc me baser sur les changement d'était du thermostat pour notifier l'état à Slack (voire faire autre chose...). Pour cela je vais stocker les états qui ont changés dans des input_number et des input_text et m'en servir pour notifier avec les bonnes variables.

La liste des courses

J'ai essayé de simplifier en minimisant les besoins, mais il nous faut des input_number: et des input_text:

input_number
  trigger_source_notify_temp_current:
    name: Température Ambiante
    mode: box
    min: 0
    max: 30
    unit_of_measurement: "°C"

  trigger_source_notify_temp_target:
    name: Température de consigne
    mode: box
    min: 0
    max: 30
    unit_of_measurement: "°C"

input_text:
  trigger_source_notify_name:
    name: Trigger Source Name

  trigger_source_notify_hvac_mode:
    name: Trigger Source HVAC Mode

  trigger_source_notify_hvac_action:
    name: Trigger Source HVAC Action
  
  trigger_source_notify_preset_mode:
    name: Trigger Source Preset Mode

  trigger_source_notify_state:
    name: Trigger Source State

Ces "inputs" vont servir à stocker temporairement les valeurs que je vais afficher dans mes notifications.

Une seule automation

Ensuite on va avoir besoin d'une automation qui va :

  1. Se déclencher (trigger) sur les changements d'état pour lesquels on souhaite envoyer une notification. Ici les attributs de deux thermostats.
  2. Ecrire (action) les valeurs liées à la source de déclenchement dans les input_number: et input_text:
  3. Notifier (action) en se servant des valeurs écrite précédemment.
- alias: Notifications des Thermostats
  trigger:
  - platform: template
    value_template: "{{ state_attr('climate.daikin','temperature')  }}"
  - platform: template
    value_template: "{{ state_attr('climate.daikin','preset_mode')  }}"
  - platform: template
    value_template: "{{ state_attr('climate.daikin','hvac_modes')  }}"

  - platform: template
    value_template: "{{ state_attr('climate.thermostat_antoine','temperature')  }}"
  - platform: template
    value_template: "{{ state_attr('climate.thermostat_antoine','preset_mode')  }}"
  - platform: template
    value_template: "{{ state_attr('climate.thermostat_antoine','hvac_modes')  }}"

  action:
  - data_template:
      entity_id: input_number.trigger_source_notify_temp_target
      value: '{{ trigger.to_state.attributes.temperature }}'
    service: input_number.set_value
  - data_template:
      entity_id: input_number.trigger_source_notify_temp_current
      value: '{{ trigger.to_state.attributes.current_temperature }}'
    service: input_number.set_value
  - data_template:
      entity_id: input_text.trigger_source_notify_name
      value: '{{ trigger.to_state.attributes.friendly_name }}'
    service: input_text.set_value
  - data_template:
      entity_id: input_text.trigger_source_notify_hvac_action
      value: '{{ trigger.to_state.attributes.hvac_action }}'
    service: input_text.set_value
  - data_template:
      entity_id: input_text.trigger_source_notify_preset_mode
      value: '{{ trigger.to_state.attributes.preset_mode }}'
    service: input_text.set_value
  - data_template:
      entity_id: input_text.trigger_source_notify_state
      value: '{{ trigger.to_state.state }}'
    service: input_text.set_value

  - service: notify.slack_hass_canaletto
    data:
      message: "{{ states.sensor.date_time.state}} > {{ states.input_text.trigger_source_notify_name.state }} : Consigne à {{ states.input_number.trigger_source_notify_temp_target.state }}° |  
                Température ambiante : {{ states('input_number.trigger_source_notify_temp_current') }}° | 
                Etat : {{ states.input_text.trigger_source_notify_hvac_action.state }} / {{ states.input_text.trigger_source_notify_preset_mode.state }} / {{ states.input_text.trigger_source_notify_state.state }}

Et on obtient une notification dans le journal Slack

2020-12-06, 20:24 > Thermostat : Antoine : Consigne à 13.0° | Température ambiante : 17.1° | Etat : idle / away / heat

Bonus

On peut s'amuser à traduire les messages d'état et ainsi les insérer en clair et en français dans la notification... En bricolant quelque chose du genre :

  {% if is_state('input_text.trigger_source_notify_hvac_action', 'heating') %}
    Etat : Chauffe
  {%-elif is_state('input_text.trigger_source_notify_hvac_action', 'cooling') %}
    Climatise
  {%-elif is_state('input_text.trigger_source_notify_hvac_action', 'dry') %}
    Désumidifie
  {%-elif is_state('input_text.trigger_source_notify_hvac_action', 'fan_only') %}
    Ventile
  {%-elif is_state('input_text.trigger_source_notify_hvac_action', 'heat_cold') %}
    Auto
  {% else %}
    Etat : Inactif
  {% endif %}

  {% if is_state('input_text.trigger_source_notify_preset_mode', 'away') %}
    Préréglage : Absent
  {%-elif is_state('input_text.trigger_source_notify_preset_mode', 'eco') %}
    Préréglage : Eco
  {%-elif is_state('input_text.trigger_source_notify_preset_mode', 'boost') %}
    Préréglage : Boost
  {% else %}
    Préréglage : Normal
  {% endif %}

  {% if is_state('input_text.trigger_source_notify_state', 'heat') %}
    Mode : Chauffage
  {%-elif is_state('input_text.trigger_source_notify_state', 'cold') %}
    Mode : Climatisation
  {%-elif is_state('input_text.trigger_source_notify_state', 'dry') %}
    Mode : Désumidifiation
  {%-elif is_state('input_text.trigger_source_notify_state', 'fan_only') %}
    Mode : Ventilation
  {%-elif is_state('input_text.trigger_source_notify_state', 'heat_cold') %}
    Mode : Auto
  {% else %}
    Mode : Arrêt
  {% endif %}

Voilà, bien entendu tout ça est adaptable à d'autres besoins....