Home Assistant, Zigbee & Legrand

On m'a demandé récemment d'intégrer des appareils Legrand dans Home Assistant, je l'avais déjà fait pour certains et j'en avais parlé dans cet article. Aujourd'hui il s'agit d'intégrer des commandes sans fil vue come des télécommandes dans HA. Vous allez me demander quel est l'intérêt quand un simple bouton Ikea (E1743) à moins de 10 fait le job pour mes volets roulants. C'est purement esthétique, quand on a un logement équipé en Legrand Céliane ou Mosaic, on veut parfois que les commandes des volets soient dans la même collection.

Soit, Legrand propose des commandes en Zigbee, il n'y a plus qu'à !

Dans la pratique on va voir que ce n'est pas si simple, en tous cas bien moins simple qu'avec mon bouton Ikea à 10 € ! Je précise le prix car les commandes Legrand sont plutôt à 80 €. Mais quand on aime on ne compte pas.

Il existe plusieurs type d'équipements Zigbee chez Legrand

  • Ceux qui sont filaires, j'en ai parlé ici (micro modules, contacteur DIN, prise connectée, etc...)
  • La gamme des commandes sans fil à pile
  • La gamme des commandes sans fil sans piles (gamme Self-e). J'y reviendrait certainement, mais d'après mes infos cette gamme supporte tous les canaux et serait mieux intégrée ZHA/Z2M.

Je me suis donc penché sur la gamme avec pile.

Première observation, ces devices sont généralement livrés avec un firmware de niveau 42 qui ne permet que l'utilisation du canal Zigbee 11. Don impossible à faire fonctionner sur mon ZHA qui est en 15. Je l'ai donc appairé sur mon Z2M en 11 et ça fonctionne. Et là je me suis dit que j'allais pouvoir mettre à jour ce firmware en OTA. Mais non, ça ne fonctionne pas et on se retrouve dans la problématique des équipements franco Français qui n'intéressent pas grand monde au niveau international (on pense par exemple à tout ce qui est lié à l'intégration Overkiz...).

Après moultes lectures des forums de diverses plateformes domotique, la conclusion est que la mise à jour du firmware ne peut se faire qu'au travers d'une passerelle officielle Legrand ! J’ai donc acheté le kit qui comprend :

  • La prise Control qui sert de passerelle
  • Un inter sans fil
  • Une ampoule

Il faut bien sur installer l’appli Legrand et ensuite tenter d’associer la prise au WIFI. Facile ? Non, ça m’a pris de plombes car vu que leur process est trop long, le mobile qui se connecte à la prise pour la configurer repasse sur le WIFI normal ou il retrouve Internet, et perd la config en cours. Solution faire ça avec un vieux mobile… Bref, un premier amateurisme car on fait facilement ce genre d'association avec la majorité des objets mobile en WIFI !

Bon, la logique de l’appli est orientée électricien très grand public, avec une logique d'électricien qui n’est pas nécessairement la notre. C'est un choix, il faut s'y faire, mais n'oublions pas que nous sommes ici uniquement pour faire un mise à jour...

Ensuite il faut ajouter les inter. Sauf que la il faut comprendre qu’on ne peut pas ajouter un inter seul. Dans la logique Legrand si tu ajoute un inter, en fin de dialogue ça te demande ce que tu veux commander, sans quoi ça bloque et il n’y a plus qu’à forcer le redémarrage de l’appli et recommencer... Je n’avais pas de prise Legrand sous la main, mais au bout d’un moment j’ai fini par penser à appairer l’ampoule. Et la je vois enfin l’inter livré et mon inter de volet roulant qui ne peut rien commander, c’est le même mais avec un firmware différent.

Mais souvenons nous que nous somme là à jouer avec ce bazar dans le seul but de mettre à jour le firmware de ces putains d’inter qui au delà de couter un rein ne fonctionnent (mal) que sur le canal 11. Hélas il n’y arien pour faire cette mise à jour, mon inter de VR apparait en 42 dans l’appli et en 002a dans z2m, donc identique l’un étant en hexa. D’après ce que j’ai pu lire, la mise à jour se fera, un jour, mais on ne peut pas la forcer. Il faut juste laisser branché, en espérant que mon inter qui n’est pas connecté à un appareil se mette à jour tout seul…

Donc je laisse branché, la suite pour bientôt…

EDIT un peu plus tard :

  • L’inter (0 677 73N) est passé de 50 à 70
  • L’inter VR (0 777 48LA) n’a pas bougé…
  • Je testerais si on peut l’appairer hors du canal 11 mais je commence à douter…

EDIT le lendemain :

  • L’inter s’appaire bien sur un autre canal après sa mise à jour. Testé en ZHA sur 15, mais ZHA ne le supporte pas et rien ne bouge, aucun event.
  • L’inter VR ne s’est toujours pas mis à jour. Un peu comme s’il lui fallait une charge...

EDIT le lendemain soir :

  • L’inter VR est bien passé de 42 à 70, mon ZHA qui est sur le canal 15 le voit mais n’en fait rien. Quand à Z2M il n’en veut plus ! Pire ça me fait planter Z2M…

EDIT le sur lendemain midi :

  • L’inter VR qui ne veut toujours pas de ZHA ou Z2M s’est appairé correctement sur deconz en canal 15, il retourne dans les event les valeurs suivantes :

Montée : 1002 / Montée puis relâché : 1002/3002
Descente : 2002 / Descente puis relâché : 2002/3003
Stop (Les deux en même temps) : 3003

Ce comportement est identique à ce qu’il était sous Z2M et contrairement à d'autres télécommandes (le on/off Ikea (E1743 par exemple) il manque le fait qu’un second appui bref provoque un stop. Il faut donc bien appuyer au milieu pour faire un « stop » et ce n’est pas toujours pris en compte (problème physique).

Qu’il ne soit pas supporté sur ZHA est un fait, il faudrait développer un quirk et ça me dépasse. Je ne sais pas pourquoi il ne veut plus s’appairer sur Z2M, mais c’est peut être lié à mon installation, ma clé, que sais-je...

Il y a des chances que l’aventure Legrand va prendre fin et je que je retourne fissa ce Kit à Amazon !

Homekit

A signaler toutefois que cette passerelle est Homekit. Ce qui veut dire que ses équipements remontent dans Home Assistant. Mais ne rêvez pas trop, si l’ampoule remonte bien, et certainement tous les actionneurs connectés (prises, modules DIN), pas les inter En fait si, il faut juste attendre un peu. Néanmoins tout ne semble pas remonter, uniquement une action par bouton ! Mais j’avais déjà remarqué ça avec les télécommandes Tuya qui ne remontent pas via une passerelle Tuya Homekit. Il y a une forme de logique, Homekit sert à commander un équipement, comme une télécommande, hors on ne commande pas une télécommande…

Par contre ça veut dire que les équipements reconnus par Legrand vont remonter dans Homekit, ce qui peut être une solution de contrôle facile pour des produit pas reconnus par HA (Profalux, Bubendorf, Aldes, etc…).

La suite

Je vous propose de poursuivre ici et que chacun y apporte ses retours.

Sources

  • https://developer.legrand.com/production-firmware-download/

Home Assistant & Mi Boxer

J'avais acheté cette télécommande Mi Boxer il y a quelques mois afin de gérer les éclairages de mon séjour, en me disant que les curseurs seraient plus pratiques que mon Opple avec ses 6 boutons actuelle. Hélas elle n'était reconnue nulle part et avait terminé sa course dans le tiroir aux oubliettes du Zigbee...

C'était sans compter sur la ténacité de quelques amateurs de reverse engineering sur lesquels je suis tombé il y a quelques semaines et qui on fait un travail formidable qui a aboutit à une extension pour Zigbee2MQTT, ce qui rend cette télécommande enfin utilisable, tout au moins partiellement pour l'instant. Mais l'essentiel est là !

Ce qui fonctionne :

  • 7 boutons avec ON et OFF (deux actions et non un toggle). Celui du bas à droite n'est pas actif et je vous déconseille de l'utiliser...
  • 1 bouton avec ON et OFF (en haut)
  • 1 bouton W (si on veut bricoler...)
  • La barre de réglage de la luminosité
  • La remontée de l'état de la batterie

Ce qui ne fonctionne pas (pour l'instant) :

  • La barre de réglage des couleurs
  • La barre de réglage de la température du blanc
  • Les touches RGB
  • Les touches de temporisation (en bas à droite)

Une fois la télécommande reconnue sous Z2M on peut commencer à créer des actions. On remarque tout de suite que l'affaire va manquer de sensor: et que certains ne fonctionnent pas. Pas de panique, il y a une solution comme je vais vous le démonter avec l'automation: qui suit. 

J'ai fait un mélange de choose: / conditions: / trigger.id . C'est un peu long, mais je la colle en entier ce qui vous évitera une fastidieuse saisie. Il faudra tout de même y coller vous id: et entity: ! Ah j'allais oublier, il faut aussi ajouter un petit input_select: ... Voir plus bas EDIT du 11/01/2024 !

J'ai tenté de faire ça avec ControllerX que j'adore et qui me sert pour toutes mes télécommandes, mais pour l'instant c'est un échec !

alias: GUI - Mi Boxer
description: ""
trigger:
  - platform: state
    entity_id:
      - sensor.mi_boxeur_brightness
    id: bright
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_1_on
    discovery_id: 0x003c84fffec29c71_zone_1_button_on
    id: 1on
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_1_off
    discovery_id: 0x003c84fffec29c71_zone_1_button_off
    id: 1off
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_2_on
    discovery_id: 0x003c84fffec29c71_zone_2_button_on
    id: 2on
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_2_off
    discovery_id: 0x003c84fffec29c71_zone_2_button_off
    id: 2off
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_3_on
    discovery_id: 0x003c84fffec29c71_zone_3_button_on
    id: 3on
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_3_off
    discovery_id: 0x003c84fffec29c71_zone_3_button_off
    id: 3off
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_4_on
    discovery_id: 0x003c84fffec29c71_zone_4_button_on
    id: 4on
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_4_off
    discovery_id: 0x003c84fffec29c71_zone_4_button_off
    id: 4off
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_5_on
    discovery_id: 0x003c84fffec29c71_zone_5_button_on
    id: 5on
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_5_off
    discovery_id: 0x003c84fffec29c71_zone_5_button_off
    id: 5off
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_6_on
    discovery_id: 0x003c84fffec29c71_zone_6_button_on
    id: 6on
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_6_off
    discovery_id: 0x003c84fffec29c71_zone_6_button_off
    id: 6off
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_7_on
    discovery_id: 0x003c84fffec29c71_zone_7_button_on
    id: 7on
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_7_off
    discovery_id: 0x003c84fffec29c71_zone_7_button_off
    id: 7off
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_8_on
    discovery_id: 0x003c84fffec29c71_zone_8_button_on
    id: 8on
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: button_short_press
    subtype: button_group_8_off
    discovery_id: 0x003c84fffec29c71_zone_8_button_off
    id: 8off
    
condition: []
action:
  - choose:
      - conditions: "{{ trigger.id in ['1on'] }}"
        sequence:
          - service: light.turn_on
            data: {}
            target:
              entity_id: light.shellydimmer_f3d426
          - service: input_select.select_option
            data:
              option: "1"
            target:
              entity_id: input_select.mi_boxer_select
      - conditions: "{{ trigger.id in ['1off'] }}"
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: light.shellydimmer_f3d426

      - conditions: "{{ trigger.id in ['2on'] }}"
        sequence:
          - service: light.turn_on
            data: {}
            target:
              entity_id: light.shellydimmer_d3e57c
          - service: input_select.select_option
            data:
              option: "2"
            target:
              entity_id: input_select.mi_boxer_select
      - conditions: "{{ trigger.id in ['2off'] }}"
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: light.shellydimmer_d3e57c

      - conditions: "{{ trigger.id in ['3on'] }}"
        sequence:
          - service: light.turn_on
            data: {}
            target:
              entity_id: light.ikea_e27_tv
          - service: input_select.select_option
            data:
              option: "3"
            target:
              entity_id: input_select.mi_boxer_select
      - conditions: "{{ trigger.id in ['3off'] }}"
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: light.ikea_e27_tv

      - conditions: "{{ trigger.id in ['4on'] }}"
        sequence:
          - service: light.turn_on
            data: {}
            target:
              entity_id: light.lidl_led_stand
          - service: input_select.select_option
            data:
              option: "4"
            target:
              entity_id: input_select.mi_boxer_select
      - conditions: "{{ trigger.id in ['4off'] }}"
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: light.lidl_led_stand

      - conditions: "{{ trigger.id in ['5on'] }}"
        sequence:
          - service: light.turn_on
            data: {}
            target:
              entity_id: light.dimmable_sm309
          - service: input_select.select_option
            data:
              option: "5"
            target:
              entity_id: input_select.mi_boxer_select
      - conditions: "{{ trigger.id in ['5off'] }}"
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: light.dimmable_sm309

      - conditions: "{{ trigger.id in ['6on'] }}"
        sequence:
          - service: light.turn_on
            data: {}
            target:
              entity_id: light.shelly_bulb_1
          - service: input_select.select_option
            data:
              option: "6"
            target:
              entity_id: input_select.mi_boxer_select
      - conditions: "{{ trigger.id in ['6off'] }}"
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: light.shelly_bulb_1

      - conditions: "{{ trigger.id in ['7on'] }}"
        sequence:
          - service: light.turn_on
            data: {}
            target:
              entity_id: light.led_strip_1
          - service: input_select.select_option
            data:
              option: "7"
            target:
              entity_id: input_select.mi_boxer_select
      - conditions: "{{ trigger.id in ['7off'] }}"
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: light.led_strip_1

      - conditions: "{{ trigger.id in ['8on'] }}"
        sequence:
          - service: light.turn_on
            data: {}
            target:
              entity_id: 
                - light.shellydimmer_f3d426
                - light.shellydimmer_d3e57c
                - light.ikea_e27_tv
                - light.dimmable_sm309
                - light.shelly_bulb_1
                - light.lidl_led_stand
                - light.plug_tz_10_switch
                - light.led_strip_1
          - service: input_select.select_option
            data:
              option: "8"
            target:
              entity_id: input_select.mi_boxer_select
      - conditions: "{{ trigger.id in ['8off'] }}"
        sequence:
          - service: light.turn_off
            data: {}
            target:
              entity_id: 
                - light.shellydimmer_f3d426
                - light.shellydimmer_d3e57c
                - light.ikea_e27_tv
                - light.dimmable_sm309
                - light.shelly_bulb_1
                - light.lidl_led_stand
                - light.plug_tz_10_switch
                - light.led_strip_1
              
      - conditions: >-
          {{ is_state('input_select.mi_boxer_select', '1') and trigger.id in ['bright'] }}
        sequence:
          - service: light.turn_on
            data:
              brightness_pct: "{{ trigger.to_state.state }}"
              transition: 0.2
            target:
              entity_id: light.shellydimmer_f3d426
            enabled: true

      - conditions: >-
          {{ is_state('input_select.mi_boxer_select', '2') and trigger.id in ['bright'] }}
        sequence:
          - service: light.turn_on
            data:
              brightness_pct: "{{ trigger.to_state.state }}"
              transition: 0.2
            target:
              device_id: 1ff4112785e14b8b8cba18d45fec3b11
            enabled: true

      - conditions: >-
          {{ is_state('input_select.mi_boxer_select', '3') and trigger.id in ['bright'] }}
        sequence:
          - service: light.turn_on
            data:
              brightness_pct: "{{ trigger.to_state.state }}"
              transition: 0.2
            target:
              entity_id: light.ikea_e27_tv
            enabled: true

      - conditions: >-
          {{ is_state('input_select.mi_boxer_select', '4') and trigger.id in ['bright'] }}
        sequence:
          - service: light.turn_on
            data:
              brightness_pct: "{{ trigger.to_state.state }}"
              transition: 0.2
            target:
              entity_id: light.lidl_led_stand
            enabled: true

      - conditions: >-
          {{ is_state('input_select.mi_boxer_select', '5') and trigger.id in ['bright'] }}
        sequence:
          - service: light.turn_on
            data:
              brightness_pct: "{{ trigger.to_state.state }}"
              transition: 0.2
            target:
              entity_id: light.dimmable_sm309
            enabled: true

      - conditions: >-
          {{ is_state('input_select.mi_boxer_select', '6') and trigger.id in ['bright'] }}
        sequence:
          - service: light.turn_on
            data:
              brightness_pct: "{{ trigger.to_state.state }}"
              transition: 0.2
            target:
              entity_id: light.shelly_bulb_1
            enabled: true

      - conditions: >-
          {{ is_state('input_select.mi_boxer_select', '7') and trigger.id in ['bright'] }}
        sequence:
          - service: light.turn_on
            data:
              brightness_pct: "{{ trigger.to_state.state }}"
              transition: 0.2
            target:
              entity_id: light.led_strip_1
            enabled: true
mode: restart

EDIT 11/01/2024

Avec la dernière mise à jour Zigbee2MQTT (1.35.1-1) il n’y a plus besoin de l’extension. Par contre ce que j’avais fait sur la base de ce qui est proposé ici ne fonctionne plus (bien que toujours dans la doc).

J’ai donc biaisé en faisant du mqtt direct :

Pour la détection (trigger:) des boutons (à dupliquer) :

- platform: mqtt
    topic: zigbee2mqtt/Mi Boxer
    payload: ('on', 101)
    value_template: "{{ value_json.action , value_json.action_group }}"
    id: 1on
  - platform: mqtt
    topic: zigbee2mqtt/Mi Boxer
    payload: ('off', 101)
    value_template: "{{ value_json.action , value_json.action_group }}"
    id: 1off

Et pour la luminosité (il me reste à trouver comment ne prendre en compte que la charge action_level) :

- platform: mqtt
    topic: zigbee2mqtt/Mi Boxer
    id: bright

Ensuite on modifie dans le chose: coté action (à dupliquer bien entendu :

      - conditions:
          - condition: template
            value_template: >-
              {{ is_state('input_select.mi_boxer_select', '2') and trigger.id in ['bright'] }}
        sequence:
          - service: light.turn_on
            data_template:
              entity_id: light.shellydimmer_d3e57c
              brightness_pct: "{{ trigger.payload_json.action_level }}"

EDIT 11/01/2024

Le problème avec la solution de mon EDIT précédent est que le trigger sur la charge MQTT globale génère trop de bruit. On va donc faire deux sensor: basés sur MQTT afin d'isoler la valeur de la luminosité (merci Mathieu) ainsi que le groupe (bouton) correspondant à la light: active, ce qui va nous permettre d'éliminer l'input_select: dans le filtrage à venir :

mqtt:
  sensor:
    - name: "Mi Boxer Bright"
      unique_id: "mi_boxer_bright"
      state_topic: "zigbee2mqtt/Mi Boxer"
      value_template: "{{ value_json.action_level |int }}"

    - name: "Mi Boxxer Group"
      unique_id: "mi_boxer_group"
      state_topic: "zigbee2mqtt/Mi Boxer"
      value_template: "{{ value_json.action_group |int }}"

En trigger: on utilisera l'action : action_brightness_move_to_level qui remonte dans HA :

trigger:
  - platform: device
    domain: mqtt
    device_id: 86c1403c24491ce021ac3ee081a86308
    type: action
    subtype: brightness_move_to_level
    discovery_id: 0x003c84fffec29c71 action_brightness_move_to_level
    id: bright

Et on complète notre action déclenchée par l'id: bright et filtrée sur le sensor: correspondant au bon bouton, et j'ai ajouté le light: actif afin de ne pas risquer une fausse manœuvre :

      - conditions:
          - condition: template
            value_template: >-
              {{ is_state('sensor.mi_boxer_group', '101') and trigger.id in ['bright'] and is_state('light.shellydimmer_f3d426', 'on') }}
        sequence:
          - service: light.turn_on
            data_template:
              entity_id: light.shellydimmer_f3d426
              brightness_pct: "{{ states('sensor.mi_boxer_bright') | int}}"

Il ne reste plus qu'à attendre que Z2M remonte les informations liées à la température du blanc et la roue chromatique et on pourra les traiter avec la même méthode.

 
 

 

 

Home Assistant & Arrosage

Je n'ai pas vraiment la main verte et et mon jardin ressemble souvent à un no man's land, mais au printemps dernier une amie m'a convaincu de créer un carré d'herbes aromatiques, l'idée à fait son chemin et au centre on a même planté des plants de tomates qui produisent bien cet été ! Bon, vous imaginez bien que je ne vais pas ici vous conter ma vie privée mais plutôt la part domotique de cette réalisation !

En Provence, si on veut un peu de verdure et espérer manger ses propres tomates, il n'y a pas de secret, il faut arroser ! J'ai donc commencé par disposer un tuyau poreux sur mon carré. Ensuite j'ai acheté un robinet Zigbee et préparé un petit scheduler qui me permet facilement d'activer ou pas la plage journalière d'arrosage. J'ai fait quelque chose de simple en m'inspirant de ce que j'avais fait pour mon chauffe eau. Je publie ici le code suite à quelques demandes, même si ça n'a pas une grande valeur ajoutée.

Pour l'instant ça ne prends pas en compte les valeurs remontés sur les capteurs de plantes car je n'en suis pas satisfait.

L'offre en matière de capteurs de plantes n'est pas énorme :

  • Le capteur Xiaomi (ou ses copies) qui fonctionnent en Bluetooth et que l'on trouve sur Amazon ou Ali Express : C'est ce qui fonctionne le "mieux", mais cela nécessite un proxy BLE à l'extérieur que l'on peut facilement se bricoler avec un ESP sous ESPHome.
  • Le capteur Rehent en Zigbee que j'ai acheté chez Domadoo : Et là c'est la déconvenue. Il est un peu encombrant mais ne pose pas de soucis particulier pour l'appairer en ZHA ou Z2M (ou sur une passerelle Tuya), sauf que si ce capteur remonte parfaitement la température du sol, l'humidité du sol reste invariablement entre 75% et 85% (voir les sources au bas de cet article). Et bien sur chez Domadoo on est pas chez Amazon, le client a tort et l'éventuel retour est à votre charge. Bref capteur et fournisseur à éviter ! Il semblerait qu'autres séries de ce capteur que l'on trouve chez AliExpress fonctionnent, mais là il faudra aussi oublier le retour...

J'utilise une vanne Zigbee Woox qui alimente le tuyaux poreux (que je devrais remplacer par un un goute à goute). Pour avoir un réseau Zigbee fiable à l'extérieur, j'ai installé sous la toiture de la terrasse des prises Zigbee dont le relais est HS mais qui continuent à remplir parfaitement leur rôle de routeur Zigbee.

Tout cela reste très expérimental... Coté intégration j'utilise Home Assistant Plant et la carte qui va avec.

Automation

Une automation de schedule simple et visuelle :

input_datetime:
  watering_start:
    has_date: false
    has_time: true
  watering_stop:
    has_date: false
    has_time: true

input_boolean:
  watering_day_monday:
    name: "WATERING : Lundi"
    icon: mdi:toggle-switch
  watering_day_tuesday:
    name: "WATERING : Mardi"
    icon: mdi:toggle-switch
  watering_day_wednesday:
    name: "WATERING : Mercredi"
    icon: mdi:toggle-switch
  watering_day_thursday:
    name: "WATERING : Jeudi"
    icon: mdi:toggle-switch
  watering_day_friday:
    name: "WATERING : Vendredi"
    icon: mdi:toggle-switch
  watering_day_saturday:
    name: "WATERING : Samedi"
    icon: mdi:toggle-switch
  watering_day_sunday:
    name: "WATERING : Dimanche"
    icon: mdi:toggle-switch

automation:

- id: 'xx8d0e1-fcb6-4412-abvxx-99c4d37be5xx'
  alias: 'WATERING ON'
  trigger:
  - platform: template
    value_template: '{{ states.sensor.time.state == states.input_datetime.watering_start.state[0:5] }}'
  condition:
    condition: or
    conditions:
      - '{{ (now().strftime("%a") == "Mon") and is_state("input_boolean.watering_day_monday", "on") }}'
      - '{{ (now().strftime("%a") == "Tue") and is_state("input_boolean.watering_day_tuesday", "on") }}'
      - '{{ (now().strftime("%a") == "Wed") and is_state("input_boolean.watering_day_wednesday", "on") }}'
      - '{{ (now().strftime("%a") == "Thu") and is_state("input_boolean.watering_day_thursday", "on") }}'
      - '{{ (now().strftime("%a") == "Fri") and is_state("input_boolean.watering_day_friday", "on") }}'
      - '{{ (now().strftime("%a") == "Sat") and is_state("input_boolean.watering_day_saturday", "on") }}'
      - '{{ (now().strftime("%a") == "Sun") and is_state("input_boolean.watering_day_sunday", "on")}}'
  action:
  - service: switch.turn_on
    entity_id: switch.vanne_woox_switch
  - service: notify.slack_hass_canaletto
    data:
      message: "{{now().strftime('%d/%m/%Y, %H:%M')}} > WATERING | START | Soil : {{ states.sensor.soil_01_soil_moisture.state }}%" 


- id: 'zz9csdfsef-76dd-4fdd-9dzz-40bfsdq158zz'
  alias: 'WATERING OFF'
  trigger:
  - platform: template
    value_template: '{{ states.sensor.time.state == states.input_datetime.watering_stop.state[0:5] }}'
  action:
  - service: switch.turn_off
    entity_id: switch.vanne_woox_switch
  - service: notify.slack_hass_canaletto
    data:
      message: "{{now().strftime('%d/%m/%Y, %H:%M')}} > WATERING | STOP | Soil : {{ states.sensor.soil_01_soil_moisture.state }}%"   

La carte Lovelace :

type: vertical-stack
cards:
  - type: entities
    entities:
      - entities:
          - entity: automation.watering_on
            name: false
          - entity: sensor.energy_total_yearly_1pm_watering
            name: false
            unit: kWh
            format: precision2
          - entity: sensor.soil_01_soil_moisture
            name: false
        entity: switch.vanne_woox_switch
        name: Arrosage
        icon: mdi:watering-can-outline
        show_state: false
        state_color: true
        type: custom:multiple-entity-row
  - type: horizontal-stack
    cards:
      - type: custom:button-card
        color_type: card
        entity: input_boolean.watering_day_monday
        name: Lundi
        show_last_changed: false
        show_state: false
        tap_action:
          action: toggle
        state:
          - value: 'on'
            color: green
            icon: mdi:water-boiler
          - value: 'off'
            color: grey
            icon: mdi:water-boiler-off
        styles:
          card:
            - height: 60px
            - border-radius: 5px
            - font-size: 12px
      - type: custom:button-card
        color_type: card
        entity: input_boolean.watering_day_tuesday
        name: Mardi
        show_last_changed: false
        show_state: false
        tap_action:
          action: toggle
        state:
          - value: 'on'
            color: green
            icon: mdi:water-boiler
          - value: 'off'
            color: grey
            icon: mdi:water-boiler-off
        styles:
          card:
            - height: 60px
            - border-radius: 5px
            - font-size: 12px
      - type: custom:button-card
        color_type: card
        entity: input_boolean.watering_day_wednesday
        name: Mercredi
        show_last_changed: false
        show_state: false
        tap_action:
          action: toggle
        state:
          - value: 'on'
            color: green
            icon: mdi:water-boiler
          - value: 'off'
            color: grey
            icon: mdi:water-boiler-off
        styles:
          card:
            - height: 60px
            - border-radius: 5px
            - font-size: 12px
      - type: custom:button-card
        color_type: card
        entity: input_boolean.watering_day_thursday
        name: Jeudi
        show_last_changed: false
        show_state: false
        tap_action:
          action: toggle
        state:
          - value: 'on'
            color: green
            icon: mdi:water-boiler
          - value: 'off'
            color: grey
            icon: mdi:water-boiler-off
        styles:
          card:
            - height: 60px
            - border-radius: 5px
            - font-size: 12px
      - type: custom:button-card
        color_type: card
        entity: input_boolean.watering_day_friday
        name: Vendredi
        show_last_changed: false
        show_state: false
        tap_action:
          action: toggle
        state:
          - value: 'on'
            color: green
            icon: mdi:water-boiler
          - value: 'off'
            color: grey
            icon: mdi:water-boiler-off
        styles:
          card:
            - height: 60px
            - border-radius: 5px
            - font-size: 12px
      - type: custom:button-card
        color_type: card
        entity: input_boolean.watering_day_saturday
        name: Samedi
        show_last_changed: false
        show_state: false
        tap_action:
          action: toggle
        state:
          - value: 'on'
            color: green
            icon: mdi:water-boiler
          - value: 'off'
            color: grey
            icon: mdi:water-boiler-off
        styles:
          card:
            - height: 60px
            - border-radius: 5px
            - font-size: 12px
      - type: custom:button-card
        color_type: card
        entity: input_boolean.watering_day_sunday
        name: Dimanche
        show_last_changed: false
        show_state: false
        tap_action:
          action: toggle
        state:
          - value: 'on'
            color: green
            icon: mdi:water-boiler
          - value: 'off'
            color: grey
            icon: mdi:water-boiler-off
        styles:
          card:
            - height: 60px
            - border-radius: 5px
            - font-size: 12px
  - type: conditional
    conditions:
      - entity: automation.watering_on
        state: 'on'
    card:
      type: custom:vertical-stack-in-card
      cards:
        - type: horizontal-stack
          cards:
            - type: markdown
              content: '#### <center> Heure de début'
            - type: markdown
              content: '#### <center> Arrosage'
            - type: markdown
              content: '#### <center> Heure de Fin'
        - type: horizontal-stack
          cards:
            - entity: input_datetime.watering_start
              type: custom:time-picker-card
              name: Début
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
            - type: glance
              show_state: true
              show_name: false
              entities:
                - switch.vanne_woox_switch
            - entity: input_datetime.watering_stop
              type: custom:time-picker-card
              layout:
                align_controls: center
                embedded: true
              hide:
                name: true
                icon: true
  - color_thresholds:
      - color: '#039BE5'
        value: 0
      - color: '#0da035'
        value: 19
      - color: '#e0b400'
        value: 25
      - color: '#e45e65'
        value: 2400
    color_thresholds_transition: hard
    entities:
      - entity: sensor.plant_01_moisture
        name: Humidité du sol
      - entity: sensor.plant_01_temperature
        name: Températire du sol
      - color: rgba(0,0,255,1)
        entity: binary_sensor.night_reworked
        name: Nuit
        show_line: false
        y_axis: secondary
    group: false
    hour24: true
    hours_to_show: 24
    line_width: 2
    name: Humidité et température du sol
    points_per_hour: 4
    show:
      extrema: true
      fill: fade
      icon: true
      labels: false
      name: true
      state: true
    state_map:
      - label: Day
        value: 'off'
      - label: Night
        value: 'on'
    type: custom:mini-graph-card
  - type: custom:flower-card
    entity: plant.jardin
    show_bars:
      - illuminance
      - humidity
      - moisture
      - conductivity
      - temperature
      - dli
    battery_sensor: sensor.demo_battery

Souces

Capteur Rehent

 

Home Assistant & Plug Security

On utilise souvent des prises commandées uniquement pour mesurer la consommation de certains appareils. Ces prises sont toujours en position ON. Ces prises, si elles ne sont pas de trop mauvaise qualité un mécanisme de sécurité intégré qui les passent OFF en cas de surtension ou de surcharge. Et dans la pratique ça arrive parfois et on constate plus tard que le lave linge ou le lave vaisselle s'est arrêté, ou pire dans le cas d'un congélateur.

Pour palier à cet inconvénient on va créer une automation qui va réarmer la prise après quelques minutes en OFF. Jusque là c'est simple, mais il se peut également que le problème soit plus grave et qu'il ne faille pas forcer indéfiniment ce réarmement automatique. Etant donné qu'on ne dispose généralement pas d'information sur la cause de ce passage en sécurité on va considérer qu'au bout de "n" tentatives sur un temps donné on ne réarme plus. Pour ça on va utiliser des compteurs (à créer avec un minimum à 0 et un maximum à 10) et on fera un reset de ceux-ci toutes les nuits...

J'ai fixé ici arbitrairement à 5 le nombre de réarmements possibles.

Attention : Je vous explique ici comment j'ai fait, mais un risque existe toujours et la mise en œuvre de cette solution est à vos risques (et périls). Je me dégage ainsi de toute responsabilité.

On va faire ça avec une seule automation que j'ai voulue la plus concise et qui servira également à notifier :

automation:
- id: '2bd0ertyyf-2687-45f98f-aed0-to-off'
  alias: "Notify Plug Off"
  description: "Notification mise en sécurité des prises"
  mode: single
  trigger:
    - platform: state
      entity_id: switch.bw_1
      to: "off"
      id: "Lave Vaisselle"
    - platform: state
      entity_id: switch.bw_2
      to: "off"
      id: "Lave Linge"
    - platform: state
      entity_id: switch.bw_3
      to: "off"
      id: "Seche Linge"
    - platform: state
      entity_id: switch.bw_4
      to: "off"
      id: "Congelateur"
    - platform: time
      at: "00:05:00"
      id: "Reset"
  condition: []
  action:
  - delay:
      hours: 0
      minutes: 5
      seconds: 0
      milliseconds: 0  
  - choose:
      - conditions: "{{ trigger.id in ['Lave Vaisselle'] and states('counter.plug_bw_1') | int < 5 }}"
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.bw_1
          - service: counter.increment
            target:
              entity_id: counter.plug_bw_1
          - service: notify.slack_hass_mondon
            data:
              message: "{{now().strftime('%d/%m/%Y, %H:%M:%S')}} > Réarmement n°{{ states('counter.plug_bw_1')}} du {{ trigger.id }}"               

      - conditions: "{{ trigger.id in ['Lave Linge'] and states('counter.plug_bw_2') | int < 5 }}"
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.bw_2
          - service: counter.increment
            target:
              entity_id: counter.plug_bw_2
          - service: notify.slack_hass_mondon
            data:
              message: "{{now().strftime('%d/%m/%Y, %H:%M:%S')}} > Réarmement n°{{ states('counter.plug_bw_2')}} du {{ trigger.id }}" 

      - conditions: "{{ trigger.id in ['Seche Linge'] and states('counter.plug_bw_3') | int < 5 }}"
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.bw_3
          - service: counter.increment
            target:
              entity_id: counter.plug_bw_3
          - service: notify.slack_hass_mondon
            data:
              message: "{{now().strftime('%d/%m/%Y, %H:%M:%S')}} > Réarmement n°{{ states('counter.plug_bw_3')}} du {{ trigger.id }}" 

      - conditions: "{{ trigger.id in ['Congelateur'] and states('counter.plug_bw_4') | int < 5 }}"
        sequence:
          - service: switch.turn_on
            target:
              entity_id: switch.bw_4
          - service: counter.increment
            target:
              entity_id: counter.plug_bw_3
          - service: notify.slack_hass_mondon
            data:
              message: "{{now().strftime('%d/%m/%Y, %H:%M:%S')}} > Réarmement n°{{ states('counter.plug_bw_4')}} du {{ trigger.id }}" 

      - conditions: 
          - condition: or
            conditions:
              - "{{ trigger.id in ['Lave Vaisselle'] and states('counter.plug_bw_1') | int >= 5 }}"
              - "{{ trigger.id in ['Lave Linge'] and states('counter.plug_bw_2') | int >= 5 }}"
              - "{{ trigger.id in ['Seche Linge'] and states('counter.plug_bw_3') | int >= 5 }}"
              - "{{ trigger.id in ['Congelateur'] and states('counter.plug_bw_4') | int >= 5 }}"
        sequence:
          - service: notify.slack_hass_mondon
            data:
              message: "{{now().strftime('%d/%m/%Y, %H:%M:%S')}} > Problème avec le {{ trigger.id }}. On ne réarme plus, une vérification s'impose." 

      - conditions: "{{ trigger.id in ['Reset'] }}"
        sequence:
          - service: counter.reset
            target:
              entity_id:
                - counter.plug_bw_1
                - counter.plug_bw_2
                - counter.plug_bw_3
                - counter.plug_bw_4

Home Assistant & Sirènes

J'ai déjà parlé ici du système d'alarme Alarmo pour Home Assistant et notamment des commandes possibles pour armer et désarmer le système. Aujourd'hui il s'agira des sirènes.

J'ai trois modèles

  • La plus basique est juste branchée sur une prise commandée Zigbee ondulée. Donc un switch: vu en tant que siren:.
  • Une sirène Heiman qui sous ZHA décroche régulièrement et que j'ai du appairer à ma seconde passerelle sous Z2M.
    • Sous ZHA elle était vue en tant que siren:.
    • Sous Z2M elle est vue en tant que rien du tout...
  • Une sirène SMaBit sous Z2M également vue en rien du tout...

Pour commander une sirène via ZHA HA propose :

    - service: siren.turn_on
      target:
        entity_id:
          - siren.heiman_sirene

Pour commander une sirène sous Z2M il faut lui envoyer le payload correspondant :

    - service: mqtt.publish
      data:
        topic: zigbee2mqtt/Sirène SMaBiT/set
        payload: >-
          {"warning": {"mode": "fire", "level": "very_high", "strobe_level": "low", "strobe": "false", "strobe_duty_cycle": "10", "duration": "360"}}

Un peu plus compliqué et moins facile à mémoriser, mais ça fonctionne.

Mon objectif a donc été de faire en sorte qu'une sirène Z2M soit vue par Home Assistant comme une sirène ZHA et ainsi pouvoir la commander par un siren.turn_on comme les autres... Et voici ce que ça donne :

mqtt:
  siren:
    - unique_id: heiman_sirene_1s
      name: Heiman Sirène 1s
      state_topic: "zigbee2mqtt/Heiman Sirène 1/set"
      command_topic: "zigbee2mqtt/Heiman Sirène 1/set"
      optimistic: false
      qos: 0
      retain: true
      state_value_template: "{{ value_json.warning['mode'] }}"
      command_template: '{"warning": {"duration": 2, "mode": "burglar", "level": "very_high", "strobe": true}}'
      command_off_template: '{"warning": {"duration": 2, "mode": "stop", "level": "very_high", "strobe": true}}'
      state_on: "burglar"
      state_off: "stop"

Problème, car il y en a un, la sirène Heiman ne retourne aucun état sous Z2M. Il y a beaucoup d'articles à ce sujet, et l'intégration ne semble pas vraiment terminée. Hors dans l'activation il y a une durée, le principe étant d'activer la sirène pour x secondes. Et donc comme elle ne retourne pas son état, son commutateur restera sur ON alors que le délais d'activation est terminé et la prochaine action n'aura aucun effet... De plus certaines sirènes proposent plusieurs fonctions et tonalités (les bips de délais d'armement et désarmement par exemple).

Donc contrairement à ZHA ou l'intégration siren: peut gérer tous les modes, chercher à gérer une sirène Z2M en tant que siren: n'a pas de sens... (hormis peut être de bricoler un template: qui en fonction de la durée d'activation repasserait le commutateur à off, on peut aussi le faire via une automation: mais là ça va alourdir la chose...).

Alternative

S'agissant d'envoyer un payload à une sirène Z2M, ce que j'ai trouvé de plus logique est un mqtt:/button: et il faut donc en faire deux :

mqtt:
  button:
    - unique_id: siren_heiman_1_warning
      name: "Sirène Heiman 1 : Waraning"
      command_topic: "zigbee2mqtt/Heiman Sirène 1/set"
      payload_press: '{"warning": {"duration": 2000, "mode": "burglar", "level": "very_high", "strobe": true}}'
      qos: 0
      retain: false
      entity_category: "config"
      device_class: "restart"

    - unique_id: siren_heiman_1_stopped
      name: "Sirène Heiman 1 : Stopped"
      command_topic: "zigbee2mqtt/Heiman Sirène 1/set"
      payload_press: '{"warning": {"duration": 2, "mode": "stop", "level": "very_high", "strobe": true}}'
      qos: 0
      retain: false
      entity_category: "config"
      device_class: "restart"   

Ou plusieurs pour gérer les différents mode de la SMaBit :

mqtt:
  button:
    - unique_id: siren_smabit_armed
      name: "Sirène SMaBit : Armed"
      command_topic: "zigbee2mqtt/Sirène SMaBiT/set"
      payload_press: '{"squawk": {"state": "system_is_armed", "level": "veryhigh", "strobe": "true"}}'
      qos: 0
      retain: false
      entity_category: "config"
      device_class: "restart"

    - unique_id: siren_smabit_disarmed
      name: "Sirène SMaBit : Disarmed"
      command_topic: "zigbee2mqtt/Sirène SMaBiT/set"
      payload_press: '{"squawk": {"state": "system_is_disarmed", "level": "veryhigh", "strobe": "true"}}'
      qos: 0
      retain: false
      entity_category: "config"
      device_class: "restart"

    - unique_id: siren_smabit_warning
      name: "Sirène SMaBit : Warning"
      command_topic: "zigbee2mqtt/Sirène SMaBiT/set"
      payload_press: '{"warning": {"mode": "fire", "level": "very_high", "strobe_level": "low", "strobe": "false", "strobe_duty_cycle": "10", "duration": "360"}}'
      qos: 0
      retain: false
      entity_category: "config"
      device_class: "restart"

    - unique_id: siren_smabit_stopped
      name: "Sirène SMaBit : Stopped"
      command_topic: "zigbee2mqtt/Sirène SMaBiT/set"
      payload_press: '{"warning": {"mode": "stop"}}'
      qos: 0
      retain: false
      entity_category: "config"
      device_class: "restart"

Vous l'aurez compris, il s'agit plus d'un exercice de style qu'autre chose. Mais ça m'aura permis de comprendre comment créer des devices MQTT non reconnus par le discovery:. Merci @Mathieu pour ta participation !

Sources

 

Sony mon amour !

Amour déçu comme on le verra plus loin ! Depuis très jeune j'ai toujours été un afficionado de la marque Sony. Et si d'aucune pourraient trouver cela déplacé, il est des domaines ou je suis fidèle ! Coté TV après avoir eu quelques Trinitrons cathodiques célèbres tel le Profeel, je me suis offert jadis le premier moniteur plasma pro de la marque, qui avait du me couter un an de salaire et que je me suis résolu récemment à le porter à la déchetterie, le pauvre il trainait dans le garage depuis bientôt 20 ans !

Je ne vais pas vous faire la liste des écrans Sony que j'ai eu successivement, ni leur cout car ça me déprimerait, mais juste vous parler des déboires de mon dernier TV. Il y a un an j'ai craqué pour le A80J, un Oled ! Un TV intelligent sous Google TV, même si d'intelligence il n'a que la pub et les traqueurs que cela induit !

Je considère Google TV / Android TV comme étant le meilleur choix car il me permet d'installer les application qui me sont utiles, en opposition aux écosystèmes fragmentés de la concurrence qui ne proposent que l'essentiel bien commercial ! C'est du business, on le sait et on l'accepte car ça nous apporte du confort.

Autres points que les pseudo journalistes testeurs oublient souvent, c'est la télécommande et l'implémentation du CEC. Celles de Sony m'ont toujours satisfait alors que je déteste certaines d'autres marques. Mais Sony est radin, et sur le A80J la télécommande n'est pas même rétroéclairée ! Qu'à cela ne tienne il suffit de commander sur Amazon celle du A90J qui elle est premium et rétro éclairée... Quant au CEC il a toujours été très bien chez Sony.

Je ne vais pas vous raconter ce que vaut cette TV, plusieurs sites l'ont déjà fait plus ou moins bien. Enfin, je parle de TV, mais je ne regarde même pas la télé et aucune antenne n'y est raccordée. Je rêve d'une version moniteur !

Domotique

Oh le gros mot ! Encore un client qui veut faire des choses louches ! Louches car je ne me plie pas à la domotique commerciale et forcément limitative (Amazon, Google, Apple, Tuya, etc...) mais que ma domotique est libre, Home Assistant.

Et pourquoi faire ? Pas grand chose, juste inclure ce TV dans me scénarios (scènes). Et on va faire simple !

  • Play / Pause : on éteint et on rallume progressivement les éclairages comme au cinéma.
  • On / Off : on mets sur pause la musique qui était diffusée.

Ce n'est pas sorcier et très simple à faire. Mais pour ca il faut pouvoir extraire les informations du TV. Ca tombe bien ce TV est reconnu sous Home Assistant par l'intégration Bravia ou HomeKit. Dès lors on dispose des information idoines et ça fonctionne très bien, il suffit d'agir en fonction de l'état remonté.

Mais alors il se plaint de quoi le râleur ? Et oui il y a un "mais" !

Consommation

Pour accéder à ces fonctions il faut que le contrôle à distance ou / et HomeKit soit activé. C'est d'ailleurs le cas par défaut. Et les ingés de chez Sony, dont je ne doute pas des prouesses en audio / vidéo s'avèrent des brèles en réseau ! Pour que ces fonctions soient accessibles ils laissent simplement la partie réseau allumée quand le téléviseur est en veille. Et comme leur carte réseau doit dater du siècle dernier (elle a du mal à accrocher le réseau et est limitée à du 10/100 en 2022 ou on est censés passer de la full 4K !), elle consomme en veille pas moins de 26/28 W alors que si ces fonctions sont désactivées la veille est à seulement 0.9 W.

Cela pose deux constats :

  • Il est inacceptable qu'un TV en veille consomme 27 W en 2022 (c-a-d + ou - 50 € / an).
  • Il est inacceptable que ces fonctionnalités soient activées par défaut et induisent cette surconsommation ! Et certainement contraire aux directives européennes en la matière. Il est en effet peu probable qu'un consommateur lambda ne pense à mesurer et désactiver ces fonctions.

En l'état il est donc inenvisageable d'utiliser ces fonctionnalités qui ajoutent un plus au téléviseur. Et il faut noter que tout pilotage par les applis standard, Chromecast, Alexa, etc pose le même problème...

Le pire étant que si on désactive ces fonctions, quand on allume le téléviseur il mets bien trop longtemps pour accrocher le réseau, que ce soit en Ethernet ou en WI-FI. Je sais c'est fait pour regarder la télé avec un râteau sur la toiture !

Alternative

Avant j'utilisait un LCD Sony bien plus bête et une NVidia Shield que je pilotait très bien sans que cela ne grève mon budget. Le Shield allumait le TV en CEC et tout était parfait. Je vais peut être en racheter un, encore que mon Shield de 2015 fonctionne encore très bien dans ma chambre. Un bel exemple de non obsolescence qui mérite d'être signalé !

Et les autres ?

Je ne sais pas comment ça se passe chez les autres constructeurs de TV car je ne suis pas client. On me remonte 1 à 2 W avec les options de pilotage activées chez Samsung ou Panasonic, mais je n'ai pas vérifié. Par contre j'ai un bel élevage de Sonos, et là non plus ce n'est pas très joli à voir : aucun progrès depuis le début, conso identique en veille sur les anciens Play 1 et 3 et les nouveaux One (gen 1 et 2), entre 3 et 4 W voire plus de 8 à 11 W sur un AMP ou 6 W pour une Beam !

Moralité

La domotique n'est pas vraiment écologique, elle crée un énorme talon de consommation de part son infrastructure (caméras, nvr, switches, micro modules et autres prise connectées). Elle apporte plus de confort qu’elle ne fait réaliser des économies (les économies c’est le bobard que racontent certains à leur compagne quand c’est elle qui tiens les cordons de la bourse, car, hélas, la domotique reste un truc de mec, comme le BBQ, n’en déplaise à Sandrine).

Un panneau solaire va devoir s'imposer à moi !

Sources

 

 

Home Assistant & Volets roulants, automatismes !

Dans un précédent article je vous avait parlé de la partie matérielle de mes volets roulants et de leur commande via des interrupteurs et des télécommandes. Mais avec une installation domotique ouverte, on peut faire bien mieux !

L'objectif ici sera pour chaque volet (ou groupe de volets) d'automatiser leur ouverture, position intermédiaire et fermeture en fonction de :

  • Le jour et la nuit
  • La température extérieure et intérieure et les contraintes météo (canicule, etc.)
  • La position du soleil
  • Les contraintes de vie (lever tardif, soirée, etc...)
  • Les contraintes de présence / absence plus ou moins longue (alarme et simulation de présence)
  • Le débrayage de l'automatisme afin de laisser en paix un invité ou les enfants en vacances avec leur propres horaires et mode de vie.

Vous allez me dire tout de suite que la tache est complexe et vous aurez raison, mais pas tant que ça car je vais m'appuyer sur le travail de Fabien qui a créé un superbe moteur pour gérer tout ça, moteur que chacun pourra adapter à ses propres besoin en lui fournissant des prérequis (inputs, binary, etc..) qui permettront de gérer les différents conditions de positionnement. Vous trouverez les détails sur son blog, mais l'installation est relativement aisée, on commence par créer les entités nécessaires, ensuite on ajoute les BluePrint's de configuration et on termine avec un peu de yaml pour la configuration des conditions.

Facile, mais ça prends un peu de temps. Et surtout commencez par mettre sur le papier le résultat que vous voulez obtenir. Dans cet article je vais vous parler de ce que j'ai mis autour de son travail, plus que de son travail. 

Avant de chercher à intégrer mes exemples, prenez le temps d'avoir une installation vraiment fonctionnelle avec les Blueprint's de Fabien.

De base et sans rien ajouter le moteur de Fabien va permettre d'ouvrir le volet au lever de soleil et de le fermer au coucher du soleil grâce à l'intégration sun.sun. Bon, pour faire ça on a pas besoin d'une usine, c'est pour ça que je vais vous montrer que son moteur permet bien d'autres choses.

Température extérieure et position du soleil

Voici un exemple des plus basic de ce que j'avais fait au départ, mais je vous conseille d'adapter la version de Fabien que j'ai moi même adoptée et qui permet d'aller plus loin, avec différentes sources et notamment un capteur de luminosité.

Exemple

J'utilise les Packages et je vais commencer par créer deux sensor: en fonction de ma localisation en m'appuyant sur sun.sun

sensor: 
  - platform: template
    sensors:
      sunelevation:
        friendly_name: "Elevation du soleil"
        value_template: "{{ state_attr('sun.sun', 'elevation') }}"
      sunazimuth:
        friendly_name: "Azimut du soleil"
        value_template: "{{ state_attr('sun.sun', 'azimuth') }}"

Ensuite je vais me servir de ce site pour trouver les bonnes valeurs et je vais créer 3 binary_sensor: avec les les valeurs basses et hautes d'azimut, ces binary passeront à ON quand le soleil tapera sur la façade concernée. Bien sur il faudra un peu tâtonner pour trouver les bonnes valeurs et les ajuster.

binary_sensor:
  - platform: threshold
    name: "Soleil : Ouest"
    upper: 170
    lower: 80
    entity_id: sensor.sunazimuth
  - platform: threshold
    name: "Soleil : Sud"
    upper: 270
    lower: 100
    entity_id: sensor.sunazimuth
  - platform: threshold
    name: "Soleil : Est"
    upper: 299
    lower: 260
    entity_id: sensor.sunazimuth

Et pour terminer je vais créer un dernier binary_sensor: pour déterminer les conditions météo. Il s'appuie sur les infos de l'intégration Météo France (ou avec un capteur de luminosité en s'appuyant sur l'exemple de Fabien) et dépend de seuils définis dans des input_number: qui pourront s'afficher dans Lovelace. (je n'ai pas pris en compte la température intérieure pour l'instant).

binary_sensor:
  - platform: template
    sensors:
      vr_sun_hot:
        device_class: heat
        delay_on: 
          seconds: 300
        delay_off:
          seconds: 300
        value_template: "{{states('sensor.avignon_temperature')|float(0) >= states('input_number.vr_sun_hot') |float(0)
                        and states('sensor.avignon_cloud_cover')|float(0) <= states('input_number.vr_cloud_cover')|float(0) }}" 
        friendly_name: "Alerte haute température"

input_number:
  vr_sun_hot:
    name: Seuil haute température
    min: "10"
    max: "40"
    step: "1"
    unit_of_measurement: "°"
    icon: mdi:sun-thermometer-outline
  vr_cloud_cover:
    name: Seuil couverture nuageuse
    min: "0"
    max: "100"
    step: "5"
    unit_of_measurement: "%"
    icon: mdi:cloud-lock-outline 

Et voici le travail :

Exploitation

Vous aurez remarqué que j'ai deux seuils. Il y aura donc deux conditions possibles dans le choose: de l'automation de positionnement qui exploite les Blueprint's de Fabien. On retrouve dans l'ordre :

  1. La ou les conditions (and), ici la température est hot et le soleil au sud.
  2. La séquence de services à lancer :
    1. Je positionne le volet dans un input_number: à partir d'un input_number: de préréglage.
    2. A des fin de debug je note la dernière condition utilisée et la position du volet dans un input_text: que je peux afficher dans Lovelace
        - conditions:
            - condition: state
              entity_id: binary_sensor.vr_sun_hot
              state: "on"
            - condition: state
              entity_id: binary_sensor.soleil_sud
              state: "on"
          sequence:
            - service: input_number.set_value
              target:
                entity_id: input_number.vr_cuisine_planned_position
              data:
                value: "{{ states('input_number.vr_global_position_sun_alert') }}"
            - service: input_text.set_value
              target:
                entity_id: input_text.vr_cuisine_condition
              data:
                value: "Condition : Ensoleillement excessif - {{ states('input_number.vr_global_position_sun_alert') }} %"

Attention : Dans un choose: l'ordre de priorité des différentes conditions est important. Il y a aussi une option par défaut si aucune des conditions n'est valide mais que l'automation est tout de même déclenchée. On placera donc en premier celles qui doivent prendre le dessus, par exemple la condition issue de l'activation de l'alarme sera en premier car il est évident que l'on se moque de position du soleil si on est en voyage et que la maison est fermée.

Cela n'empêchera éventuellement pas une solution de simulation de présence qui agirait sur les volets de fonctionner. Je suis en train de tester cette intégration, attention à bien valider avant de partir en vacances...

Contraintes de vie

Réveil

Ici c'est au petit bonheur de chacun. En ce qui me concerne je n'ai pas d'horaires réguliers et je suis un lève tard. J'ai donc créé un script qui se déroule quand je dis bonjour (ou bonne nuit) à Alexa ou que j'appuis simplement sur un bouton de télécommande. Ce script va entre autres choses faire un push, si je suis présent dans la maison, sur un input_button: que je vais utiliser ici pour entrouvrir le volet de ma chambre et de la baie du séjour (une baie vitrée coulissante s'ouvre très facilement si elle n'est pas protégée, je vais donc éviter d'ouvrir son volet quand je dors). On a donc deux input_button: :

input_button:
  lionel_up:
    name: Je me lève
    icon: mdi:human-greeting-variant
  lionel_down:
    name: Je me couche
    icon: mdi:bed

La condition correspondant au réveil (la position du volet est stockée dans un input_number: afin d'être facilement ajustée depuis l'interface)

        - conditions:
            - "{{ as_timestamp(states('input_button.lionel_up'))|timestamp_custom('%H:%M', true) == states('sensor.time')  }}"
          sequence:
            - service: input_number.set_value
              target:
                entity_id: input_number.vr_lionel_planned_position
              data:
                value: "{{ states('input_number.vr_global_position_reveil') }}"

Il faut bien sur ne pas oublier de déclarer input_button: dans le BluePrint correspondant au volet (en condition immédiate) et l'actionner dans le script ou l'interface :

  - service: input_button.press
    target:
      entity_id: input_button.lionel_up

Et pour ce volet je vais modifier l'action par défaut d'ouverture dans l'automation afin qu'il ne se passe rien avant 13 heures... mais également rien si le volet est déjà un peu ouvert par une condition fugitive (button.press).

            - conditions:
                - condition: numeric_state # On ouvre le volet si le soleil est au dessus de l'horizon
                  entity_id: sun.sun
                  attribute: elevation
                  above: 0
                - condition: time
                  after: "13:00:00"
                - condition: state
                  entity_id: cover.vr_baie
                  state: "closed"

Soirée

En soirée, surtout en été, je n'ai pas envie que les volets soient complètement fermés car c'est l'heure ou on ouvre les fenêtres pour faire entrer un air plus frais. Je vais donc définir un binary-sensor: correspondant à ma soirée (merci Fabien) :

binary_sensor:
  - platform: template
    sensors:
      vr_evening:
        value_template:  "{{ (state_attr('sun.sun', 'azimuth') | float(0.0)) > 180 and (state_attr('sun.sun', 'elevation') | float(0.0)) < 0 and ('12:00:00' < states('sensor.time')) }}"
        friendly_name: "Soirée"

J'aurais pu y ajouter un and supplémentaire afin de valider la saison en me basant sur l'intégration Season. Sauf que les saisons tout le monde sait que ça ne veut plus dire grand chose et qu'ici depuis le début du mois de mail il fait plus ou moins 30° en journée, je testerais donc un input_boolean: de plus pour valider cette condition (que j'aurais également pu valider dans le binary-sensor:) :

        - conditions:
            - condition: state
              entity_id: binary_sensor.vr_evening
              state: "on"
            - condition: state
              entity_id: input_boolean.vr_evening
              state: "on"
          sequence:
            - service: input_number.set_value
              target:
                entity_id: input_number.vr_cuisine_planned_position
              data:
                value: "{{ states('input_number.vr_global_position_soir') }}"
            - service: input_text.set_value
              target:
                entity_id: input_text.vr_cuisine_condition
              data:
                value: "Condition : Soirée - {{ states('input_number.vr_global_position_soir') }} %"

On peut aussi faire la même chose avec d'autres conditions et sans binary_sensor: et tout mettre dans les conditions...

        - conditions:
            - condition: numeric_state
              entity_id: sun.sun
              attribute: azimuth
              above: 180
            - condition: numeric_state
              entity_id: sun.sun
              attribute: elevation
              below: 0
            - condition: time
              after: "18:00:00"            
            - condition: state
              entity_id: input_boolean.vr_evening
              state: "on"     

Présence / Absence et Alarme 

Pour ce chapitre c'est un peu plus compliqué. J'utilise en parallèle deux système. Ma centrale Visonic indépendante mais que je pilote dans Home Assistant et l'intégration Alarmo. Lors de mes test je me suis aperçu que tester uniquement l'état "armed" pouvait conduire à des surprises. Lors de l'armement et surtout le déclenchement d'une alarme (possiblement fausse) l'état de alarm_control_panel: passe par des phases successives peu simple à gérer, et ça peu conduire à une ouverture des volets non souhaitée.

Chez moi j'ai différentes possibilités pour armer ces deux systèmes (télécommande, code, tag), je vais donc me servir d'Alarmo qui permet de lancer un script selon un état pour basculer un input_boolean: qui me servira dans la condition immédiates du volet :

  • On passe tous les volets en mode automatique (certains auraient pu êtres débrayés comme on le verra plus loin)
  • On active l'input_boolean: qui va nous servir dans la condition de l'automatisation
  • Sur le mode Vacances (absence de longue durée) je rebascule tous les volets en mode manuel. Une sécurité de plus afin d'empêcher qu'ils soient ouverts par erreur.
script:
  alarmo_after_arm:
    alias: Alarmo - after Arm
    sequence:
      - if:
        then:
        - service: input_select.select_option
          target:
            entity_id:
              - input_select.vr_cuisine_immediate
              - input_select.vr_marie_immediate
              - input_select.vr_baie_immediate
              - input_select.vr_sejour_immediate
              - input_select.vr_antoine_immediate
              - input_select.vr_lionel_immediate
          data:
            option: Automatique
      - if: "{{ is_state('alarm_control_panel.alarmo', 'armed_away') }}"
        then:
          - service: input_boolean.turn_on
            target:
              entity_id: input_boolean.alarmo_armed_away
      - if: "{{ is_state('alarm_control_panel.alarmo', 'armed_vacation') }}"
        then:
          - service: input_boolean.turn_on
            target:
              entity_id: input_boolean.alarmo_armed_vacation
          - delay: "00:02:30"
          - service: input_select.select_option
            target:
              entity_id:
                - input_select.vr_cuisine_immediate
                - input_select.vr_marie_immediate
                - input_select.vr_baie_immediate
                - input_select.vr_sejour_immediate
                - input_select.vr_antoine_immediate
                - input_select.vr_lionel_immediate
            data:
              option: Manuel              
      - if: "{{ is_state('alarm_control_panel.alarmo', 'armed_home') }}"
        then:
          - service: input_boolean.turn_on
            target:
              entity_id: input_boolean.alarmo_armed_home
                entity_id: input_boolean.alarmo_armed_away

Et au retour sur le disarm :

  • Je repasse les input_boolean: à off
  • Je repasse tous les volets en automatique afin que les conditions courantes prennent effet.
  • Et si ma fille ou mon fils sont présents présente je vais basculer leurs volets en manuel afin qu'ils soient gérés comme bon leur semble. Mais j'attend un peu afin que l'automatisation du volet  le place dans la position de la contrainte en cours.
script:
  alarmo_after_to_disarm:
    alias: Alarmo - after Disarm
    sequence:
      - if:
        then:
        - service: input_boolean.turn_off
          target:
            entity_id: 
              - input_boolean.alarmo_armed_away
              - input_boolean.alarmo_armed_vacation
              - input_boolean.alarmo_armed_home
        - service: input_select.select_option
          target:
            entity_id:
              - input_select.vr_cuisine_immediate
              - input_select.vr_marie_immediate
              - input_select.vr_baie_immediate
              - input_select.vr_sejour_immediate
              - input_select.vr_antoine_immediate
              - input_select.vr_lionel_immediate
          data:
            option: Automatique
      - if: "{{ is_state('input_boolean.presence_marie', 'on') }}"
        then:
          - delay: "00:01:30"
          - service: input_select.select_option
            target:
              entity_id:
                - input_select.vr_marie_immediate
            data:
              option: Manuel
      - if: "{{ is_state('input_boolean.presence_antoine', 'on') }}"
        then:
          - delay: "00:01:30"
          - service: input_select.select_option
            target:
              entity_id:
                - input_select.vr_antoine_immediate
            data:
              option: Manuel

Ensuite il me reste qu'à configurer la contrainte dans l'automatisation, et surtout de la placer en priorité (la première).

        - conditions:
            - condition: state
              entity_id: input_boolean.alarmo_armed_away
              state: "on"
          sequence:
            - service: input_number.set_value
              target:
                entity_id: input_number.vr_cuisine_planned_position
              data:
                value: "{{ states('input_number.vr_global_position_alarm') }}"
            - service: input_text.set_value
              target:
                entity_id: input_text.vr_cuisine_condition
              data:
                value: "Condition : Alarmo Away - {{ states('input_number.vr_global_position_alarm') }} %"

J'ai deux positions d'alarme, ARMED_AWAY et ARMED_VACATION. J'utilise la première quand que pars quelques heures, ça laisse certains volets entrouverts afin de simuler une présence avec d'autres artifices, et la seconde pour une fermeture totale.

Le débrayage

Les automatismes c'est bien, mais si mes enfants sont ici en vacances ou des amis occupent leurs chambres, je ne voudrais pas qu'un automatisme les sortent du lit à l'aube. Pour mes enfants qui disposent de l'application Home Assistant je pourrait imaginer un automatisme, mais on va oublier et leur laisser de la liberté, d'autant plus que s'agissant d'amis je ne vais pas leur imposer ma domotique.

Par contre je dispose facilement de leur date de départ et d'arrivée et une automation bascule déjà un input_boolean: qui sert au chauffage. Je vais donc m'en servir pour passer le volet de leur chambre en mode manuel :

automation:
- id: a800970c-9bf4-48ce-aedg-a67c29093eb3
  description: Change vr_marie_immediate
  alias: VR Global - Change mode VR Marie
  mode: restart
  trigger:
    - platform: state
      entity_id: input_boolean.presence_marie
  action:
    - choose:
        - conditions:
            - condition: state
              entity_id: input_boolean.presence_marie
              state: "on"
          sequence:
            - service: input_select.select_option
              target:
                entity_id:
                  - input_select.vr_marie_immediate
              data:
                option: Manuel
        - conditions:
            - condition: state
              entity_id: input_boolean.presence_marie
              state: "off"
          sequence:
            - service: input_select.select_option
              target:
                entity_id:
                  - input_select.vr_marie_immediate
              data:
                option: Automatique

Enfants et amis commanderont donc leur volets avec la petite télécommande de leur chambre.

Suspension

Si vous êtes arrivés là et que vous avez lors de la configuration des BluePrint's la durée de suspension vous vous demandez peut être de quoi il s'agit. En fait configure là le durée pendant laquelle l'automatisme sera suspendu. Imaginons que celle ci est réglée du 60 minutes, il est 13 heures, le soleil frappe fort et vous décidez de déjeuner dans le cuisine. Vous remontez le volet avec la télécommande (ou l'application), il restera levé le temps de manger (suspension de 60 minutes) et l'automatisme reprendra le dessus ensuite avec la contrainte idoine... Magique !

Conclusion

Tout cela m'a pris un peu de temps et j'ai servi de cobaye dans la phase de test d'appréhension de la solution que Fabien viens de mettre à disposition. Quand vous aurez parfaitement validé le fonctionnement souhaité sur un volet pour pourrez le dupliquer sur l'ensemble des volets.

Pour cela il faut :

  1. Dupliquer le fichier qui contient les informations propres à chaque volet (mes fichiers sont ici) et changer l'ID: de l'automation.
  2. Repérer les informations des 3 BluePrint's du premier volet dans le fichier /config/automations.yaml, les dupliquer à la suite en changeant les informations propres à chaque volet et les ID: . Si vous ne le sentez pas configurez les BluePrint's individuellement à la main.
  3. Vérifier une fois de plus que tous les ID: ont bien été changés. J'insiste sur ce point car ça peut être la cause de bien des dysfonctionnements. Vous trouverez ici un générateur d'UUID.
  4. Redémarrer Home Assistant
  5. S'armer de patience car ce que l'on imagine parfait pour le premier volet ne le sera pas nécessairement pour les autres.
  6. Et surtout tester toutes les conditions car il sera désagréable de voir s'ouvrir le volet de la chambre quand on dort (vécu) voir certains volets ne pas se fermer sur l'alarme enclenchée quand on part que l'on est déjà en retard (vécu avec obligation de tous les passer en mode manuel et de les baisser à la main).

Avec plaisir pour en parler, ici ou mieux sur ce fil HACF.

Sources

Home Assistant & Wake on Lan

L'heure est aux économies d'énergies et il parait qu'il faut éteindre "la" Wi-Fi ! Ce n'est peut être pas ce qui consomme le plus et notre domotique fait certainement mieux.... Par contre nombre d'entre nous ont des PC énergivores qui mériteraient de passer en veille. Sauf que dans la pratique on laisse souvent ce PC allumé en se disant que l'on pourra en avoir besoin depuis l'extérieur... Ce que j'ai longtemps fait !

Un PC moderne avec un SSD sait sortir de veille en quelques secondes. Alors pourquoi ne pas ressortit une vieille fonctionnalité très peu utilisée, le Wake on Lan.

Sous Linux, Windows, voire même les mobiles il existe des application GUI ou CLI. Il faut tout de même savoir que Wake on Lan n'a pas été conçu à l'époque pour fonctionner à distance, et même s'il existe des possibilités ça reste compliqué. Alors pourquoi ne pas simplement utiliser Home Assistant qui lui sera toujours disponible, voire même simplement ajouter ça à un ESP existant, sous ESP Home par exemple..

La première chose pour vos tests sera de vérifier en local que le Wake on Lan fonctionne (il y a pas mal d'utilitaires et d'explications sur ce site).

Avec Home Assistant

On commence par ajouter l'intégration au fichier de configuration et on redémarre :

wake_on_lan:

Ensuite on peur créer un switch: , mais aujourd'hui on préfèrera un button: dans Lovelace qui actionnera le service correspondant :

  - action_name: 'Sleep/UnSleep'
    double_tap_action:
      action: call-service
      service: button.press
      target:
        entity_id: button.lia_monitors_off
      action: call-service
      confirmation:
        text: Etes vous sur ?
    tap_action:
      action: call-service
      service: wake_on_lan.send_magic_packet
      data:
        mac: 00-0C-29-FB-77-97
    icon: mdi:microsoft-windows
    name: 'Windows 10 PC'
    type: button

Vous remarquerez que j'ai configuré ça sur le tap_action:, le double_tap_action: étant lui configuré pour la commande de mise en veille via Home Assistant Agent.

Avec ESP Home

J'ai un client full cloud qui dispose d'une douzaine de PC, mais pas de serveur et encore moins de Home Assistant. La première solution était de laisser un PC allumé et y installer de quoi faire du Wake on Lan en remote. Il y a également des possibilités via TeamViewer et hélas pas de base dans l'UDM Pro d'Ubiquiti. On verra ce l'on utilisera mais j'ai également pensé faire ça via un simple ESP qui lui pourrait resté allumé...

Dans ESP Home on va éditer la config et simplement ajouter (attention, ici les séparateurs ne sont pas des "-" mais ":") :

web_server:    

button:
- platform: wake_on_lan
  name: "VM Annia"
  target_mac_address: 00:0C:29:FB:77:97

A partir de là il est possible de presser notre button: qui remonte dans Home Assistant, sur la page web de l'Esp, mais également via la commande curl (la doc est ici) :

curl -X POST http://192.168.210.153/button/vm_annia/press

Et comme on ne va pas demander à l'utilisateur final de faire un "curl" on va lui emballer tout ça dans une page web accessible que l'on sécurisera afin que lui seul puisse y accéder, en partant de cette base :

<HTML>
<center>

	<form name="myform" action="http://192.168.210.153/button/vm_annia/press" method="post">
  	<button>PC Annia ON</button>
	</form>

</center>
</HTML>

Variante avec un WebHook

L'ESP remonte sur Home Assistant via ESPHome. De fait on peu presser un bouton pour réveiller le PC distant, voire même intégrer ce bouton sur un autre Home Assistant en remote. Mais il est également possible de créer un WebHook et de déclencher avec un raccourcis depuis le PC de l'utilisateur distant :

- alias: Réveil PC Carole (WebHook)
  description: ""
  trigger:
    - platform: webhook
      webhook_id: "secret-id"
  condition: []
  action:
    - service: button.press
      data: {}
      target:
        entity_id: button.reveil_pc_carole
  mode: single

Et le raccourcis à créer sur le PC de l'utilisateur qui doit réveiller son PC de bureau...

curl -X POST https://ha-online.suptel.org/api/webhook/<secret-id>

Infos

  • Attention, Microsoft a introduit un concept de veille moderne qui souvent demandera à être désactivé. Des infos ici et .
  • Il n'est pas possible de réveiller un PC connecté en Wi-Fi ou en Ethernet via USB.
  • A noter que la Freebox dispose d'une option permettant de laisser passer du Wake on Lan. Ce n'est pas le cas pour tous les routeurs et il faut parfois ruser avec le port 9.

Sources

 

Home Assistant & BLE Proxy

J'entend souvent parler d'esp32, je ne suis pas un crack du fer à souder, ma vue baisse et je ne me lance généralement pas dans des montages au delà de mes compétences. De plus j'aime que ce que je déploie soit maintenable le plus facilement possible par des tiers. Je reste donc dans les standards, par exemple tous mes Shelly ont leur firmware d'origine, là ou ils seraient plus simples à gérer en ESP Home...

Ceci étant, j'aime aussi bricoler et j'avais un problème à résoudre. Si les petits capteurs de température Aqara en Zigbee sont parfaits, pas mal d'utilisateurs ont une préférence pour des sondes avec afficheur. Et le marché ne nous propose quasiment que des sondes afficheur en Bluetooth (BLE), Xiaomi, Aqara, Swithboot, Goovee ou encore InkBird... En général on peut facilement les gérer avec BLE Monitor et un bon dongle Bluetooth. Mais.

Depuis la version 2022.09 Bluetooth est géré nativement dans Home Assistant, il travaillent avec le développeur de BLE Monitor, une intégration qui devrait disparaitre à terme. Dans la pratique tout n'est pour l'instant pas reconnu mais les premiers résultats avec la version intégrée sont très honorables et j'ai obtenu des meilleurs résultats en BLE Proxy qu'avec des dongles USB ou du Bluthoot de base (NUC).

Pour autant se pose toujours le problème de la porté des sondes, dans notre cas d'usage BLE est uni directionnel et l'intégration se contente d'écouter, de décoder les trames et d'intégrer ce qui est exploitable. Sauf que s'il ne reçoit rien il n'intègre rien, et tout le monde sait que la portée du Bluetooth n'est pas phénoménale.

Lors de mes débuts avec HA j'avais essayé avec des potes des passerelles BLE, l'idée était de dédier des RPI Zero judicieusement placés à cet usage. Ca c'était soldé par un échec, certainement trop débutant que nous étions.

Mais avec la release 2022.09 une nouvelle option intégrée à HA a vu le jour : BLE Proxy

L'idée géniale est de se servir d'un module ESP configuré sous ESP Home et disposant du Bluetooth afin de capter les trames et les présenter à HA qui les décode. Et bien sur ce module sera judicieusement placé là ou on a besoin (cave, dépendance, aile gauche du château, etc...), pour peu que l'endroit dispose de Wi-Fi ou à minima d'Ethernet.

Le plus basic

La méthode la plus simple consiste à acheter un ESP-WROOM-32 (ici ou encore moins cher sur Ali), de le connecter au PC en USB après avoir installé les drivers et de le configurer à partir de cette page qui en fin de configuration vous proposera de l'intégrer à Home Assistant. 

Pour ceux qui jouent déjà avec l'add-on ESP Home il est aussi possible d'intégrer directement ce module et d'ajouter le code que vous trouverez ici et qui ressemble à ça :

esphome:
  name: "esp32-ble-proxy"
esp32:
  board: esp32dev
  framework:
    type: arduino
logger:
api:
  encryption:
    key: "AnUMyESfgsfgsdfhgjfjhhjkhg1WJLcGZHTiD7NoOEog="
ota:
  password: "3fb1ee84wgfsdhgsgh46sgh869d9a4856"
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "Ss Fallback Hotspot"
    password: "ws7qfgsfgh6Bl"
captive_portal:
web_server:
dashboard_import:
  package_import_url: github://esphome/bluetooth-proxies/esp32-generic.yaml@main
esp32_ble_tracker:
  scan_parameters:
    interval: 1100ms
    window: 1100ms
    active: true
bluetooth_proxy:
button:
- platform: safe_mode
  name: Safe Mode Boot
  entity_category: diagnostic

Une fois que le module est intégré à Home Assistant celui ci décodera les trames et vous proposera les sondes qu'il reçoit. Il est également possible de les ajouter avec leur adresse MAC.

Le plus compact

L'Atom Lite me plait bien (détails ici). D'une part il n'est pas plus gros qu'une pièce de monnaie, et d'autre part il est livré dans un petit boitier plastique ce qui sera toujours plus élégant que le précédent. De plus on peut avec un coupleur USB-A / USB-C le brancher directement sur un chargeur, à défaut un câble court fera l'affaire. On trouve l'Atom Lite sur Ali ou sur le site de son fabricant.

La mise en œuvre est identique, et en bonus on peut piloter la led multicolore qui pourra éventuellement servir à notifier visuellement des états de ce que vous voulez via HA :

light:
  - platform: fastled_clockless
    chipset: WS2812B
    pin: 27
    num_leds: 1
    rgb_order: GRB
    id: status_led
    name: Stack Light
    effects:
      - random:
      - flicker:
      - addressable_rainbow:  

Et comme il expose des ports GPIO rien n'empêche, comme pour les autres de s'en servir pour faire autre chose. Je vous conseille d'ailleurs d'aller explorer l'univers M5STACK ou vous pourrez piocher des idées, comme ce lecteur RFID par exemple....

Le plus complet

L'Olimex (qui s'achète ici) est le plus complet car il dispose en plus du Wi-Fi d'un port Ethernet ce qui permettra de le placer là ou le Wi-Fi est absent. De plus il est disponible dans une version avec antenne externe pour une meilleure réception, ainsi qu'une version industrielle garantie de -40 + 85C. Le sports GPIO sont présents mais il faudra sortit le fer à souder...

Coté configuration c'est identiques au autres. On peut commence par configurer en USB en Wi-Fi et ensuite on active le port Ethernet en mettant à jour en Wi-Fi. Il semble déconseillé d'utiliser le port USB et le port Ethernet en même temps et on ne va pas jouer... Par contre je vous conseille de figer l'adresse affectée par la résa DHCP dans les deux cas car dans le cas contraire ESP Home a un peu de mal à le retrouver via son adresse en .local qui par définition changera.

# wifi:
#   ssid: !secret wifi_ssid
#   password: !secret wifi_password
#   ap:
#     ssid: "Esp-Test Fallback Hotspot"
#     password: "MHPdiJUnJ8i8"
#   use_address: 192.168.210.69
  
ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO17_OUT
  phy_addr: 0
  power_pin: GPIO12
  use_address: 192.168.210.108

Alternatives

Il existe d'autre options que je n'ai pas testé, comme le GL-Inet GL-S10, que je n'ai pas pu me procurer. Dans un petit boitier on a du Wi-FI, de l'Ethernet et une antenne externe pour $ 25.

Shelly

Les modules Plus de Shelly permettent également d'activer le mode proxy et ainsi jouer ce rôle. Donc si on a des Shelly+ ou des Prises commandées Plusg S+ on pourra se passer d'un ESP dédié. J'ai validé ça avec succès sur un appartement en duplex de 175 m² avec 2 Shelly+ 1PM. C'est la solution la plus simple.

Usages

En fait tout cela nous ouvre pas mal de portes. Imaginons que je veuille faire des relevés de température / humidité sur un site distant ou il n'y a pas de domotique. J'ai alors le choix d'un ESP du genre Atom Lite sur une prise USB qui va remonter les informations des sondes locale sou Bluetooth vers un site Home Assistant distant qui le verra dès lors que j'ai ouvert le port 6053. Mon ESP communiquera alors via l'API avec Home Assistant comme s'il était local et je pourrais le maintenir, changer sa config et le mettre à jour avec ESPHome. L'alternative consiste à monter MQTT et à le faire communiquer avec un brooker. Ca évite d'avoir un port à ouvrir, mais je ne pourrais plus le maintenir.

On peut échanger ici ou sur HACF.

Alternative

Si le mode proxy est intéressant pour couvrir une surface plus grande, il est également possible de déclarer les sondes BLE dans ESPHome et ainsi les voir remonter dans Home Asistant :

sensor:
  - platform: xiaomi_cgg1
    mac_address: "58:2D:34:10:F8:22"
    temperature:
      name: "Cuisine Temperature"
    humidity:
      name: "Cuisine Humidity"
    battery_level:
      name: "Cuisine Battery Level"    

  - platform: xiaomi_lywsd03mmc
    mac_address: "A4:C1:38:AB:59:F7"
    bindkey: "93f19252bcdfhfdqshdhb4f9957424"
    temperature:
      name: "Square Temperature"
    humidity:
      name: "Square Humidity"
    battery_level:
      name: "Square Battery Level"  

  - platform: xiaomi_lywsdcgq
    mac_address: "4C:65:A8:D1:DB:22"
    temperature:
      name: "Terrasse Temperature"
    humidity:
      name: "Terrasse Humidity"
    battery_level:
      name: "Terrasse Battery Level"

  - platform: xiaomi_lywsd02
    mac_address: "3F:59:C8:82:70:2A"
    temperature:
      name: "Hall Temperature"
    humidity:
      name: "Hall Humidity"
    battery_level:
      name: "Hall Battery Level"

EDIT 15/08/2023

Deux choses :

  • Un composant pour les sondes qui ne sont pas reconnues de base.
  • Un constat, beaucoup d'ESP en WIFI donnent des résultats parfois aléatoires. Dans tous mes tests les résultat le plus fiable et constant est celui obtenu avec un ESP Olimex en Ethernet (POE), et avec son antenne externe il couvre toute la maison, terrasse et jardin compris.
  • Eviter plus de 3 proxy (attention avec les Shelly qui jouent ce rôle).
  • Pour les équipements Xiaomi / Aqara cette passerelle Xiaomi peut constituer la bonne alternative avec l'intégration locale idoine. Elle remonte BLE + Zigbee et de plus elles est compatible Homekit.

Home Assistant & Entity Controler

Vous voilà content, vous pouvez allumer, gérer et éteindre votre ampoule via Home Assistant. Je suis sur que vous savez également faire ça via un bouton sans fil, par exemple via ControlerX etc... Mais on peut mieux faire et automatiser en détectant la présence dans une pièce avec quelques lignes et un capteur d'occupation genre Aqara ou Xiaomi... Et pour ça il y a plusieurs méthodes.

EDIT 25/10/2022 : Finalement le problème majeur de Entity Controler est que toute modification impose un redémarrage complet de Home Assistant. J'ai donc basculé sur AD-Automoli qui lui est sous AppDaemon, donc dynamique car les modifications sont prises en compte immédiatement. A considérer également Light Automation pour des éclairages  heures fixes.

L'option classique en YAML

- id: 'fab33b5b-b526-4f6c-a23f-ee98c300012c'
  alias: "PRESENCE : Eclairage Bureau"
  description: ''
  trigger:
    - platform: device
      type: occupied
      id: enter
      device_id: a65b2d5c507be85f0964131f26f8d31e
      entity_id: binary_sensor.motion_bureau_occupancy
      domain: binary_sensor
    - platform: device
      type: not_occupied
      id: leave
      device_id: a65b2d5c507be85f0964131f26f8d31e
      entity_id: binary_sensor.motion_bureau_occupancy
      domain: binary_sensor
  condition:
  action:
    - if:
      - condition: trigger
        id: enter
      then:
        - service: light.turn_on
          data:
            entity_id: light.shellydimmer_db2f18
    - if:
        - condition: trigger
          id: leave
      then:
        - service: light.turn_off
          data:
            entity_id: light.shellydimmer_db2f18

Alternatives

Pour les plus flémards on peu aussi faire ça avec des BluePrint's, ou même en NodeRed si on veut se compliquer la vie (je déteste mais ça vous le savez). Mais j'ai encore plus simple à vous proposer.

Entity Controler

EC pour les intimes. EC est un moteur qui va contrôler des entités (light, switch, scènes, etc...) en fonction d'informations binaires. Donc les sensor: d'entrée seront de détecteurs de présence ou de mouvement, des capteurs d'ouverture ou dans l'absolu n'importe quel binary_sensor: , et pourquoi pas ceux que vous aurez créé dans un usage détourné... A ce contrôle on va bien sur pouvoir appliquer diverses contraintes (horaires, nuit/jour) ou simplement dire que dès lors qu'une lampe s'est allumée automatiquement elle ne s'étendra que manuellement. C'est assez souple et adaptable à quasiment tous les besoins. En tous cas les miens.

Le Git est ici et la doc très complète .

Voici un exemple pour allumer une lampe pendant 180 secondes (durée par défaut) : 3 lignes

motion_light:
  sensor: binary_sensor.motion_sejour
  entity: light.shellydimmer_d3e57c

On peut bien sur régler la durée :

  delay: 320

Faire en sorte que ça ne s'allume que la nuit, avec ici un offset :

  start_time: sunset - 00:30:00
  end_time: sunrise + 00:30:00

Et appliquer des valeurs à la lampe :

  service_data:
    brightness: 255
    color_name: blue
  service_data_on: 
    transition: 5
  service_data_off: 
    transition: 10

Faire en sorte que ça ne s'éteigne pas !

  stay_mode: on

Ou applique une contrainte externe. Ici j'ai mis un input_boolean: mais ça aurait pu être l'état d'une autre lampe, voire un media_player:. A noter que l'on peu également se servir des overrides pour simplement désactiver le contrôle. J'ai par exemple un éclairage extérieur avec des projecteurs que je ne veux pas voir s'allumer quand on dine et que l'éclairage via la guirlande est amplement suffisant et plus agréable.

  overrides:
    - input_boolean.auto_motion_salon

Pareil quand on regarde un film il ne faut pas que l'éclairage se mette en route si on bouge sur le canapé. Pour ça j'ai déjà une automation qui fonctionne sur le play/pause et qui augmente lentement l'éclairage quand je fait pause et inversement (comme au cinéma). Pour l'instant ça ne fonctionne que sur Emby, il faut que je l'intègre à Android TV et EC., je métrais à jour ici quand je le ferait).

Conclusion

Un peu comme ControlerX, voilà de quoi simplifier la chose et gagner des lignes de code. A noter que sous AppDaemon il y Wasp in a Box qui est un peu moins complet ou AD-Automoli qui lui est trop complet...

On peut en discuter ici ou sur HACF.