Et je l'ai fait… et refait !
J'aurai pu choisir n'importe qu'elle langage de programmation pour le faire, mais je suis fainéant et le temps me manque pour apprendre un nouveau langage, du coup je me suis rabattu sur Nore-RED, parfait pour moi, prendre des nœuds et les déplacer dans une fenêtre c'est à mon niveau. Je vous laisse visiter le site officiel pour savoir ce qu'est Node-RED. Parmi les avantages, une large palette de modules complémentaires, d'exemples, une facilité de bricolage, de débogage, la connexion native à MQTT et à bien d'autres outils utiles à ma domotique, etc...
L'écriture de la première version du cœur de ma domotique en Node-RED m'a pris 1 an, la seconde version 4 mois. (Je ne l'ai pas encore terminé mais ça tourne en prod.) donc même si c'est simple, ça prend du temps. Et encore je suis très loin de ce que je voudrais notamment un système d'ajout automatique de matériel, ça sera pour la prochaine version peut-être.
Fonctionnement de ma domotique
Lorsque dans le suite du billet ou même des prochains articles je parle du cœur (ou kernel) de ma domotique, ça sera le partie centrale du code, le flow sous Node-RED qui gère ma domotique.
Comme rapidement évoqué dans le billets précédents, ma domotique est basée sur la transmission de messages à 4 niveaux. Le message est composé d'un chemin appelé topic ou path dans le cœur de ma domotique et ce topic propage une valeur correspondante. Par exemple, pour l'information d'une lampe en court de traitement dans le coeur, ce sera :
iotnode/douche/lumiere/plafonnier = true
- Le premier niveau n'est pas spécialement important (ou que rarement) il correspond à l'outil que génère ce topic, sa
source
. - Le seconde niveau est la pièce de ma maison dans laquelle se situe l'élément, dans le cœur on l'appel
bucket
- Le troisième niveau est le type d'élement, dans le coeur on l'appel
measurement
- Le quatrième niveau est l'élement, dans le coeur on l'appel
field
Ce qui donne :
source / bucket / measurement / field = value
A y regarder de plus près, ça ressemble à ce qu'on retrouve dans des outils comme InfluxDB et Grafana, et qu'on appelle des metric, ce n'est pas par hasard car 100% des valeurs générées par ma domotique sont stockées dans une base de données InfluxDB de laquelle j'extrais de jolis graphiques grâce à Grafana. On en reparlera dans de prochains billets
Le cœur de ma domotique va simplement faire une boucle avec ce topic et chaque élément relié au cœur va pouvoir lire ce message est l'utiliser si besoin. Par exemple, lorsque la lampe s'allume un nouveau minuteur va se lancer et tenter de l'éteindre dans xx minutes. Mais là ou cela devient fou c'est que ce même minuteur génère un message qui va être lu par un scenario qui à son tour dira de ne pas l'éteindre mais de prolonger le minuteur car il y a eu un mouvement dans la pièce, etc... Mais du coup, les boucles peuvent s'empiler et devenir difficile à gérer, par exemple pour la gestion du chauffage ou des volets avec de nombreux éléments à prendre en compte comme les ouvertures, la présence, l'heure, la température, la lumière etc... Du coup il faut quand même mettre des garde-fou en place pour éviter des boucles infinies. Finalement une idée simple fini en casse tête. Voila pour le préambule à la gestion de ma domotique, il faut simplement retenir qu'on fait une boucle avec un message.
Un élément (ou metric) à l'entrée du cœur doit également suivre des règles bien précises. Il doit être enregistré dans le cœur avant de pouvoir être utilisé correctement. En effet un lampe peut-être simplement en tout ou rien, mais elle peut également être dimmable (valeur entre 0 et 99 par exemple) il faut donc l'expliquer au cœur. Lorsqu'on enregistre un nouvel élément il doit donc renseigner :
- sa pièce :
bucket
(douche) - son type :
measurement
(lumiere) - son nom :
field
(plafonnier) - son type de valeur :
format
(boolean) - sa valeur par défaut :
default
(true)
Avec ça le cœur saura comment nettoyer et retransmettre une représentation propre de l'élément. Par exemple si un élément tiers demande d'allumer la lampe avec un ordre true
mais que ma lampe est dimmable, le cœur saura transformer cette valeur en 99
et la retransmettre.
Dans la seconde version du cœur de ma domotique j'ai ajouté un début de gestion commune de certains appareils, en effet j'ai par exemple des interrupteurs connectés de même modèle un peu partout, donc au lieu répéter du code dans chaque pièce, j'utilise une base commune, pour cela j'ai ajouté dans l'enregistrement d'un élément la possibilité de renseigner un modèle :
protocol
: le protocole utilisé par l'élément (ex: zigbee)model
: le model de l'élément (ex: snbz-02 pour un capteur zigbee)id
: l'identifiant dans le protocole (zwave c'est un nombre, zigbee c'est arbitraire dans zigbee2mqtt, etc)option
: une option entre 1 et 3, utile si un élément à deux fonctions identiques (ex double interrupteur) ou un changement de comportement (capteur d'ouverture sur un interrupteur Qubino)
Avec ça on est quasi paré à toutes éventualités.
On continue de s'éloigner du cœur et on va aller voir comment fonctionne un élément de ma domotique dans Node-RED.
On vient de voir la première fonction de l'élement qui est d'enregistrer dans le cœur sa définition.
La seconde fonction est de transmettre les informations du matériel vers le cœur. Pour cela j'ai simplifié la tache en ajoutant des portes de/vers chaque protocole de ma domotique dans Node-RED. En effet tous mes protocoles domotiques atterrissent dans MQTT il est donc simple de les lire dans Node-RED. Si je reprend l'exemple de ma lampe, son élément va se relier à l'entrée Zwave du cœur et lire les informations dont elle a besoin dans son topic dédié, comme l'état de la lampe dans /zwave/10/37/0/currenValue
qu'il va retransmettre au cœur dans /device/douche/lumiere/plafonnier = true
.
La troisième fonction, vous l'aurez sans doute devinez, est transmettre les ordres du cœur vers le matériel. Ici on fait le travail inverse de la seconde fonction, on écoute le cœur pour attraper l'ordre d'allumer la lumière qui sera le message /ionode/douche/lumiere/plafonnier = true
et on le transmet à la sortie Zwave du cœur dans le message /zwave/10/37/0/targetValue/set = true
. La théorie est très simple.
J'ai ajouté une quatrième fonction qui permet d'afficher dans une interface web ces informations. Pour ma lampe un bouton pour l'allumer ou l'éteindre. Node-RED a une extension dashboard qui est une interface web, encore une fois le travail est ici facilité. A noter que je n'ai pas poussé la mise en page de cette interface car pour moi la domotique doit fonctionner toute seule et être transparente au quotidien et de plus je n'ai pas assez de temps pour jouer avec cette partie design.
Voila avec tout ça, un élément représentant un matériel physique (ou une partie de matériel) est géré dans le cœur de ma domotique.
Maintenant commence le vrai rôle de la domotique, animer tout ça. Pour cela j'utilise des nœuds de scénario, que ce soit l'ouverture des volets, la gestion du chauffage, l'éclairage des escaliers, ou le rappel pour sortir les poubelles, le principe reste le même. Un scénario écoute les messages transmis en boucle par le cœur et va attraper ceux qui l'intéressent. Par exemple pour le volet du salon, je vais écouter les messages de mode Jour/Nuit, ceux des ouvertures de la baie vitrée, ceux de mouvement et même ceux du capteur de luminosité, ben oui si le soleil tape trop fort j'ajoure le volet ! Je ferais des billets plus précis plus tard avec des exemples concrets et complets, ici je présente juste le principe.
Ce fût long et pourtant je n'ai quasi rien montré de concret du code. Je vais m'arrêter là pour ces premières explications, mais il y a bien d'autres choses dans ce cœur. Il y a les agendas, les assistants vocaux, les graphiques, un peu de configuration, etc. On verra si j'arrive à avancer dans mes billets de présentation pour pousser plus loin les explications…
Installation du server Node-RED
Reportez vous aux billet précédents (ici et là ) pour en savoir plus sur ma configuration Proxmox et Docker.
On se rend dans le shell de notre serveur Proxmox et on créer notre machine virtuelle à partir du template docker et on lui adresse une IP fixe :
qm clone 910 122 --name nodered
qm set 122 --ipconfig0 ip=10.1.4.122/23,gw=10.1.4.1
On peut démarrer notre VM.
Maintenant on se rend dans l'interface web de Portainer, on lie notre VM, et on ajoute Stack qui ressemble à ça :
version: "3.8"
services:
nodered:
container_name: nodered
image: nodered/node-red:latest
restart: always
environment:
- TZ=Europe/Paris
ports:
- "80:1880"
- "3456:3456"
networks:
- net
volumes:
- data:/data
networks:
net:
volumes:
data:
On va modifier le mot de passe de Node-RED , comme pour MQTT cela se fait dans le Docker. Pour cela on va dans la console Proxmox de la VM, et on ouvre une session docker :
docker exec -it nodered /bin/bash
On demande de hacher notre mot de passe avec :
node-red admin hash-pw le_mot_de_passe
On obtient en retour notre mot de passe crypté qu'il va falloir mettre dans le ficher de configuration de Node-Red. On retourne dans la console Proxmox (ou en SSH) de la VM et on va modifier la configuration de node-RED :
sudo nano /var/lib/docker/volumes/nodered_data/_data/settings.js
le fichier doit ressembler à ça :
module.exports = {
flowFile: 'flows.json',
credentialSecret: false,
flowFilePretty: true,
adminAuth: {
type: "credentials",
users: [{
username: "bob",
password: "le_mot_de_passe_crypté",
permissions: "*"
}]
},
httpAdminRoot: '/admin',
httpNodeRoot: '/',
diagnostics: {
enabled: true,
ui: true,
},
runtimeState: {
enabled: false,
ui: false,
},
logging: {
console: {
level: "info",
metrics: false,
audit: false
}
},
contextStorage: {
default: {
module: "localfilesystem"
},
memoryOnly: {
module: "memory"
},
},
exportGlobalContextKeys: false,
externalModules: {
},
editorTheme: {
palette: {
},
projects: {
enabled: false,
workflow: {
mode: "manual"
}
},
codeEditor: {
lib: "monaco",
options: {
}
},
markdownEditor: {
mermaid: {
enabled: true
}
},
},
functionExternalModules: true,
functionTimeout: 0,
functionGlobalContext: {
},
ui: { path: "" },
debugMaxLength: 1000,
mqttReconnectTime: 15000,
serialReconnectTime: 15000,
}
Par rapport au fichier d'origine, on a ajouté le login, modifié les URI qui finiront par /admin pour la partie administration (la fenêtre de développement de Node-RED) et à la racine pour l'interface web. On indique également d'utiliser deux sortes de mémoire : une vive et une en fichier. (Qui va permettre de garder certaines valeurs lors d'un redémarrage, ou coupure de courant.)
On relance le conteneur et notre instance Node-Red est accessible sur http://10.1.4.122/admin.
Dans le prochain billet, on va faire une petite pause et parler plus généralement de la domotique et de ce qu'on peut en tirer.