Posted on 18 février 2018
Dans de nombreux projets, l'IHM est constituée d'une application web accessible depuis un navigateur. Il n'est pas toujours aisé de programmer le lien entre le matériel (capteurs et/ou actionneurs) et la page web fournie à l'utilisateur. Une solution consiste à utiliser un script Python dont l'exécution peut être planifiée avec Cron, pour interagir avec le matériel, lire les données des capteurs et les stocker dans une base de données comme MySQL. Un serveur web comme Apache2, via une page web php, met à disposition des utilisateurs les informations. Cette solution décrite dans un précédant article nécessite la mobilisation de nombreuses technologies et plusieurs langages de programmation, contraignant les développeurs du projet à retarder la mise œuvre d'un prototype pour se former.
Combiné avec une solution matérielle constituée d'une Raspberry et éventuellement une carte Arduino, Node Red se révèle être une alternative très intéressante :
Node-RED est un outil puissant pour construire des applications de l'Internet des Objets (IoT) en mettant l'accent sur la simplification de la programmation qui se fait grâce à des blocs de code prédéfinis, appelés «nodes» pour effectuer des tâches. Il utilise une approche de programmation visuelle qui permet aux développeurs de connecter les blocs de code ensemble. Les nœuds connectés, généralement une combinaison de nœuds d'entrée, de nœuds de traitement et de nœuds de sortie, lorsqu'ils sont câblés ensemble, constituent un «flow».
Installation
Sur un Raspberry Pi qui tourne sur Raspbian, Node Red est déja installé. Pour le lancé, il suffit de cliquer sur le menu Programmation puis Node Red.
Node Red peut également être lancé ou stoper à partir d'un terminal :
$ node-red-start
Démarrer Node Red
$ node-red-stop
Arrêter Node Red
L'accès à l'interface de programmation se fait à partir d'un navigateur. Il est préférable d'utiliser le navigateur d'un ordinateur connecté sur le même réseau que la Raspberry :
La palette de Nodes d'origine est déjà bien fournie mais il en manque au moins une essentielle qui va permettre de réaliser l'interface web. Elle se nomme Dashboard. Mais avant, il faut installer le gestionnaire de Nodes. L'environnement d'exécution est construit sur Node.js, le gestionnaire de Nodes utilisé par Node Red est donc naturellement npm :
$ node-red-stop
$ sudo apt-get update
$ sudo apt-get install npm
$ cd /home/pi/.node-red
$ npm rebuild
$ node-red-start
On dispose désormais du gestionnaire de nodes dans le menu :
Cliquez sur Manage Palette puis recherchez la palette de nodes à installer :
Remarquez qu'il y a actuellement 1335 palettes de nodes disponible à l'heure actuelle. Vous devriez pouvoir y trouver votre bonheur !
Une nouvelle palette est désormais disponible :
Premier Flow
Commençons par réaliser un flow très simple composé d'un node Slider et d'un node Gauge. Pour modifier la configuration par défaut des nodes, il suffit de double-cliquer dessus.
Pour que ces composants soient bien disposés sur la page web, il faut les mettre dans un Tab, puis dans un Groupe, dans la boite de sélection du groupe, cliquez sur Add new ui_group, puis créer un groupe (ici : "Le groupe") et faite de même pour le Tab (ici : "Test").
Cliquez ensuite sur le bouton Done, puis déployer le Flow. On accède au site web généré par l'url : http://ip_raspberry:1880/ui
Actionnez le Slider et constatez l'action sur la jauge (Gauge).
Modifiez le flow en ajoutant un node Numeric, déployez et constatez le résultat :
Pour conserver un historique des valeurs affichés, on peut utiliser un node Chart :
Si la propriété Y-axis est laissée vide, l'échelle est automatique. L'affichage de la courbe est réactualisé à chaque nouvelle valeur.
Nous allons maintenant rajouter une information textuelle en bas du groupe. Cette information contiendra par exemple la valeur du slider, la date et l'heure de modification. L'ajout d'un node Text ne suffit pas car il permet d'ajouter un label et le contenu d'un message. Or si on se contente de connecter la sortie du node slider à l'entrée du node Text, on pourra écrire :
Label="Valeur = "
Value Format="{{msg.payload}}"
Il nous faut intercaler un node pour insérer la date dans le message à afficher. Nous allons utiliser un node Function, ce node permet d'effectuer n'importe quel traitement sur le flux entrant pour fabriquer un ou plusieurs flux sortant. Le code est du JavaScript :
var now = new Date();
var year = now.getFullYear();
var month = now.getMonth()+1;
var day = now.getDate();
var hour = now.getHours();
var minute = now.getMinutes();
var second = now.getSeconds();
msg.payload = day+"/"+
month+"/"+
year+" à "+
hour+":"+
second+" => "+
msg.payload;
return msg;
A noter : Les messages transmis dans les flux d'entrée et de sortie des nodes sont des objets. C'est l'attribut payload, soit "charge utile" en français qui contient l'information à transmettre. Pour observer cette charge utile de message vous pouvez utiliser un node Debug.
Aller plus loin ...
Pour aller plus loin, je vous conseille la lecture du tutoriel Node RED Programming Guide.
Interaction avec Arduino
Installation de l'IDE Arduino sur le Raspberry pi :
sudo apt-get update
sudo apt-get install arduino
Ecriture du sketch Arduino
Nous allons utiliser ici un montage élémentaire constitué de deux potentiomètres pour simuler des entrées de capteurs analogiques et deux LED pour simuler une sortie logique TOR (Tout Ou Rien) et une sortie analogique (PWM).
/***************************************************************************
*
* Programme : rasp2ardu.ino
* Auteur : M.S
* Date : 18/02/2018
* Rev : 0.1
* Description : Protocole de communication entre Arduino et Raspberry Pi
* Pilote la LED13 en tout ou rien, LED10 en PWM
* Lit et retourne la valeur de deux pot sur A0 et A1
* Protocole :
* - Reception :
* * L13 : Allumer la LED sur la broche 13
* * l13 : Eteindre la LED 13
* * num : num est un nombre compris entre 0 et 100
* * sinon : éteint la LED 13 (toInt() retourne 0 si recu pas num)
* - Transmission :
* * pot1;pot2\n : pot1 et pot2 sont des nombres compris entre
* 0 et 1023 issue de la CAN sur les pin A0 et A1
* Matériel :
* - 1 LED sur le pin 13
* - 1 LED sur le pin 10 (PWM)
* - 1 potentiomètre sur le pin A0
* - 1 potentiomètre sur le pin A1
*
**************************************************************************/
#define LED 13
#define DIM 10
int pwm =0;
String recu;
void setup() {
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
pinMode(LED,OUTPUT);
}
// the loop routine runs over and over again forever:
void loop() {
if(Serial.available()>0)
{
recu = Serial.readStringUntil('\n');
// Ordre d'allumer la LED13
if(recu=="L13") digitalWrite(LED,HIGH);
else
// Ordre d'éteindre la LED13
if(recu == "l13") digitalWrite(LED,LOW);
else
// Ordre de gradation de la LED10
if((pwm=recu.toInt())<=100 && pwm>=0) analogWrite(DIM,map(pwm,0,100,0,255));
}
int pot1 = analogRead(A0);
int pot2 = analogRead(A1);
Serial.print(pot1);
Serial.print(";");
Serial.print(pot2);
Serial.print("\n");
delay(500);
}
rasp2ardu.ino
Flow Node RED
Attention : Avant de commencer un nouveau Flow, pensez à supprimer ou désactiver (double-clic sur l'onglet du flow puis Disabled) sous peine de servir deux flows, soit deux programmes en même temps, ce qui risque d'entrainer de curieux effets.
Les informations issues de la carte Arduino parviennent à la Raspberry via le port série généralement nommé /dev/ttyACM0. Nous allons donc placer sur notre flow un node Serial et le configurer pour une connexion à ce port avec comme caractéristiques :
- débit : 9600 bauds
- Stop : 1
- parité : aucune
- contrôle de flux : aucun
- caractère de séparation : "\n"
Un node Debug permettra d'observer la bonne acquisition de la trame envoyée par Arduino.
Réalisons maintenant un flow complet pour exploiter le potentiel de notre montage au travers de cette IHM :
Commençons par la saisie :
Préparation de la page web
Le Layout est ici composé d'un Tab nommé "Arduino" et de 5 groups dans lesquels sont répartis les nodes :
Group |
Node name |
Node type |
Size |
Fonction |
Board |
Template |
template |
6x5 |
Code HTML pour afficher l'image de la carte Arduino |
Les LEDs |
LED13 |
Switch |
auto |
Pilote la LED 13 en TOR |
slider |
slider |
auto |
Pilote la LED 10 en PWM |
LED10 |
Gauge |
6x3 |
Affiche la valeur PWM en % |
Jauge |
Pot1 |
Gauge |
6x5 |
Affiche la valeur contenue dans le premier champ de la trame série |
Niveau |
Pot2 |
Gauge / Level |
6x5 |
Affiche la valeur contenue dans le second champ de la trame série après avoir été divisé par 10 |
Trame |
Text |
Text |
24x1 |
Affiche le contenue de la trame est d'autres informations. Modifier la taille du groupe à 24. |
Code des fonctions
var msgOut;
var o;
o = msg.payload.split(";");
msgOut = "frame : "+msg.payload+" - " +"POT 1 : "+o[0]+" - POT 2 : "+o[1].replace("\n","");
var msg={};
msg.payload = msgOut;
return msg;
prepareText
var o;
var msg1={};
var msg2={};
o = msg.payload.split(";");
msg1.payload = o[0];
msg2.payload = o[1];
msg2.payload = msg2.payload / 10;
return [msg1,msg2]
prepareMsg
Modifiez le nombre de sorties (Outputs) à 2 : La fonction retourne un tableau de messages.
var o ={};
if (msg.payload===true)
{
o.payload = "L13\n";
}
if (msg.payload===false)
{
o.payload = "l13\n";
}
return o;
prepare message
var o;
if(msg.payload=="L13\n") o = "ON";
else
if(msg.payload=="l13\n") o = "OFF";
else o = msg.payload;
return {
topic: "Alert !",
payload:"La led à changer d'état : "+o
};
PrepareEmail
var valeur={};
valeur.payload=msg.payload + "\n";
return valeur;
prepareValeur
Insérer une image
J'utilise pour insérer une image un node template qui permet d'insérer du code HTML brute :
<img src='/arduinoUno.png'>
Télécharger l'image ici.
Remarquez le dossier dossier dans lequel doit se trouver l'image : "/" ce qui signifie qu'elle se trouve à la racine du serveur web. Le réglage de l'emplacement des fichiers utilisateur dans node red se fait par l'intermédiaire du fichier de configuration settings.js situé dans le dossier /home/pi/.node-red. Editez ce fichier et décommentez la directive httpStatic qui indique le dossier où sera situé le contenu statique :
// When httpAdminRoot is used to move the UI to a different root path, the
// following property can be used to identify a directory of static content
// that should be served at http://localhost:1880/.
httpStatic: '/home/node-red-static/'
Évidemment, il faut redémarrer Node Red pour que la modification soient prise en compte.
Envoyer un email
Rien de plus simple avec le node email. Cependant, votre compte google doit être configurer pour accorder l'accès aux applications moins sécurisées : https://myaccount.google.com/lesssecureapps
Le code complet du flow Arduino
Pour ceux qui sont pressés d'essayer, copiez le code ci-dessous puis dans le menu de Node Red, cliquez sur Import puis Clipboard et enfin collez le code
[
{
"id":"2215afc9.e61ac8",
"type":"tab",
"label":"Arduino",
"disabled":false,
"info":""},
{
"id":"31f93469.4a6584",
"type":"serial in",
"z":"2215afc9.e61ac8",
"name":"ttyACM0",
"serial":"edacb78f.e46bb8",
"x":134.5,
"y":124,
"wires": [["fee09822.52407","ce799423.b57ea8","28d99f10.e44a2"]]
},
{
"id":"1a320e5a.943dc2",
"type":"ui_gauge",
"z":"2215afc9.e61ac8",
"name":"Pot1",
"group":"21394d77.4f7fe2",
"order":3,
"width":"6",
"height":"5",
"gtype":"gage",
"title":"Pot 1",
"label":"units",
"format":"{{value}}",
"min":0,
"max":"1023",
"colors":["#00b500","#e6e600","#ca3838"],
"seg1":"512",
"seg2":"768",
"x":457.5,
"y":273,
"wires":[]
},
{
"id":"fee09822.52407",
"type":"function",
"z":"2215afc9.e61ac8",
"name":"PrepareMsg",
"func":"var o;\nvar msg1={};\nvar msg2={};\no = msg.payload.split(\";\");\nmsg1.payload = o[0];\nmsg2.payload = o[1];\nmsg2.payload = msg2.payload / 10;\nreturn [msg1,msg2]",
"outputs":"2",
"noerr":0,
"x":243.5,
"y":279,
"wires":[
["1a320e5a.943dc2"],["77c423c7.82d454"]
]
},
{
"id":"77c423c7.82d454",
"type":"ui_gauge",
"z":"2215afc9.e61ac8",
"name":"Pot2",
"group":"9a0face5.d762e8",
"order":4,
"width":"6",
"height":"5",
"gtype":"wave",
"title":"",
"label":"cm",
"format":"{{value}}",
"min":0,
"max":"110",
"colors":["#00b500","#e6e600","#ca3838"],
"seg1":"",
"seg2":"",
"x":458.5,
"y":348,
"wires":[]
},
{
"id":"32fb1d6c.48537a",
"type":"ui_text",
"z":"2215afc9.e61ac8",
"group":"f35c38ff.36261",
"order":0,
"width":"24",
"height":"1",
"name":"Text",
"label":"Valeurs : ",
"format":"{{msg.payload}}",
"layout":"col-center",
"x":650,
"y":120,
"wires":[]
},
{
"id":"ce799423.b57ea8",
"type":"function",
"z":"2215afc9.e61ac8",
"name":"PrepareText",
"func":"var msgOut;\nvar o;\no = msg.payload.split(\";\");\n\nmsgOut = \"frame : \"+msg.payload+\" - \" +\"POT 1 : \"+o[0]+\" - POT 2 : \"+o[1].replace(\"\\n\",\"\");\nvar msg={};\nmsg.payload = msgOut;\nreturn msg;",
"outputs":1,
"noerr":0,
"x":410,
"y":120,
"wires":[
["32fb1d6c.48537a"]
]
},
{
"id":"a741f7f.1a00f88",
"type":"ui_switch",
"z":"2215afc9.e61ac8",
"name":"LED13",
"label":"LED13",
"group":"93b7dedd.2373d",
"order":1,
"width":0,
"height":0,
"passthru":false,
"decouple":"false",
"topic":"",
"style":"",
"onvalue":"true",
"onvalueType":"bool",
"onicon":"",
"oncolor":"",
"offvalue":"false",
"offvalueType":"bool",
"officon":"",
"offcolor":"",
"x":110,
"y":500,
"wires":[
["e8bcb3b.29bc05"]
]
},
{
"id":"e824d567.5a34b",
"type":"serial out",
"z":"2215afc9.e61ac8",
"name":"ttyACM0",
"serial":"edacb78f.e46bb8",
"x":820,
"y":500,
"wires":[]
},
{
"id":"e8bcb3b.29bc05",
"type":"function",
"z":"2215afc9.e61ac8",
"name":"Prepare message",
"func":"var o ={};\nif (msg.payload===true) \n{\n o.payload = \"L13\\n\";\n}\nif (msg.payload===false) \n{\n o.payload = \"l13\\n\";\n}\n\n\nreturn o;",
"outputs":1,
"noerr":0,
"x":290,
"y":500,
"wires":[
[
"e824d567.5a34b",
"2e92dd3d.44b3fa"]
]
},
{
"id":"db249fb1.9a0aa",
"type":"function",
"z":"2215afc9.e61ac8",
"name":"prepareValeur",
"func":"var valeur={};\nvaleur.payload=msg.payload + \"\\n\";\nreturn valeur;",
"outputs":1,
"noerr":0,
"x":280,
"y":560,
"wires":[
["e824d567.5a34b"]
]
},
{
"id":"8edf2a46.d71ff8",
"type":"ui_slider",
"z":"2215afc9.e61ac8",
"name":"",
"label":"slider",
"group":"93b7dedd.2373d",
"order":2,
"width":0,
"height":0,
"passthru":true,
"topic":"",
"min":0,
"max":"100",
"step":1,
"x":84.5,
"y":558.7999725341797,
"wires":[
[
"db249fb1.9a0aa",
"2247f35.b53900c"
]
]
},
{
"id":"28d99f10.e44a2",
"type":"debug",
"z":"2215afc9.e61ac8",
"name":"",
"active":true,
"console":"false",
"complete":"false",
"x":330,
"y":200,
"wires":[]
},
{
"id":"2247f35.b53900c",
"type":"ui_gauge",
"z":"2215afc9.e61ac8",
"name":"Led10",
"group":"93b7dedd.2373d",
"order":0,
"width":"6",
"height":"3",
"gtype":"gage",
"title":"LED 10",
"label":"%",
"format":"{{value}}",
"min":0,
"max":"100",
"colors":["#00b500","#e6e600","#ca3838"],
"seg1":"",
"seg2":"",
"x":257.49998474121094,
"y":618.9999847412109,
"wires":[]
},
{
"id":"8fd5f1f2.358ea",
"type":"e-mail",
"z":"2215afc9.e61ac8",
"server":"smtp.gmail.com",
"port":"465",
"secure":true,
"name":"your_mail_address@gmail.com",
"dname":"email",
"x":690,
"y":440,
"wires":[]
},
{
"id":"2e92dd3d.44b3fa",
"type":"function",
"z":"2215afc9.e61ac8",
"name":"prepareEmail",
"func":"var o;\nif(msg.payload==\"L13\\n\") o = \"ON\";\nelse\nif(msg.payload==\"l13\\n\") o = \"OFF\";\nelse o = msg.payload;\nreturn {\n topic: \"Alert !\",\n payload:\"La led à changer d'état : \"+o\n};",
"outputs":1,
"noerr":0,
"x":530,
"y":440,
"wires":[
[
"8fd5f1f2.358ea",
"d61b30af.f4f13"
]
]
},
{
"id":"a0512576.6cb97",
"type":"ui_template",
"z":"2215afc9.e61ac8",
"group":"cfbca1bb.7a494",
"name":"",
"order":0,
"width":"6",
"height":"5",
"format":"",
"storeOutMessages":true,
"fwdInMessages":true,
"templateScope":"local",
"x":80,
"y":60,
"wires":[[]]
},
{
"id":"d61b30af.f4f13",
"type":"ui_toast",
"z":"2215afc9.e61ac8",
"position":"top right",
"displayTime":"3",
"highlight":"",
"outputs":0,
"ok":"OK",
"cancel":"",
"topic":"Un notification a été envoyée par mail",
"name":"notification",
"x":710,
"y":340,
"wires":[]
},
{
"id":"edacb78f.e46bb8",
"type":"serial-port",
"z":"",
"serialport":"/dev/ttyACM0",
"serialbaud":"9600",
"databits":"8",
"parity":"none",
"stopbits":"1",
"newline":"\\n",
"bin":"false",
"out":"char",
"addchar":false
},
{
"id":"21394d77.4f7fe2",
"type":"ui_group",
"z":"",
"name":"Jauge",
"tab":"21744230.c4774e",
"order":3,
"disp":true,
"width":"6",
"collapse":false
},
{
"id":"9a0face5.d762e8",
"type":"ui_group",
"z":"",
"name":"Niveau",
"tab":"21744230.c4774e",
"order":4,"disp":true,
"width":"6",
"collapse":false
},
{
"id":"f35c38ff.36261",
"type":"ui_group",
"z":"",
"name":"Trame",
"tab":"21744230.c4774e",
"order":5,
"disp":true,
"width":"24",
"collapse":false
},
{
"id":"93b7dedd.2373d",
"type":"ui_group",
"z":"",
"name":"Les LEDs",
"tab":"21744230.c4774e",
"order":2,
"disp":true,
"width":"6",
"collapse":false
},
{
"id":"cfbca1bb.7a494",
"type":"ui_group",
"z":"",
"name":"Board",
"tab":"21744230.c4774e",
"order":1,
"disp":true,
"width":"6",
"collapse":false
},
{
"id":"21744230.c4774e",
"type":"ui_tab",
"z":"",
"name":"Mon Arduino",
"icon":"dashboard"
}
]
flow Node Red Arduino
A vous de jouer !