Depuis quelques semaines, j’ai fait pas mal d’automatisation/domotique à la maison, et plus particulièrement de suivi des consommations (eau, gaz, électricité). En faisant cela, j’ai découvert ESPHome, qui simplifie beaucoup la programmation d’ESP32 et leur intégration avec HomeAssistant.
Mais depuis quelques années, j’évite le plus possible de déployer quoi que ce soit manuellement. J’utilise Ansible pour tout automatiser, et réduire énormément le travail nécessaire à redéployer n’importe quel morceau de mon « infra », que cela soit à la maison ou sur mon serveur perso. Du coup, ESPHome seul ne me suffisait pas, et je voulais pouvoir réinstaller un ESP32 d’une seule ligne de commande.
Dans cet article, je vais partager les parties de mon repository Ansible qui me permettent de faire cela, et expliquer à quoi sert chaque partie, en prenant pour exemple l’ESP32 qui me sert à suivre la consommation d’eau à la maison. J’espère que cela pourra intéresser et/ou aider quelqu’un sur internet, même si c’est du Ansible assez basique et que je suppose que n’importe qui qui gère sa maison avec Ansible peut le faire tout·e seul·e. Désolé de ne pas simplement partager un lien Github ou équivalent : c’est gitté localement, et je n’ai pas envie de dupliquer la chose.
Voici donc mon répertoire Ansible :
srv-installer
├── playbook.yml
├── secrets.yml
├── inventory
│ ├── group_vars
│ │ └── lan.yml
│ ├── host_vars
│ │ └── esp-water-meter.lan.yml
│ └── master
└── roles
├── esphome
│ └── tasks
│ └── main.yml
└── esp-upload
├── tasks
│ └── main.yml
└── templates
└── esp-water-meter.yml.j2
Le fichier inventory/master contient tous les hôtes gérés par Ansible, rangés dans des groupes afin de mutualiser certaines variables. En voici l’extrait qui nous intéresse :
[lan:children]
esp
[esp]
esp-water-meter.lan
Le fichier inventory/group_vars/lan.yml contient quelques variables utilisées dans la maison, comme le nom du réseau Wifi :
wifi_ssid: my-wifi-access-point
Le fichier inventory/host_vars/esp-water-meter.lan.yml contient les variables nécessaires pour l’ESP chargé de surveiller la consommation d’eau, ici seul le modèle d’ESP32 est nécessaire :
esp_model: esp-wrover-kit
Le fichier secrets.yml file contient… les variables « secrètes », chiffrées via ansible-vault (Non, je ne vais pas déployer Hashicorp’s Vault ou quoi que ce soit du genre, ce serait un peu exagéré même pour moi):
wifi_psk: !vault |
$ANSIBLE_VAULT;1.1;AES256
6230...464313235613A32423082934579203475920380056639
1342...464313239859234854657904247890234850348502639
Et voilà pour les variables. On arrive au playbook principal, qui appelle divers rôles pour les diverses machines ou groupes de machines. Voici la partie spécifique aux ESP32 :
- hosts: esp
vars_files:
- secrets.yml
gather_facts: no
serial: 1
roles:
- { role: 'esphome', tags: ['esphome'] }
- { role: 'esp-upload', tags: ['esp-upload'] }
Je mets gather_facts à no, forcément, étant donné qu’un ESP32 n’a pas de ssh, et encore moins de Python. Vous verrez plus bas que chaque tâche Ansible est déléguée à localhost, car en réalité, tout le travail est fait sur mon laptop.
Je positionne aussi serial à 1, de manière à ce que le playbook joue les rôles en séquence pour chaque ESP32. Cela me permettra d’en brancher un, si nécessaire, via l’adaptateur USB/FTDI. Cela est utile pour le premier déploiement – lors des suivants, les mises à jour se font Over the Air.
Le premier rôle, esphome, est très simple : Il se contente d’installer l’outil esphome sur mon ordinateur. Voici le fichier roles/esphome/tasks/main.yml :
---
- name: install esphome
pip:
executable: pip3
name: esphome
state: present
become: yes
delegate_to: localhost
Le deuxième rôle est celui qui prépare et déploie le fichier Yaml source pour chaque ESP32. Je fais cela avec un template par ESP32, nommé suivant l’ESP32, et cela me permet d’enregistrer le source sans y mettre de modèle, point d’accès Wifi, passphrase Wifi ou quoi que ce soit en dur dans le fichier. Si je renomme mon point d’accès Wifi, par exemple, je n’aurai qu’un fichier à modifier (inventory/group_vars/lan.yml), et non chaque source.
Voici le fichier de tâches pour ce rôle, roles/esp-upload/tasks/main.yml :
- name: build short name
set_fact:
short_name: "{{ inventory_hostname | regex_replace('\\.[a-z]+$', '') }}"
delegate_to: localhost
- name: build source file
template:
src: "{{ short_name }}.yml.j2"
dest: "/tmp/{{ short_name }}.yml"
delegate_to: localhost
- name: compile source
shell:
chdir: /tmp
cmd: "esphome compile {{ short_name }}.yml"
delegate_to: localhost
- name: connect ESP
pause:
prompt: Please connect ESP to USB FTDI if OTA is not possible
delegate_to: localhost
- name: upload to esp
shell:
chdir: /tmp
cmd: "esphome upload {{ short_name }}.yml"
delegate_to: localhost
Tout d’abord, on construit la variable short_name à partir du nom d’hôte. Elle sera utilisée pour choisir le bon fichier source dans les templates, et dans le fichier source lui-même.
Puis on prépare le fichier source définitif avec template, remplaçant les variables par leur valeur. (Le fichier source est plus bas, pour référence), et on le range dans /tmp.
Ensuite, on compile le firmware avec esphome compile.
Après cela, on fait une pause, de manière à me laisser le temps de brancher physiquement l’ESP32 si nécessaire.
Enfin, après que j’aie validé le prompt avec Entrée, on envoie le firmware sur l’ESP32 avec la commande esphome upload.
Dans ce morceau de playbook et d’inventaire, il n’y a qu’un ESP32, celui qui suit ma consommation d’eau. Mais si – non, quand ! – j’en ajouterai d’autres, le playbook continuera ensuite pour le déploiement du suivant.
Pour référence, voici un extrait du yaml source EspHome pour cet ESP32, rangé dans roles/esp-upload/templates/esp-water-meter.yml.j2 :
esphome:
name: {{ short_name }}
esp32:
board: {{ esp_model }}
framework:
type: arduino
# Enable Home Assistant API
api:
password: ""
#Yes, I should add passwords on both api and ota.
ota:
password: ""
wifi:
ssid: "{{ wifi_ssid }}"
password: "{{ wifi_psk }}"
sensor:
- platform: pulse_counter
id: "pin14_pulse"
pin:
number: 14
inverted: true
mode:
input: true
pullup: true
name: "pin14_pulse"
count_mode:
rising_edge: DISABLE
falling_edge: INCREMENT
use_pcnt: false
internal_filter: 25ms
total:
unit_of_measurement: 'L'
accuracy_decimals: 0
name: "pin14_liters_index"
Et voilà : je peux maintenant mettre à jour mes ESP32 d’une seule commande :
$ ansible-playbook --ask-become-pass --ask-vault-pass --inventory inventory/ --limit esp playbook.yml
BECOME password:
Vault password:
PLAY [esp] *********************************************************
TASK [install esphome] *********************************************
ok: [esp-water-meter.lan]
TASK [esp-upload : build short name] *******************************
ok: [esp-water-meter.lan]
TASK [esp-upload : build source file] ******************************
changed: [esp-water-meter.lan]
TASK [esp-upload : compile source] *********************************
changed: [esp-water-meter.lan]
TASK [esp-upload : connect ESP] ************************************
[esp-upload : connect ESP]
Please connect ESP to USB FTDI if OTA is not possible:
ok: [esp-water-meter.lan]
TASK [esp-upload : upload to esp] **********************************
changed: [esp-water-meter.lan]
PLAY RECAP *********************************************************
esp-water-meter.lan : ok=6 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Peut-être qu’il faudrait que je m’applique autant sur la partie matérielle que sur la partie logicielle ?
Je n’ai même pas raccourci les fils des capteurs, j’ai un peu honte.
Pour conclure, voici le résultat, visualisé dans Grafana, après que l’ESP32 commence à envoyer des données dans HomeAssistant, qui les pousse ensuite dans InfluxDB :
@Colin super intéressant :) l'écran de consommation des énergies (ressources ?) pourrait avoir un gros intérêt pédagogique pour les enfants de la maison ici (beaucoup de ressources de type "cordes vocales" déjà consommées sans succès pour l'instant pour limiter la consommation d'eau, électricité…)