Contrôle d’accès par lecture de plaques d’immatriculation (ALPR)

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)

Diagramme de cas d’utilisation

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.

Constitution du système

  • Une caméra IP, elle sera positionnée afin de capter la plaque d’immatriculation du véhicule lorsque ce dernier de présentera à l’entrée du parking.
  • Une barrière automatisée. Elle est fermée par défaut et s’ouvre si la plaque du véhicule est reconnue.
  • Un ordinateur qui contient la base de données des plaques autorisées, l’IHM web permettant la maintenance de la BDD, un logiciel de reconnaissance de plaques en relation avec la caméra IP et un interlogiciel (middleware) permettant de faire communiquer les différentes applications.

Topologie du système

Solution retenue

Matériels

  • Une carte Raspberry (2 ou 3) / 16GB raspbian JESSIE (nov 2016) -> serveur
  • Une carte Arduino avec le firmware Firmata Standard -> extension E/S
  • Une caméra IP. On doit pouvoir accéder à l’image via le web. Pour l’exemple nous utiliserons une petite caméra motorisée db power clone d’une caméra FOSCAM FI8908W[1]. Sur cette caméra, d’après la documentation[2], l’accès au flux s’effectue à l’URL : http://<IP_CAMERA>:<PORT_CAMERA>//videostream.cgi?user=admin&pwd=&resolution=32&rate=0
  • Une barrière automatique pilotée par un signal logique (« 1 » ouverture, « 0 » fermeture). La barrière gère elle-même la sécurité de passage.

Logiciels

  • openALPR pour Raspberry : logiciel open source de reconnaissance de plaques d’immatriculation.
  • MongoDB et mongoExpress : serveur de base de données et client web.
  • NODE-RED avec node-red-contrib-gpio, node-red-dashboard, node-red-contrib-mongoDB2, ObjectID et mongoDB2 : logiciel de programmation orienté flux, permettant l’interconnexion d’applications hétérogènes devant échanger des données.

Installation Logicielle.

Node-red

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

OpenAlpr

voir openAlpr sur Raspberry Pi

Base de données mongoDB

voir installation de mongoDB et mongo-express

Mise en oeuvre.

Test de openALPR avec la caméra

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 -fet 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.

 

OpenALPR et node-red : lecture et affichage d’une plaque.

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érification du fichier fichier /etc/openalpr/alprd.conf

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 node-red

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

Utilisation de mongoDB et ALPR dans node-red

L’objectif est de comparer la plaque lue par alprd avec une plaque contenue dans la base de données.

Ajout du node mongoDB2 à la palette de nodes :

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

code du Flow (à importer via le clipboard)
[{"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é.

code du Flow (à importer via le clipboard)
[{"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 ».

code du Flow (à importer via le clipboard)
[{"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…

Outil d’administration de la base de donnée

voir : Gestion de mongoDB à l’aide de jsGrid et node-red

code du Flow (à importer via le clipboard)

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.

capture

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 = [{&quot;nom&quot;:&quot;TestName&quot;,&quot;prenom&quot…

c’est-à-dire que les « ” » sont remplacés par leur code html « &quot ; » 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.

  1. http://www.gadgetvictims.com/2008/08/foscam-fi8908w-firmware-history-page.html
  2. https://docs.google.com/fileview?id=0B9kFXSWngqLWYjBlYjcyOWUtYzgxYy00MWYxLThhYjAtOTgwODgzOTRmOGM5&hl=en

jQuery et AJAX

Gérer une liste à l’aide de jQuery

La page que nous allons utiliser ressemble à celle-ci. Il s’agit d’une liste de véhicules ayant participé au challenge EducEco en 2012. Chaque véhicule est accompagné de sa vignette. La page est construite avec le framework Bootstrap. La barre de menu supérieure permet changer la couleur de fond une ligne sur deux, de mettre le nom de la voiture en vert. Le premier lien quant à lui permet de restituer l’état initial.

Dans ce document la liste est statique, c’est à dire qu’elle est inscrite “en dur” dans le code html, ce qui n’est pas très pratique si on désire faire une mise à jour (par exemple rajouter ou enlever un véhicule de la liste). La page statique est test-liste-educEco (pour la télécharger faire un clic droit et enregistrer la cible du lien sous…)

AJAX[1]

L’idée va donc être de séparer les données de la page html. La page html assure la mise en forme, les données, elles seront contenues dans un fichier séparé au format json (fichier de données javascript). C’est un script jQuery qui va formuler une requête asynchrone (AJAX) afin de récupérer le jeu de données et intégrer ces dernières dans la page html.

Utilisation de JQUERY pour manipuler des jeux de données

Jquery peut effecteur des requêtes de type AJAX afin de récupérer des jeux de données puis les injecter dans la page web. Le diagramme de séquence est le suivant :

Le client initialise la requête web, le code HTML est alors renvoyé. Le code HTML contient des liens vers les feuilles de styles CSS et les scripts JS, ce qui provoque d’autres requêtes. Une fois le(s) script(s) rapatrié(s), il(s) s’exécute(nt). Un script peut effectuer des requêtes asynchrones auprès du serveur (c’est-à-dire en tâche de fond). Cela permet de mettre à jour la page sans la recharger complètement ce qui apporte un confort visuel pour l’utilisateur et une moindre occupation de la bande passante

Format d’une requête AJAX dans JQUERY :


$(document).ready(function () {
$.ajax({
type: "GET", // type de requête : GET ou POST
url: "http://.../data.json", // l'URL pour accéder aux données
dataType: "json", // le format des données JSON ou XML
success: parseData, // fonction appelée en cas de succès les données seront passée en argument de cette fonction
error: function (jqXHR, textStatus, errorThrown) { // fonction traitée en cas d'erreur
alert(textStatus);
alert(errorThrown);
}
});

function parseData(data) {…} // l’argument data correspond au jeu de données en retour de la requête AJAX
});

La fonction parseData récupère en argument les données au format JSON issu de la requête asynchrone. Et peut donc modifier le document HTML à l’aide de ce jeu de données.

Imaginons que le jeu de données JSON soit de la forme :

[
{
"Numero": "4",
"Nomvehicule": "MIRELEC09",
"Ville": "Mirepoix",
"Groupe": "EcoCitadin",
"Motorisation": "Electrique"
},
{
"Numero": "5",
"Nomvehicule": "Vincicar",
"Ville": "Blois",
"Groupe": "EcoCitadin",
"Motorisation": "Electrique"
},…
]

On voit qu’il s’agit d’un tableau (balises « [] ») contenant des objets JSON (balises « {} »). Chaque objet possède les propriétés “Numero”, “Nomvehicule”, “Ville”, “Groupe” et “Motorisation”. Si on désire afficher tous les éléments du tableau, il faut prendre les objets un par un et afficher chacune des propriétés. Pour cela, on peut s’aider de la boucle « for/in » propre au langage JS : Cette boucle peut itérer tous les index d’un tableau ou d’un objet.

function parseData(data) {
for (var i in data) {
$("#content").append(
"<li class='col-md-6' data-filter=" + data[i].Groupe + ">"
+ "<div class='vignette'>"
+ "<a href='#'>"
+ "<img width='150' height='80' alt='Photo vehicule' src='http://www.rgot.org/wp-content/uploads/img/" + data[i].Numero + ".jpg'>"
+ "</a>"
+ "<h4>"
+ "<a href='#'>" + data[i].Nomvehicule + "</a>"
+ "</h4>"
+ "<h4>" + data[i].Motorisation + "</h4>"
+ "<p>" + data[i].Ville + "</p>"
+ "</div>"
+ "</li>"
);
};
};

Dans le code précédent « data[i] » représente l’objet de position « i » dans le tableau.
La page web et le fichier json sont téléchargeables ici :
jquery manipulation de données

  1. Acronyme d’Asynchronous JAvascript and Xml, AJAX  permet en autreMise à jour d’une page web sans avoir à la recharger.Demander (requête) des données au serveur, après chargement de la page.Recevoir des données du serveur, après chargement de la page.Envoyer des données au serveur en tâche de fond.

jQuery

Scripts coté client : JS / JQUERY

JavaScript est un langage de scripts qui peuvent s’exécuter depuis le navigateur. Les scripts permettent d’agrémenter l’expérience de l’utilisateur (effet visuels, vérification d’une saisie…) , ils peuvent interagir avec le DOM afin de modifier la mise en page (ajout/masquage de textes, modification dynamique de la mise en page, interaction avvec les feuilles CSS…). Les scripts peuvent aussi effectuer des requêtes asynchrones (c’est-à-dire des requêtes qui n’ont pas lieux en même temps que le chargement de la page) afin de compléter un contenu de page ( effet de lazy loading (chargement différé) pour les images…).

Javascript a un formaliste proche du langage C/C++. Mais c’est un langage faiblement typé (pas besoin de déclaré le type d’une variable lors de sa création) ce qui semble alléchant à priori, mais se révèle très lourd à l’usage. Par exemple maVariable=0000 : le moteur sera incapable de savoir si 0 est un nombre ou une chaine de caractères…

Depuis quelques années on voit apparaitre des frameworks (structure ou environnement logiciel) JavaScript. Ces frameworks regroupent des bibliothèques JavaScript créées pour faciliter l’écriture de scripts côté client dans le code HTML des pages web. Une des plus connue est jQuery.

La bibliothèque jQuery contient notamment les fonctionnalités suivantes :

  • Parcours et modification du DOM (y compris le support des sélecteurs CSS 1 à 3 et un support basique de XPath) ;
  • Événements ;
  • Effets visuels et animations ;
  • Manipulations des feuilles de style en cascade (ajout/suppression des classes, d’attributs…) ;
  • Ajax (requêtes asynchrones formulées par le navigateur pour, par exemple, récupérer un jeu de données ;
  • Plugins ;
  • Utilitaires (version du navigateur web…).

1. bases de jquery

2. Notes de cours

Fondamentalement le Framework jQuery est un script JavaScript. Pour l’utiliser il suffit juste d’intégrer ce script dans la page html. Pour cela il faut télécharger jQuery (https://jquery.com/download/ ), placer le script dans le dossier du site (l’idéal est de créer un sous dossier nommé JS et qui contiendra tous les fichiers de script).

Dans la page html, il suffit alors de charger ce script et, pour pouvoir utiliser jQuery, on rajoute le lien vers le fichier qui contiendra les scripts actifs sur la page (demo.js dans l’exemple ci-dessous).

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title></title>
    <!-- déclaration du moteur jQuery. Dans cet exemple il se nomme jquery.js et est localisé dans le dossier JS-->
    <script src="Scripts/jquery-1.9.1.min.js"></script>
    <!-- script de la page-->
    <script>
        $(function () {
            //ici on place les fonctions qui permettent de personnaliser la page
            console.log("ready!"); // indication dans la console que jQuery est correctement initialisé
            $("#cachemoi").hide(3000); // au chargement de la page la division d'id "cahemoi" disparait en 3000ms
            //…
        });
    </script>
</head>
<body>
    <div id="conteneur">
        <h1>Le titre de la page</h1>
        <p>bonjour, comment ça va ? Je suis sûr que si tu <span>cliques ici</span> ça fait quelque chose</p>
        <p>2ème paragraphe</p>
        <div id="makemegreen">Fait-moi devenir tout vert</div>
        <div id="cachemoi">Cache moi !</div>
    </div>
</body>
</html> 

 

 

Pour initialiser les scripts de la page le contenu du fichier de script de la page (demo.js) doit avoir la forme suivante :

$(function () {
  //ici on place les fonctions qui permettent de personnaliser la page
  console.log("ready!"); // indication dans la console que jQuery est correctement initialisé
  $("#cachemoi").hide(3000); // au chargement de la page la division d'id "cahemoi" disparait en 3000ms
//…
});

 

Pour plus d’information : http://learn.jquery.com/

    1. Sélection des éléments du DOM

Voir : http://learn.jquery.com/using-jquery-core/selecting-elements/

Les balises html contiennent souvent des attributs. Par exemple un lien hypertexte contient des attributs tels que « href » (lien hypertexte), « title » (info bulle)… jQuery permet d’interroger ces attributs pour lire leur valeur ou pour la changer

Exemple avec la balise img (image)

<img id="greatphoto" src="brush-seller.jpg" alt="brush seller">

L’attribut « alt » (comme alternative) est le texte qui apparait à la place de l’image si cette dernière, pour une quelconque raison, ne peut être affichée.

Avec le jQuery suivant :

$("#greatphoto").attr("alt", "Brush Manquant");

L’image d’id « greatphoto », verra son attribut alt remplacé par « Brush Manquant »

Avec jQuery on peut modifier le contenu de la page

Autre exemple pour remplacer du code html

$("#conteneur").html("<p>Texte de remplacement</p>");

Dans cet exemple le contenu de la div d’id « conteneur » sera remplacé par un paragraphe de texte « Texte de remplacement ».

Si on désire rajouter du texte à la fin d’un paragraphe :

$("#conteneur p:first").append(" texte rajouté à la fin");

jQuery cherche le premier paragraphe contenu dans la div « conteneur » et rajoute à la fin le texte «  texte rajouté à la fin »

De la même manière,

$("#conteneur p:first").prepend("Texte rajouté au début<br/>");

Rajoute du texte au début du même paragraphe

Les évènements permettent de capturer les interactions entre l’utilisateur et le navigateur (déplacement ou clic de souris, saisie clavier…) et d’exploiter ces informations pour modifier le contenu ou l’apparence de la page.

Exemple détection d’un clic de souris :

$(document).keyup(function (e) {
      console.log(e.keyCode); // option de debug : permet d'afficher la valeur de la touche pressée.
         if (e.keyCode == 82) {  // 82 valeur de la touche 'r'
          $("#makemegreen").css("color", "red");// modification de la couleur
          var nouveauTexte = $("#makemegreen").text().replace("vert", "rouge"); // remplacement de  "vert" par "rouge"
          $("#makemegreen").text(nouveauTexte);// affichage du texte remplacé
     };
});

 

Instrumentation modbus

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.

Vidéos

La playlist suivante contient 4 vidéos :

  1. Présentation du projet
  2. Intégration du nœud Slave Modbus, extraction des données et sauvegarde dans le contexte de flux.
  3. Création d’une page web exploitant le contexte de flux à l’aide d’AJAX.
  4. Intégration de sonicGauge pour afficher les valeurs.

Code Arduino

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

Mémento HTML

  1. Balises de premier niveau[1]

    Les balises de premier niveau sont les principales balises qui structurent une page HTML. Elles sont indispensables pour
    réaliser le « code minimal » d’une page web.

    Balise Description
    <html> Balise principale
    <head> En-tête de la page
    <body> Corps de la page

    Code minimal d’une page HTML :

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>Titre</title>
    <!—Facultatif mais très recommandé : l’intégration de scripts et feuilles de styles-->
            <script type="text/javascript" src="http://***** monScript.js"></script>
            <link rel="stylesheet" href="http://******maFeuilleDeStyle.css" />
           <!— Fin de Facultatif -->
        </head>
        <body>
        …
        </body>
    </html>
    

     

  2. Balises d’en-tête

    Ces balises sont toutes situées dans l’en-tête de la page web, c’est-à-dire entre <head> et </head> :

    Balise Description
    <link /> Liaison avec une feuille de style
    <meta /> Métadonnées de la page web (charset, mots-clés, etc.)
    <script> Code JavaScript
    <style> Code CSS
    <title> Titre de la page
  3. Balises sectionnantes[2]

    http://www.alsacreations.com/xmedia/doc/full/schema2.png
    Figure 1 Exemple de construction de page utilisant les balises sectionnantes HTML5 Ces balises permettent de construire le
    squelette de notre site web.

    Balise Description
    <header> En-tête
    <nav> Liens principaux de navigation
    <footer> Pied de page
    <section> Section de page
    <article> Article (contenu autonome)
    <aside> Informations complémentaires
  4. Balises génériques

    Les balises génériques sont des balises qui n’ont pas de sens sémantique. En effet, toutes les autres balises HTML ont un
    sens : <p> signifie « Paragraphe », <h2> signifie « Sous-titre », etc. Parfois, on a besoin d’utiliser
    des balises génériques (aussi appelées balises universelles) car aucune des autres balises ne convient.
    On utilise le plus souvent des balises génériques pour construire son design. Il y a deux balises génériques : l’une est
    inline, l’autre est block.

    Balise Description
    <span> Balise générique de type inline
    <div> Balise générique de type block

    Class id style

    
    Contenu de l'article, contenu de l'article, contenu de l'article, contenu de l'article...
    
    

    class: indique le nom de la classe CSS à utiliser.

    id: donne un nom à la balise. Ce nom doit être unique sur toute la page car il permet d’identifier la balise.
    Vous pouvez vous servir de l’ID pour de nombreuses choses, par exemple pour créer un lien vers une ancre, pour un style CSS
    de type ID, pour des manipulations en JavaScript, etc.

    style: cet attribut vous permet d’indiquer directement le code CSS à appliquer. Vous n’êtes donc pas obligés
    d’avoir une feuille de style à part, vous pouvez mettre directement les attributs CSS. Notez qu’il est préférable de ne pas
    utiliser cet attribut et de passer à la place par une feuille de style externe, car cela rend votre site plus facile à mettre
    à jour par la suite.

  5. Balises de structuration du texte

    Balise Description
    <abbr> Abréviation
    <blockquote> Citation (longue)
    <cite> Citation du titre d’une œuvre ou d’un évènement
    <q> Citation (courte)
    <sup> Exposant
    <sub> Indice
    <strong> Mise en valeur forte
    <em> Mise en valeur normale
    <mark> Mise en valeur visuelle
    <h1> Titre de niveau 1
    <h2> Titre de niveau 2
    <h3> Titre de niveau 3
    <h4> Titre de niveau 4
    <h5> Titre de niveau 5
    <h6> Titre de niveau 6
    <img /> Image
    <figure> Figure (image, code, etc.)
    <figcaption> Description de la figure
    <audio> Son
    <video> Vidéo
    <source> Format source pour les balises <audio> et <video>
    <a> Lien hypertexte
    <br /> Retour à la ligne
    <p> Paragraphe
    <hr /> Ligne de séparation horizontale
    <address> Adresse de contact
    <del> Texte supprimé
    <ins> Texte inséré
    <dfn> Définition
    <kbd> Saisie clavier
    <pre> Affichage formaté (pour les codes sources)
    <progress> Barre de progression
    <time> Date ou heure
  6. Balises de listes

    Cette section énumère toutes les balises HTML permettant de créer des listes (listes à puces, listes numérotées, listes de
    définitions…)

    Balise Description
    <ul> Liste à puces, non numérotée
    <ol> Liste numérotée
    <li> Élément de la liste à puces
    <dl> Liste de définitions
    <dt> Terme à définir
    <dd> Définition du terme
  7. Balises de tableau

    Balise Description
    <table> Tableau
    <caption> Titre du tableau
    <tr> Ligne de tableau
    <th> Cellule d’en-tête
    <td> Cellule
    <thead> Section de l’en-tête du tableau
    <tbody> Section du corps du tableau
    <tfoot> Section du pied du tableau

     

  8. Balises de formulaire

    Balise Description
    <form> Formulaire
    <fieldset> Groupe de champs
    <legend> Titre d’un groupe de champs
    <label> Libellé d’un champ
    <input /> Champ de formulaire (texte, mot de passe, case à cocher, bouton, etc.)
    <textarea> Zone de saisie multiligne
    <select> Liste déroulante
    <option> Élément d’une liste déroulante
    <optgroup> Groupe d’éléments d’une liste déroulante
  1. D’après https://openclassrooms.com/courses/apprenez-a-creer-votre-site-web-avec-html5-et-css3/memento-des-balises-html
    Voir aussi http://s-jdm.developpez.com/tutoriels/html/traductions/presentation-html5/
    et http://tcuvelier.developpez.com/tutoriels/web-semantique/html5-microdonnees/introduction/
  2. http://www.alsacreations.com/article/lire/1376-html5-section-article-nav-header-footer-aside.html

Memento CSS

CSS Cascade Style Sheet (feuille de style en cascade)

  1. Propriétés de mise en forme du texte

    Qu’est-ce que la mise en forme de texte ? C’est tout ce qui touche à la présentation du texte proprement dit : le gras, l’italique, le souligné, la police, l’alignement, etc.

    Propriété Valeurs (exemples) Description
    font-family police1, police2, police3, serif, sans-serif, monospace Nom de police
    @font-face Nom et source de la police Police personnalisée
    font-size 1.3em, 16px, 120%… Taille du texte
    font-weight bold, normal Gras
    font-style italic, oblique, normal Italique
    text-decoration underline, overline, line-through, blink, none Soulignement, ligne au-dessus, barré ou clignotant
    font-variant small-caps, normal Petites capitales
    text-transform capitalize, lowercase, uppercase Capitales
    font Super propriété de police. Combine : font-weight, font-style, font-size, font-variant, font-family.
    text-align left, center, right, justify Alignement horizontal
    vertical-align baseline, middle, sub, super, top, bottom Alignement vertical (cellules de tableau ou éléments inline-block uniquement)
    line-height 18px, 120%, normal… Hauteur de ligne
    text-indent 25px Alinéa
    white-space pre, nowrap, normal Césure
    word-wrap break-word, normal Césure forcée
    text-shadow 5px 5px 2px blue (horizontale, verticale, fondu, couleur) Ombre de texte
  2. Propriétés de couleur et de fond

    Propriété Valeurs (exemples) Description
    color nom, rgb(rouge,vert,bleu), rgba(rouge,vert,bleu,transparence), #CF1A20… Couleur du texte
    background-color Identique à color Couleur de fond
    background-image url(‘image.png’) Image de fond
    background-attachment fixed, scroll Fond fixe
    background-repeat repeat-x, repeat-y, no-repeat, repeat Répétition du fond
    background-position (x y), top, center, bottom, left, right Position du fond
    background Super propriété du fond. Combine : background-image, background-repeat, background-attachment, background-position
    opacity 0.5 Transparence
  3. Propriétés des boîtes

    Propriété Valeurs (exemples) Description
    width 150px, 80%… Largeur
    height 150px, 80%… Hauteur
    min-width 150px, 80%… Largeur minimale
    max-width 150px, 80%… Largeur maximale
    min-height 150px, 80%… Hauteur minimale
    max-height 150px, 80%… Hauteur maximale
    margin-top 23px Marge en haut
    margin-left 23px Marge à gauche
    margin-right 23px Marge à droite
    margin-bottom 23px Marge en bas
    margin 23px 5px 23px 5px (haut, droite, bas, gauche) Super-propriété de marge. Combine : margin-top, margin-right, margin-bottom, margin-left.
    padding-left 23px Marge intérieure à gauche
    padding-right 23px Marge intérieure à droite
    padding-bottom 23px Marge intérieure en bas
    padding-top 23px Marge intérieure en haut
    padding 23px 5px 23px 5px (haut, droite, bas, gauche) Super-propriété de marge intérieure. Combine : padding-top, padding-right, padding-bottom, padding-left.
    border-width 3px Épaisseur de bordure
    border-color nom, rgb(rouge,vert,bleu), rgba(rouge,vert,bleu,transparence), #CF1A20… Couleur de bordure
    border-style solid, dotted, dashed, double, groove, ridge, inset, outset Type de bordure
    border 3px solid black Super-propriété de bordure. Combine border-width, border-color, border-style. Existe aussi en version border-top,
    border-right, border-bottom, border-left.
    border-radius 5px Bordure arrondie
    box-shadow 6px 6px 0px black (horizontale, verticale, fondu, couleur) Ombre de boîte
  4. Propriétés de positionnement et d’affichage

    Propriété Valeurs (exemples) Description
    display block, inline, inline-block, table, table-cell, none… Type d’élément (block, inline, inline-block, none…)
    visibility visible, hidden Visibilité
    clip rect (0px, 60px, 30px, 0px) rect (haut, droite, bas, gauche) Affichage d’une partie de l’élément
    overflow auto, scroll, visible, hidden Comportement en cas de dépassement
    float left, right, none Flottant
    clear left, right, both, none Arrêt d’un flottant
    position relative, absolute, static Positionnement
    top 20px Position par rapport au haut
    bottom 20px Position par rapport au bas
    left 20px Position par rapport à la gauche
    right 20px Position par rapport à la droite
    z-index 10 Ordre d’affichage en cas de superposition. La plus grande valeur est affichée par-dessus les autres.
  5. Propriétés des listes

    Propriété Valeurs (exemples) Description
    list-style-type disc, circle, square, decimal, lower-roman, upper-roman, lower-alpha, upper-alpha, none Type de liste
    list-style-position inside, outside Position en retrait
    list-style-image url(‘puce.png’) Puce personnalisée
    list-style Super-propriété de liste. Combine list-style-type, list-style-position, list-style-image.
  6. Propriétés des tableaux

    Propriété Valeurs (exemples) Description
    border-collapse collapse, separate Fusion des bordures
    empty-cells hide, show Affichage des cellules vides
    caption-side bottom, top Position du titre du tableau
  7. Autres propriétés

    Propriété Valeurs (exemple) Description
    cursor crosshair, default, help, move, pointer, progress, text, wait, e-resize, ne-resize, auto… Curseur de souris

 

Framework Bootstrap

Initiation à Bootstrap

(merci au site apprendre-a-coder.com)

Intégration de Bootstrap dans Visual studio

Exercice

En s’aidant de ce qui a été fait précédemment reproduire au plus près la page web suivante à l’aide du framework Bootstrap.

Pour information :

initiation WEB avec Visual Studio

Créer un site web avec Visual Studio

  • Création d’un site web et utilisation du serveur local iisExpress
  • Incorporation d’images
  • Utilisation de l’interface graphique WYSIWYG (what you see is what you get)

Mise en page à l’aide des <div>

  • présentation des div
  • mise en forme automatique Visual Studio
  • affectation d’id aux div, modification d’un style pour une div.
  • utilisation de classe pour assurer un centrage de texte

Positionnement des éléments d’une page.

  • Réalisation d’une petite galerie photo
  • Utilisation des styles float et  margin pour gérer le flottement des éléments.

Structuration d’une page grâce aux balises sectionnantes HTML5

  • Réalisation d’une page dont la structuration est la suivante :

Pour l’utilisation de Framework CSS voir la page Framework Bootstrap

Tutoriel

Créer une page web qui ressemble à ceci :

Le plan de structuration de la page est le suivant :

Aide pour le bouton bleu « See more… »
Ce bouton est créé à l’aide d’une div qui contient un texte inclus dans une balise h2. Cette div se situe dans une section ayant pour id : finArticle

Le code de la section est alors :

<section id="finArticle">
     <p>Etiam rhoncus nunc quis nulla dapibus pellentesque vitae vitae risus. </p>
     <div>
          <h2> See more...</h2>
     </div>
</section>

On peut constater que dans la section tous les textes sont centrés , nous pouvons donc déclarer le style suivant :

#finArticle{
             text-align:center;
           }

Ce style ne s’applique qu’à la balise ayant pour id : finArticle.
Pour appliquer des modifications à une balise div incluse dans finArticle, il faut faire comme ceci :

#finArticle div {
              width: 200px; /*largeur*/
              height: 75px; /*hauteur*/
              margin: auto; /*centrage de la div dans la page*/
              background-color: #0094ff;/*couleur de fond (bleu)*/
              border: 1px solid #ccc;/*bordure grise du "bouton"*/
              border-radius:8px;/*angles arrondis*/
              text-shadow: -1px -1px rgba(0, 0, 0, 0.31);/*effet d'ombrage du texte "See more" */
              color: #FFFFFF;/*couleur du premier plan : le texte écrit "See more..."*/
}

Technologie Web

Html, css, javascript…

Introduction

Le web est un système client/ serveur qui permet au client d’afficher des pages web sur un terminal. L’envoi de requêtes et leurs décryptages est assuré par un logiciel nommé navigateur. Les terminaux qui peuvent se connecter à internet sont de type et de technologie différente (ordinateur, tablette, téléphone portable, objet connecté…).

Le format web doit pouvoir s’adapter à tous ces types de connexions. Le web s’appuie sur 3 langages :

  • HTML pour véhiculer le contenu (texte, images, son, vidéo…),
  • CSS pour la mise en forme et l’adaptation aux différents terminaux
  • JS pour rendre la page interactive avec l’utilisateur ou le serveur.

Requête http

Le diagramme de séquence d’une requête web est la suivante :

  • Le client appelle la page web grâce à son URI. On parle de requête de type GET.
  • Le serveur renvoie la page demandée au format html.
  • La page contenant des liens hypertextes dans son entête reformule elle-même des requêtes GET afin de rapatrier les autres éléments de la page web : JS et CSS.
  • Une fois l’ensemble des éléments reçus, la page s’affiche avec sa mise en forme et sa capacité à interagir avec l’utilisateur.
  • La page html peut aussi appeler des images, des vidéos, du son…

En fait tous les éléments constituant la page sont rapatriés et c’est le navigateur qui s’occupe de tout mettre en place en fonction du flux html et du fichier CSS

HTML[1]

L’HyperText Markup Language, généralement abrégé HTML, est le format de données conçu pour représenter les pages web. C’est un langage de balisage permettant d’écrire de l’hypertexte, d’où son nom HTML.

Les balises sont invisibles à l’écran du navigateur, mais elles permettent à ce dernier de comprendre ce qu’il doit afficher. Les balises se repèrent facilement. Elles sont entourées de « chevrons », c’est-à-dire des symboles < et >, comme ceci :

<balise>

Elles indiquent la nature du texte qu’elles encadrent. Elles veulent dire par exemple : «Ceci est le titre de la page», «Ceci est une image», «Ceci est un paragraphe de texte», etc.

<titre>Ceci est un titre</titre>

On distingue une balise ouvrante <titre> et une balise fermante </titre> qui indique que le titre se termine.

Il existe des balises dites orphelines c’est-à-dire qu’on n’a pas de texte à insérer entre l’ouverture de la balise et sa fermeture. Par exemple <br/> (back return = saut de ligne) ou <image/> qui permet l’insertion d’image…

Les documents HTML ont une véritable structure en arbre, avec un élément racine contenant tous les autres éléments. Par exemple <html> contient <head> et <body>. Cet « arbre du document » est utilisé pour repérer les éléments de la page et permettre leur formatage ou l’interaction avec l’utilisateur par le biais de scripts.

liste des balises HTML voir le memento HTML

Tutoriel

On veut réaliser la page suivante :
(pour savoir comment créer un site et des pages avec Visual Studio cliquer ici):

Le code html est le suivant:


<!doctype html>
<html lang="fr">
<head>
    <meta charset="utf-8">
    <title>Démonstration des éléments de section HTML5</title>
    <style>
        span.elem {
            display: none;
        }
    </style>
</head>

<body>
    <!-- Header -->
    <header>
        <h1>Nouveaux éléments de section, article, header, footer, aside, nav</h1>
        <!-- nav principale -->
        <nav>
            <ul>
                <li><a href="#">Rubrique 1</a></li>
                <li><a href="#">Rubrique 2</a></li>
                <li><a href="#">Rubrique 3</a></li>
                <li><a href="#">Rubrique 4</a></li>
            </ul>
        </nav>
    </header>
    <!-- Main -->
    <section id="main">
        <span class="elem">section</span>
        <article>
            <span class="elem">article</span>
            <header>
                <span class="elem">header</span>
                <h2>En tête de l'article</h2>
                <p>Date de publication, auteur</p>
            </header>
            <p>Contenu de l'article, contenu de l'article, contenu de l'article, contenu de l'article, contenu de l'article,
                contenu de l'article, contenu de l'article...</p>
        </article>
        <article>
            <span class="elem">article</span>
            <header>
                <span class="elem">header</span>
                <h2>Harry Gruyaert</h2>
                <p>Par François, le
                    <time datetime="2016-11-05">5 novembre 2016</time>
                </p>
            </header>
            <p>Harry Gruyaert (né le 25 août 1941 à Anvers) est un photographe belge. Il est membre de l'agence Magnum Photos
                depuis 1981</p>
            <aside>
                <span class="elem">aside</span>
                <p><b>Magnum Photos</b> est une coopérative photographique. Créée en 1947 par Robert Capa, Henri Cartier-Bresson,
                    George Rodger, William Vandivert et David Seymour, elle fut la première de ce genre à voir le jour1.</p>
            </aside>
            <footer>
                <span class="elem">footer</span>
                <p>Source : <a href="http://fr.wikipedia.org/wiki/Magnum_Photos">Wikipedia</a></p>
            </footer>
        </article>
    </section>
    <!-- sidebar -->
    <aside id="sidebar">
        <span class="elem">aside</span>
        <h2>Sidebar</h2>
        <h3>Navigation</h3>
        <nav>
            <span class="elem">nav</span>
            <ul>
                <li><a href="#">Lien 1</a></li>
                <li><a href="#">Lien 2</a></li>
                <li><a href="#">Lien 3</a></li>
            </ul>
        </nav>
        <h3>Autre titre</h3>
        <p>Autres informations...</p>
    </aside>
    <!-- footer -->
    <footer>
        <span class="elem">footer</span>
        <p class="mentions">Page de démonstration de l'article <a href="/article/lire/1376-html5-section-article-nav-header-footer-aside.html">Nouveaux éléments de section, article, header, footer, aside, nav</a></p>
    </footer>
</body>

</html>

L’appel de cette page dans un navigateur montre le contenu mais la mise en page est linéaire est suit le flux, c’est-à-dire l’ordre d’apparition des éléments dans l’ordre d’écriture de la page

En reprenant la page et en faisant apparaitre les éléments de section HTML5, on peut voir que les sections s’imbriquent ou s’empilent les unes sur les autres.

Ajout de style afin de modifier l’aspect de la page

C’est grâce aux feuilles de style  CSS (Cascade Style Sheet) que le positionnement des sections va avoir lieu. Si on ajoute les instructions suivantes dans la balise <style> de l’entête du fichier html :

<!doctype html>
<html lang="fr">
<head>
    <meta charset="utf-8">
    <title>Démonstration des éléments de section HTML5</title>
    <style>
        span.elem {
            display: none;
        }

        body {
            width: 980px;
            margin: 0 auto 0 auto;
        }

        #main {
            float: left;
            width: 75%;
            margin: 0 0 50px 0;
        }

        #sidebar {
            float: left;
            width: 22%;
            margin-left: 1%;
            margin-top: 1em;
        }

        footer {
            clear: both;
        }

        nav ul li {
            display: inline-block;
        }
    </style>
</head>
<body>
    <!-- Header -->
...

La page prend l’aspect attendu. En faisant apparaitre les sections, on peut constater que la section “aside” est passée à droite (en rose).

Conclusion : Le code html contient le texte, les balises permettent de structurer la page c’est-à-dire définir les zones de contenu. Les feuilles de styles quant à elles, permettent de dimensionner et positionner les balises sectionnantes. Les feuilles de styles jouent aussi sur les éléments décoratifs de la page (couleurs, polices, encadrement…). Le code html contient le fond (la structure et le texte), le code css contient la forme (mise en page). Cette séparation permet à la page de s’adapter à n’importe quel terminal (écran d’ordinateur, tablette, téléphone…) En ajoutant à CSS quelques scripts, on peut créer une  mise en page « responsive » ou page réactive qui s’auto-adapte à l’écran.

Pour en savoir plus, voir le Memento CSS

  1. https://fr.wikipedia.org/wiki/Hypertext_Markup_Language#Description_de_HTML

Lecteur WebRadio avec node-red, MPD et MPC

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) :
MPD
Pré-requis
Vérifier que le driver audio est correctement installé :

lsmod

Il doit apparaître le driver snd_bcm2835 dans le résultat. Si ce n’est pas le cas, essayer :

sudo modprobe snd_bcm2835

Forcer la sortie son sur la prise casque :

amixer cset numid=3 1
sudo alsactl store

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.

# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
hdmi_drive=2

Installation

L’installation est facile (après mise à jour des dépôts) :

sudo apt-get update
sudo apt-get install mpc

Sous raspbian Jessie, pour mpd c’est un peu plus compliqué, en effet la version disponible dans le dépôt est buguée (elle se plante).
Nous allons donc installer une version plus récente, mais l’installation va se faire “à la main”

wget http://repo.volumio.org/Packages/Mpd/mpd_0.19.9-2_armhf.deb
sudo dpkg -i mpd_0.19.9-2_armhf.deb
sudo apt-get -f install

Sous Raspbian Stretch, un simple

sudo apt install mpd

est suffisant, par contre il faut é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 : sudo /etc/init.d/mpd restart

charger le fichier playlist : mpc load radio

lancer la diffusion : mpc play 1

Vidéos

La playlist suivante contient 5 vidéos :

  1. installation de MPD et MPC
  2. Affichage de la liste des radio (DahBoard node-red)
    Remarque : bien décocher les cases “Pass through…” et “Add output…” dans le template  de la liste des radios.
  3. Déclenchement de la lecture au clic sur un élément de la liste.
  4. Ajout de boutons de commandes
  5. Affichage du nom de la radio et du titre en cours

Annexe

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;
}

 

  1. Voir : http://www.peyregne.info/wp/radio-internet-sur-raspberry-pi-partie-1-mpdmpc/