Les vidéos présentent :
- présentation de l’interface graphique
- utiliser un Raspberry Pi à distance avec WinSCP et Putty
- Installation du serveur web Apache
La carte Raspberry Pi avec son système d’exploitation Raspbian constitue un nano ordinateur “à tout faire”. Nous découvrirons ici comment accéder à cette carte via son interface graphique puis via une console.
Les vidéos présentent :
Ce système permet d’autoriser l’accès à un parking par lecture des plaques d’immatriculation. Il s’appuie sur une caméra IP associée à une carte Raspberry Pi et le logiciel de reconnaissance de plaques openALPR. Une base de donnée mongoDB contient la liste des plaques autorisées. Le middleware est assuré par un “flow” node-red. (liaison openALPR / mongoDB, déclenchement de l’ordre d’ouverture et IHM pour la gestion de la base)
La lecture des plaques d’immatriculation va s’intégrer dans le cas d’utilisation « Contrôle d’accès ». L’ajout et la mise à jour de nouveaux véhicules sera lié au cas d’utilisation « Mise à jour de la base données ». Le système autorise l’accès au parking aux véhicules dont le numéro de plaque d’immatriculation est inscrit dans la base de données.
http://<IP_CAMERA>:<PORT_CAMERA>//videostream.cgi?user=admin&pwd=&resolution=32&rate=0
dans une console lancer node-red : node-red-start
puis à l’aide d’un navigateur accéder à http://<ip raspberry>:1880
. Si le noeud Johnny5 est visible, il n’y a rien à faire sinon voir Réinstallation de node-red avec Johnny5
voir openAlpr sur Raspberry Pi
voir installation de mongoDB et mongo-express
Compléter le fichier /etc/openalpr/alprd.conf
et indiquer le chemin d’accès au flux vidéo de la caméra IP
; This configuration file overrides the default values specified ; in /usr/local/share/openalpr/config/alprd.defaults.conf [daemon] ; country determines the training dataset used for recognizing plates. Valid values are us, eu country = eu pattern = fr ; text name identifier for this location site_id = rpi01 ; Declare each stream on a separate line ; each unique stream should be defined as stream = [url] ; adresse de la camera IP accessible par le web stream = http://192.168.5.23:8080/videostream.cgi?user=admin&pwd=&resolution=32&rate=0 ; topn is the number of possible plate character variations to report topn = 5 ; Determines whether images that contain plates should be stored to disk store_plates = 0 store_plates_location = /home/pi/plateimages/ ; upload address is the destination to POST to upload_data = 1 upload_address = http://localhost:1880/plate/
Lancer la commande alprd -f
et placer une image comportant une plaque d’immatriculation devant la caméra
Observer la console :
pi@Raspberry Pipi:~ $ alprd -f INFO - Running OpenALPR daemon in the foreground. INFO - Using: /etc/openalpr/alprd.conf for daemon configuration Missing config value for company_id Missing config value for pattern INFO - Using: /home/pi/plateimages/ for storing valid plate images INFO - country: fr -- config file: /etc/openalpr/openalpr.conf INFO - pattern: INFO - Stream 1: http://192.168.5.23:8080/videostream.cgi?user=admin&pwd=&resolution=32&rate=0 INFO - Starting camera 1 INFO - Video stream connecting... INFO - Video stream connected DEBUG - Writing plate AV865XG (rpi01-cam1-1483036354101) to queue.
Video de Guy TechLab : mise en oeuvre de ce tuto.
Node-red permet d’interconnecter des applications hétérogènes devant échanger des informations. On parle d’échange de messages. Aplrd permet d’envoyer une requête POST à un serveur web lors de la lecture d’une plaque. Nous allons donc créer un flux qui intercepte cette requête POST, va en extraire la donnée et publier cette dernière dans une fenêtre de débogage.
Vérifier que les 3 dernières lignes du fichier alprd.cong correspondent à :
; upload address is the destination to POST to upload_data = 1 ; « 1 » = envoi des données upload_address = http://localhost:1880/plate/ ; adresse du serveur web où sont envoyées les requêtes POST
Ce qui impliquera que les données seront postées à l’adresse http://localhost:1880/plate
Flux composé d’un node « http in », « http response » et « debug »
Lors de la reconnaissance d’une plaque on obtient un objet msg.payload en sortie du node « http in » qui une fois remis en forme ressemble à ceci :
{ "version": 2, "data_type": "alpr_results", "epoch_time": 1483090959429, "img_width": 640, "img_height": 480, "processing_time_ms": 395.173157, "regions_of_interest": [], "results": [ { "plate": "AV865XG", "confidence": 90.980667, "matches_template": 0, "plate_index": 0, "region": "", "region_confidence": 0, "processing_time_ms": 171.828781, "requested_topn": 5, "coordinates": [ {…
On peut observer qu’il s’agit d’un objet JSON dont la propriété « results » est un tableau contenant les plaques reconnues avec leur score « confidence ».
On peut donc interroger l’objet msg.payload afin d’en extraire le numéro de plaque. Pour cela on introduit un script
On peut alors voir le numéro de la plaque apparaitre dans la fenêtre de débogage
L’objectif est de comparer la plaque lue par alprd avec une plaque contenue dans la base de données.
Sélectionner le Menu de Node-red -> Manage palette
Sélectionner l’onglet « Install »
Dans l’espace de recherche taper « mongo »
On voit alors apparaitre les nodes disponibles. Cliquer sur le bouton « install » du node « node-red-contrib-mongoDB2 »
Le nœud s’installe est devient alors disponible dans la palette des nodes.
Installer aussi le node « node-red-contrib-objectid », il permet de gérer les propriétés « _id » des documents.
Paramétrage du nœud mongoDB2 et test de connexion
Faire glisser un node mongoDB2 dans l’espace de travail et l’éditer.
Sélectionner « External service » puis cliquer sur le petit crayon afin d’ajouter une connexion au serveur mongoDB | Ajouter un serveur, l’URI est normalement : mongodb://localhost:27017/db |
Une fois le serveur paramétré, sélectionner « db » dans l’option « Server », compléter l’option « Collection » avec le nom de la collection « LicencePlate » enfin dans « Operation » sélectionner « findOne » et cliquer sur « Done » | Ajouter un nœud « Inject » dans l’espace de travail et le paramétrer de la manière suivante : |
Compléter le flow de la manière suivante : ajouter un node « json » et un node « debug ». Lier l’ensemble.
Quand on clique sur le bouton du nœud inject, on voit apparaitre le document au format JSON dans la fenêtre de debug
[{"id":"fcc8de73.b9a8e","type":"mongodb2 in","z":"de8a40ec.19323","service":"_ext_","configNode":"1d02b15c.4cf227","name":"","collection":"LicencePlate","operation":"findOne","x":1234.7665710449219,"y":157.85000610351562,"wires":[["c37c79dd.bbd448"]]},{"id":"5934a9db.1c3ee8","type":"json","z":"de8a40ec.19323","name":"","x":996.7665710449219,"y":158.01666259765625,"wires":[["fcc8de73.b9a8e"]]},{"id":"c37c79dd.bbd448","type":"debug","z":"de8a40ec.19323","name":"","active":true,"console":"false","complete":"false","x":1474.7666015625,"y":157.01666259765625,"wires":[]},{"id":"fea87bf7.46af38","type":"inject","z":"de8a40ec.19323","name":"","topic":"","payload":"{\"nom\":\"Hollande\"}","payloadType":"str","repeat":"","crontab":"","once":false,"x":774.7666320800781,"y":158.38333129882812,"wires":[["5934a9db.1c3ee8"]]},{"id":"1d02b15c.4cf227","type":"mongodb2","z":"","uri":"mongodb://localhost:27017/db","name":"db","options":"","parallelism":"-1"}]
Nous venons d’effectuer une requête dans la base mongoDB
Reconnaissance d’une plaque d’immatriculation.
En associant la gestion de la requête POST fournit par le logiciel alprd et une requête dans la base de données, on peut déterminer si l’accès est autorisé.
[{"id":"cd7d103b.308a78","type":"mongoDB2 in","z":"1ee78c77.4f0c84","service":"_ext_","configNode":"d3816939.e303e8","name":"","collection":"LicencePlate","operation":"count","x":1201,"y":144,"wires":[["ae24a545.fb9ea","3fc0b1.16e58f5"]]},{"id":"ae24a545.fb9ea","type":"debug","z":"1ee78c77.4f0c84","name":"","active":true,"console":"false","complete":"false","x":1477.7666015625,"y":66.71665954589844,"wires":[]},{"id":"d96d3dcc.a4c3a8","type":"http in","z":"1ee78c77.4f0c84","name":"","url":"/plate","method":"post","swaggerDoc":"","x":697.7666015625,"y":147.18333435058594,"wires":[["e36dbbe7.4063c8"]]},{"id":"e36dbbe7.4063c8","type":"function","z":"1ee78c77.4f0c84","name":"extraction num plaque","func":"var plaque={}; //objet vide\nplaque.immatriculation=msg.payload.results[0].plate;\nmsg.payload=plaque;\nreturn msg;","outputs":1,"noerr":0,"x":939.7666015625,"y":145.9499969482422,"wires":[["cd7d103b.308a78","b918e1ce.d2721"]]},{"id":"b918e1ce.d2721","type":"debug","z":"1ee78c77.4f0c84","name":"","active":true,"console":"false","complete":"false","x":1184.7666015625,"y":61.716644287109375,"wires":[]},{"id":"3fc0b1.16e58f5","type":"http response","z":"1ee78c77.4f0c84","name":"","x":1457.7666015625,"y":142.4166717529297,"wires":[]},{"id":"d3816939.e303e8","type":"mongoDB2","z":"","uri":"mongoDB://localhost:27017/db","name":"db","options":"","parallelism":"-1"}]
Le script dans le node function (extraction num plaque) est le suivant :
var plaque={}; plaque.immatriculation=msg.payload.results[0].plate; msg.payload=plaque; return msg;
Le message de sortie sera donc de la forme : { "immatriculation": "AV865XG" }
. Ce message sert d’argument d’entrée à la requête « count » du node mongoDB2. Autrement dit le node mongoDB2 va compter les documents qui contiennent un champ « immatriculation » ayant pour valeur « AV865XG ». Le message de sortie du node mongoDB sera un nombre. Si ce dernier vaut 0, c’est que le numéro de plaque n’est pas enregistré dans la base. Reste à commander l’ouverture de la barrière en fonction de cette information.
Pour cela nous allons utiliser une carte Arduino qui sera pilotée par la carte Raspberry.
Compléter le flow de la manière suivante en ajoutant les nœuds « switch », « trigger » et « gpio ».
[{"id":"7ec1525b.cd9fac","type":"trigger","z":"1ee78c77.4f0c84","op1":"1","op2":"0","op1type":"str","op2type":"str","duration":"2000","extend":false,"units":"ms","reset":"","name":"","x":1270.7666015625,"y":243.18331909179688,"wires":[["1a99c7a1.ed34b8"]]},{"id":"1a99c7a1.ed34b8","type":"gpio out","z":"1ee78c77.4f0c84","name":"arduino","state":"OUTPUT","pin":"13","i2cDelay":"0","i2cAddress":"","i2cRegister":"","outputs":0,"board":"4a53a7ab.eb3e7","x":1475.7666015625,"y":242.11666870117188,"wires":[]},{"id":"fd89d95c.482bb","type":"switch","z":"1ee78c77.4f0c84","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"1","vt":"num"}],"checkall":"true","outputs":1,"x":1043.7666015625,"y":243.76663208007812,"wires":[["7ec1525b.cd9fac"]]},{"id":"4a53a7ab.eb3e7","type":"nodebot","z":"","name":"","username":"","password":"","boardType":"firmata","serialportName":"/dev/ttyUSB0","connectionType":"local","mqttServer":"","socketServer":"","pubTopic":"","subTopic":"","tcpHost":"","tcpPort":"","sparkId":"","sparkToken":"","beanId":"","impId":"","meshbluServer":"https://meshblu.octoblu.com","uuid":"","token":"","sendUuid":""}]
Le switch sera paramétré de la manière suivante : | Le trigger : |
Le nœud gpio quant à lui sera paramétré pour piloter une broche digital d’une carte Arduino. La carte Arduino sera programmée avec le programme Firmata : (Menu fichier > Exemples > Firmata > StandardFirmata ) puis connectée à la carte Raspberry Pi par un câble USB.
Editer le nœud gpio puis cliquer sur le crayon afin de définir la cible : |
Cliquer sur la loupe afin de trouver la carte Arduino connectée à la carte Raspberry (dans l’exemple c’est /dev/tty/USB0, mais cela peut être autre chose…). Configurer le reste comme suit : |
Compléter alors le nœud gpio comme suit : |
Si la configuration est correcte la broche 13 (et la led qui y est rattachée) passe à l’état logique « 1 » pour une durée de 2 sec lorsqu’une plaque est reconnue. Reste à interconnecter l’ensemble à la vraie barrière…
voir : Gestion de mongoDB à l’aide de jsGrid et node-red
il est disponible ici : http://flows.nodered.org/flow/e32f2b942bce77ef6079c0642b93c036
Ce flow fabrique une interface homme machine qui permet de maintenir la base de données des utilisateurs autorisés à accéder au parking. Il s’appuie sur un template html et le plugin jsGrid (dépend de jQuery). Son fonctionnement est dit « RESTfull » c’est-à-dire que la grille utilise une requête ajax de type POST pour insérer des valeurs dans la base, une requête de type PUT pour effectuer la mise à jour et une requête DELETE pour effacer un document. Pour fonctionner ce flow a besoin du node « ObjectId », qu’il faut installer si ce n’est pas déjà fait.
Le format des documents doit être :
{ "_id": ObjectID("585a4732ac11400192a60b85"), "nom": "nameOfUser", "prenom": "FirstNameOfUser", "immatriculation": "123456", "heure": "13", "minute": "30" }
La grille est accessible à l’adresse : http://<node-red>:<port>/plates
Le code du node template est le suivant :
<!DOCTYPE html> <html lang="fr"> <head> <title>Licence Plate</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-timepicker/0.5.2/css/bootstrap-timepicker.min.css" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.css" /> <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid-theme.min.css" /> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.js"></script> <script type="text/javascript"> $(function () { var db = {{#payload}}{{{.}}}{{/payload}}; $("#jsgrid").jsGrid({ width: "100%", inserting: true, editing: true, sorting: true, paging: true, data: db, fields: [ { title:"Nom", name: "nom", type: "text", width: 50 }, { title:"Prénom", name: "prenom", type: "text", width: 50 }, { title:"Plaque", name: "immatriculation", type: "text", width: 50 }, { title:"Heure", name: "heure", type:"number", width: 25}, { title:"Minute", align:"left", name: "minute", type:"number" , width: 25}, { type: "control" } ], controller: { insertItem: function(item) { return $.ajax({ type: "POST", url: "/insert", data: item }); }, updateItem: function(item) { return $.ajax({ type: "PUT", url: "/update", data: item }); }, deleteItem: function(item) { return $.ajax({ type: "DELETE", url: "/delete", data: item }); } } }); }); </script> </head> <body class="container"> <section class="row"> <div class="col-md-6"></div> <div class="col-md-6" id="jsgrid"> </div> </section> </body> </html>
La ligne var db = {{#payload}}{{{.}}}{{/payload}};
utilise la notation « mustache » c’est la notation qui permet de à la page web de récupérer les informations contenues dans le message d’entrée du node. Dans notre cas msg.payload est le résultat de la requête effectuée dans la base de données. Il s’agit d’un tableau d’objets JSON le format est le suivant :
[ { "nom": "TestName", "prenom": "Toto", "immatriculation": "AV865XG", "_id": "58667499d93f5401a2b4d2d5" }, { "_id": "586666b73a3b9e2011963257", "nom": "Hollande", "prenom": "Francois", "immatriculation": "HE123LY" } ]
Le moteur de template mustache va itérer le tableau et passer les objets qu’il contient au rendu de la page web
Lors du rendu de la page, le texte du template
… $(function () { var db = {{#payload}}{{{.}}}{{/payload}}; $("#jsgrid").jsGrid({ width: "100%", inserting: true, …
est remplacé par
… $(function () { var db = [{"nom":"TestName","prenom":"Toto","immatriculation":"AV865XG","_id":"58667499d93f5401a2b4d2d5"},{"_id":"586666b73a3b9e2011963257","nom":"Hollande","prenom":"Francois","immatriculation":"HE123LY"}]; $("#jsgrid").jsGrid({ width: "100%", inserting: true, …
{{{.}}}
(Avec triple « mustache ») signifie que l’objet entier doit être passé de manière brute, c’est-à-dire sans être mise au format html
Avec une {{.}}
(double mustache) le texte serait remplacé par
var db = [{"nom":"TestName","prenom"…
c’est-à-dire que les « ” » sont remplacés par leur code html « " ; » de ce fait la variable « db » n’est plus un tableau JSON au vu du script.
Voir https://github.com/janl/mustache.js/#non-empty-lists et https://github.com/janl/mustache.js/#variables pour plus d’informations.
L’instrumentation consiste ici à équiper un véhicule électrique de sondes afin de mesurer la vitesse, la température du moteur ainsi que sa vitesse de rotation. Les sondes sont construites autour de cartes Arduino. Les cartes Arduino sont reliées à un bus RS485. Une application Node-red embarquée dans un Raspberry Pi permet de communiquer via le protocole modBus avec les cartes Arduino. Les données recueillies sont affichées sur un tableau de bord : une page web développée elle aussi avec Node-red équipée de jauges.
Une Carte Raspberry équipée de node-red se comporte comme un maitre modBus. Une carte Arduino effectue une mesure et se comporte comme un esclave modBus. Le maitre interroge cycliquement la carte Arduino qui envoie alors ses résultats. La trame modBus est constituée d’un paquet de 7 données (voir le tableau dans le code Arduino). Node-red récupère ces données et les encapsule dans un objet json qui est stocké dans le contexte du flux. Une page web est fabriquée à l’aide de node-red cette page affiche une jauge grâce au script sonicGauge. Des requêtes Ajax à intervalles réguliers récupèrent l’objet stocké dans le contexte, ce qui permet la mise à jour de la jauge en temps réel.
La playlist suivante contient 4 vidéos :
La bibliothèque ModbusRtu.h est accessible ici : https://github.com/smarmengol/Modbus-Master-Slave-for-Arduino
Le code Arduino simule une acquisition de température allant de 0 à 42°C ce manière cyclique.
#include <ModbusRtu.h> #include <SoftwareSerial.h> #define SERIAL 0 #define MODBUS_ADR 1 #define TXEN 3 #define ID 0xCAFE #define UNITE 0x09 // #define TYPE 0x03 #define MIN 0 #define MAX 100 // data array for modbus network sharing // format : {Id_appareil,id_unite,id_type,min,max,valEntiere,valDeci} // // id_appareil : numéro unique /* ╔═════════╦════════════════╦══════════╦═════════╦═════╦═════╦════════════╦═════════╗ ║ index ║ 0 ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 6 ║ ╠═════════╬════════════════╬══════════╬═════════╬═════╬═════╬════════════╬═════════╣ ║ trame ║ id_appareil ║ id_unite ║ id_type ║ min ║ max ║ valEntiere ║ valDeci ║ ╠═════════╬════════════════╬══════════╬═════════╬═════╬═════╬════════════╬═════════╣ ║ exemple ║ 51966 (0xCAFE) ║ 9 ║ 3 ║ 5 ║ 95 ║ 33 ║ 90 ║ ╚═════════╩════════════════╩══════════╩═════════╩═════╩═════╩════════════╩═════════╝ tableau des unité : ╔══════════╦══════╦═════╦══════╦═══╦════╦═══╦═══╦═════════╦═══════════╦════╗ ║ id_unite ║ 0 ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 6 ║ 7 ║ 8 ║ 9 ║ ╠══════════╬══════╬═════╬══════╬═══╬════╬═══╬═══╬═════════╬═══════════╬════╣ ║ unité ║ sans ║ m/s ║ Km/h ║ m ║ km ║ s ║ h ║ Tours/s ║ Tours/min ║ °C ║ ╚══════════╩══════╩═════╩══════╩═══╩════╩═══╩═══╩═════════╩═══════════╩════╝ tableau des types : ╔═════════╦══════╦══════╦═════╦═════════╗ ║ id_type ║ 0 ║ 1 ║ 2 ║ 3 ║ ╠═════════╬══════╬══════╬═════╬═════════╣ ║ Type ║ Char ║ Byte ║ Int ║ decimal ║ ╚═════════╩══════╩══════╩═════╩═════════╝ */ // min : valeur minimum // max : valeur max // valEntiere: valeur entière // valDeci : valeur decimale uint16_t mesure[] = { ID, UNITE, TYPE, MIN, MAX, 0, 0 }; /** * Modbus object declaration * u8id : node id = 0 for master, = 1..247 for slave * u8serno : serial port (use 0 for Serial) * u8txenpin : 0 for RS-232 and USB-FTDI * or any pin number > 1 for RS-485 */ Modbus slave(MODBUS_ADR,SERIAL,TXEN); // this is slave @1 and RS-232 or USB-FTDI unsigned long previousMillis = 0; // will store last time LED was updated // constants won't change : const long interval = 75; void setup() { slave.begin( 19200 ); // baud-rate at 19200 } void loop() { static long temp = 0; unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { // save the last time you blinked the LED previousMillis = currentMillis; temp += 10; int entier = temp / 100; int decimal = temp - (entier *100); mesure[5] = entier; mesure[6] = decimal; if (temp>4200) { temp = 0; } } slave.poll( mesure,sizeof(mesure) ); }
Fichier JS du nœud “data2obj”
/* ╔═════════╦════════════════╦══════════╦═════════╦═════╦═════╦════════════╦═════════╗ ║ index ║ 0 ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 6 ║ ╠═════════╬════════════════╬══════════╬═════════╬═════╬═════╬════════════╬═════════╣ ║ trame ║ id_appareil ║ id_unite ║ id_type ║ min ║ max ║ valEntiere ║ valDeci ║ ╠═════════╬════════════════╬══════════╬═════════╬═════╬═════╬════════════╬═════════╣ ║ exemple ║ 51966 (0xCAFE) ║ 9 ║ 3 ║ 5 ║ 95 ║ 33 ║ 90 ║ ╚═════════╩════════════════╩══════════╩═════════╩═════╩═════╩════════════╩═════════╝ tableau des unité : ╔═══════╦══════╦═════╦══════╦═══╦════╦═══╦═══╦═════════╦═══════════╦════╗ ║ index ║ 0 ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ 6 ║ 7 ║ 8 ║ 9 ║ ╠═══════╬══════╬═════╬══════╬═══╬════╬═══╬═══╬═════════╬═══════════╬════╣ ║ unité ║ sans ║ m/s ║ Km/h ║ m ║ km ║ s ║ h ║ Tours/s ║ Tours/min ║ °C ║ ╚═══════╩══════╩═════╩══════╩═══╩════╩═══╩═══╩═════════╩═══════════╩════╝ tableau des types : ╔═══════╦══════╦══════╦═════╦═════════╗ ║ index ║ 0 ║ 1 ║ 2 ║ 3 ║ ╠═══════╬══════╬══════╬═════╬═════════╣ ║ Type ║ Char ║ Byte ║ Int ║ decimal ║ ╚═══════╩══════╩══════╩═════╩═════════╝ par exemple data = [ 51966, 9, 3, 5, 95, 33, 90 ] id = 51966 = 0xCAFE unité = 9 => °C type = 3 => decimal min = 5 => minimum indiqué sur la gauge max = 95 => max indiqué sur la gauge valEntiere = 33 et vaDeci = 90 => tempétature = 33.90°C format de l'objet de sortie : 4 propriétés : unite, valeur, min et max { "unite": "°C", "valeur": 33.9, "min": 5, "max": 95 } */ var data = msg.payload; var obj={}; var unite = ['','m/s','km/h','m','km','s','h','tours/s','tours/min','°C']; if(data[0]==0xCAFE) { obj.unite=unite[data[1]]; if(data[2]==3){ obj.valeur= (data[5]*100 + data[6])/100.0; } obj.min=data[3]; obj.max=data[4]; } flow.set('message',obj); msg.payload=obj; return msg;
Fichier html du noeud “html”
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script> <script>{{{script}}}</script> <div id='thermometre' class="gauge"></div> <script> var thermometre= $('#thermometre').SonicGauge(); $.getJSON( "/data", function(data){ var options ={}; options.label= data.unite; options.start = {angle: -225, num: data.min}; options.end = {angle: 45, num: data.max}; thermometre.SonicGauge('setOptions', options); thermometre.SonicGauge ('draw'); }); setInterval(function () { $.getJSON( "/data", function(data){ thermometre.SonicGauge('val',data.valeur); }); },200); </script>
Script SonicGauge à coller dans le nœud “script”
/** * Sonic Gauge jQuery Plugin v0.3.0 * jQuery plugin to create and display SVG gauges using RaphaelJS * * Copyright (c) 2013 Andy Burton (http://andyburton.co.uk) * GitHub https://github.com/andyburton/Sonic-Gauge * * Licensed under the MIT license (http://andyburton.co.uk/license/mit.txt) */ (function(b){var a={init:function(c){if(!this.length){return this}this.options=b.extend(true,{},b.fn.SonicGauge.defaultOptions);this.settings={};this.SonicGauge("setOptions",c);this.SonicGauge("draw");return this},setOptions:function(c){if(c){b.extend(true,this.options,c)}this.settings.canvas_d=this.options.diameter;this.settings.canvas_r=this.settings.canvas_d/2;this.settings.speedo_d=this.settings.canvas_d-this.options.margin*2;this.settings.speedo_r=this.settings.speedo_d/2;this.settings.increment=(this.options.end.angle-this.options.start.angle)/(this.options.end.num-this.options.start.num);return this},draw:function(){var f=this;this.width(this.settings.canvas_d);this.height(this.settings.canvas_d);this.gauge=Raphael(this.attr("id"),this.settings.canvas_d,this.settings.canvas_d);var e=this.gauge.circle(this.settings.canvas_r,this.settings.canvas_r,this.settings.speedo_r).attr(this.options.style.outline);var i=this.settings.canvas_r;var h=this.settings.canvas_r-(this.settings.canvas_r/4);if(typeof this.options.label=="object"){if(this.options.label.margin_x){i+=this.options.label.margin_x}if(this.options.label.margin_y){h+=this.options.label.margin_y}}else{if(typeof this.options.label=="string"){this.options.label={value:this.options.label}}}if(this.options.label){var d=this.gauge.text(i,h,this.options.label.value).attr(this.options.style.label)}this.sectors=[];b.each(this.options.sectors,function(n){this.style=b.extend(true,f.options.style.sector,this.style);if(!(isNaN(this.start)||isNaN(this.end))){var p=f.settings.increment*(this.start-f.options.start.num)+f.options.start.angle;var m=f.settings.increment*(this.end-f.options.start.num)+f.options.start.angle;var j=this.radius?this.radius:f.settings.speedo_r;var q=Math.PI/180;var l=f.settings.canvas_r+j*Math.cos(p*q),k=f.settings.canvas_r+j*Math.cos(m*q),t=f.settings.canvas_r+j*Math.sin(p*q),s=f.settings.canvas_r+j*Math.sin(m*q);var o=f.gauge.path(["M",f.settings.canvas_r,f.settings.canvas_r,"L",k,s,"A",j,j,0,+(m-p>180),0,l,t,"z"]).attr(this.style);f.sectors.push(o)}});var g=[];b.each(this.options.markers,function(){if(this.line){if(!this.line.width){this.line.width=10}if(!this.line.height){this.line.height=1}var v=f.gauge.rect(f.settings.canvas_r+f.settings.speedo_r-this.line.width,f.settings.canvas_r-Math.floor(this.line.height/2)).attr(this.line).hide()}var n=1;while(this.gap<1){n*=10;this.gap*=10}var j=f.options.start.num*n;var l=f.options.end.num*n;for(var o=j;o<=l;o+=this.gap){var k=n>1?o/n:o;if(this.toFixed){k=k.toFixed(this.toFixed)}if(this.toPrecision){k=k.toPrecision(this.toPrecision)}var t=f.settings.increment*(k-j)+f.options.start.angle;if(t+Math.abs(f.options.start.angle)>=360){t=(t+Math.abs(f.options.start.angle))%360+f.options.start.angle}if(b.inArray(t,g)>=0){continue}g.push(t);if(this.line){var p=v.clone().rotate(t,f.settings.canvas_r,f.settings.canvas_r)}if(this.text){if(!this.text.space){this.text.space=0}var m=k;if(typeof this.value=="object"){if(this.value.divide){m/=this.value.divide}if(this.value.multiply){m*=this.value.multiply}}var q=t.toRadians();var s=f.settings.canvas_r+(this.text.space+f.settings.speedo_r)*Math.cos(q);var r=f.settings.canvas_r+(this.text.space+f.settings.speedo_r)*Math.sin(q);var u=f.gauge.text(s,r,m).attr(this.text)}}if(this.line){v.remove()}});if(this.options.digital){this.digital=b("<div>").addClass("digital").css({"margin-top":Math.ceil(this.settings.speedo_r/2),width:"20%","font-family":"Arial","font-size":20,color:"#fff","text-align":"center",border:"2px solid #777","border-radius":10,padding:5,"background-color":"#111"}).css(this.options.digital).text(this.options.default_num).appendTo(this).center()}this.needles=[];b.each(this.options.needles,function(k){if(!this.default_num){this.default_num=f.options.default_num}this.style=b.extend(true,f.options.style.needle,this.style);var m=this.default_num-f.options.start.num;if(typeof this.value=="object"){if(this.value.divide){m/=this.value.divide}if(this.value.multiply){m*=this.value.multiply}}var j=f.settings.increment*m+f.options.start.angle;var l=f.gauge.rect(f.settings.canvas_r,f.settings.canvas_r,f.settings.speedo_r).attr(this.style).transform("r"+j+","+f.settings.canvas_r+","+f.settings.canvas_r);f.needles.push(l)});if(typeof this.options.style.center=="object"){var c=this.gauge.circle(this.settings.canvas_r,this.settings.canvas_r,this.options.style.center.diameter).attr(this.options.style.center)}this.trigger("drawn");return this},val:function(e){if(this.digital){var c=e;if(this.options.digital_toFixed){c=c.toFixed(this.options.digital_toFixed)}if(this.options.digital_toPrecision){c=c.toPrecision(this.options.digital_toPrecision)}this.digital.text(c)}var d=this;b.each(this.needles,function(h){var f=e;if(typeof d.options.needles[h].value=="object"){var g=d.options.needles[h].value;if(g.divide){f/=g.divide}if(g.multiply){f*=g.multiply}}this.animate({transform:"r"+(d.settings.increment*(f-d.options.start.num)+d.options.start.angle)+","+d.settings.canvas_r+","+d.settings.canvas_r},d.options.animation_speed)});this.trigger("update",e);return this}};b.fn.SonicGauge=function(c){if(a[c]){return a[c].apply(this,Array.prototype.slice.call(arguments,1))}else{if(typeof c==="object"||!c){return a.init.apply(this,arguments)}else{b.error("Method "+c+" does not exist on jQuery.SonicGauge")}}};b.fn.SonicGauge.defaultOptions={margin:35,diameter:350,start:{angle:-225,num:0},end:{angle:45,num:100},default_num:0,animation_speed:1000,digital:{},digital_toFixed:0,needles:[{}],sectors:[{}],markers:[{gap:10,line:{width:20,stroke:"none",fill:"#eeeeee"},text:{space:22,"text-anchor":"middle",fill:"#333333","font-size":18}},{gap:5,line:{width:8,stroke:"none",fill:"#999999"}}],style:{outline:{fill:"#333333",stroke:"#555555","stroke-width":8},center:{fill:"#eeeeee",diameter:10},needle:{height:1,stroke:"none",fill:"#cc0000"},label:{"text-anchor":"middle",fill:"#fff","font-size":16}}}})(jQuery);if(typeof(Number.prototype.toRadians)==="undefined"){Number.prototype.toRadians=function(){return this*Math.PI/180}}if(typeof(Number.prototype.decimalPlaces)==="undefined"){Number.prototype.decimalPlaces=function(){return(this.toFixed(20)).replace(/^-?\d*\.?|0+$/g,"").length}}if(typeof(jQuery.fn.center)==="undefined"){jQuery.fn.center=function(a){if(typeof a==="undefined"){a=this.parent()}a.css("position","relative");return this.css("position","absolute").css({top:Math.max(0,((a.height()-this.outerHeight())/2)+a.scrollTop()),left:Math.max(0,((a.width()-this.outerWidth())/2)+a.scrollLeft())})}};
Le code complet du flow node-red : http://flows.nodered.org/flow/f0d416178add2b981ac21afeaf0bc0f7
Cet article propose de créer une IHM pour piloter un lecteur de radio internet. Le système est embarqué dans une carte Raspberry Pi équipée de node-red. Nous verrons d’abord comment installer MPD et MPC qui permettent de transformer la carte en lecteur audio. Nous verrons ensuite comment créer une interface web à l’aide de node-red qui permette de piloter le lecteur.
MPD est un lecteur média qui fonctionne comme un service (daemon). Il met en relation des sources de musiques, des codecs et une API de pilotage via le réseau. En fait il se comporte comme un serveur auquel on envoie des commandes de type « play », « stop »,… afin qu’il les exécute.
MPD / MPC[3]
Nous allons installer MDP (Music Player Daemon) qui va servir de lecteur de musique embarqué, ainsi que MPC (Music Player Command) qui servira à piloter MPD (telle une télécommande) :
Pré-requis
Vérifier que le driver audio est correctement installé :
[shell]
lsmod[/shell]
Il doit apparaître le driver snd_bcm2835 dans le résultat. Si ce n’est pas le cas, essayer :
[shell]sudo modprobe snd_bcm2835[/shell]
Forcer la sortie son sur la prise casque :
[shell]amixer cset numid=3 1
sudo alsactl store
[/shell]
Pour que la sortie audio soit toujours la prise Jack (au lieu de la prise HDMI), éditer le fichier /boot/config.txt et dé-commenter la ligne hdmi_drive=2 en enlevant le dièse en début de ligne.
[shell]
# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
hdmi_drive=2
[/shell]
Sous Raspbian stretch, l’installation est facile (après mise à jour des dépôts) :
sudo apt update sudo apt install mpc mpd
Il faut ensuite éditer le fichier /etc/mpd.conf et retirer le commentaire de la ligne “device” dans la section “audio_output”
# An example of an ALSA output: # audio_output { type "alsa" name "My ALSA Device" device "hw:0,0" # optional
il faut ensuite redémarrer MPD :
sudo /etc/init.d/mpd restart
Télécharger le fichier radio.m3u. Ce fichier contient la liste de quelques radios, il peut être édité. Pour ajouter d’autres radios rendez-vous à : www.ecouter-en-direct.com/
Coller le fichier playlist radio.m3u dans /var/lib/mpd/playlists/
Le fichier de configuration de mdp est /etc/mdp.conf. On pourra y paramétrer les dossiers où MPD ira chercher la musique et les playlist par exemple. Une fois les réglages effectués, redémarrer le service : [shell]sudo /etc/init.d/mpd restart[/shell]
charger le fichier playlist : [shell]mpc load radio[/shell]
lancer la diffusion : [shell]mpc play 1[/shell]
La playlist suivante contient 5 vidéos :
Le fichier css pour la mise en forme du tableau:
table { color: #fff; font-family: Helvetica, Arial, sans-serif; width: 100%; border-collapse: collapse; border-spacing: 0; } td, th { border: 1px solid transparent; /* No more visible border */ height: 30px; transition: all 0.3s; /* Simple transition for hover effect */ } th { background: #DFDFDF; /* Darken header a bit */ font-weight: bold; } td { background: #3a3a3a; text-align: center; } /* Cells in even rows (2,4,6...) are one color */ tr:nth-child(even) td { background: #313131; } /* Cells in odd rows (1,3,5...) are another (excludes header cells) */ tr:nth-child(odd) td { background: #3e3e3e; } tr td:hover { background: #F1F1F1; color: #000; }
jsGrid est une bibliothèque jQuery qui permet de gérer un tableau de données et offre les options de CRUD (Create Read Update Delete) au travers de requêtes AJAX. L’objet de l’article consiste à mettre en œuvre ce composant en liaison avec une collection mongoDB, à travers l’interface de développement Node-Red.
La playlist suivante contient 5 vidéos :
Mongodb est un moteur de base de données noSQL (not only SQL) orienté documents. Ce genre de base de données est structurée autour de classeurs qu’on nomme “collections”, chaque collection contenant des documents. Les documents sont indexés afin de pouvoir y accéder rapidement. Les bases de données noSQL favorisent la vitesse d’accès aux données, au détriment parfois de la compacité. Nous verrons ici comment installer mongoDB sur Raspberry Pi, puis comment l’utiliser au sein de Node-Red.
Installation du nœud mongodb2 et insertion d’un document dans une collection par injection :
Création d’un formulaire d’insertion de données à l’aide des nœuds Dashboard.
Pour mongoDB c’est plutôt simple :
sudo apt-get install mongodb
On peut vérifier que cela fonctionne :
Démarrage du service mongodb
$ sudo /etc/init.d/mongodb start
Le serveur mongo db démarre normalement à l’adresse 127.0.0.1 :27017
$ mongo
Ça lance une console
> use myDB > db.LicencePlate.insert({})
Confirmation de l’existence de la base et de la collection :
> show dbs > show collections
Mongo-express est une interface utilisateur permettant de manipuler mongoDB de manière visuelle. Il faut que node-red et npm soit installés (voir le document d’installation de node-red avec Johnny5)
sudo npm install -g mongo-express
Par défaut le login et le mot de passe sont admin et pass cela peut être changé en modifiant le fichier de configuration. Sur raspberry le fichier de configuration se trouve dans /usr/lib/node_modules/mongo-express
Il faut recopier en le renommant config.js puis l’éditer:
sudo cp /usr/lib/node_modules/mongo-express/config.default.js /usr/lib/node_modules/mongo-express/config.js sudo nano /usr/lib/node_modules/mongo-express/config.default.js
Si la base myDB a été créée (voir l’installation de mongodb et les commandes shell), il faut modifier le fichier config.js :
// Accessing Bluemix variable to get MongoDB info if (process.env.VCAP_SERVICES) { var dbLabel = 'mongodb-2.4'; var env = JSON.parse(process.env.VCAP_SERVICES); if (env[dbLabel]) { mongo = env[dbLabel][0].credentials; } } else { mongo = { db: 'myDB', host: 'localhost', // password: 'pass', port: 27017, ssl: false, url: 'mongodb://localhost:27017/db', // username: 'admin', }; }
Si on veut accéder à l’interface utilisateur depuis un autre ordinateur que le raspberry, dans la section « site », il faut renseigner l’adresse IP du raspberry à la place de localhost et modifier le login et le mot de passe:
site: { // baseUrl: the URL that mongo express will be located at - Remember to add the forward slash at the start and end! baseUrl: process.env.ME_CONFIG_SITE_BASEURL || '/', cookieKeyName: 'mongo-express', cookieSecret: process.env.ME_CONFIG_SITE_COOKIESECRET || 'cookiesecret', host: process.env.VCAP_APP_HOST || '0.0.0.0', //connexion quelle que soit l’adresse de la carte Raspberry port: process.env.VCAP_APP_PORT || 8081, //port du serveur web requestSize Limit: process.env.ME_CONFIG_REQUEST_SIZE || '50mb', sessionSecret: process.env.ME_CONFIG_SITE_SESSIONSECRET || 'sessionsecret', sslCert: process.env.ME_CONFIG_SITE_SSL_CRT_PATH || '', sslEnabled: process.env.ME_CONFIG_SITE_SSL_ENABLED || false, sslKey: process.env.ME_CONFIG_SITE_SSL_KEY_PATH || '', }, //set useBasicAuth to true if you want to authenticate mongo-express loggins //if admin is false, the basicAuthInfo list below will be ignored //this will be true unless ME_CONFIG_BASICAUTH_USERNAME is set and is the empty string useBasicAuth: process.env.ME_CONFIG_BASICAUTH_USERNAME !== '', //useBasicAuth: false, basicAuth: { username: process.env.ME_CONFIG_BASICAUTH_USERNAME || 'admin', password: process.env.ME_CONFIG_BASICAUTH_PASSWORD || 'pass', },
Ensuite on peut lancer mongo-express :
cd /usr/lib/node_modules/mongo-express/ && node app.js
ou plus simplement
$ mongo-express
Lancer un navigateur http://<adresse_IP_Raspberry>:8081 puis s’authentifier avec le l’identifiant et le mot de passe choisit précédemment :
Page d’accueil de mongo express | Collections de la base myDB(cliquer sur « View » sur la page d’accueil pour voir les collections) |
Documents de la collection « LicencePlate », on voit qu’une plaque d’immatriculation est inscrite (AV865XG) | Cliquer sur « New Document » pour créer une nouvelle plaque d’immatriculation : ajouter “immatriculation”:”XX123YY”, avant “_id”: ObjectID() puis cliquer sur Save. |
En cliquant sur le numéro de plaque on peut modifier le document, en cliquant sur le bouton rouge représentant une poubelle, on peut effacer ce document.
OpenALPR est un logiciel de reconnaissance de plaque d’immatriculation. Ce logiciel s’appuie sur la bibliothèque openCV et le logiciel de reconnaissance de caractères tesseract.
On trouve sur les forums tout ce qu’il faut pour télécharger, compiler et installer ce package depuis les sources. L’inconvénient est que la compilation sur raspberry est très longue. Nous proposons ici une version packagée de openALPR, ce qui réduit considérablement le temps d’installation.
sudo apt-get update sudo apt-get install liblog4cplus-1.0-4 beanstalkd libgtk2.0-0 libtiff5 libdc1394-22 libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libxvidcore-dev libx264-dev wget https://www.rgot.org/wp-content/uploads/2016/12/openalpr.tgz tar -zxvf openalpr.tgz sudo dpkg -i openalpr_20161219-1-complet_armhf.deb rm openalpr_20161219-1-complet_armhf.deb rm openalpr.tgz
Pour déinstaller :
sudo dpkg -r openalpr
il suffit de suivre les instructions disponibles sur le github de openalpr
sudo apt-get update && sudo apt-get install -y openalpr openalpr-daemon openalpr-utils libopenalpr-dev
La version intégrée de node-red permet de s’initier mais ne permet pas d’utiliser la bibliothèque raspi-io utilisée par le raspberry pour communiquer avec l’extérieur (problème de droits d’accès aux broches du raspi).
Pour pouvoir communiquer avec l’extérieur (capteurs, afficheurs, leds…) il faut donc réinstaller node.js et node-red.
Télécharger le fichier install_johnny5 , le rendre exécutable puis le lancer:
wget https://www.rgot.org/wp-content/uploads/2016/12/install_johnny5.txt sudo chmod 755 install_johnny5.txt ./install_johnny5.txt
autre méthode : exécuter les lignes suivantes dans une console. (démarche issue de la note d’installation de node-red : http://nodered.org/docs/hardware/raspberrypi )
sudo apt-get update sudo apt-get remove nodered sudo apt-get remove nodejs nodejs-legacy curl -sL https://deb.nodesource.com/setup_4.x | sudo bash - sudo apt-get install -y build-essential python-rpi.gpio nodejs sudo npm cache clean sudo npm install -g --unsafe-perm node-red sudo apt-get update && sudo apt-get install python-rpi.gpio sudo wget https://raw.githubusercontent.com/node-red/raspbian-deb-package/master/resources/nodered.service -O /lib/systemd/system/nodered.service sudo wget https://raw.githubusercontent.com/node-red/raspbian-deb-package/master/resources/node-red-start -O /usr/bin/node-red-start sudo wget https://raw.githubusercontent.com/node-red/raspbian-deb-package/master/resources/node-red-stop -O /usr/bin/node-red-stop sudo chmod +x /usr/bin/node-red-st* sudo systemctl daemon-reload sudo npm i -g npm@2.x
Pour les raspi 2 et 3 il faut aussi exécuter les lignes suivantes pour installer raspi-io (gestion des entrées/sorties) et johnny5 (bibliothèque permettant la programmation d’automates)
sudo npm install -g johnny-five --unsafe-perm --force sudo npm install -g raspi-io --unsafe-perm --force sudo npm install -g node-red-contrib-gpio --unsafe-perm --force sudo npm install serialport --unsafe-perm --build-from-source sudo npm i -g oled-js oled-font-5x7
Éditer le fichier /lib/systemd/system/nodered.service
et remplacer USER=pi
par USER=root
ainsi la commande node-red-start lancera node-red en mode root (dans le dossier /root/.node-red
)
Depuis la dernière version de Raspbian Jessie (nov 2016) ssh est déactivé par défaut. Il faut donc se connecter via l’interface graphique et activer ssh dans la configuration de raspberry ( bouton framboise -> Préférences -> configuration du Raspberry Pi puis dans l’onglet “Interfaces” cocher SSH)
Si on possède une carte Raspberry qui n’est pas connectée à un ensemble clavier/souris/écran, il semble impossible de se connecter…
La solution consiste à ajouter un fichier (vide) nommé ssh dans la partition boot (c’est la partition visible lorsque la carte SD est introduite dans un PC Windows).
Au premier boot Raspberry détecte le fichier ssh, active le protocole ssh et efface le fichier ssh sur la partition boot.