Marc Silanus

Tutoriel Node-Red

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 !

Commentaires (0) Trackbacks (0)

Désolé, le formulaire de commentaire est fermé pour le moment

Trackbacks are disabled.