I. Présentation du système▲
Voici pour commencer l’architecture du prototype de notre système :
Au sein du réseau local en WiFi, on utilisera :
- une carte Arduino Uno R4 WiFi : la carte Arduino sera le contrôleur autonome pour l’acquisition de mesures et la transmission des données avec le protocole MQTT. Les données mesurées seront la température et l’humidité relative grâce à un module Si7021 d’Adafruit ;
- le nano-ordinateur Raspberry Pi 3B+, 4 ou 5 : il comprend le broker MQTT (Mosquitto) qui centralise et relaie les messages publiés par le (ou les) nœuds de capteurs. La Raspberry Pi s’occupe donc de toute la phase d’ingestion des données : réception et traitement des messages MQTT, stockage des données horodatées (avec InfluxDB, spécialisé dans le stockage des séries temporelles), et finalement, présentation des données dans un tableau de bord pour la visualisation et la surveillance avec un navigateur Internet (Grafana). Dans cette phase d’ingestion, le flux des données est dirigé grâce à Node-RED, une plateforme low code avec de nombreux services et protocoles pour l’Internet des Objets (IdO).
Tous ces outils open source seront disponibles sous la forme de conteneurs Docker fournis par la pile IOTstack et tournant de concert grâce à Docker Compose. Toutefois, ne vous inquiétez pas si vous êtes peu familiers avec la conteneurisation sous Docker. Pour nos premiers pas, la pile IOTstack est proposée avec un utilitaire bien pratique et une interface textuelle pour les opérations essentielles d’administration des conteneurs.
II. Acquisition et transmission des données environnementales▲
II-A. Le nœud de capteurs avec Arduino▲
Un microcontrôleur comme celui de la carte Arduino ne dispose pas d’une puissance de traitement et de capacité de stockage suffisantes pour traiter localement une grande quantité de données comme la Raspberry Pi. Mais il peut se charger, à faible coût, de l’acquisition des données et utiliser un système de communication radio consommant peu de bande passante et d’énergie pour transmettre les données à un serveur central.
La carte Arduino, ses capteurs, le système de transmission radio et l’alimentation de l’ensemble constituent un système autonome communément appelé « nœud de capteurs ».
Le scénario à envisager pour une installation domotique plus complète serait de disposer de plusieurs nœuds indépendants dans les différentes pièces de vie de votre maison avec des capteurs d’environnement (température, humidité, taux de CO2, de radon, de particules, etc.). Chaque nœud du réseau, à portée de WiFi, transmet ses données encapsulées dans des paquets TCP/IP à une passerelle centrale pour la transformation et le traitement des données.
La carte Arduino Uno R4 WiFi n’est pas forcément la meilleure candidate pour le contrôleur du nœud de capteurs (dans le sens où l’on peut trouver satisfaction parmi des cartes moins chères, moins encombrantes et moins énergivores), mais les « arduinautes » savent aussi à quel point l’environnement Arduino permet un développement rapide de prototypes. Avec le WiFi, sa matrice de LED intégrée pour informer l’utilisateur de l’état du nœud et ses nombreuses bibliothèques mises à disposition par la communauté, le développement sur cette carte sera même très rapide…
II-B. Le protocole MQTT▲
MQTT (Message Queuing Telemetry Transport) est un protocole open source particulièrement prisé dans le transport de messages pour l’Internet des Objets (IdO). Dans le modèle TCP/IP, on le retrouve au sommet de la pile au niveau de la couche Application :
Ce protocole léger conçu pour des réseaux à bande passante limitée fonctionne suivant un modèle de publication-abonnement (publish-suscribe).
Dans cette architecture, vous avez les clients qui publient des messages dans une rubrique (un topic), par exemple \home\bedroom pour les données environnementales de la chambre, et les clients qui s’abonnent au topic et recevront les données les concernant. Le dispositif central de l’architecture est le broker (courtier) qui relaie les messages publiés aux abonnés.
Le broker peut être local ou situé dans le nuage. Dans ce dernier cas, certains brokers sont même disponibles gratuitement et en accès public pour découvrir le fonctionnement du protocole et faire des tests (par exemple : HiveMQ Public Broker, Eclipse Mosquitto, et bien d’autres encore).
Pour notre prototype, le broker sera local et embarqué dans la carte Raspberry Pi. Des applications clientes également installées sur la carte pourront s’y connecter pour lire les messages :
Pour compléter cette découverte du protocole MQTT, on évoquera aussi les niveaux de qualité de service (ou QoS pour Quality Of Service). Plus la QoS est élevée, et plus la transmission des messages est fiable, mais en contrepartie, la consommation de bande passante sera élevée.
Par exemple, pour un message publié avec une QoS égale à zéro, le message ne sera envoyé qu’une seule fois, sans attendre d’accusé de réception (Fire and forget). Avec une QoS égale au niveau maximum (niveau 2), une connexion est d’abord établie en quatre étapes (handshake) avant l’envoi du message. À ce niveau de qualité, les deux parties, émetteur et récepteur, sont sûres que le message a été transmis, une seule fois, et avec un accusé de réception.
On vous renvoie au site officiel pour approfondir le protocole : MQTT: The Standard for IoT Messaging
II-C. Arduino et MQTT▲
Les codes Arduino des paragraphes qui suivent ne sont que des extraits pour montrer les principales étapes du programme. L’archive du code complet est donnée au chapitre Le programme Arduino completLe programme Arduino complet.
II-C-1. Branchement du capteur et installation des bibliothèques▲
Pour communiquer suivant le protocole MQTT, on pourra installer la bibliothèque ArduinoMqttClient qui se trouve parmi les bibliothèques officielles de la plateforme Arduino. Dans l’EDI Arduino officiel, vous pourrez donc la trouver facilement et l’installer depuis le Gestionnaire de bibliothèque.
Pour le module Si7021 (capteur de température et humidité relative), Adafruit qui est partenaire d’Arduino propose aussi sa propre bibliothèque : Adafruit Si7021Library.
Quand vous commandez ce module Si7021 (sur le site d’Adafruit ou sur celui d’un revendeur plus proche de chez soi), pensez à vérifier si le câble avec les connecteurs Qwiic est fourni avec. Il faudra le commander à part sinon. Sauf si vous préférez souder des broches, le module et la carte Arduino Uno R4 WiFi intègrent effectivement ce connecteur bien pratique en phase de prototypage. Voir Arduino UNO R4 WiFi Qwiic Connector.
II-C-2. Lire la valeur de température et d’humidité▲
Voici un programme minimum pour récupérer en boucle, toutes les cinq secondes, les valeurs de température (en °C) et d’humidité relative (en %) du module Si7021 :
#include
<Adafruit_Si7021.h>
// https://github.com/adafruit/Adafruit_Si7021
Adafruit_Si7021 sensor =
Adafruit_Si7021(&
Wire1);
void
setup() {
Serial
.begin(115200
);
Serial
.println(F("Si7021 test!"
));
if
(!
sensor.begin()) {
Serial
.println(F("Did not find Si7021 sensor!"
));
while
(true
)
;
}
}
void
loop() {
float
temp =
sensor.readTemperature(); // return NAN if the sensor is disconnected
float
hum =
sensor.readHumidity(); // return NAN if the sensor is disconnected
if
(!
(isnan(temp) ||
isnan(hum))) {
Serial
.print(F("Temperature = "
));
Serial
.println(temp, 2
);
Serial
.print(F("Humidity = "
));
Serial
.println(hum, 2
);
Serial
.println();
}
else
{
Serial
.println(F("Failed to read from Si7021 sensor!"
));
}
delay
(5000
);
}
Ce code n’amène pas de commentaires particuliers. On instancie le capteur, et s’il est détecté sur le port I2C du connecteur Qwiic, on lit la température et humidité relative en boucle.
Dans les exemples de la bibliothèque d’Adafruit, vous trouverez l’instanciation du capteur Si7021 suivante :
Adafruit_Si7021 sensor =
Adafruit_Si7021();
Il faudra la compléter de la façon suivante pour utiliser le deuxième port I2C de la carte (Wire1), celui qui est dirigé vers le connecteur Qwiic de la carte Arduino Uno R4 WiFi :
Adafruit_Si7021 sensor =
Adafruit_Si7021(&
Wire1);
II-C-3. Publier des messages formatés en JSON avec le protocole MQTT▲
Une fois la carte connectée au réseau WiFi (fonction init_wifi()), il faut se connecter au broker sur la Raspberry Pi grâce à une fonction init_mqtt() :
#include
<WiFiS3.h>
// WiFi library for Arduino Uno R4 WiFi
#include
<ArduinoMqttClient.h>
// https://github.com/arduino-libraries/ArduinoMqttClient
WiFiClient wifiClient;
MqttClient mqttClient(wifiClient);
// …
void
init_wifi(const
char
*
ssid, const
char
*
pass) {
Serial
.print(F("Attempting to connect to WPA SSID: "
));
Serial
.println(ssid);
while
(WiFi.begin(ssid, pass) !=
WL_CONNECTED) {
// failed, retry
Serial
.print("."
);
delay
(5000
);
}
int
init_mqtt(const
char
*
broker, int
port) {
Serial
.print(F("Attempting to connect to the MQTT broker: "
));
Serial
.print(broker);
Serial
.print(F(":"
));
Serial
.println(port);
if
(!
mqttClient.connect(broker, port)) {
Serial
.print(F("MQTT connection failed! Error code = "
));
Serial
.println(mqttClient.connectError());
return
0
;
}
Serial
.println(F("You're connected to the MQTT broker!"
));
Serial
.println();
return
1
;
}
Une fois la température et l’humidité relative relevées, on prépare un message au format JSON sous la forme :
{
"temperature"
:
23
.
2541879
,
"humidity"
:
48
.
65128745
}
,
grâce à la bibliothèque ArduinoJson fournie par Benoît Blanchon, à installer aussi depuis le Gestionnaire de bibliothèque de l’EDI Arduino.
// ...
#include
<ArduinoJson.h>
// https://github.com/bblanchon/ArduinoJson
// ...
void
setup() {
// ...
}
void
loop() {
// ...
// Allocate the JSON document
JsonDocument doc;
// Add values in the document
doc["temperature"
] =
temp;
doc["humidity"
] =
hum;
// Generate the minified JSON
char
message[60
];
serializeJson(doc, message);
// ...
}
Il reste à envoyer le message JSON grâce à une fonction publish_mqtt() :
// ...
int
publish_mqtt(const
char
*
topic, char
*
message) {
mqttClient.beginMessage(topic);
mqttClient.print(message);
return
mqttClient.endMessage();
}
II-C-4. La matrice de LED▲
La nouvelle carte Arduino Uno R4 WiFi intègre donc une matrice de 12x8 LED (voir [Arduino Uno R4 WiFi] Jouer avec la matrice de LED). Celle-ci nous permettra d’intégrer quelques images et animations pour donner des indications sur l’état du nœud de capteurs, notamment dans les situations suivantes (voir Using the Arduino UNO R4 WiFi LED Matrix) :
Démarrage de la carte Arduino. |
Sélectionnez
|
Le module Si7021 n’a pas été trouvé. Vérifier le branchement du module Si7021. Faire un reset de la carte. |
Sélectionnez
|
Recherche du réseau en WiFi. |
Sélectionnez
|
Connexion au WiFi et au broker MQTT réussie. |
Sélectionnez
|
Échec de la publication du message (double clignotement). Attendre le retour de la connexion ou faire un reset de la carte. |
Sélectionnez
|
Échec de l’acquisition des mesures du module Si7021. Vérifier le câble avec les connecteurs Qwiic, et l’état du module Si7021. Faire un reset de la carte. |
Sélectionnez
|
Acquisition des mesures et publication du message MQTT réussies. |
Sélectionnez
|
Voici une petite vidéo de démonstration. Dans les premiers instants, le module Si7021 n’est pas branché :
Cliquez pour lire la vidéo
II-C-5. Le programme Arduino complet▲
Lien vers l’archive du programme Arduino : si7021-mqtt-v2.zip
si7021_mqtt_v2
|
+- si7021_mqtt_v2.ino
+- arduino_secrets.h
+- mqtt_broker.h
+- mqtt_publish.h
+- mqtt_publish.cpp
Avant de téléverser le programme, il faudra renseigner les paramètres suivants :
- SSID et mot de passe pour accéder au réseau WiFi :
#define SECRET_SSID
"Your SSID"
#define SECRET_PASS
"Your password"
- L’URL ou l’adresse IP du broker, le port du broker MQTT (1883 par défaut, sans chiffrement des messages, ni authentification), le topic de publication des messages et des données sur la fréquence de publication des messages :
#define MQTT_BROKER
"192.168.0.40"
//#define MQTT_BROKER "test.mosquitto.org"
#define MQTT_PORT (1883)
#define MQTT_TOPIC
"/home/bedroom/unor4wifi-01/si7021"
// Smaller time interval in milliseconds
#define MQTT_PUBLISH_MIN_INTERVAL (60 * 1000)
// largest time interval in milliseconds
#define MQTT_PUBLISH_MAX_INTERVAL (1200 * 1000)
// Minimum temperature change (°C)
#define MQTT_PUBLISH_MIN_TEMP_VAR (0.5)
// Minimum humidity change (%RH)
#define MQTT_PUBLISH_MIN_HUM_VAR (1)
L’algorithme programmé permet de publier les mesures à intervalles réguliers (ici, toutes les 20 min au moins, soit 1200 s), mais aussi d’accélérer la fréquence des publications si les variations de température ou d’humidité sont importantes (ici, le système peut publier toutes les 60 s si la variation de température est supérieure à 0.5 °C ou si la variation d’humidité relative est supérieure à 1% par rapport à la publication précédente). Le but est d’éviter la saturation du réseau avec de nouvelles données alors que les mesures sont jugées relativement stables, mais de ne pas manquer non plus les variations rapides et importantes si elles se produisent.
Si la carte Arduino est reliée au port USB de votre PC, vous pouvez suivre les étapes de fonctionnement dans le Terminal Série (115 200 bauds) :
Si7021 test!
Attempting to connect to WPA SSID: XXX-YYYY
You're connected to the network
IP address: 192.168.0.25
Attempting to connect to the MQTT broker: 192.168.0.40:1883
You're connected to the MQTT broker!
{
"temperature": 24.67567253,
"humidity": 58.25094604
}
{
"temperature": 24.64349937,
"humidity": 58.11361694
}
Mais à cet instant du tutoriel, vous n’avez sans doute pas préparé de broker…
II-C-6. Test avec un broker public▲
En attendant de disposer d’un broker local installé sur votre Raspberry Pi, vous pouvez relayer vos messages publiés en passant par un broker public sur Internet, par exemple celui de Mosquitto. Pour cela, téléversez à nouveau votre programme Arduino après avoir modifié la ligne comme suit :
#define MQTT_BROKER
"test.mosquitto.org"
Sur Internet, vous trouverez une pléthore de clients MQTT gratuits sur PC ou sur mobile.
Par exemple, sur Android :
On se connecte au broker test.mosquitto.org, port 1883, version 3. On s’abonne au topic /home/bedroom/unor4wifi-01/si7021 (bouton Subscribe) et on regarde les messages reçus dans le tableau de bord (bouton Dashboard). Par défaut, QoS = 0 (Quality Of Service), au niveau le plus bas.
III. La pile IOTstack▲
Ce tutoriel a été mis en œuvre sans difficulté sur ma vieille Raspberry Pi 3B+ avec le Raspberry Pi OS 64 bits à jour installé (carte SD 16 Go). C’est la configuration minimale recommandée.
Les applications à installer sont nombreuses :
- Mosquitto, broker/client MQTT pour la publication des messages et les abonnements aux topics ;
- Node-RED pour la transformation des données ;
- InfluxDB pour le stockage en local ;
- Grafana pour la visualisation des données.
On pourrait les installer une par une depuis les dépôts officiels, mais on vous propose plutôt de créer la pile IOTstack de ces logiciels conteneurisés avec Docker.
L’intérêt de déployer les applications dans des conteneurs Docker est que toutes les configurations et dépendances nécessaires au fonctionnement d’une application sont aussi regroupées dans le conteneur. Tout ce qui se passe dans le conteneur n’affectera pas votre système d’exploitation, ce qui permet de créer des environnements de tests idéals. Vous ne risquez pas ainsi de casser des dépendances lors des installations, désinstallations ou mises à jour, et de créer des interactions néfastes au fonctionnement de vos applications (comme des incompatibilités entre versions).
Si vous n’avez plus besoin d’une application, vous détruisez son conteneur tout simplement, et cela restera sans conséquence sur le système d’exploitation et le fonctionnement des autres applications. Et si vous conservez son image, vous pourrez reconstruire le conteneur.
Les développeurs d’IOTstack proposent une série d’images de services et d’applications IdO pour Raspberry Pi déjà configurées et prêtes à être déployées dans des conteneurs. L’interface textuelle vous permettra d’administrer facilement les conteneurs de la pile, sans avoir à connaître les commandes Docker. On essaiera malgré tout quelques commandes Docker pour aller un peu plus loin.
III-A. Installation de la pile▲
La procédure d’installation est décrite dans le wiki d’IOTstack.
Dans un terminal de la Raspberry Pi, commencez par mettre à jour vos paquets :
sudo apt update
sudo apt upgrade
Éventuellement, assurez-vous de l’installation de curl :
sudo apt install curl
Puis exécutez la commande suivante :
curl -fsSL https://raw.githubusercontent.com/SensorsIot/IOTstack/master/install.sh |
bash
Cette commande est prévue pour être relancée plusieurs fois, avec un redémarrage du Pi à chaque étape. À chaque étape, suivez les instructions, redémarrez le Pi et relancez la commande jusqu’au succès complet de l’installation.
Faites alors un dernier redémarrage :
sudo reboot
Docker est maintenant installé, ce sera même le seul logiciel « installé », les autres seront isolés dans des conteneurs. Vous êtes prêts à configurer la pile…
III-B. Configurer la carte Raspberry Pi avec une adresse IP fixe▲
Mais auparavant, ce serait quand même plus pratique si vous n’aviez pas à retrouver l’adresse IP de la carte à chaque redémarrage…
Par défaut, la Raspberry Pi connectée à votre Box domestique prendra l’adresse IP allouée par le service DHCP, mais quand le bail qui fixe la durée de l’attribution est terminé, l’adresse IP peut changer. Vous pouvez configurer le serveur DHCP pour attribuer une adresse fixe en renseignant l’adresse MAC de la carte.
Pour connaître l’adresse MAC de votre interface réseau, saisissez la commande ifconfig dans un terminal de la Raspberry Pi :
wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.0.40 netmask 255.255.255.0 broadcast 192.168.0.255
inet6 fe80::7f2:4bc9:c3e8:48d5 prefixlen 64 scopeid 0x20<link>
ether b8:27:eb:83:06:78 txqueuelen 1000 (Ethernet)
RX packets 85288 bytes 59184850 (56.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 103218 bytes 69198620 (65.9 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
L’adresse IP attribuée par le service DHCP se trouve probablement au niveau de votre interface WiFi wlan0, au niveau de la ligne inet (ici, 192.168.0.40).
L’adresse MAC de la forme xx:xx:xx:xx:xx:xx est au niveau de la ligne ether (ici, b8:27:eb:83:06:78).
Pour fixer l’adresse IP, il faut trouver comment configurer le service DHCP (Dynamic Host Configuration Protocol) dans les pages d’administration de votre Box/routeur (en général, à l’adresse 192.168.0.1 ou 192.168.1.1). Par exemple :
III-C. configurer et démarrer la pile▲
Il faut maintenant configurer la pile et préparer les conteneurs des différentes applications qui seront utiles pour ce tutoriel. Dans un terminal de la Raspberry Pi, lancez le menu de configuration :
cd ~/IOTstack
./menu.sh
Validez le menu Build Stack et sélectionnez les applications nécessaires avec la touche <Espace>, à savoir (par ordre alphabétique) : grafana, influxdb, mosquitto, nodered, et portainer-ce (interface web pour l’administration de Docker).
Pour nodered, il faudra en plus entrer dans les options, mais vous pourrez valider celles proposées par défaut.
Revenez au menu principal (touche [Escape] ou menu [Back]).
Sélectionnez maintenant le menu Docker Commands :
Validez le sous-menu Start stack. Ici, vous avez le temps de prendre un café… La première fois, le système télécharge toutes les images (plus de 2 Go) avant de construire les conteneurs.
En fin de traitement, vous serez avertis que les conteneurs ont démarré, et vous pourrez quitter l’interface avec le menu Exit.
Start Stack:
docker-compose up -d --remove-orphans
WARN[0000] /home/pi/IOTstack/docker-compose.override.yml: `version` is obsolete
[+] Running 8/8
✔ Network iotstack_default Created 0.3s
✔ Container portainer-ce Started 1.8s
✔ Container mosquitto Started 1.8s
✔ Container nodered Started 1.9s
✔ Container grafana Started 1.8s
✔ Container influxdb Started 1.8s
Stack Started
Process terminated. Press [Enter] to show menu and continue.
Avec la commande docker ps, vous constaterez effectivement que les conteneurs sont démarrés (et « en bonne santé » (healthy)) :
pi@raspberrypi:~ $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6c97b3c15b93 iotstack-nodered "./entrypoint.sh" 32 hours ago Up 12 hours (healthy) 0.0.0.0:1880->1880/tcp, :::1880->1880/tcp nodered
a29a937530fa grafana/grafana "/run.sh" 32 hours ago Up 23 hours (healthy) 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp grafana
968b9f1d2a54 iotstack-mosquitto "/docker-entrypoint.…" 32 hours ago Up 23 hours (healthy) 0.0.0.0:1883->1883/tcp, :::1883->1883/tcp mosquitto
3ff5cdca5b30 portainer/portainer-ce "/portainer" 32 hours ago Up 12 hours 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp, ... portainer-ce
eb31ba356db7 influxdb:1.8 "/entrypoint.sh infl…" 32 hours ago Up 23 hours (healthy) 0.0.0.0:8086->8086/tcp, :::8086->8086/tcp influxdb
III-D. Administrer la pile avec Portainer CE▲
Si vous voulez y voir plus clair dans l’administration de Docker, l’un de ces conteneurs que vous venez justement de démarrer est l’application Portainer CE (Community Edition), une interface graphique web bien plus intuitive que la ligne de commande.
Dans votre navigateur, saisissez l’URL http://localhost:9000 si vous êtes sur la Raspberry Pi ou http://<adresse IP de la Raspberry Pi>:9000 d’un poste distant sur le même réseau.
Un identifiant et mot de passe administrateur sont demandés lors du premier accès à l’interface.
Une fois vos identifiants saisis, on voit sur la page d’accueil que Docker est détecté et son environnement « local » est créé par défaut :
Dans les détails de l’environnement, on voit la pile et ses sept conteneurs actifs :
Pour dire simplement les choses : une pile (stack) est une collection de services conteneurisés, et interconnectés par des interfaces réseau pour tourner de concert (on parle d’orchestration des conteneurs).
Une « image » est ce qui contient les informations nécessaires pour construire les conteneurs. Vous pouvez récupérer des images de services et applications, officielles ou non, sur le Docker Hub.
On peut lister les conteneurs de la pile iotstack et leur image associée : portainer-ce lui même, ainsi que les conteneurs nommés mosquitto, nodered, influxdb, grafana et d’autres que j’ai utilisés.
Vous pouvez obtenir une vue d’ensemble complète de chaque conteneur et de leur état, par exemple celui d’influxdb ci-dessous :
Une chose intéressante à constater sur la copie d’écran suivante pour ce conteneur est la création de « volumes managés » qui permettent de garder en mémoire des données de manière permanente, même après la destruction du conteneur. Par exemple, tout fichier de données créé dans le dossier var/lib/influxdb du conteneur se retrouvera aussi dans le dossier /home/pi/IOTstack/volumes/influxdb/data de la Raspberry Pi hôte. Les données de la base vont donc survivre à la destruction du conteneur. Et si vous reconstruisez le conteneur influxdb plus tard, le conteneur retrouvera ses données conservées sur la Raspberry Pi hôte.
Il y aurait beaucoup de choses à dire encore sur la technologie Docker, mais comprendre les grands principes de la conteneurisation vous permettra de mieux gérer votre système, son état de santé, et ses évolutions.
Pour suivre ce tutoriel, on peut très bien se contenter de l’interface avec les menus en mode texte du terminal pour composer les services de la pile, et démarrer ou arrêter les conteneurs (Start stack ou Stop stack) puisque tout cela est déjà configuré par les créateurs de cette pile pour tourner avec un minimum d’interventions de la part de l’utilisateur.
IV. Développement de l’application sur Raspberry Pi avec Node-RED▲
Il reste à développer l’application sur la Raspberry Pi. Le développement va consister à :
- créer un client MQTT qui va s’abonner au topic des messages délivrés par le nœud de capteurs Arduino ;
- traiter et mettre en forme ces données et les insérer dans une base de données InfluxDB .
On pourrait programmer cela avec un langage de programmation, comme Python. Mais on vous propose de le faire avec très peu de code grâce à Node-RED qui fait partie des applications retenues dans la pile IOTstack.
Si la pile est démarrée, vous accéderez à Node-RED depuis un navigateur à l’URL http://localhost:1880 si vous travaillez sur la Rapsberry Pi ou http://<adresse IP de la Raspberry Pi>:1880 d’un poste distant sur le même réseau.
IV-A. Premier déploiement de flux : s’abonner et lire les messages MQTT▲
Dan la palette de nœuds à gauche, à la rubrique « Réseau », vous avez le nœud « mqtt in » pour se connecter au broker et s’abonner à un topic :
Faites glisser ce nœud ainsi que le nœud « Debug » dans la fenêtre de travail du flux :
Double-cliquez ensuite sur le nœud nommé « mqtt » pour le configurer comme indiqué ci-dessous :
Le « Sujet » est le topic d’abonnement.
Au niveau du champ « Serveur », cliquez sur l’icône en forme de crayon pour renseigner le broker MQTT :
Ma Raspberry Pi où est installé le broker MQTT est à l’adresse IP 192.18.0.40, port 1883 par défaut.
Une fois le nœud renseigné et validé, connectez les deux nœuds en reliant, à la souris, la sortie du nœud MQTT à l’entrée du nœud Debug. Ceci va permettre de diriger le flux des données récupérées vers une console pour les afficher.
Il ne vous reste plus qu’à déployer ce flux (bouton ), de brancher votre Arduino si ce n’est pas encore fait et de cliquer sur la petite icône « Messages de débogage » à droite :
Vous devriez voir en principe défiler les messages de température et d’humidité relative publiés par l’Arduino.
Vous noterez en développant les messages en sortie que ceux-ci ne sont pas que de simples chaînes de caractères, et qu’ils sont interprétés comme des objets JSON (JavaScript Object Notation) :
Ainsi, vous pourrez poursuivre le traitement des données de l’objet msg.payload, avec msg.payload.temperature et msg.payload.humidity comme en JavaScript.
IV-B. Création de la base de données InfluxDB▲
Avant de poursuivre le développement du flux afin de stocker les données dans la base de données InfluxDB, il faut créer la base.
Rappelez-vous qu’InfluxDB n’est pas vraiment installé sur la Raspberry Pi, ce logiciel est isolé dans un conteneur Docker.
Si la pile IOTstack est bien démarrée, ouvrez un terminal dans la Raspberry Pi, et exécutez la commande Docker suivante :
docker exec -it influxdb influx
Cette commande permet d’exécuter la commande influx du conteneur influxdb. L’option -it permet l’interaction avec l’utilisateur depuis un terminal de la Raspberry Pi hôte.
Vous devriez alors accéder à InfluxDB (version 1.x) en ligne de commande, avec l’invite > :
Après l’invite de commande >, on saisit l’instruction de création de la base en langage InfluxQL :
>
CREATE
DATABASE
sensors4dvp
Ce n’est pas du SQL des bases de données relationnelles, c’est de l’InfluxQL (Influx Query Language), mais cela y ressemble un peu…
Pour le moment, c’est la seule opération à effectuer. Vous pouvez quitter InfluxDB avec la commande quit :
IV-C. Diriger le flux des données vers InfluxDB▲
On poursuit le développement du flux dans l’interface Web de Node-RED. Dans la palette des nœuds à gauche, trouvez maintenant le nœud « influx batch » dans la catégorie « Stockage », et glissez-le dans la fenêtre de travail du flux :
Ce nœud va permettre de créer un client InfluxDB et transformer les données entrantes au format JSON, en requêtes InfluxQL envoyées au serveur InfluxDB (voir la documentation dans node-red-contrib-influxdb).
Faites également glisser un nœud « function » situé dans la catégorie « Fonction » :
Ce dernier nœud va recomposer un nouveau JSON au format attendu en entrée par le nœud « influx batch ».
Double-cliquez sur le nœud « function », puis saisissez le code JavaScript qui suit dans l’onglet « Message reçu » :
msg.
payload =
[
{
measurement
:
"home_measurement"
,
fields
:
{
temperature
:
Math.round
(
msg.
payload.
temperature *
10
) /
10
,
humidity
:
Math.round
(
msg.
payload.
humidity *
10
) /
10
},
tags
:
{
location
:
"bedroom"
,
device
:
"unor4wifi-01"
,
sensor
:
"si7021"
}
}
]
return msg;
Dans le code, on voit que les valeurs de température et d’humidité relative sont arrondies à un seul chiffre après la virgule, mais d’autres descripteurs sont également ajoutés.
Dans InfluxDB :
- les séries de valeurs sont enregistrées dans des measurements. Ici, ce sera le measurement nommé home_measurement ;
- les tags sont indexés dans InfluxDB. Ils constituent les descripteurs d’une mesure, ici le lieu de la mesure (location), le contrôleur du nœud de capteurs (device) et le capteur utilisé (sensor).
- Enfin, les fields sont les données de mesures à proprement parler (temperature et humidity).
Il reste à injecter les données transformées dans le nœud du serveur InfluxDB.
Double-cliquez sur le nœud « influx batch » :
Sur la ligne « Server », cliquez sur la petite icône avec le crayon . Renseignez l’adresse IP de la Raspberry Pi, avec le port 8086 par défaut. La base créée précédemment s’appelle « sensors4dvp » :
Terminez les connexions, activez un nouveau nœud « debug », et déployez le flux :
La fenêtre « Débogage » affiche les données transformées et sans avertissement particulier si les données sont bien insérées dans la base. Dans le cas contraire, le ou les messages d’erreur dans la console devraient vous aider à comprendre le problème.
IV-D. Vérification des données insérées dans InfluxDB▲
Avec un terminal de la Raspberry Pi saisissez la commande suivante pour lancer InfluxDB dans son conteneur :
docker exec -it influxdb influx -precision rfc3339
La RFC3339 précise le format de l’horodatage des données sur Internet, les dates seront affichées dans un format plus présentable (format YYYY-MM-DDTHH:MM:SS.nnnnnnnnnZ).
À l’invite de commande >, SHOW DATABASES montre la seule base sensors4dvp créée jusqu’à maintenant :
>
SHOW DATABASES
name: databases
name
----
sensors4dvp
On indique que l’on va utiliser cette base :
>
USE sensors4dvp
Using database sensors4dvp
Grâce au flux Node-RED, une nouvelle série de mesures home_measurement a été insérée :
>
SHOW MEASUREMENTS
name: measurements
name
----
home_measurement
On regarde maintenant les dix dernières mesures avec une requête en InfluxQL :
>
SELECT * FROM home_measurement ORDER BY time DESC LIMIT 10
name: home_measurement
time device humidity location sensor temperature
---- ------ -------- -------- ------ -----------
2024
-05
-18T20:41
:08
.154387272Z unor4wifi-01
57
.3
bedroom si7021 21
.9
2024
-05
-18T20:41
:03
.094059264Z unor4wifi-01
57
.3
bedroom si7021 21
.9
2024
-05
-18T20:40
:58
.093900836Z unor4wifi-01
57
.3
bedroom si7021 21
.9
2024
-05
-18T20:40
:53
.090311576Z unor4wifi-01
57
.2
bedroom si7021 21
.9
2024
-05
-18T20:40
:48
.086497526Z unor4wifi-01
57
.2
bedroom si7021 21
.9
2024
-05
-18T20:40
:43
.082520091Z unor4wifi-01
57
.3
bedroom si7021 21
.9
2024
-05
-18T20:40
:38
.080398593Z unor4wifi-01
57
.2
bedroom si7021 21
.9
2024
-05
-18T20:40
:33
.07078099Z unor4wifi-01
57
.2
bedroom si7021 21
.9
2024
-05
-18T20:40
:28
.069981776Z unor4wifi-01
57
.2
bedroom si7021 21
.9
2024
-05
-18T20:40
:23
.066917134Z unor4wifi-01
57
.3
bedroom si7021 21
.9
À chaque mesure collectée par InfluxDB, un horodatage (descripteur time) a été rajouté (date et heure UTC).
Pour connaître le nombre de mesures insérées :
>
SELECT count
(
*) FROM home_measurement
name: home_measurement
time count_humidity count_temperature
---- -------------- -----------------
1970
-01
-01T00:00
:00Z 320
320
Le flux de données est bien dirigé vers la base de données. Avec cette source de données continuellement mise à jour, vous allez maintenant pouvoir présenter des graphiques à l’utilisateur…
V. Création de tableaux de bord avec Grafana▲
Si la pile est démarrée, vous accéderez à Grafana depuis un navigateur à l’URL http://localhost:3000 si vous êtes sur la Raspberry Pi ou http://<adresse IP de la Raspberry Pi>:3000 d’un poste distant sur le même réseau.
Il faut commencer par créer une source de données en lui donnant un nom, ici ce sera home_sensors, de type InfluxDB, et ce sera la source par défaut :
Poursuivez en renseignant l’URL de la Raspberry Pi, avec le port 8086 par défaut pour le serveur InfluxDB :
Sélectionnez ensuite la base de données, ici sensors4dvp, puis sauvegardez :
Un message vous avertit que la base a été trouvée, et que vous pouvez maintenant créer un tableau de bord (dashboard) :
Rendez-vous alors dans le menu Dashboards :
On commence par une première visualisation :
La source à visualiser dans un graphique provient de home_sensors créée précédemment :
On ajoute un panneau (panel), qui sera un graphique de type Time series avec le temps en abscisses. Par défaut, l’heure UTC stockée en base sera affichée en tenant compte des paramètres locaux de la Raspberry Pi (Europe, France, Paris ici). Une requête doit être préparée dans un assistant, ici pour un graphe des températures :
Avec la carte Arduino, j’avais choisi de faire une mesure toutes les 5 secondes pour accélérer les tests. On peut décider alors par exemple de filtrer les valeurs en créant un point toutes les minutes avec la moyenne des températures (mean, field(temperature)) par tranche d’une minute (GROUP BY time(1m)), soit une moyenne de 12 valeurs de température chaque minute :
N’oubliez pas de sauvegarder à chaque étape.
Voici un exemple de représentation. Dans les options, on peut choisir de rafraîchir le graphique toutes les minutes et voir les points s’afficher en direct au fur et à mesure des acquisitions :
Vous pouvez éditer le tableau de bord, ajouter un panneau (panel) de représentation avec un graphique pour l’humidité relative de la même façon que pour la température, des panneaux avec des jauges ou d’autres types de représentation encore.
Voici un exemple de tableau de bord complet avec les mesures de température et d’humidité :
VI. Accéder à vos données depuis Internet▲
Dans ce projet, plusieurs évolutions sont possibles, comme donner l’accès aux serveurs de la Raspberry Pi depuis Internet, pour consulter vos graphiques en étant à l’extérieur de la maison, et pourquoi pas avec un nom de domaine personnalisé.
C’est cette évolution que l’on va décrire ici.
Mettre vos données en ligne sur Internet génère forcément une faille de sécurité potentielle sur votre réseau. Et vous aurez remarqué que toutes les options de sécurisation (authentification et chiffrement) ont été passées sous silence dans ce tutoriel.
Il est donc conseillé de reprendre les services que vous mettez en ligne et d’activer le chiffrement des données si vous estimez qu’elles sont sensibles. A minima, il faut mettre en place une authentification sur les services avec un mot de passe fort (à ce jour, 12 caractères au minimum sont recommandés, avec au moins une lettre majuscule, une lettre minuscule, un chiffre et un caractère spécial).
Bien entendu, et ce même en fonctionnement local, vous devez changer l’identifiant et le mot de passe par défaut de la Raspberry Pi comme cela est proposé maintenant dès l’installation du Raspberry Pi OS. Ne laissez pas les accès par défaut avec l’utilisateur pi et le mot de passe raspberry connus de tous, c’est le minimum en matière de sécurité.
Vous êtes maintenant prévenus…
VI-A. Configurer un service DNS dynamique▲
Pour sortir de votre réseau privé, le fournisseur d’accès Internet (FAI) de votre Box vous fournit une adresse IP dite « publique », la seule qui soit visible depuis l’extérieur. Un mécanisme de translation d’adresse dans votre Box se charge de faire la passerelle entre votre réseau privé et Internet.
*Image d’après Introduction aux réseaux TCP IP
Cette différentiation entre adresse IP privée et adresse IP publique est déjà une première barrière de sécurité.
Pour vous connecter à votre réseau depuis Internet, il faudrait donc à priori connaître votre adresse IP publique ce qui n’est déjà pas très pratique à retenir. De plus, vous ne la contrôlez pas, et votre FAI peut la changer périodiquement.
C’est pourquoi il est pratique de s’inscrire sur un service DNS (Domain Name System) dynamique qui fera le lien entre un nom de domaine personnalisé et une adresse IP publique.
VI-B. Construire le conteneur Duck DNS▲
Un de ces services DNS est tout trouvé, il est gratuit et libre : Duck DNS.
Et il se trouve aussi que la pile IOTstack vous propose un conteneur Duck DNS, qui va installer un daemon sur votre Raspberry Pi. Ce daemon va régulièrement scruter votre adresse IP publique afin de maintenir à jour le lien avec votre nom de domaine personnalisé.
Commencez par créer un compte sur le site Duck DNS :
Pour la création du domaine, il faudra renseigner :
- le nom de domaine personnalisé (domain) souhaité. L’URL sera de la forme http://nom-de-domaine/duckdns.org. Vous n’êtes pas obligé de fournir un nom de domaine trop simple et le diffuser à n’importe qui non plus…
- l’adresse IP publique courante (current IP) que vous trouverez dans la page d’administration de votre Box, ou depuis les paramètres « Réseau et Internet » de Windows, ou plus simplement en passant par un service en ligne comme https://whatismyipaddress.com/.
Duck DNS vous renvoie alors un jeton (token) à conserver.
Vous pouvez ensuite chercher l’image du conteneur Duck DNS depuis l’IOTstack et lancer la construction du conteneur. Il vous suffit de retourner dans le menu de configuration de la pile :
cd ~/IOTstack
./menu.sh
Sélectionnez le menu Build Stack, recherchez Duck DNS dans la liste, sélectionnez-le, puis validez pour commencer la construction du conteneur :
La configuration du conteneur est assez simple et décrite dans la documentation.
Il faut créer un fichier ~/IOTstack/docker-compose.override.yml avec le contenu suivant, à adapter en fonction du token et du nom de domaine que vous avez choisi :
version
:
'3.6'
services
:
duckdns
:
environment
:
TOKEN
:
xxxxxxx-
yyyy-
zzzz-
uuuu-
vvvvvvvvvvv
SUBDOMAINS
:
mon-
nom-
de-
domaine
puis lancer une mise à jour pour tester la configuration avec les commandes :
docker-compose up -d duckdns
docker-compose logs -f duckdns
En cherchant dans les logs qui s’affichent, vous devriez trouver la ligne indiquant le succès de l’opération :
... Your IP was updated
VI-C. Configurer le transfert ou la redirection des ports▲
Pour autant, vous n’aurez toujours pas accès aux services de la Raspberry Pi depuis Internet, car les services comme Grafana que vous aimeriez bien consulter de l’extérieur sont des serveurs qui tournent sur des ports TCP spécifiques (port 3000 pour Grafana) qui ne sont pas ouverts sur votre Box.
Ainsi, l’accès à Grafana avec l’URL http://nom-de-domaine.duckdns.org:3000 est voué à l’échec, car le port 3000 est fermé.
Pour ouvrir le port, vous devez trouver les options de « transfert de ports », de « redirection de ports » ou de « port forwarding » dans les menus d’administration de votre Box (qui sont différents pour chaque Box et routeur).
En général pour un transfert de ports, il faut sélectionner la plage des ports que vous souhaitez transférer, le protocole (TCP, UDP ou les deux), et l’adresse IP du serveur dans votre réseau local.
L’image ci-dessous montre un exemple de règle de transfert pour le port 3000 utilisé par Grafana, vers ma carte Raspberry Pi à l’adresse privée 192.168.0.40 :
Et voici l’accès à Grafana réussi sur mon mobile avec l’URL personnalisée chez Duck DNS à l’extérieur de la maison :
VI-D. Et pour aller plus loin▲
Si l’adresse IP est publique, et donc exposée sur Internet, elle est traçable… Aujourd’hui, le moyen le plus rapide pour masquer son adresse IP en ligne consiste à se connecter à l’aide d’un service VPN (Virtual Private Network ou réseau privé virtuel) qui va aussi chiffrer les échanges dans un « tunnel » sécurisé au passage.
Si vous souhaitez apporter une couche de sécurité supplémentaire avec un VPN, la pile IOTstack propose aussi un conteneur pour installer un serveur WireGuard avec toute la procédure de configuration, mais on sort du cadre de ce tutoriel…
VII. Conclusion▲
Dans ce tutoriel, vous avez vu comment installer et configurer un système communicant en prototype, avec des capteurs environnementaux dans la maison. Toute une pile de logiciels et de serveurs sont embarqués en local dans une Raspberry Pi sous la forme de conteneurs Docker. Les données des capteurs sont visualisées dans des graphiques consultables dans un navigateur Web, au sein de votre réseau local ou depuis Internet.
Le protocole de communication MQTT choisi est léger et s’appuie sur TCIP/IP. Il est particulièrement adapté dans la transmission de messages pour l’Internet des Objets. Mais si vous prévoyez un réseau maillé d’équipements, il faudra envisager une communication radio avec une autre passerelle matérielle et un protocole différent (comme Zigbee, et pourquoi pas alors conserver votre serveur domotique sur RaspBerry Pi grâce à une passerelle Zigbee2MQTT).
Grâce à l’environnement Arduino et ses nombreuses bibliothèques, le développement du nœud de capteurs est grandement facilité. Du côté de la Raspberry Pi, la plateforme low code Node-RED accélère aussi le développement.
Il vous reste à ajouter des nœuds de capteurs, avec une autre Arduino, une carte ESP32 ou une Raspberry Pi Pico W située dans une autre pièce de votre maison, avec différents capteurs environnementaux. Chaque nouveau nœud enverra ses données dans un topic MQTT, et d’autres flux fonctionneront en parallèle dans Node-RED pour lire les données des topics et insérer les données dans la base InfluxDB, dans le même measurement ou dans un nouveau.
Stockage dans InfluxDB : le deuxième nœud de capteurs est situé dans le salon (location=livingroom). Il tourne autour d’une carte Espressif ESP32 (device=esp32-01) reliée à un capteur de température DS18B20 (sensor=ds18b20).
pi@raspberrypi:~ $ docker exec -it influxdb influx -precision rfc3339
Connected to http://localhost:8086 version 1.8.10
InfluxDB shell version: 1.8.10
> use sensors4dvp
Using database sensors4dvp
> select * from home_measurement order by time desc limit 10
name: home_measurement
time device humidity location sensor temperature
---- ------ -------- -------- ------ -----------
2024-05-27T17:44:34.196893649Z unor4wifi-01 50.6 bedroom si7021 21.9
2024-05-27T17:44:29.199577403Z unor4wifi-01 50.6 bedroom si7021 21.9
2024-05-27T17:44:26.889396559Z esp32-01 livingroom ds18b20 20.8
2024-05-27T17:44:24.188381799Z unor4wifi-01 50.6 bedroom si7021 21.9
2024-05-27T17:44:19.227531078Z unor4wifi-01 50.6 bedroom si7021 21.8
2024-05-27T17:44:14.887636484Z esp32-01 livingroom ds18b20 20.1
2024-05-27T17:44:14.185308226Z unor4wifi-01 50.6 bedroom si7021 21.9
2024-05-27T17:44:09.181313405Z unor4wifi-01 50.6 bedroom si7021 21.8
2024-05-27T17:44:04.330309657Z esp32-01 livingroom ds18b20 20.6
2024-05-27T17:44:04.173032228Z unor4wifi-01 50.6 bedroom si7021 21.9
> select * from home_measurement where location='livingroom' order by time desc limit 10
name: home_measurement
time device humidity location sensor temperature
---- ------ -------- -------- ------ -----------
2024-05-27T17:45:03.009925365Z esp32-01 livingroom ds18b20 20.2
2024-05-27T17:44:50.919245362Z esp32-01 livingroom ds18b20 20.9
2024-05-27T17:44:38.898989468Z esp32-01 livingroom ds18b20 20
2024-05-27T17:44:26.889396559Z esp32-01 livingroom ds18b20 20.8
2024-05-27T17:44:14.887636484Z esp32-01 livingroom ds18b20 20.1
2024-05-27T17:44:04.330309657Z esp32-01 livingroom ds18b20 20.6
2024-05-27T17:43:50.923476775Z esp32-01 livingroom ds18b20 20.8
2024-05-27T17:43:39.421963414Z esp32-01 livingroom ds18b20 20.6
2024-05-27T17:43:26.877618567Z esp32-01 livingroom ds18b20 20.3
2024-05-27T17:43:14.876225114Z esp32-01 livingroom ds18b20 20.6
>
C’est maintenant à vous de poursuivre le développement en fonction de vos besoins…
J’espère que ce tutoriel est suffisamment progressif pour surmonter les quelques obstacles que vous pourriez rencontrer. Au besoin, un espace de discussion à propos de ce tutoriel est ouvert, et les forums Arduino et Raspberry Pi sont là pour vous dépanner sur des points particuliers…
Pour conclure, je remercie chrtophe et escartefigue pour leur travail de relecture de ce tutoriel.