Une Raspberry?

pizero.png, déc. 2020

Si l'on dispose déjà d'un appareil sous Linux (ex un NAS), qui est toujours allumé ce peut aussi un bon candidat. Il suffit donc de faire l'installation de node red dessus si celui-ci le supporte (ex : Synology ou Netgear).

Mais si on n'a pas cette possibilité, le choix d'une Raspberry par rapport à un ordinateur, est guidé par la consommation. En effet, une Raspberry consomme au maximum 5V X 2A = 10W. Un ordinateur portable consomme de 50 à 100W, et un fixe de 250 W à 600W (PC gamer, serveur). Avec le kw/h à 0.1557 euros, 1 Watt à l'année coute 1.364 euros.... L’hébergent annuel d'une Raspberry va donc couter 13.64 euros / an, contre 477 euros / an pour un petit serveur de 350W... No comment! La Raspberry va être vite payée par les économies d'énergie...

Le choix du modèle Pi Zero est un compromis coût/performances. En effet, dans la mesure ou ne fonctionneront que des services réseaux dessus, il n'est pas nécessaire de disposer d'une grande quantité de ram ou d'un processeur surpuissant avec accélération graphique. A cet titre, elle est intéressante car peu chère, et toute petite.

La Raspberry Pi Zero ne sera connectée à un écran que durant la phase d'installation. Ensuite, tout se fera en ligne de commande ou par interface web. Il est bien sur possible d'utiliser une Raspberry plus puissante que la zero, mais cette dernière avec un processeur à 1 Ghz et 512Mo de ram est déjà très suffisante pour faire tourner notre application.

Liste de courses

Nos besoins seront les suivants :

  • L'application Google assistant ou un Google home mini (d'occasion sur le bon coin, à partir de 10 euros)
  • Une Raspberry PI Zero w (20 euros, à partir de 12 euros d'occasion sur le bon coin) ou zéro avec un dongle wifi (15 euros neuf+ le dongle)
  • Une alimentation 5V 2.5A
  • Carte micro SD, SDHC classe 10 / 8 Go mini.
  • Un adaptateur Micro HDMI pour connecter l'écran, le câble HDMI et bien sur un écran HDMI
  • Un adaptateur USB OTG (micro USB vers USB femelle)
  • Un clavier souris sans fil (à brancher sur l'unique prise de l'adaptateur OTG)
  • OU un hub USB + Clavier et souris filaire.
  • Éventuellement si vous préférez connecter la Raspberry en filaire, une interface USB <> Ethernet compatible. En effet, la Zero n'est pas équipée de port Ethernet.

A noter que l'on trouve des kits Raspberry Pi Zero sur Aliexpress, qui pour 25 euros fournissent une carte SD de 16 Go, l'adaptateur secteur, un boitier acrylique, l'adaptateur et le câble HDMI, le câble USB OTG et un radiateur de refroidissement pour le processeur. Il s'agit de carte officielles, fabriquées dans les lignes chinoises de la marque.

Exemple de kit:

kitzero.jpg, déc. 2020

Installation

Pré requis :

Préparation :

  • Décompressez le fichier ZIP de l'image téléchargée
  • Lancer l'imager, Dans "Choose OS", sélectionnez "Use Custom", et sélectionnez le fichier ".img" précédemment décompressé
  • Introduisez la carte SD dans le PC et sélectionnez là dans le PI Imager à l'aide du bouton "Choose SD Card"
  • Cliquez ensuite sur "Write". Ça prend un peu de temps... (dépend de la vitesse / performance de votre lecteur de carte SD)

Branchement de la Raspberry

  • Insérer la carte SD dans la Raspberry
  • La prise USB de gauche reçoit l'adaptateur OTG.
  • Si vous êtes en clavier souris sans fil, il suffit de mettre le dongle sur la prise femelle de l'USB OTG.
  • Sinon, brancher le hub USB dessus, et brancher le clavier et la souris sur le HUB. Si le hub dispose d'une alimentation c'est mieux.... Cela évitera de "tirer" sur les alimentations de la Raspberry.
  • Si vous préférez vous connecter en réseau filaire et disposez d'un kit chinois intégrant l'adaptateur USB <> Ethernet compatible, c'est le moment de le brancher sur le hub USB.
  • Brancher le câble HDMI et l’adaptateur HDMI
  • Et enfin, brancher l’alimentation sur la prise USB de droite

Premier démarrage

Juste après branchement, la Raspberry indique qu'elle va étendre la taille du système de fichier et redémarrer. C'est d'ailleurs ce qu'elle fait :).. pour nous amener ensuite dans l'interface graphique.

Wifi

wifi.png, déc. 2020

Nous allons tout de suite nous connecter au réseau Wifi. Pour ce faire, cliquer en haut à droite sur l'icône réseau (à côté du haut parleur) et suivre les instructions pour sélectionner votre réseau et associer la clé Wifi. Si vous avez saisi vos informations sans erreurs, au bout d'une dizaine de secondes vous allez être raccordé à votre réseau. Cette étape n'est pas nécessaire si vous êtes connectés en réseau filaire.

Assistant d'installation

L'assistant se lance pour nous permettre de personnaliser l'installation. Renseigner avec les informations suivantes :

- Country = France
- Langage = French
- Timezone = Paris
- Valider

Vous allez également devoir mettre un nouveau mot de passe En choisir un suffisamment fort (10 caractères mini, panachage de majuscules, minuscules, chiffres et symboles), car nous allons activer l'accès à distance par SSH...

Et ensuite ?

Mises à jour et paquets complémentaires

Il va ensuite falloir mettre à jour le système avec les derniers correctifs et faire quelques installations complémentaires.

Pour ce faire, il va vous falloir lancer un terminal (icône noire à gauche) et lancer les commandes suivantes :

terminal.png, déc. 2020

- Pour les mises à jour du système :

sudo apt-get update
sudo apt-get upgrade

- Pour l'installation du bureau à distance

sudo apt-get install xrdp

- Pour les prérequis afin de compiler certains paquets de node :

sudo apt-get install -y libavahi-compat-libdnssd-dev

- Et pour l'installation quelques utilitaires complémentaires

sudo apt-get install -y mc telnet git iftop vim screen neofetch
Configuration de Vi (optionnel)

Une petite configuration de l'éditeur Vi peut s'avérer nécessaire pour ceux qui l'utilisent afin de supprimer le mode visuel qui empêche les copier coller classiques. Editer .vimrc dans /root et /home/pi et ajouter la ligne

set mouse-=a

Editer également /etc/vim/vimrc et dé-commenter :

syntax on
Paramétrage profond de la Raspberry

La Raspberry nécessite un paramétrage système. Ceci a pour effet d'activer les interfaces intégrées à la Raspberry (SPI, I2C, etc..), l'accès Shell distant, et surtout de paramétrer le nom du système de façon à éviter tout conflit avec une éventuelle autre Raspberry PI existante sur votre réseau. Nous allons également reparamétrer la Raspberry pour que par défaut elle ne lance plus un environnement graphique afin d'économiser la ram. C'est assez facile à réaliser sous shell avec le programme spécifique fourni :

sudo raspi-config

raspi-config.png, déc. 2020

Puis en utilisant les flèches de déplacement curseur et entrée, naviguez dans le menu et paramétrez :

Dans "System Options"

	 S4 - hostname :  choisir un nom d'hôte unique sur votre réseau local (exemple "ma-raspberry")
	 S5 - Boot / auto login
		Choisir B1 - Console

Dans "Interface Options"

	P2 ssh  > enable
	P4 spi > enable
	P5 i2c > enable
	P7 1wire >  enable

Quitter raspi-config et ne pas accepter le reboot. Nous allons d'abord terminer le paramétrage...

Geekerie : Neofetch (optionnel)

Cette étape n'est pas indispensable, mais il est pratique de disposer d'une bannière en ligne de commande qui nous indique quelques paramètres vitaux sur le système (système, processeur, mémoire, température, uptime, etc..):

Neofetch.png, déc. 2020

L'installation est simple :

sudo nano /etc/profile.d/motd.sh

Coller les lignes suivantes dans le fichier en cours d'édition :

#!/bin/bash
echo
neofetch --cpu_temp C

Il y a un petit bug sur neofetch sur Raspberry, nous empêchant d'afficher la température de la carte. Il est aisément corrigeable en éditant le fichier /usr/bin/neofetch (sudo nano /usr/bin/neofetch) pour remplacer :

[[ "$(< "${temp_dir}/name")" =~ (ccoretemp|fam15h_power|k10temp) ]] && \

par

[[ "$(< "${temp_dir}/name")" =~ (cpu_thermal|ccoretemp|fam15h_power|k10temp) ]] && \
Installation de NodeRed

Suivre les instructions d'installation à partir de la page officielle de NodeRed: https://nodered.org/docs/getting-started/raspberrypi. Dans un shell, (déjà ouvert au chapitre précédent), lancer la commande :

bash <(curl -sL https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered)

L'opération prend un temps non négligeable (de l'ordre de la demi heure..). A l'issue de l'installation, un message indique que vous pouvez vous connecter sur http://ma-raspberry.local:1880 (ou le nom que vous avez choisi à l'étape précédente). Ce peut être une bonne idée de vous y connecter pour vérifier que tout va bien....

Nous allons ensuite lancer automatiquement node-red au démarrage :

sudo systemctl enable nodered.service

Il existe une très grosse bibliothèque de compléments pour NodeRed, la communauté étant très active. Le site dispose d'une rubrique dédiée au partage de flux ("Flows") et de bibliothèques ("Nodes") : https://flows.nodered.org/ . Certains proposent même des solutions complètes ("Collections"), combinant les flux et bibliothèques nécessaires pour un projet complet. Chaque module dispose de sa page d'aide. Tout ceci est bien sur open source. Nous allons lancer l'installation de quelque uns des paquets complémentaires pour Node Red.

Pour installer des modules en ligne de commande, il faut d'abord se placer dans le bon répertoire :

cd /home/pi/.node-red/

Pour Google Home :

npm install node-red-contrib-googlehome

Pour l'interfaçage des périphériques Ewelink (https://flows.nodered.org/node/node-red-contrib-ewelink):

npm install node-red-contrib-ewelink 

Pour le support ds emails sous NodeRed (https://flows.nodered.org/node/node-red-node-email) (cela nous servira pour mettre en place une sauvegarde automatique) :

npm install node-red-node-email 

Pour le support de Modbus (https://flows.nodered.org/node/node-red-contrib-modbus) (nous l'utiliserons ici pour supporter un compteur d'énergie)

npm install node-red-contrib-modbus 

Et enfin pour disposer d'un panneau d'affichage graphique dans NodeRed (https://flows.nodered.org/node/node-red-dashboard) :

npm install node-red-dashboard 

A noter que l'installation en ligne de commande des compléments pour Node Red n'est pas la seule méthode. Nous verrons qu'il existe aussi dans l'interface graphique de node-red de quoi le faire. Les deux méthodes sont équivalentes.

Il est maintenant temps de rebooter..

sudo shutdown -r now
Se connecter à la Raspberry

Après reboot, vérifiez que tout va bien et que vous pouvez vous connecter :

  • En http sur l'interface de node-red à l'adresse http://ma-raspberry.local:1880
  • En SSH, en utilisant le programme Putty (Windows), avec l'utilisateur "pi" et le mot de passe que vous avez fixé à l'étape précédente.
  • A distance sur l'interface graphique avec le bureau à distance intégré à WIndows. Sur mac, vous pourrez installer le client Microsoft à partir d'ici: https://apps.apple.com/fr/app/microsoft-remote-desktop/id1295203466?mt=12 .

Si tout va bien, vous pouvez à ce stade déconnecter clavier, souris, écran, etc.. Nous allons à partir de maintenant uniquement accéder à la Raspberry en réseau.

Dépannage

Si vous ne pouvez pas vous connecter (y compris par l'adresse IP), c'est que quelque chose ne va pas sur la config réseau. Vous disposez toujours de la possibilité en local (écran + clavier+souris) sur un terminal simple sans environnement graphique afin de rechercher l'origine du problème. Vous pouvez vous y logger par l'utilisateur "pi" et le mot de passe choisi à l'étape précédente.

De ce terminal, vous pouvez ensuite lancer un environnement graphique afin de reconfigurer le réseau avec la commande :

startx

Premiers pas sous node-red

Découverte de l'environnement

La connexion à node-red se fait via un navigateur web sur le port 1880 ( http://ma-raspberry.local:1880 ou le nom que vous avez attribué aux étapes précédentes ).

Vue de l'écran principal de node-red (cliquer pour zoomer) :

Node-RED1.png, déc. 2020

La zone centrale est l'espace de travail dans lequel on dépose et l'on câble les nodes, qui sont les fonctions élémentaires de node-red.

Un panneau à gauche contient les différents nodes disponibles. Les nodes sont filtrables à l'aide d'un moteur de recherche au dessus. En dessous, les différents nodes sont regroupés par catégorie :

  • Common : Nodes utilitaires (injection, debug) , et liens interfeuilles
  • Function : Toutes les fonctions agissant sur un flux.
  • Network : Toutes les fonctions agissant sur le réseau, MQTT, http, websockets, sockets et autres..
  • Output : Sortie audio uniquement par défaut.
  • Sequence : Agit sur les séquences de messages produits par les nodes.
  • Parser : Interpréteur de pages html, fichiers csv, json, xml, etc..
  • Storage : Tout ce qui concerne le stockage ou la lecture de fichiers.
  • Social : Tout ce qui concerne la messagerie et les réseaux sociaux si les extensions ont été installées.

D'autres ensembles de nodes ont été installées dans la procédure ci dessus, et notamment :

  • eWelink : dialogue avec les périphériques Ewelink (Sonoff)
  • Google Assistant : interactions avec Google Home
  • Modbus : Entrées sorties Modbus, que ce soit en IP ou en RS485
  • Dashboard : Panneau de contrôle et d'affichage pour Nodered.

La zone de droite contient les fonctions d'édition des propriétés. En haut de celle-ci, différentes icônes permettent l'accès aux infos (propriétés), aide, l'onglet de debug, et bien d'autres fonctions. Tout au dessus, à droite, le bouton "Deploy" permet de déployer toute modification faite sur l'espace de travail. C'est impératif de le faire après chaque modification à tester, sinon elles ne seront pas publiées et vous fonctionnerez sur la version précédente.

Je ne vais pas me lancer dans un tutoriel sur nodered, d'une part car la doc officielle en est farcie ( https://nodered.org/docs/tutorials/ ) mais aussi parce que le web en fourmille...

Nodered et Google Home

Tout d'abord, il peut paraitre inintéressant de coupler node-red et Google Home. En effet, avec Google home et des périphériques supportés, il n'y a besoin de rien d'autre pour allumer ou éteindre des appareils à la voix.. Node Red va nous permettre de faire des choses qui ne sont pas prévus par Google Home comme :

  • Faire des scénarios plus ou moins complexes, comme par exemple allumer un périphérique à la voix, qui s'éteindra automatiquement au bout d'un certain temps. Ceci peut permettre par exemple d'éteindre automatiquement ampli de chaine hifi ou une imprimante, que l'on pourrait oublier d'éteindre manuellement.
  • Par le même procédé, on peut aussi réaliser une commande de portail qui allume un relais uniquement 3 secondes par exemple.
  • Remplacer IFTTT, devenu payant (3 commandes max dans la version gratuite), afin de lier une commande vocale à un appel à une URL avec des paramètres. Ceci ouvre des possibilités de pilotage avec Google Home de périphériques DIY qui ne sont pas interfacables avec Google Home. Je l'utilise par exemple pour commander un émetteur infrarouge qui pilote mes clims / pompe à chaleur en commande vocale.

A noter que sur ce dernier scénario d'usage, contrairement à IFTTT nous n'aurons pas la possibilité de définir un nouveau vocabulaire. Il faudra s'appuyer sur celui existant ( "OK Google, Allume le Chauffage à 19 Degrés", ou "OK Google Allume l'imprimante" ). C'est une limitation, mais c'est aussi un bénéfice, car avec IFTT à contrario, il n'était pas possible d'utiliser "allume" ou "éteint" comme mots clés, réservés pour les périphériques standards.. Ici, nous n'utiliserons donc que des commandes intégrées, évitant à l'utilisateur d'apprendre de nouvelles commandes.

Afin de tester nous allons créer un switch, nommé ampli, qui allumera ou éteindra l'ampli home cinema avec la commande vocale "allume/éteint l'ampli", mais en plus l'éteindra automatiquemetn au bout de 3 heures si il n'y a pas eu de commande d'extinction de prononcée dans l'intervalle. 3 heures, ca fait déjà un beau film :)..

Pour utiliser les nodes Google Home de Node Red( https://flows.nodered.org/search?term=node-red-contrib-googlehome ), nous allons devoir créer un compte sur https://googlehome.hardill.me.uk/register. Une fois le compte créé et que nous sommes identifiés, il nous faut créer un périphérique dans https://googlehome.hardill.me.uk/user/devices .

Ici, nous allons créer un périphérique simple nommé "Ampli". A noter que lors de la création, il est possible de donner des caractéristiques ("Traits") au périphérique, qui vont déterminer quelles sont les commandes auxquelles le périphérique va répondre et les statuts qu'il est susceptible de nous retourner. Pour info, ces "traits", sont renseignés automatiquement lorsque l'on sélectionne un type, mais on peut les compléter à la demande. Pour notre ampli, nous allons sélectionner un "Switch". Par défaut le type Switch n'a que le "trait" On/Off d'activé, ce qui nous suffit.

create-ampli.png, déc. 2020

Vous pouvez ou pas l'associer à une pièce en fonction de ce que vous voulez faire. Si par exemple, vous souhaitez que lorsque vous sortez vous puissez dire "OK Google, éteint Salon", cela coupe toutes les lumières et l'ampli, cela peut avoir du sens. Il faut malgré tout penser que si l'on allume le salon de la même manière, l'ampli sera aussi allumé ... pour 3 heures. Dans mon cas, j'ai choisi de le dissocier du salon.

A partir de ce stade, Google Home sera en mesure de comprendre les commandes d'allumage ou d'extinction d'un "switch" nommé "Ampli". Mais comme il n'est pas encore câblé, cela ne fera physiquement rien.

Nous allons ensuire enregistrer dans l'application Google Assistant, sur mobile, un périphérique physique, capable d'allumer ou d'éteindre l'ampli. Il va nous falloir lui donner un nom différent de "ampli", par exemple amplificateur (ou rhododendron, si ca vous amuse). Dans la mesure ou nous le piloterons jamais en direct, on se fout un peu du nom dès lors qu'il n'est pas susceptible d'entrer en conflit avec le nom que vous voulez utiliser et qu'il ne risque pas d'être actité par inadvertance. Dans mon cas, j'ai pris un Sonoff DIY à 4 euros, que j'ai d'abord enregistré dans l'application mobile Ewelink, puis je l'ai ajouté dans Google Assistant. Je l'ai nommé "Amplificateur", n'étant pas particulièrement fan de rhododendron.

La dernière étape consiste à "brancher" les 2 dans node red, et mettre l'intelligence pour l'extinction automatique. Comme je suis avec des produits Sonoff, il va tout d'abord falloir identifier le switch que je viens d'ajouter. Malheureusement, on doit dialoguer avec celui-ci par un numéro unique d'identification, qu'il faut d'abord relever.

Pour ce faire nous allons batir un premier Flow qui va permettre d'interroger la liste des périphériques supportés :

lister-sonoff.png, déc. 2020

En voici le source qui pourra être importé dans Nodered par le menu tout en haut à droite, "Import" / "Clipboard" et en collant le code Json ci dessous.

[{"id":"3dbccd96.909d22","type":"tab","label":"Lister les périphériques Sonoff","disabled":false,"info":""},{"id":"1023c8cb.31d547","type":"debug","z":"3dbccd96.909d22","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":650,"y":60,"wires":[]},{"id":"1f4535fb.fe42d2","type":"ewelink-devices","z":"3dbccd96.909d22","name":"Compte perso","auth":"d2328f67.881f68","x":460,"y":60,"wires":[["1023c8cb.31d547"]],"info":"Ca ne se connecte pas à tout les coups."},{"id":"6a8c47e3.62ed9","type":"inject","z":"3dbccd96.909d22","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":280,"y":60,"wires":[["1f4535fb.fe42d2"]]},{"id":"465b619b.dfc7f8","type":"comment","z":"3dbccd96.909d22","name":"Lister les ewelink","info":"","x":100,"y":60,"wires":[]},{"id":"d2328f67.881f68","type":"ewelink-credentials"}]

A noter que le code json ci dessus est assez moche et difficile car optimizé. Si vous désirez le visualiser sous forme lisible, il suffit de le copier coller dans notepad++ et d'installer le plugin "JSON Viewer". Dans "Modules d'extension" / "JSON Viewer", vous trouverez ensuite une option "Format JSON" qui mettra les directives une par ligne et les tabulations associées pour en faciliter la lecture.

Pour des raisons de sécurité, les nodes exportés ne contiennent pas les informations de configuration (comptes, mots de passes, etc...). Il va donc vos falloir reparamétrer le node d'interrogation (en bleu) avec vos identifiants de compte Ewelink (les mêmes que ceux que vous mettez dans l'application mobile Ewelink).

Après publication (cliquer "Deploy"), vous allez pouvoir le déclencher en cliquant sur le bouton à gauche de "Timestamp". Si tout s'est bien passé (notamment au niveau de l'authentification), vous devriez, en cliquant sur "Debug" (1), Payload (2) et en sélectionnant le bon objet (3), trouver votre switch nommé "Amplificateur". Juste au dessus, se trouve le "deviceid" qu'il va nous falloir garder pour la suite du paramétrage.

amplificateur.png, déc. 2020

Nous allons maintenant que 'id est connu pouvoir mettre en place la programmation pour l'allumage extinction du switch. Importez la feuille ci dessous avec la même méthode que celle vue précédemment :

[{"id":"54677a23.6603c4","type":"tab","label":"Ampli","disabled":false,"info":""},{"id":"32be4281.a23356","type":"comment","z":"54677a23.6603c4","name":"Gestion de l'ampli avec Google Home et un Sonof DIY à 4 USD, timer 3 heures","info":"","x":300,"y":40,"wires":[]},{"id":"af140768.365f9","type":"google-home","z":"54677a23.6603c4","conf":"144ed61b.13aaca","device":"6640","acknowledge":true,"name":"Ampli","topic":"","x":245,"y":80,"wires":[["e32913a7.610908"]]},{"id":"bd24774d.045e88","type":"google-home-response","z":"54677a23.6603c4","conf":"144ed61b.13aaca","device":"6640","name":"Ampli","x":685,"y":180,"wires":[]},{"id":"e32913a7.610908","type":"function","z":"54677a23.6603c4","name":"","func":"if (msg.payload.command == "action.devices.commands.OnOff") {\n    if (msg.payload.params.on) {\n        msg.payload = "on";\n    } else {\n        msg.payload = "off";\n    }\n} \n  return [msg,null];","outputs":1,"noerr":0,"initialize":"","finalize":"","x":395,"y":80,"wires":[["bf337f24.a6f4d","a1e5f2fc.66eb68"]]},{"id":"bf337f24.a6f4d","type":"trigger","z":"54677a23.6603c4","name":"","op1":"","op2":"off","op1type":"nul","op2type":"str","duration":"360","extend":true,"overrideDelay":false,"units":"min","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":475,"y":140,"wires":[["bc06065.8cf8ef8","a1e5f2fc.66eb68"]]},{"id":"bc06065.8cf8ef8","type":"function","z":"54677a23.6603c4","name":"","func":"msg.payload = {\n    command: "action.devices.commands.OnOff",\n    params: {\n        on: false ,\n        online: true\n    }\n};\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":555,"y":180,"wires":[["bd24774d.045e88"]]},{"id":"a1e5f2fc.66eb68","type":"ewelink-power-state-write","z":"54677a23.6603c4","name":"Ampli","deviceId":"1000f0d61a","channel":1,"auth":"d2328f67.881f68","x":625,"y":80,"wires":[[]]},{"id":"60e6f006.ab3b7","type":"comment","z":"54677a23.6603c4","name":"Update Google Homegraph state","info":"","x":895,"y":180,"wires":[]},{"id":"4231e968.4973d","type":"comment","z":"54677a23.6603c4","name":"Allume immédiatement","info":"","x":855,"y":80,"wires":[]},{"id":"dbfa2871.66531","type":"inject","z":"54677a23.6603c4","name":"On","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"on","payloadType":"str","x":245,"y":180,"wires":[["bf337f24.a6f4d","a1e5f2fc.66eb68"]]},{"id":"f029e3af.19ac88","type":"comment","z":"54677a23.6603c4","name":"Eteint au bout de 3 heures ou sur ordre","info":"","x":915,"y":140,"wires":[]},{"id":"144ed61b.13aaca","type":"google-home-conf","username":"moncompte","localControl":false},{"id":"d2328f67.881f68","type":"ewelink-credentials"}]

Ce sketch va nécessiter un peu de paramétrage. Double cliquez sur le node bleu (Ampli) et sélectionnez dans la liste déroulante les identifiants précédemment créés pour le compte Ewelink. Puis double cliquez sur les 2 nodes gris "Ampli", et y mettre les identifiants du compte créé sur le site https://googlehome.hardill.me.uk.

node-red-ampli.png, déc. 2020

Après publication, vous pourrez vérifier le bon fonctionnement de Nodered > Ewelink en cliquant sur "On". Il vous restera à terser les commandes vocales sous Google Home: "Allume" Ampli", "Eteint Ampli" A noter que lorsque le trigger est bien déclenché (par la commande vocale ou l'appui manuel sur "on"), un petit point bleu s'affiche à côté. Il 'éteindra ici tout seul au bout de 3 heures. Le temps peut être ajusté dans le trigger (double cliquez sur le node violet..).

trigger-declenche.png, déc. 2020

Nous venons donc d'ajouter un nouveau périphérique nommé ampli, qui s'éteint tout seul au bout de 3 heures..

Nodered et Modbus

Modbus

Modbus est un protocole de 1979, qui permet de discuter avec des périphériques en les adressant individuellement. Initialement développé pour des automates programmables et avec une couche transport reposant sur une liaison série RS485, permettant de raccorder plusieurs périphériques sur une même ligne de transmission, il est aujourd'hui très répandu car il est royalty-free et relativement facile à mettre en oeuvre.

Aujourd'hui, le protocole modbus existe en 2 variantes majeures : RTU (sur liaison série RS232, RS422, RS485) et TCP (réseau filaire et wifi). En mode RTU, nous sommes en maitre / esclave. C'est le maitre qui pose les questions aux esclaves qui répondent tour à tour si leur adresse est visée. C'est grace à cette astuce qu'il ne peut pas y avoir de collision, puisqu'un et un seul élève peut répondre à la fois à un maitre, et uniquement lorsqu'il est interrogé.

modbus-serial-query.png, janv. 2021

En mode TCP, nous sommes en client serveur, et le serveur est passif. Il attend les connexions des clients qui doivent écrire dans le serveur.

Nous allons plus particulièrement nous intéresser modbus RTU sur liaison RS485. En effet, de nombreux périphériques sont désormais disponibles à prix abordables pour le grand publix à ce format : micro automates programmables, modules d'entrées sorties, capteurs de pression, de température, pilotage de moteurs à fréquence variables, compteurs d'énergie, etc.. Il suffit de 2 fils pour les raccorder, de leur attribuer une adresse différente et bien sur de disposer de la documentation de référence Modbus de l'appareil pour savoir quels sont les registre disponibles. Les périphériques sont mis en parallèle sur une ligne de transmission comme suit :

modbus-serial.png, janv. 2021

En principe, il ne faut pas dépasser 32 périphériques modbus sur une même ligne de transmission. Au delà il faudra mettre un répéteur. Il faut également qu'une ligne de transmission modbus soit chargeé à ses 2 extrémités avec une résistance de 100 ou 120 ohms, qui soit accordée à l'impédance caractéristique du câble pour limiter les pertes de transmission. Sur de très petites distances, et avec un seul périphérique, c'est beaucoup moins critique, et nous allons pouvoir l'oublier, mais il faudra y penser si vous envisagez de placer un câble de plusieurs dizaines de mètres entre la raspberry et le module modbus. Le choix du cable devient alors important.

Pour l'exemple nous alllons interfacer un SDM230 (100A max) ou SDM 120 (50A max), compteur d'énergie de EASTRON. Disponible à 25 euros, ce module se met dans le tableau électrique, en tête de réseau, pour comptabiliser l'énergie consommée dans la maison. Il est capable de fournir la tension, le courant et la puissance instantanée, ainsi que le comptage d'énergie totale consommée en KW/H.

La Raspberry fournissant une simple liaison série 3.3V, il va nous falloir un adaptateur série <> Modbus série pour pouvoir dialoguer avec nos périphériques Modbus. On en trouve de nombreux sur Aliexpress à partir de 2 euros : https://fr.aliexpress.com/wholesale?SearchText=module+TTL+RS485

Voici celui que j'ai acheté, car il dispose d'une bonne protection contre les décharges électrostatiques et les surtensions ainsi que 2 leds d'activité TX/RX:

RS485-adapter.jpg, janv. 2021

Côté branchements, un côté se raccorde à une alimentation (3.3V/GND) de la Raspberry et à la liaison série (TX = GPIO14, RX=GPIO15). Le TXd de la raspberry se branche sur le TXd de la carte RS485 et vice/versa.

Raspberry-PI-Zero-Pinout-schema.webp, janv. 2021

L'aute côté (RS485) se raccorde en branchant le A+ de l'adaptateur sur le A+ du SDM230, et même chose pour le B-.

Protocole Modbus

Pour plus d'explication sur le protocole Modbus et notamment avoir les adresses des registres disponibles, vous pouvez consulter la Documentation du protocole SDM230. J'ai également trouvée une doc détaillée sur les registres disponibles sur le SDM230.

A la lecure de la doc, on a l'impression que c'est compliqué mais vous allez voir que via Nodered et les modules additionnels c'est finalement assez simple.

Les valeurs à lire sont dans les "Input Registers". Il est dit dans la doc du SDM230 que les valeurs sont contenues dans 2 registres consécutifs de 16 bits (soit 4 octets) et qu'ils sont encodés en virgule flottante IEEE754.

Si nous souhaitons lire le courant (1), nous allons donc devoir lire 2 registre consécutifs à l'adresse 6 (2) comme l'indique la doc :

InputRegisters.png, janv. 2021

Attention, l'adresse du regitre (2) est en hexadécimal.

Il va également nous falloir connaitre l'adresse Modbus du périphérique. Par défaut, elle est à 1 mais je l'ai changée à 30 par le panneau avant du SDM340. Enfin, j'ai conservé la vitesse par défaut de Modbus (9600 bauds), ainsi que le format (n,8,1). Ca y es tnous avons tout ce qu'il faut pour questionner notre périphérique Modbus:

Nous allons donc poser la question au périphérique d'adresse 30 (1), pour lire les Inpupt Registers (2), en lui demandant 2 registres de 16 bits (4) commençant à l'adresse 6 (3), et allons dialoguer sur la liaison série /dev/serial0 paramétrée en 9600 bauds, 8 bits, no parity, 1 stop bit :

ModbusQuestion.png, janv. 2021

Nous savons maintenant comment mettre en corrélation une table décrivant les registres modbus, avec une vraie demande faite par Node-Red. Nous allons voir plus loin comment l'implémenter.

Le dernier point restant à régler est que ces 4 octets représentant les valeurs en virgule flottante ne sont pas faciles à comprendre. Vous trouverez en ligne bon nombre de convertisseurs susceptibles de vous aider comme par exemple https://www.h-schmidt.net/FloatConverter/IEEE754.html .

Dans NodeRed, nous disposerons d'une fonction nommée "parsefloat" qui permettra d'effectuer cette conversion.

Modbus dans Nodered

Ici pour faciliter les choses, je suis parti d'un Flow venant de la bibliothèque NodeRed pour SDM120 (https://flows.nodered.org/flow/75a4715fb6a3595057343a92abc493e4 ) . Il a été à peine adapté le SDM230 étant compatible en terme de protocole.

En plus de node-red-contrib-modbus, ce flow utilise node-red-dashboard pour ajouter une page de supervision avec des widgets graphiques. Ceux ci seront visibles sur http://ma-raspberry.local:1880/ui

Voici le code modifié pour SDM230 du Flow à importer (triple cliquer pour sélectionner tout le bloc):

[{"id":"f89db908.ccfc58","type":"tab","label":"SDM230","disabled":false,"info":""},{"id":"ebbc4482.1edd58","type":"comment","z":"f89db908.ccfc58","name":"SDM230 Energy Meter","info":"","x":140,"y":40,"wires":[]},{"id":"75c0e766.f23c68","type":"modbus-read","z":"f89db908.ccfc58","name":"SDM 230 Tension","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"0","quantity":"2","rate":"10","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"40f20c7f.13a934","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":146.00001525878906,"y":156,"wires":[["49b8d724.64fea","1b079b9e.95ab2c"],[]]},{"id":"49b8d724.64fea","type":"function","z":"f89db908.ccfc58","name":"Tension","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(1));\nmsg.topic = "voltage";\n\nnode.status({fill:"blue",shape:"ring",text:msg.topic + ":" + msg.payload});    \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":390,"y":148,"wires":[["1495c496.dd7023","36fe9de8.3ed38a"]]},{"id":"62bde4e.246551c","type":"modbus-queue-info","z":"f89db908.ccfc58","name":"SDM230_file_attente","topic":"","unitid":"30","queueReadIntervalTime":"1000","lowLowLevel":"1","lowLevel":75,"highLevel":150,"highHighLevel":300,"server":"40f20c7f.13a934","errorOnHighLevel":false,"showStatusActivities":false,"updateOnAllQueueChanges":false,"updateOnAllUnitQueues":false,"x":540,"y":520,"wires":[["5531e192.8a2948"]]},{"id":"44db4c34.a23e2c","type":"inject","z":"f89db908.ccfc58","name":"","repeat":"","crontab":"","once":false,"topic":"","payload":"","payloadType":"date","x":160,"y":520,"wires":[["e736a009.8a33a"]]},{"id":"e736a009.8a33a","type":"change","z":"f89db908.ccfc58","name":"Reset queue","rules":[{"t":"set","p":"resetQueue","pt":"msg","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":520,"wires":[["62bde4e.246551c"]]},{"id":"86caa608.7adf88","type":"function","z":"f89db908.ccfc58","name":"Courant","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(1));\nmsg.topic = "current";\n\nnode.status({fill:"blue",shape:"ring",text:msg.topic + ":" + msg.payload});    \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":389,"y":206,"wires":[["a7e4a3d5.23f35","36fe9de8.3ed38a"]]},{"id":"900e06b4.93eca8","type":"function","z":"f89db908.ccfc58","name":"Puissance","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(1));\nmsg.topic = "power";\n\nnode.status({fill:"blue",shape:"ring",text:msg.topic + ":" + msg.payload});    \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":400,"y":259,"wires":[["d92d71c3.167b9","36fe9de8.3ed38a","9837f252.bb0e"]]},{"id":"6179779.d820a08","type":"function","z":"f89db908.ccfc58","name":"Fréquence","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(1));\nmsg.topic = "frequency";\n\nnode.status({fill:"blue",shape:"ring",text:msg.topic + ":" + msg.payload});    \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":400,"y":315,"wires":[["d76382a3.74ea78","36fe9de8.3ed38a"]]},{"id":"1c155c0e.286224","type":"function","z":"f89db908.ccfc58","name":"Consommation","func":"var rawData = new ArrayBuffer(4);\nvar intView = new Uint16Array(rawData);\nvar fltView = new Float32Array(rawData);\n\nintView[0] = msg.payload[1]; //low\nintView[1] = msg.payload[0]; //high\n\nmsg.payload = parseFloat(fltView[0].toFixed(2));\nmsg.topic = "energy";\n\nnode.status({fill:"blue",shape:"ring",text:msg.topic + ":" + msg.payload});    \n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":410,"y":375,"wires":[["9cd2baf1.c3108","36fe9de8.3ed38a"]]},{"id":"ce99604f.7fd198","type":"modbus-read","z":"f89db908.ccfc58","name":"SDM 230 Courant","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"6","quantity":"2","rate":"10","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"40f20c7f.13a934","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":154.00001525878906,"y":212,"wires":[["86caa608.7adf88"],[]]},{"id":"ac524aef.e3dc3","type":"modbus-read","z":"f89db908.ccfc58","name":"SDM 230 Puissance","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"12","quantity":"2","rate":"10","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"40f20c7f.13a934","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":156.00001525878906,"y":265,"wires":[["900e06b4.93eca8"],[]]},{"id":"bae1c9a1.1ea118","type":"modbus-read","z":"f89db908.ccfc58","name":"SDM 230 Fréquence","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"70","quantity":"2","rate":"10","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"40f20c7f.13a934","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":155.00001525878906,"y":322,"wires":[["6179779.d820a08"],[]]},{"id":"20c62d66.26be7a","type":"modbus-read","z":"f89db908.ccfc58","name":"SDM 230 Consommation","topic":"","showStatusActivities":true,"logIOActivities":false,"showErrors":true,"unitid":"30","dataType":"InputRegister","adr":"342","quantity":"2","rate":"10","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"40f20c7f.13a934","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":175.00001525878906,"y":381,"wires":[["1c155c0e.286224","e77afdf2.850098"],[]]},{"id":"1495c496.dd7023","type":"ui_text","z":"f89db908.ccfc58","group":"2c2fdfeb.fe41e","order":1,"width":0,"height":0,"name":"","label":"Tension","format":"{{msg.payload}} V","layout":"row-spread","x":576,"y":150,"wires":[]},{"id":"a7e4a3d5.23f35","type":"ui_text","z":"f89db908.ccfc58","group":"2c2fdfeb.fe41e","order":2,"width":0,"height":0,"name":"","label":"Courant","format":"{{msg.payload}} A","layout":"row-spread","x":577,"y":205,"wires":[]},{"id":"d92d71c3.167b9","type":"ui_text","z":"f89db908.ccfc58","group":"d9aabaeb.cf26","order":1,"width":0,"height":0,"name":"","label":"Puissance","format":"{{msg.payload}} W","layout":"row-spread","x":593,"y":260,"wires":[]},{"id":"d76382a3.74ea78","type":"ui_text","z":"f89db908.ccfc58","group":"2c2fdfeb.fe41e","order":3,"width":0,"height":0,"name":"","label":"Fréquence","format":"{{msg.payload}} Hz","layout":"row-spread","x":593,"y":316,"wires":[]},{"id":"9cd2baf1.c3108","type":"ui_text","z":"f89db908.ccfc58","group":"d9aabaeb.cf26","order":2,"width":0,"height":0,"name":"","label":"Consommation","format":"{{msg.payload}} kWh","layout":"row-spread","x":605,"y":376,"wires":[]},{"id":"36fe9de8.3ed38a","type":"function","z":"f89db908.ccfc58","name":"Build object","func":"watch_topic = "energy";\nvar output = {};\n\ncontext.set(msg.topic,msg.payload);\n\nif (context.get("voltage")!==undefined) {\n    output.voltage = context.get("voltage");\n}\nif (context.get("current")!==undefined) {\n    output.current = context.get("current");\n}\nif (context.get("power")!==undefined) {\n    output.power = context.get("power");\n}\nif (context.get("frequency")!==undefined) {\n    output.frequency = context.get("frequency");\n}\nif (context.get("energy")!==undefined) {\n    output.energy = context.get("energy");\n}\nmsg.payload = output;\n\nif (msg.topic===watch_topic) {\n    msg.topic = "sdm230";\n    return msg;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","x":766,"y":233,"wires":[["34123b57.819dec","a1b03462.1d9bf"]]},{"id":"34123b57.819dec","type":"debug","z":"f89db908.ccfc58","name":"","active":false,"console":"false","complete":"true","x":930,"y":233,"wires":[]},{"id":"e0b31d56.021408","type":"function","z":"f89db908.ccfc58","name":"Diagnostic input message structure","func":"// setting a global flag that the solar system is down\n\nmsg.payload = "High voltage warning: " + msg.payload.voltage +" V";\nmsg.system = 1; // System id, use 1 for Dummy\n//msg.state = 1; // specify if the message is to change system status\nmsg.severity = 1; // 0: information, 1: warning, 2: error\nmsg.email = false; // if separate email should be sent\n//msg.emailtext = "Clean up step of the SAIA log backup has failed";\nreturn msg;","outputs":1,"noerr":0,"x":1481,"y":121,"wires":[["802fdccc.5b6de8"]]},{"id":"802fdccc.5b6de8","type":"link out","z":"f89db908.ccfc58","name":"","links":["13e089a7.73cb46"],"x":1688,"y":143,"wires":[]},{"id":"f511148e.b4aec","type":"switch","z":"f89db908.ccfc58","name":"Voltage check","property":"payload.voltage_check","propertyType":"msg","rules":[{"t":"eq","v":"high","vt":"str"},{"t":"eq","v":"low","vt":"str"}],"checkall":"true","outputs":2,"x":1189,"y":145,"wires":[["e0b31d56.021408"],["4e2e71eb.c57fd"]]},{"id":"4e2e71eb.c57fd","type":"function","z":"f89db908.ccfc58","name":"Diagnostic input message structure","func":"// setting a global flag that the solar system is down\n\nmsg.payload = "Low voltage warning: " + msg.payload.voltage +" V";\nmsg.system = 1; // System id, use 1 for Dummy\n//msg.state = 1; // specify if the message is to change system status\nmsg.severity = 1; // 0: information, 1: warning, 2: error\nmsg.email = false; // if separate email should be sent\n//msg.emailtext = "Clean up step of the SAIA log backup has failed";\nreturn msg;","outputs":1,"noerr":0,"x":1481,"y":166,"wires":[["802fdccc.5b6de8"]]},{"id":"a1b03462.1d9bf","type":"function","z":"f89db908.ccfc58","name":"Voltage check","func":"var high = 250.0;\nvar low = 220.0;\n\nif (msg.payload.voltage > high) {\n    msg.payload.voltage_check = "high";\n}\nif (msg.payload.voltage < low) {\n    msg.payload.voltage_check = "low";\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":976,"y":146,"wires":[["f511148e.b4aec"]]},{"id":"5fc9ee63.923f1","type":"function","z":"f89db908.ccfc58","name":"reset on HighHigh","func":"if("high high level reached" === msg.state) {\n    msg.resetQueue = true;\n    return msg;\n}\n","outputs":1,"noerr":0,"x":416.00000762939453,"y":651.2500247955322,"wires":[["62bde4e.246551c","4edc5371.db5b5c"]]},{"id":"e91de07.8cdba2","type":"catch","z":"f89db908.ccfc58","name":"Catch queue errors","scope":["62bde4e.246551c"],"x":178.00000762939453,"y":651.2500247955322,"wires":[["5fc9ee63.923f1"]]},{"id":"5c3bd69d.46156","type":"comment","z":"f89db908.ccfc58","name":"Error handling","info":"","x":144.00000762939453,"y":606.2500247955322,"wires":[]},{"id":"4edc5371.db5b5c","type":"function","z":"f89db908.ccfc58","name":"Diagnostic input message structure","func":"// setting a global flag that the solar system is down\n\nmsg.payload = "SDM230 modbus queue reached high level, resetting. (" + msg.state + ")";\nmsg.system = 4; // System id, use 1 for Dummy\n//msg.state = 1; // specify if the message is to change system status\nmsg.severity = 1; // 0: information, 1: warning, 2: error\nmsg.email = false; // if separate email should be sent\n//msg.emailtext = "Clean up step of the SAIA log backup has failed";\nreturn msg;","outputs":1,"noerr":0,"x":716.0000076293945,"y":650.2500247955322,"wires":[["c50380ac.51ffb8"]]},{"id":"c50380ac.51ffb8","type":"link out","z":"f89db908.ccfc58","name":"","links":["13e089a7.73cb46"],"x":902.0000076293945,"y":650.2500247955322,"wires":[]},{"id":"30e686ee.3e8a42","type":"catch","z":"f89db908.ccfc58","name":"Modbus read errors","scope":["ce99604f.7fd198","20c62d66.26be7a","bae1c9a1.1ea118","ac524aef.e3dc3","75c0e766.f23c68","ebbc4482.1edd58"],"x":178.00000762939453,"y":694.2500247955322,"wires":[["d0359fe9.a43e88"]]},{"id":"d0359fe9.a43e88","type":"function","z":"f89db908.ccfc58","name":"Diagnostic input message structure","func":"// setting a global flag that the solar system is down\n\nmsg.payload = "SDM230 modbus error: " + msg.error.message;\nmsg.system = 4; // System id, use 1 for Dummy\n//msg.state = 1; // specify if the message is to change system status\nmsg.severity = 1; // 0: information, 1: warning, 2: error\nmsg.email = false; // if separate email should be sent\n//msg.emailtext = "Clean up step of the SAIA log backup has failed";\nreturn msg;","outputs":1,"noerr":0,"x":467.00000762939453,"y":694.2500247955322,"wires":[["2742f67e.2c0132"]]},{"id":"2742f67e.2c0132","type":"link out","z":"f89db908.ccfc58","name":"","links":[],"x":653.0000076293945,"y":694.2500247955322,"wires":[]},{"id":"5531e192.8a2948","type":"debug","z":"f89db908.ccfc58","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":710,"y":520,"wires":[]},{"id":"da547ef3.a86e98","type":"inject","z":"f89db908.ccfc58","name":"Check","props":[{"p":"payload","v":"","vt":"date"},{"p":"topic","v":"timecheck","vt":"string"}],"repeat":"1","crontab":"","once":false,"topic":"timecheck","payload":"","payloadType":"date","x":160,"y":440,"wires":[["e77afdf2.850098"]]},{"id":"bd6193e4.61b32","type":"switch","z":"f89db908.ccfc58","name":"Update diag?","property":"warning","propertyType":"msg","rules":[{"t":"true"}],"checkall":"true","outputs":1,"x":609,"y":440,"wires":[["6765dc93.bbff4c"]]},{"id":"6765dc93.bbff4c","type":"link out","z":"f89db908.ccfc58","name":"","links":["13e089a7.73cb46"],"x":728.3015727996826,"y":439.57140350341797,"wires":[]},{"id":"9837f252.bb0e","type":"ui_gauge","z":"f89db908.ccfc58","name":"Puissance","group":"ab711794.074a9","order":1,"width":0,"height":0,"gtype":"gage","title":"Puissance","label":"W","format":"{{value}}","min":0,"max":"13000","colors":["#00b500","#e6b100","#ca3838"],"seg1":"7000","seg2":"10000","x":590,"y":80,"wires":[]},{"id":"e77afdf2.850098","type":"function","z":"f89db908.ccfc58","name":"Health check","func":"var devicename = "sdm230"; // Device name used for context variable\nvar system_id = 4; // System id number for diagnostic update\nvar online_threshold = 10; // Seconds between updates under which the device is considered online\nvar offline_threshold = 30; // Seconds between updates above which the device is considered offline\n\nvar temp = context.get(devicename+"_update");\nvar current = new Date();\nmsg.payload = "No data";\nmsg.warning = false;\nif (msg.topic!=="timecheck") {\n    // Do not update the context if it is triggered by the check inject node\n    context.set(devicename+"_update",current);\n}\nif (temp===undefined) {\n    // this will be the case when node-red is booting up or redeployed\n    context.set(devicename+"_update",current);\n}\n\nif (temp!==undefined) {\n    current = current - temp;\n    current = Math.floor(current/1000);\n    var minute = Math.floor(current/60);\n    var hour = Math.floor(minute/60);\n    var day = Math.floor(hour/24);\n    if (current>24*60*60) {\n        msg.payload = "Last update " + day + " days, " + hour%24 + " hours, " + minute%60 + " minutes, " + current%60 + " seconds ago";\n    } else if (current>60*60) {\n        msg.payload = "Last update " + hour%24 + " hours, " + minute%60 + " minutes, " + current%60 + " seconds ago";\n    } else if (current>60) {\n        msg.payload = "Last update " + minute%60 + " minutes, " + current%60 + " seconds ago";\n    } else {\n        msg.payload = "Last update " + current%60 + " seconds ago";\n    }\n\n    if (context.get(devicename+"_state")!==1) {\n        if (current<online_threshold) {\n            msg.payload = "SDM230 is now online";\n            msg.system = system_id; // System id, use 1 for Dummy\n            msg.state = 1; // specify if the message is to change system status\n            msg.severity = 0; // 0: information, 1: warning, 2: error\n            //msg.email = true; // if separate email should be sent\n            //msg.emailtext = ""; this a long text which goes into the email  \n            msg.warning = true;\n            context.set(devicename+"_state",1);\n        }\n    } else {\n        if (current>offline_threshold) {\n            msg.payload = "SDM230 is not transmitting";\n            msg.system = system_id; // System id, use 1 for Dummy\n            msg.state = 99; // specify if the message is to change system status\n            msg.severity = 2; // 0: information, 1: warning, 2: error\n            //msg.email = true; // if separate email should be sent\n            //msg.emailtext = ""; this a long text which goes into the email            \n            msg.warning = true;\n            context.set(devicename+"_state",99);\n        }\n    }\n    \n    \n}\n\nnode.status({fill:"blue",shape:"ring",text:msg.payload});\n\nreturn msg;","outputs":1,"noerr":0,"x":414,"y":440,"wires":[["bd6193e4.61b32"]]},{"id":"1b079b9e.95ab2c","type":"debug","z":"f89db908.ccfc58","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":400,"y":100,"wires":[]},{"id":"40f20c7f.13a934","type":"modbus-client","name":"Serial_9600_8_N_1","clienttype":"serial","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"tcpHost":"127.0.0.1","tcpPort":"502","tcpType":"DEFAULT","serialPort":"/dev/serial0","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","unit_id":"","commandDelay":"30","clientTimeout":"2000","reconnectOnTimeout":false,"reconnectTimeout":"5000","parallelUnitIdsAllowed":false},{"id":"2c2fdfeb.fe41e","type":"ui_group","name":"UIF","tab":"72b1a4dc.4f488c","order":2,"disp":true,"width":6,"collapse":false},{"id":"d9aabaeb.cf26","type":"ui_group","name":"PC","tab":"72b1a4dc.4f488c","order":3,"disp":true,"width":6,"collapse":false},{"id":"ab711794.074a9","type":"ui_group","name":"Graphes","tab":"72b1a4dc.4f488c","order":1,"disp":true,"width":6,"collapse":false},{"id":"72b1a4dc.4f488c","type":"ui_tab","name":"Modbus","icon":"memory","order":15,"disabled":false,"hidden":false}]

Je vous encourage à double cliquer sur les noeuds pour voir comment ceux ci sont configurés, et notamment les noeuds de fonction juste derrière les noeuds d'acquisition, ceux ci contenant les fonctions de conversion des 4 octets récupérés vers un nombre flottant.

Ces valeurs sont ensuites affichées dans le Dashboard grâce aux noeuds en bleu, mais aussi traitées pour générer une exception si la tension d'entrée n'est pas dans une plage définie (Voltage Check).

Il y a encore d'autres fonctions comme une vérification de bonne santé (health Check), ou un traitement d'erreur sur la fle d'attente que je vous laisse découvrir en double cliquant sur les noeuds pour en analyser le fonctionnement.

Simulation Modbus

Si vous ne disposez pas du SDM 230, il est possible de simuler une communication modbus avec un logiciel adequat. Pour sa mise en oeuvre il va nous falloir relier la liason série de la Raspberry avec lePC. Pour faire ces tests j'ai utilisé le convertisseur modbus ajouté sur la Raspberry à l'étape précédente, et un convertisseur USB <> 485 à 2/3 euros. Il est aussi possible de relier le PC à la Raspberry via un convertisseur USB / Série 3.3V. A noter que si on veut faire une configuration hybride avec la Raspberry, au moins un vrai périphérique et un périphérique simulé par le PC, on ne pourra utiliser que le RS485, car seul ce protocole de transport permet de mettre plusieurs prériphériques sur la même ligne de transmission.

Côté PC, il va nous falloir un logiciel. On trouve sur Sourceforge ModRsim2, qui fait très bien le boulot : https://sourceforge.net/projects/modrssim2/. Il est largement paramétrable et permet de fonctionner en RTU (série) ou TCP. Ici, c'est le mode RTU qui va nous intéresser. Afin que le logiciel retourne des valeurs valides à notre Raspberry, j'ai préparé un fichier de configuration qui va remonter des valeurs typiques de courant / tension, etc. pour un périquérique configuré à l'adresse 30.

Vous le trouverez en téléchargement ici. Après décompression et en le plaçant dans le même répertoire que le logiciel, il sera automatiquement chargé au démarrage du simulateur.

Vous noterez dans la fenetre principale du logiciel :

  • L'affichage de l'adresse du registre en hexadécimal ou en décimal (1)
  • Le format d'affichage de la donnée contenu dans le registre (2)
  • Le protocole (mettre RS232) (3)
  • L'affichage des valeurs de registre qui vont être envoyées à la Raspberry (4)

modbus1.png, déc. 2020

Pour rappel, les données que j'ai insérées dedans sont des nombres flottants encodés sur 4 octets. Voici par exemple la convestion réalisée pour une valeur de 230.1 Volts dans un convertisseur el ligne. La valeur hexadécimale résultante est 43 66 19 9A. Vous la retrouverez encodée dans le tableau précédent à l'adresse 3000000 (tension en volts).

modbus2.png, déc. 2020

A noter que j'ai ouvert un ticket auprès de l'auteur pour voir comment entrer directement des valeurs en fottant dans le simulateur. Ceclui-ci a répondu ici : https://sourceforge.net/p/modrssim2/tickets/15/, et c'est possible... Par contre, il est imératif de cocher la case "Clone" à droite pour que l'interprétation du flottant se fasse correctement. Il faut aussi que l'adresse du flottant soit un multiple de 4 ce qui n'est pas toujours le cas avec le SDM230... Du coup, sila première valeur est bien interprétée (tension), les autres ne le sont pas nécessairement, n'étant pas alignées sur un multiples de 4.

Authentification sous node red

L'authentification repose sur la section "adminAuth" du fichier de configuration (/home/pi/.node-red/settings.js). Elle est normalement commentée (// devant), et donc inactive. On voir ci dessous qu'on dispose d'un identfiant, d'un mot de passe chiffré, et de la définition d'un niveau de permissions. Ceci peut par exemple permettre de mettre un utilisateur en lecture seule (ici, george).

//adminAuth: {
//    type: "credentials",
//    users: [
//        {
//            username: "admin",
//            password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.",
//            permissions: "*"
//        },
//        {
//            username: "george",
//            password: "$2b$08$wuAqPiKJlVN27eF5qJp.RuQYuy6ZYONW7a/UWYxDTtwKFCdB8F19y",
//            permissions: "read"
//        }
//    ]
//}

Pour activer cette section, il nous faut d'abord générer un mot de passe. Node Red dispose d'une commande pour cela, qui va vous demander un mot de passe et vous générer le hash :

pi@pizero:~/.node-red $ node-red admin hash-pw
Password:
$2b$08$PW2YaBxUoyGEi5CQ8Wef/OsUMYjukOyMGy1xbA.hX0QjsWNqE03wK
//

Il va ensuite nous suffire de décommenter la section, et d'injecter le mot de passe ainsi généré :

   adminAuth: {
       type: "credentials",
       users: [{
           username: "admin",
           password: "$2b$08$PW2YaBxUoyGEi5CQ8Wef/OsUMYjukOyMGy1xbA.hX0QjsWNqE03wK",
           permissions: "*"
       }]
   },

Pour activer la configuration, il suffit de redémarrer node red :
/// 
systemctl restart nodered.service

Ensuite, tester la configuration en se connectant sur node-red. Un bannière de login devrait apparaitre au lieu de la page d'accueil. Si cela ne fonctionne pas, il peut être intéressant de consulter les journaux de node-red à l'aide de la commande node-red-log A noter que NodeRed dispose d'autres méthodes d'authentification comme OAUTH (Twitter, Github). Se référer à la documentation ( https://nodered.org/docs/user-guide/runtime/securing-node-red pour plus d'informations. Il est même possible de mettre en place ses propres méthodes d'authentification (par exemple pou rse coupler sur un annuaire LDAP... Mais c'est un bien autre sujet....

Https sur node red

Bien que nous disposions d'une authentification, notre installation n'est pas sécurités pour autant. En effet, il est assez facile de piéger les échanges entre un navigateur et un serveur en http, qui ont le mauvais gout d'échanger en clair le login et le mot de passe. Pas bon du tout... C'est d'ailleurs pour cela que les acteurs de l'internet, Google en tête, exigent maintenant que les sites soient sécurisés en SSL. Ceci n'a d'intérêt que si vous voulez rendre accessible votre installation NodeRed de l'extérieur de votre réseau local.

Il convient donc de mettre en œuvre le chiffrement SSL pour éviter que ces échanges ne soient espionnés. A ce stade, nous avons 3 possibilités :

  • Utiliser un certificat auto signé. Gratuit, mais va générer une alerte au niveau du navigateur (que l'on peut acquitter). Cela peut être suffisant. C'est d'ailleurs la seule solution possible lorsque l'on est sur un réseau local et que le serveur node-red n'est pas joignable de l'extérieur.
  • Utiliser un certificat SSL payant, émanant d'une autorité de certification. C'est payant, avec des tarifs très variables (à partir de 70 euros par an). Plus facile à entretenir, c'est le cout qui est problématique. Il faut aussi que l'installation node-red soit associée à un nom de domaine qui portera ce certificat.
  • Utiliser un certificat SSL gratuit "Lets Encrypt". C'est aussi gratuit, mais pas très simple à installer et à tester. Il nous faudra également un nom de domaine pour porter ce certificat. Bien que ceux ci disposent d'une procédure de renouvellement qui peut être automatisée, le certificat ne durant que 3 mois.

Nous allons utiliser la 3e technique. Cela suppose que vous ayez paramétré votre routeur domestique pour rediriger le port 443 (https) et le port 80 sur votre Raspberry dans l'interface de gestion de votre routeur. Ceci se trouve généralement dans la section "NAT" ou "Redirections".

Par ailleurs, la plupart des routeurs disposent de fonctionnalités qui permettent de rendre permanent les baux DHCP pour une adresse mac donnée. Ce sera indispensable car sinon votre Raspberry pi risque de changer d'adresse ip sur le réseau local, et donc de ne plus correspondre aux règles de redirectionsque vous aurez mises en place. Je vous recommande de suivre les tutos ou la doc correspondants à votre routeur.

Chez Free, par exemple, tout ceci passe par l'interface de gestion sur http://mafreebox.freebox.fr :

  • Paramètres de la freebox / Gestion des ports pour gérer les redirections des ports 80 et 443
  • Paramètres de la freebox / DHCP / Baux statique pour gérer l'affectation permanente de l'adresse ip à la raspberry.

https://discourse.nodered.org/t/node-red-ssl-using-letsencrypt-certbot/17606

Bonus: installer un LCD texte (2ligne ou 4 lignes de 20 caractères) en I2C sur Raspberry

Idée générale

L'intérêt de le raccorder en I2C est de limiter le nombre de broches de raccordement. En i2C, seules 2 broches (en plus des alimentations) sont nécessaires : I2C Clock et i2C Data. Il faut par conte disposer d'un adaptateur LCD <> I2C, souvent nommé "LCD I2C Backpack", comme celui d'Adafruit ( https://www.adafruit.com/product/292 ). Sur Aliexpress (https://fr.aliexpress.com/af/i2c-lcdadapter.html) ou Banggood ( https://www.banggood.com/fr/search/adapter-i2c.html ) on trouver ces adaptateurs en version MCP23017 ou PCF8574 pour 2/3 euros.

Tous reposent sur la même idée mais n'utilisent pas nécessairement le même composant pour la mise en œuvre. Il s'agit d'ajouter une extension de ports I2C, permettant d'ajouter des entrées sorties, dont celles ci sont raccordées au LCD afin d'économiser les i/o internes de la Raspberry. Trois composants différents sont fréquemment utilisés :

  • MCP23008 (Adafruit)
  • MCP230017 (certains chinois)
  • PCF8574 (la plupart des chinois)

Câblage

Pour les tests suivants, j'ai utilisé l'adaptateur d'Adafruit, basé sur un MCP 23008. Mais le programme est aisément adaptable aux autres solutions. Le raccordement de l'adaptateur I2C/LCD s'effectue comme suit :

|---------------------------------|
| LCD Adapter | PI ZERO           |
|---------------------------------|
| GND         | Broche 2 (5V)     |
| 5V          | Broche 6 (Ground) |
| DAT         | Broche 3 (SDA1)   |
| CLK         | Broche 5 (SCL1)   |
|---------------------------------|

A titre indicatif, voici les brochages du connecteur d'extension de la Raspberry :

pi_zero_pinout_zoom.png, déc. 2020

Nous allons ensuite pouvoir vérifier que notre adaptateur LCD est bien détecté sur le Bus I2C à l'aide de la commande i2cdetect :

pi@pizero:~ $ i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Ici, nous voyons bien le contrôleur MCP23008 en 0X20.

Logiciel

Vient le temps d'installer des éléments logiciels pour nous faciliter la vie dans la mise en œuvre du LCD en ligne de commande. Nous allons utiliser une bibliothèque Python 3/2 qui permet le pilotage de LCD textes basés sur des contrôleurs Hitachi HD44780 (c'est le contrôleur utilisé sur ces afficheurs texte). Cette bibliothèque support à la fois le pilotage parallèle (consommant un grand nombre d'entrées sorties de la Raspberry) et aussi bien que via une extension de port I²C port (comme le PCF857, le MCP23008 ou le MCP230017 ).

L'installation est simple et se fait en ligne de commande:

# Voir https://github.com/dbrgn/RPLCD#documentation
sudo apt install python-smbus
sudo pip install gpiozero
sudo pip install RPLCD
sudo pip install psutil

Nous allons maintenant pouvoir tester. On doit préciser sur la lige de commande l'adresse précédemment détectée, ainsi que le type du port expander utilisé (MCP23008 / MCP23017 / PCF8574 ) :

rplcd-tests i2c testsuite  expander=MCP23008 addr=0x20 port=1 cols=20 rows=4 charmap=A00 gpio_bank=A

Plusieurs message devraient d'afficher sur l'écran. Une fois le dialogue entre la pi et le LCD vérifié, il est temps de développer un programme. Pour ce faire la doc est d'un grand secours : https://rplcd.readthedocs.io/en/latest/

Voici un petit programme de test que j'a réalisé, qui va nous afficher :

  • La date et l'heure
  • La température du processeur
  • L'adresse ip de la carte
  • La quantité de ram totale (T), Utiisée (U) et Libre (L)
  • Même chose pour l'espace de stockage sur la carte SD

Le résultat :

lcd.png, déc. 2020

Et le source du programme de test :

#!/usr/bin/python
# -*- coding: utf8 -*-

"""
Bibliothèques à installer :
 - pip install rplcd  https://github.com/dbrgn/RPLCD (Doc sur https://rplcd.readthedocs.io/en/latest/ )
 - pip install gpiozero
 - pip install psutil
"""

# Affichages sur lcd
from RPLCD.i2c import CharLCD
# température processeur
from gpiozero import CPUTemperature
# Pour afficher l'heure
import time
# Pour recuperer l'adresse IP
import socket
# Pour capacité disque
import psutil

# Recuprere l'adresse IP
def get_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        # n'a pas besoin d'être joignable
        s.connect(('10.255.255.255', 1))
        IP = s.getsockname()[0]
    except Exception:
        IP = '127.0.0.1'
    finally:
        s.close()
    return IP

# Définit des caractères personnalisés
def lcd_chars():
    smiley = (
        0b00000,
        0b01010,
        0b01010,
        0b00000,
        0b10001,
        0b10001,
        0b01110,
        0b00000,
    )
    degre = (
        0b01000,
        0b10100,
        0b01000,
        0b00011,
        0b00100,
        0b00100,
        0b00011,
        0b00000,
    )
    lcd.create_char(0, smiley)
    lcd.create_char(1, degre)
    return

# Init
# Init LCD
lcd = CharLCD(i2c_expander='MCP23008', address=0x20, port=1,
            cols=20, rows=4, dotsize=8,
            charmap='A00',
            auto_linebreaks=True)
lcd_chars()
IP = get_ip()
cpu = CPUTemperature()
hdd = psutil.disk_usage('/')
ram = psutil.virtual_memory()

lcd.clear();
# Ligne 1
lcd.cursor_pos = (0, 0)
lcd.write_string(time.strftime("%d/%m/%Y  %H:%M:%S"))

# Ligne 2
lcd.cursor_pos = (1, 0)
lcd.write_string(IP)
lcd.cursor_pos = (1,14)
lcd.write_string("|%2.1f\x01" % (cpu.temperature))

# Ligne 3
lcd.cursor_pos = (2, 0)
lcd.write_string("T:%3.1fG U:%3.1fG F:%3.1fG" % (float(hdd.total) / (2.0**30), float(hdd.used) / (2.0**30),float(hdd.free) / (2.**30) ))

# Ligne 4:w
lcd.cursor_pos = (3, 0)
lcd.write_string("T:%3.0fM U:%3.0fM F:%3.0fM" % (float(ram.total) / (2.0**20), float(ram.used) / (2.0**20),float(ram.free) / (2.**20) ))

# Si on veut préserver le backlight et ne l'allumer que quelques secondes toutes les minutes
# décommenter les lignes suivantes
#time.sleep(5)
#lcd.backlight_enabled = False
lcd.close()

Quelques commandes utilises sous SSH

Arrêter le système (ne jamais débrancher!!!! arrêter proprement.) :

sudo shutdown -h now

Redémarrer le système

sudo shutdown -r now

Afficher les journaux de node-red (quitter avec CTRL+ C) :

node-red-log

Mettre à jour le système :

sudo apt-get update
sudo apt-get upgrade

Quitter ssh :

 
exit

Il pourra être aisément appelé toutes les minutes en l'installant dans une tache planfiée (cron) à l'aide d'un "crontab -e", et en ajoutant la ligne :

* * * * * /home/pi/lcd.py >> /home/pi/log.txt 2>&1