[TUTO] Application web dynamique

Proposer ou rechercher un tutoriel concernant le Raspberry Pi

Modérateur : Francois

Répondre
Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: [TUTO] Application web dynamique

Message par Bud Spencer » sam. 3 févr. 2018 12:50

En discutant sur un autre topic, je me suis aperçu que ce tuto n’avait même pas de ‘hello world’ ni même de ‘blink led’ tout simple :?

Voilà qui va corriger ce manque :

Hello World :

Code : Tout sélectionner

console.log("Hello World");
Blink Led (avec rpio):

Code : Tout sélectionner

var rpio = require('rpio');
var led = 12; 
rpio.open(led,rpio.OUTPUT,rpio.LOW);

var blink = setInterval(function(){
	rpio.write(led,!rpio.read(led) ? rpio.HIGH : rpio.LOW);
},500);
Si on veut finir proprement et être sur que la led soit éteinte quand on quitte le programme avec ctrl+c, il suffit de rajouter une trappe sigint

Code : Tout sélectionner

process.on('SIGINT', function(){
	rpio.write(led,rpio.LOW);
	process.exit();
});
voila, maintenant ca ressemble plus a un vrai tuto :lol:
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: [TUTO] Application web dynamique

Message par Bud Spencer » dim. 18 mars 2018 17:49

On avait parlé autrefois de mettre à profit le tuto pour développer une application complète et utilisable. Le problème c’est de trouver une idée qui puisse servir. Et bien j’en ai trouvé une et comme elle n’est pas de moi, on ne pourra pas prétendre que je fais du favoritisme. Il s’agit d’un petit datalogger et le cahier des charges est exposé ici : viewtopic.php?f=75&p=27783#p27783

Pour ce projet, je ne vais pas utiliser les sondes suposées par le demandeur. Cela rendrait le projet trop propriétaire. Un dataloggeur est censé pouvoir servir à beaucoup de chose. L’idée c’est donc d’utiliser un adc ce qui permettra de répondre au besoin et dans l’absolu de logger quasiment tout ce que l’on veut en choisissant ses capteurs sans avoir à revenir sur le code.

Pour l’adc, ce sera un MCP3208 déjà été expérimenté sur les pages de ce tuto (viewtopic.php?f=44&t=3033&start=50#p22504). C’est un composant que j’utilise souvent et qui est très facile à mettre en œuvre. On le trouve très facilement pour 4 ou 5€. Il est plutôt robuste et son boitier DIL le rend facilement utilisable. Au niveau caractéristique, il possède 8 canaux à 12 bits de résolution et permet une fréquence d’horloge de liaison SPI jusqu’à 2 MHz en alimentation 5v.

Datasheet mcp3208 : http://ww1.microchip.com/downloads/en/D ... 21298c.pdf

Suivant l’expérience de chacun, on a tous notre façon de travailler. En ce qui me concerne, je commence toujours par les points les plus critiques. Dans un programme, ce sont généralement eux qui dictent comment on va pouvoir broder autour. Ici, le point central du projet, c’est d’arriver à interfacer le composant et dialoguer avec. Il serait en effet complètement illusoire de commencer par dessiner la GUI sans savoir si on va réellement pouvoir récupérer des valeurs venues du monde extérieur.

La première partie de ce projet va donc être de câbler le composant et de discuter avec lui. Dans un premier temps le but n’est pas de construire l’interface hard, mais de faire une maquette pour pouvoir développer tout en contrôlant le bon fonctionnement du programme. Voilà donc le câblage que je vous propose. Bien entendu, n’importe quel modèle de PI fait l’affaire.
cablage_1.png
cablage_1.png (125.82 Kio) Vu 6515 fois
A noter que contrairement au schéma de la leçon 7, le circuit n’est plus alimenté en 3.3v mais en 5v, ce qui explique la présence du diviseur entre la broche Dout du composant et la broche MISO du PI (j’ai déjà expliqué ca cette semaine ici -> viewtopic.php?f=65&p=27792#p27773 ).

Le fait de passer la maquette en 5v va permettre de travailler directement avec des conversions de valeurs réelles qui seront utilisée en production. Sinon j’ai choisi le /CS 0 et c’est le canal 3 qui va servir de cobaye pour contrôler la variation des valeurs dans un premier temps. Les valeurs de composants ne sont pas très critique mais attention quand meme à ne pas pomper trop de courant. On peut raisonnablement utiliser un 4.7K ou un 10K pour le potentiomètre P1, une résistance de 3.3K pour R1 et 4.7K pour R2. Idéalement il faudrait aussi un petit condo de découplage (100nF) au plus proche de l’alimentation du MCP, mais pour la maquette, ce n’est pas indispensable.

Nous voilà donc avec du hardware minimaliste mais largement suffisant pour pouvoir commencer à développer et valider le principe. La prochaine étape sera de créer ‘informatiquement’ le projet et mettre un peu de code pour discuter avec le hardware.

Je précise que je vais réaliser le projet en live donc il pourrait y avoir des couacs. Bon, d’un autre côté, ça fait 3 décennies que je construis des systèmes d’acquisition en tous genre donc je ne prends pas trop de risque. Reste l’inconnu de la mise en pratique avec nodeJS sur un PI, mais j’ai suffisamment tâtonné le truc pour pouvoir être quasi certain que ça pourra le faire.

PS : J’en profite pour remercier tous ceux qui ont parcouru ce tuto. Je me suis rendu compte la semaine dernière qu’il avait dépassé les 17000 hits et qu’il est devenu de loin le topic le plus lu de ce forum. Dans l’absolu, ça n’a pas beaucoup d’importance et on s’en fout un peu, mais ça m’a déjà pris quelques heures pour écrire et expérimenter tout ça (ce que je fais vraiment par plaisir de bricoler et partager) et ça fait juste plaisir de savoir que des gens s’y intéressent. C’est un peu ce qui m’a motivé à le poursuivre.
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: [TUTO] Application web dynamique

Message par Bud Spencer » lun. 19 mars 2018 13:50

Avant d’attaquer la partie Code, je vais tenter d’expliquer concrètement comment on va dialoguer avec le composant. Pour le transport, j’avais utilisé Wiring-Pi pour ce composant dans la leçon 7. Cette fois ci on va utiliser le package npm rpio qui va très bien et qui wrap (qui interface) l’excellente libraire BCM2835 de Mike McCauley. On aurait pu créer notre propre wrapper et même notre propre librairie de transport, mais ça, ce n’est pas le boulot d’un code en javascript et cela sortirait du cadre de ce tuto. Je vous renvois à l’exemple avec le mcp23s17 qui se trouve dans les autres pages du tuto pour plus d’info sur la lib bcm2835 et le package rpio.

En revanche, on ne va pas utiliser d’extension existante pour le composant, mais on va développer nous-même la nôtre. Le MCP3208 étant très utilisé, on pourrait sans peine en trouver une toute faite, mais perso, je ne les utilise jamais. Je trouve qu’il est beaucoup instructif d’éplucher la datasheet d’un composant pour le comprendre avant de l’utiliser et au moins on a une totale maitrise du dialogue.

Commençons donc par voir comment fonctionne ce mcp3208.

Si on se rend à la page 15 da la Datasheet, on voit que le registre de configuration du composant est relativement simpliste puisqu’il est unique et ne dispose que de 4 bits d’options. 3 servent a identifier les 8 canaux (D2,D1,D0) et le quatrième sélectionne le mode d’utilisation qui peut être soit en mode comparateur ou en mode simple (Single/Diff). Dans notre cas, on va utiliser le mode Single puisque le but n’est pas de faire des comparaisons entre 2 entrés mais de lire la tension présente sur chacune d’elle. Le bit Single/Diff sera donc pour nous toujours mis a 1. L’adressage du n° de canal est quant à lui explicite puisque cela va de 000 pour le canal 0 à 111 le canal 7.

Allons maintenant à la page 18 pour voir comment organiser ces bits à transmettre au composant. On voit que le transfert se fait sur 24 bits (3 octets). En partant de la gauche, cela commence par 5 bits à 0, suivie d’un bit de start à 1, puis du bit de mode Single/Diff et des 3 bits d’adressage du canal visé. Les 14 bit de droite sont ‘ignorés’ et ce sont eux qui contiendront le résultat donc on les initialisera à 0.

Ce qui nous donne respectivement pour lire chaque canal (binaire ; [décimal] ; [Hexa])

Ch0 : 00000110 00000000 00000000 ; [6 , 0 , 0] ; [0x06,0x00,0x00]
Ch1 : 00000110 01000000 00000000 ; [6 , 64 , 0] ; [0x06,0x40,0x00]
Ch2 : 00000110 10000000 00000000 ; [6 , 128 , 0] ; [0x06,0x80,0x00]

Ch7 : 00000111 11000000 00000000 ; [7 , 192 , 0] ; [0x07,0xC0,0x00]

Ce qui est emmerdant (appelons un chat un chat …), c’est que ces 5 bits utiles se trouvent séparé sur 2 octets. Il aurait été tellement plus simple qu’ils occupent la partie droite d’un seul octet, ce qui nous aurait permis de n’avoir qu’une valeur pour Ch0 et d’ajouter la valeur du n° de canal souhaité pour les autres, mais ce n’est pas le cas. Pour pallier ça, il y a plusieurs solutions. La plus simple serait de définir un tableau de 3 octet pour chaque canal et d’utiliser des instructions ‘If Then Else’ ou ‘Select Case’ pour choisir le tableau à envoyer en fonction du canal voulu. Un autre plus élaborée consiste à trouver une équation qui va permette de définir les valeurs du tableau en utilisant qu’une seule valeur variable, à savoir celle du n° de canal. Pour ça le plus simple est de raisonner en binaire pour chaque octet.

Si on regarde bien, l’octet de gauche est toujours égal à 6 pour les ch0 à 3 et est égal à 7 pour les ch4 à 7. Au sens Boolean on peu donc dire qu’il est égal à (n° Ch/4) + 6, qui simplifié en utilisant une rotation de bit à droite plutôt qu’une division correspond à (n° Ch >> 2) + 6

Le second octet contient sur sa gauche les 2 bits de poids faible de définition du n° de canal. On peut donc écrire qu’il est égal à (n° Ch * 64) % 256 . Ce qui par rotation et mask de bit peut être écrit plus simplement par (n° Ch < < 6) AND 255

L’octet de droite étant toujours a 0, pas besoin de le traduire.

On arrive donc à la conclusion que la façon la plus rapide et la plus courte pour initialiser notre buffer de 3 octet que l’on va envoyer au composant pour lire chacune des entrées peut se résumer à ça :
[(N° Ch > > 2) + 6 , (N° Ch < < 6) & 0xFF, 0]

En revenant à la page 18 de la datasheet, on peut voir que le composant va nous retourner ce tableau de 3 octets et que le résultat de la lecture est ordonné dans l’octet de droite et dans les 4 bits faible de l’octet du milieu. Pour décoder la valeur il suffira donc juste d’additionner la valeur de l’octet de droite à la valeur des 4 bits faibles de l’octet du milieu que l’on aura multiplié par 256. Ce qui peut s’écrire :
(([1] AND 0x0F) * 256) + [2] ou plus simplement (([1] & 0x0F) < < 8) + [2]

Finalement beaucoup de baratin pour s’apercevoir que 2 simples operations booléenne vont nous suffire pour dialoguer avec le composant avec le luxe d’avoir appris le faire. Je sais maintenant comment questionner le composant et comment traduire ce qu’il va me répondre.

Dans la prochaine étape on va mettre tout ça en pratique. En attendant, je vous renvois au reste du tuto pour préparer le projet, ce qui se résume à :

1 - Contrôler la bonne installation de NodeJs et npm (version à peu près à jour de préférence)

2 - Création d’un répertoire pour le projet et de ses sous rep. /public et /templates

3 – Exécution de la commande npm init depuis la racine du projet pour créer le package.json. Renseigner un nom de projet, une désignation et un nom d’auteur. Les autres valeurs peuvent rester par défaut. Pour ne pas avoir de warn avec npm à cause du repository git, vous pouvez ensuite éditer le fichier package.json créer par le init et lui ajouter une entré ‘’private’’ : true

4 – Installation des package express, ejs , jquery, socket.io et rpio avec la commande
npm install express ejs jquery socket.io rpio
depuis la racine du projet

5 – Contrôle le la bonne création de l’arborescence node_modules et de la présence des différents package dedans.
Modifié en dernier par Bud Spencer le mar. 20 mars 2018 10:47, modifié 1 fois.
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: [TUTO] Application web dynamique

Message par Bud Spencer » lun. 19 mars 2018 22:32

Votre projet est prêt ? C'est bien, Pas le mien …

Je valide donc la procédure de création du projet en live
- PI3 avec rasbian 9.1 (Stretcht)
- NodeJS Version 8.9.4
- npm Version 5.7.1
- création des répertoires
- init npm et modif du package.json pour ajouter le "private" : true
- install des packages

Et voilà la tronche de mon package.json (qui a bien pris en compte les version des packages npm installés):
package_json.png
package_json.png (70.75 Kio) Vu 6472 fois
Jusque-là, ça va (j’adore quand un plan se déroule sans accrocs :mrgreen: )

Accessoirement, Pour la pratique, je code avec un Visual Studio 2017 depuis un PC sous Win10 Pro et mon dir de projet qui se trouve sur PI et est partagé avec Samba. J’exécute mes commande sur le PI en ssh avec un Bash WSL 4.3.11(1) en Ubuntu ‘trusty’ 14.04.5 LTS.

Aller hop, j’ouvre mon dossier de projet dans vs et je commence par créer le fichier index.js (fichier main du projet décrit dans le package.json) et tout de suite un autre fichier que j’appelle spi_io.js. Fractionner le code en plusieurs fichiers est bien pratique. Déjà c’est plus lisible et ça permet de réutiliser ces fichiers dans d’autres programmes. Le fichier spi_io est un module qui va contenir les fonctions et property des différents composants que l’on va utiliser sur la SPI. Voilà le code que je lui mets pour l’instant :
spi_io.png
spi_io.png (124.08 Kio) Vu 6471 fois
On peut assimiler MCP3208 à un objet qui va recevoir en paramètres le rpio que l’on déclarera dans l’index, le CS qu’il devra utiliser ainsi que l’indice qui va déterminer la fréquence d’horloge qu’il va utiliser.

Je lui ais mis une fonction privé qui va se charger du dialogue avec le composant physique (adcResult), une fonction public qui va retourner la valeur numérique d’une conversion sur un canal passé en paramètre (ReadChannel ) et une autre fonction public qui fait la meme chose mais qui retournera un tableau des 8 valeurs des 8 canaux (ReadAll). Ces 2 fonctions commencent par faire le setting de la fréquence d’horloge et du /CS (et oui, rien ne dit que je ne vais pas dialoguer avec un autre composants qui a des paramètres différents) et on retrouve les operations booléenne décrites plus haut pour initialiser les buffers a transférer.

Reste à maintenant à déclarer tout ça dans le fichier main du programme (index.js) et exécuter le tout par une commande sudo.
index.png
index.png (75.23 Kio) Vu 6471 fois
Include du package rpio et de notre module spi_io
Init de la SPI (voir la doc du npm rpio et la doc de la lib bcm2835)
Une petite fonction test qui va écrire le tableau des 8 valeurs dans la console
Un timer qui appelle cette fonction toute les secondes
Et une petite trappe SIGINT pour libérer la SPI quand on sort du programme.

Mon potentiomètre P1 est à un poil plus de la moitié. J’ai un peu de flottement sur mes 3 premières valeurs qui devraient normalement être a 4095, mais ma maquette est vraiment flanqué à l’arrache sur une (très) vielle plaque d’essais avec ~40 cm de fil entre le pi et le composant, ce qui explique le pourquoi. A noter qu’un écart de 1 point correspond seulement à 5v/4095 soit 0.0012 volts …

La prochaine fois on verra comment convertir ces valeurs numériques en valeur réelles de capteur divers.
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: [TUTO] Application web dynamique

Message par Bud Spencer » mar. 20 mars 2018 22:11

Bon, on va déjà commencer par les petits méa-culpa pour le code précèdent. Comme je vous l’ais dit, c’est un projet en live et meme si c’est fonctionnel, ça n’évite pas les petites erreurs que l’on ne voit qu’a la relecture.
Dans le code du fichier spi_io.js, vous pouvez supprimer la ligne 5 qui déclare une variable _rpio qui ne sert à rien. Vous pouvez aussi modifier la ligne 29 (b = adcResult… ) en ajoutant var devant (var b = adcResult …). Ce sont des petits détails, mais c’est justement ce genre de petits détails qui vous pourrissent un programme.

Bien, on sait maintenant discuter avec un convertisseur analogique/digitale sur une liaison SPI. Pour l’instant on n’a que des valeurs numériques qui n’ont pas vraiment de sens par rapport au monde extérieur. On va donc transformer tout ça en capteurs paramétrables.

Si je résume un capteur de base il a déjà un minimum de propriétés. Déjà je peux lui donner un nom ne serait-ce que pour l’identifier par rapport à d’autres. Ensuite il a une unité de mesure. Ça peut être des Volts, des °C, des PSI, des kilogrammes …. Il a aussi une plage de mesure, donc une valeur minimum et une valeur maximum. Par principe je vais aussi lui donner un id unique ce qui pourra servir ultérieurement. Pour faciliter la lecture, je peux aussi définir le nombre de décimal que je veux afficher pour le capteur.

Dans l’absolu, je n’ai pas encore de capteur, mais les lignes d’entrés analogiques savent déjà mesurer des tensions de 0 et 5 volts. Donc je peux déjà définir mes capteurs de base comme étant des voltmètres mesurant cette plage de valeur.

Ecrit au format JSON le capteur 0 peut ressembler à ca :
{"Iden": 0, "Name": "Voltmètre 0","Unite": "Volt","Vmin": 0.0, "Vmax": 5.0, "Dec" :3 }

Je vais donc créer un fichier de paramètre qui va contenir un tableau de 8 objets capteur que je vais appeler Analog (eh oui, mon fichier pourrait aussi contenir d’autres paramètres …). Voilà ce que ça donne. J'ai nommé le fichier sensor.json
sensorjson.png
sensorjson.png (16.48 Kio) Vu 6448 fois
Après, pour convertir mes données numériques en valeurs réelle, il va falloir faire une petite opération mathématique. La valeur analogique que me retourne le composant est comprise entre 0 et 4095 pour une tension comprise entre 0 et 5 volts. Pour transformer ça en en valeur volts il suffit juste que je fractionne ces 5v en 4095 et que je multiplie cette valeur par la valeur numérique que me retourne la lecture de la ligne analogique. Ce qui donne :
Résultat en volt = 5/4095 * valeur numérique lue.

La dessus il faut aussi penser que je peux avoir des capteurs qui feront des mesures qui n’ont pas forcement 0 comme valeur minimum et meme certain qui auront des valeurs négative. Donc je complète l’opération ce qui m’amène a ca :
Résultat réel = (valeur maxi – valeur mini) /4095 * valeur numérique lue + valeur mini

Théoriquement, avec cette opération toute simple, je peux potentiellement utiliser n’importe quel capteur linéaire qui retourne une tension qui sera comprise en 0 et 5 volts. Electroniquement parlant, je peux meme aller plus loin et dire que je peux utiliser n’importe quel capteur qui retourne une tension linéaire quelle qu’elle soit et tout ça sans modifier autre chose que mon fichier de paramètre de capteur.

Pour le code, c’est tout simple. Comme NodeJs sait nativement désérialisé un fichier Json pour le transformer en objets j’ajoute juste l’include de mon fichier capteur.json (ligne 3) et je modifie la fonction test pour afficher mes valeurs réelle de capteur.
index2.png
index2.png (34.99 Kio) Vu 6448 fois
Je relance mon petit programme et j’ai bien mes 8 voltmètres.
vltmtr.png
vltmtr.png (11.94 Kio) Vu 6448 fois
Ça fonctionne mais Il y a pourtant un petit détail qui n’échappe pas à l’œil averti d’un développeur. La variable r dans la fonction test est entièrement recalculée à chaque lecture de capteur alors que si on regarde bien, la partie (analog.Vmax - analog.Vmin) / 4095 est invariable pour chaque capteur et pourrait être convertie en un coef une seule fois pour toute juste après le chargement du fichier sensor.json. C’est encore un petit détail me direz-vous, mais c’est quand meme une soustraction et une multiplication de nombre décimaux à chaque lecture et ce n’est pas anodin.

La prochaine fois, on verra comment améliorer ça et je vais tacher de trouver 3 capteurs susceptible de répondre aux besoins de Didier (voir cahiers des charges).

Voila les codes actuels pour éviter de perdre du temps les recopier (ce qui ne vous empêche pas de les lire pour les comprendre ...).

index.js

Code : Tout sélectionner


var rpio = require("rpio");
var spi_io = require("./spi_io.js");
var analog = require("./sensor.json").Analog;

rpio.init({ gpiomem: false, mapping: 'gpio' });
rpio.spiBegin();

var mcp3208 = new spi_io.MCP3208(rpio, 0, 512);

var test = function () {
    var b = mcp3208.ReadAll();
    for (var i = 0; i < analog.length; i++) {
        var r = (analog[i].Vmax - analog[i].Vmin) / 4095 * b[i] + analog[i].Vmin;
        console.log(analog[i].Name + ": " + r.toFixed(analog[i].Dec) + " " + analog[i].Unite);       
    }
    console.log("\r\n");
};
setInterval(test, 1000);


process.on('SIGINT', function () {
    rpio.spiEnd();
    process.exit();
});

spi_io.js

Code : Tout sélectionner

var Spi_IO = {

    MCP3208: function (rpio, cs, clk) {

        this.Cs = cs;
        this.Clk = clk;

        var adcResult = function (reg) {
            rpio.spiTransfer(reg, reg, reg.length);
            return reg;
        };

        this.ReadAll = function () {
            rpio.spiSetClockDivider(this.Clk);
            rpio.spiChipSelect(this.Cs);
            var x = [];
            var b = [];
            for (i = 0; i < 8; i++) {
                b = adcResult(Buffer([(i >> 2) + 6, i << 6 & 0xFF, 0x00]));
                x[i] = ((b[1] & 0x0F) << 8) + b[2];
            }
            return x;
        };

        this.ReadChannel = function (channel) {
            rpio.spiSetClockDivider(this.Clk);
            rpio.spiChipSelect(this.Cs);
            var b = adcResult(Buffer([(channel >> 2) + 6, channel << 6 & 0xFF, 0x00]));
            return ((b[1] & 0x0F) << 8) + b[2];
        };

    }

};
module.exports = Spi_IO;

sensor.json

Code : Tout sélectionner

{
  "Analog": [
    {
      "Iden": 0,
      "Name": "Voltmetre 0",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 1,
      "Name": "Voltmetre 1",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 2,
      "Name": "Voltmetre 2",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 3,
      "Name": "Voltmetre 3",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 4,
      "Name": "Voltmetre 4",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 5,
      "Name": "Voltmetre 5",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 6,
      "Name": "Voltmetre 6",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 7,
      "Name": "Voltmetre 7",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    }
  ]
}
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

DidierV
Messages : 9
Enregistré le : jeu. 15 mars 2018 07:49

Re: [TUTO] Application web dynamique

Message par DidierV » mer. 21 mars 2018 16:44

Bonjour Bud,

Ding dong, c'est l'heure de la 1ere ânerie... :roll:

Au lancement de la commande : sudo node index.js

J'obtiens ça :

pi@raspberrypi:~/datalogger $ sudo node index.js
module.js:327
throw err;
^

Error: Cannot find module 'rpio'
at Function.Module._resolveFilename (module.js:325:15)
at Function.Module._load (module.js:276:25)
at Module.require (module.js:353:17)
at require (internal/module.js:12:17)
at Object.<anonymous> (/home/pi/datalogger/index.js:2:12)
at Module._compile (module.js:409:26)
at Object.Module._extensions..js (module.js:416:10)
at Module.load (module.js:343:32)
at Function.Module._load (module.js:300:12)
at Function.Module.runMain (module.js:441:10)
pi@raspberrypi:~/datalogger $

Nonobstant le fait que la console me parle dans une langue étrange et qu’après la lecture des 8 pages plus une journée complète sur juste les trois derniers post (!), je ne peut que constater ce que je craignais : Je suis dans la position du gros faisant qui a ouvert une boite à vitesse : les pignons sont tombé et je ne sais pas les remettre en place... :mrgreen:

:oops: :oops: :oops:

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: [TUTO] Application web dynamique

Message par Bud Spencer » mer. 21 mars 2018 20:47

Faut positiver. C’est bien les messages d’erreur. Ça permet souvent d’apprendre beaucoup plus de choses que quand tout fonctionne du premier coup ;)

Pour moi le message est très explicite (ouais, je sais, je ne suis pas un bon exemple :mrgreen: ) :

Cannot find module ‘rpio’
Que l’on peut traduire par :
Ne trouve pas le p^$@ù% de module ‘rpio

Donc la première question que je te poserais c’est : Mais Didier, as tu installé le module rpio ?
Si tu ne sais plus, le plus simple c’est de taper la commande : npm ls rpio (depuis la racine du projet les commande npm !!!!!)
npm_ls.png
npm_ls.png (14.59 Kio) Vu 6417 fois
Si le truc te renvois un -(empty), c’est que le module n’est pas installé. Pour l’installer il suffit de taper la commande :
npm install rpio
(toujours depuis la racine du projet les commande d'Install de package npm !!!!!)
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

DidierV
Messages : 9
Enregistré le : jeu. 15 mars 2018 07:49

Re: [TUTO] Application web dynamique

Message par DidierV » jeu. 22 mars 2018 10:42

Merci Bud pour l'explication.

Effectivement, après installation, je lance la commande sudo node index.js et la miracle : j'ai l'affichage des voltmètres sur la console :D

Bon je sais, Bud devrait me répondre "ya pas de miracles, juste un code qui tourne bien !"... :lol:

Vivement la suite ;-)

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: [TUTO] Application web dynamique

Message par Bud Spencer » ven. 23 mars 2018 19:13

Aller la suite :

Dialoguer périodiquement avec notre composant et faire des conversions en valeurs réelles, c’est réglé. On pourrait en rester là pour cette partie, mais après 8 pages de tuto, on va peut-être commencer à faire un peu plus … ou plutôt un peu moins … enfin mieux pas pire quoi :mrgreen: .

Déjà, on oublie cette valeur 4095 qui pique les yeux :shock: . Sa vraie valeur c’est la valeur maxi de la résolution de l’ADC. Je défini donc une propriété Resolution à mon objet ADC3208 et je lui mets la valeur 12.

Ensuite, mon programme il s’en fout du mcp3208, ce qui l’intéresse, c’est la valeur lue sur chaque capteur analogique. Je vais donc créer une class qui va contenir des objets 'capteur analogique' et c’est elle qui aura pour mission d’aller interroger le composant.

A la construction de cette class, je vais charger la config des capteurs et leur ajouter 2 propriétés à chacun. La première que je vais appeler Coef et qui n’est rien d’autre que le pas de conversion de base digitale/reelle et une seconde qui contiendra la valeur réelle et formatée que je vais appeler Value. Ensuite je vais lui ajouter 1 méthode Read qui aura pour rôle d’interroger le composant et d’initialiser les Values. Pour que ce soit plus rigolo (et surtout instructif), cette propriété Read aura 2 possibilités. Appelée sans paramètre, elle prendra en charge tous les capteurs mais on pourra aussi l’appeler en lui passant un un n° de canal pour ne traiter qu’un seul capteur (ouais, je sais, ça ne nous sert à rien pour l’instant, mais qui sait ….)

Aller nouveau fichier sensor.js (à ne pas confondre avec sensor.json qui contient les propriétés de config des capteurs) et on met ca dedans :
sensojs.png
sensojs.png (38.02 Kio) Vu 6379 fois
Dans le fichier spi_io on n’oublie pas de rajouter la propriété ‘this.Resolution = 12 ; a l’objet MCP3208’. J’ai aussi remplacé Analog par ANALOG dans le fichier sensor.json (conventionnellement parlant, c’est plus mieux :lol: )
modif.png
modif.png (25.91 Kio) Vu 6379 fois
Et on peut modifier le fichier index pour arriver à ca :
index.png
index.png (32.98 Kio) Vu 6379 fois
Mouais, vous me direz, beaucoup de code pour finalement ne rien faire de plus. Bha moi je ne suis pas de cet avis. Déjà ça a plus de gueule (très important ca) et ça va énormément nous faciliter la vie pour la suite puisque maintenant on va pouvoir enrichir nos objets capteur, ce qui nous sera très utile pour répondre à tous les critères du cahier des charges.

Pour vous amuser, et voir ou je veux en venir, vous pouvez remplacer la boucle for de fonction test par un simple :
Console.log (sensorAn) ;

La prochaine fois, on remplacera la console locale par une page web :P .
les codes :

sensor.js

Code : Tout sélectionner

var Sensor = {

    ANALOG: function (sensorfile, adc) {

        var ch = require(sensorfile).ANALOG;
        var dmax = Math.pow(2, adc.Resolution) - 1;

        for (var i = 0; i < ch.length; i++) {
            ch[i].Coef = (ch[i].Vmax - ch[i].Vmin) / dmax;
            ch[i].Value = 0.0;         
        }
      
        this.Read = function (channel) {
            if (typeof channel !== "undefined") {
                let v = adc.ReadChannel(channel);
                ch[channel].Value = (ch[channel].Coef * v +
                    ch[channel].Vmin).toFixed(ch[channel].Dec);
            }
            else {
                let v = adc.ReadAll();
                for(var i = 0; i<ch.length; i++) {
                    ch[i].Value = (ch[i].Coef * v[i] +
                        ch[i].Vmin).toFixed(ch[i].Dec);
                }
            }       
        };
        this.Channel = ch;
    }
};
module.exports = Sensor;
sensor.json

Code : Tout sélectionner

{
  "ANALOG": [
    {
      "Iden": 0,
      "Name": "Voltmetre 0",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 1,
      "Name": "Voltmetre 1",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 2,
      "Name": "Voltmetre 2",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 3,
      "Name": "Voltmetre 3",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 4,
      "Name": "Voltmetre 4",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 5,
      "Name": "Voltmetre 5",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 6,
      "Name": "Voltmetre 6",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    },
    {
      "Iden": 7,
      "Name": "Voltmetre 7",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3
    }
  ]
}
index.js

Code : Tout sélectionner

var spi_io = require("./spi_io.js");
var sensor = require("./sensor.js");
var rpio = require("rpio");
    rpio.init({ gpiomem: false, mapping: 'gpio' });
    rpio.spiBegin();

var mcp3208 = new spi_io.MCP3208(rpio, 0, 512);
var sensorAn = new sensor.ANALOG("./sensor.json", mcp3208);

var test = function () {
    sensorAn.Read();
    for (var i = 0; i < sensorAn.Channel.length; i++) {
        var c = sensorAn.Channel[i];
        console.log(c.Name + " " + c.Value + " " + c.Unite);
    }
    console.log("\r\n");
};

setInterval(test, 1000);

process.on('SIGINT', function () {
    rpio.spiEnd();
    process.exit();
});
spi_io.js

Code : Tout sélectionner

var Spi_IO = {

    MCP3208: function (rpio, cs, clk) {

        this.Cs = cs;
        this.Clk = clk;
        this.Resolution = 12;

        var adcResult = function (reg) {
            rpio.spiTransfer(reg, reg, reg.length);
            return reg;
        };

        this.ReadAll = function () {
            rpio.spiSetClockDivider(this.Clk);
            rpio.spiChipSelect(this.Cs);
            var x = [];
            var b = [];
            for (i = 0; i < 8; i++) {
                b = adcResult(Buffer([(i >> 2) + 6, i << 6 & 0xFF, 0x00]));
                x[i] = ((b[1] & 0x0F) << 8) + b[2];
            }
            return x;
        };

        this.ReadChannel = function (channel) {
            rpio.spiSetClockDivider(this.Clk);
            rpio.spiChipSelect(this.Cs);
            var b = adcResult(Buffer([(channel >> 2) + 6, channel << 6 & 0xFF, 0x00]));
            return ((b[1] & 0x0F) << 8) + b[2];
        };
    }
};
module.exports = Spi_IO;


**********************************
Quelques capteurs susceptible de répondre cahiers des charges :

Capteur de courant 20 Amp. /20Vdc
https://www.gotronic.fr/art-capteur-de- ... -26058.htm
Sortie = 0.1v/Amp ; Vref/2=0 Amp, Donc allimenté en 5v : Vmin = -(2.5 / 0.1) = -25 ;Vmax = (2.5/0.1) = 25
On a donc une amplitude de 50A que l’on peut diviser par 4095 soit 0.01221001 Amp/Unité numérique

Capteur de temperature 0 à 110°
https://www.gotronic.fr/art-capteur-de- ... -18965.htm
Vmin : -40 ;Vmax : 125
Ou sinon :
https://www.gotronic.fr/art-capteur-de- ... -19287.htm
10mV/1°C. Donné pour 0 à 100° mais peut électriquement peut faire l’affaire pour des pointes à 100.
Vmin : 0 ;Vmax : 250

Capteur de tension 0 à 20 VDC
Ici, simple diviseur de tension suffit. Perso j’utiliserais un potentiomètre multitour (4.7k a 10k) que j’ajusterais pour avoir une division précise par 5 ce qui permettrais de mesurer des tensions de 0 à 25v. Dans ce cas :
Vmin : 0 ; Vmax : 25
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: [TUTO] Application web dynamique

Message par Bud Spencer » sam. 24 mars 2018 18:58

Aller op, on passe coté web. Pour ça, rien de nouveau, il suffit de définir le coté serveur dans le fichier index.js (comme pour toutes les autres leçons du toto) ainsi que des routes et des socket.io event :
dec_webserver.png
dec_webserver.png (93.27 Kio) Vu 6350 fois
De créer des pages web (nouveau fichier console.html) :
consolehtml.png
consolehtml.png (51.75 Kio) Vu 6339 fois
Et bien penser à démarrer le serveur en fin de script (perso, en dev j’utilise le port 8080) :
httpserver.listen(8080) ;

J’ai mis 2 routes pour l’instant.
http://adressedupi:8080/console qui affiche une page avec les valeurs de capteurs
http://adressedupi:8080/sensorAn qui permet d’utiliser l’appli comme un web service json qui renvois les capteurs analogiques et leurs valeurs réelles à l’instant.


J’ai aussi configuré mon fichier sensor.json avec 3 capteurs (1,2 et 3) que j’ai donnés dans le post dessus.

Pour résumer le fonctionnement des échanges web, c’est très simple.
A la connexion, le serveur envois la configuration complète des capteurs a la page web et celle-ci l’initialise en une variable objet sensorAn identique à celle que l’on a sur le serveur.
Toutes les seconde, le serveur exécute la fonction Read() de la class sensorAn pour initialiser la valeur à l’instant de chacun des capteur et broadcast le résultat a tous ses clients socket, ce qui déclenche chez les client un évènement qui remet à jour le contenu du Div console.


Désormais, notre appli sait transmettre en temps réel les valeurs de 8 capteurs analogique interfacés par un convertisseur AD relié au PI en SPI. Reste à implémenter les alarmes et voir ce qu’on peut faire coté GUI (ce que j’appelle la broderie). Juste à titre informatif, j’ai noté à chaque fois le temps que je passais pour le développement de ce datalogger (pas celui pour écrire le tuto ni pour monter la maquette hardware). J’avais provisionné 2 à 3 heures de code en répondant à Didier qui n’avait pas trop idée du temps nécessaire pour faire faire ce genre de chose. Je vous dirais à combien on arrive quand ce sera fini, mais pour l’instant, on est très largement dans les clou …

le index.js modifié

Code : Tout sélectionner


// #region Declarations Web server

var express = require('express');
var ejs = require('ejs');
var basicauth = require('basic-auth-connect');
var app = express();
    app.engine('html', ejs.renderFile);
    app.set('views','./templates');
    app.use(express.static('./'));
    app.use('/public', express.static('./public/'));
    app.use('/jquery', express.static('./node_modules/jquery/dist'));
    app.use('/socket.io', express.static('./node_modules/socket.io-client/dist/'));   
    app.use(basicauth('bud', 'spencer'));
var httpserver = require('http').createServer(app);
var socket = require('socket.io').listen(httpserver);

// #endregion

// #region Declarations Digital/Analogic

var rpio = require("rpio");
    rpio.init({ gpiomem: false, mapping: 'gpio' });
    rpio.spiBegin();

var spi_io = require("./spi_io.js");
var sensor = require("./sensor.js");

var mcp3208 = new spi_io.MCP3208(rpio, 0, 512);
var sensorAn = new sensor.ANALOG("./sensor.json", mcp3208);

// #endregion

// #region Routes

app.get('/console', function (req, res) {
    res.render("console.html");
});


app.get('/sensorAn', function (req, res) {
    res.json(sensorAn);
});

// #endregion

// #region sokcet.io

socket.sockets.on("connection", function (client) {
    console.log("Client connected from " + client.request.connection.remoteAddress);
    client.emit("sensorAn", { sensorAn : sensorAn });
});


var test = function () {
    sensorAn.Read();
    var data = [];
    for (var i = 0; i < sensorAn.Channel.length; i++) {
        data[i] = sensorAn.Channel[i].Value;
    }
    socket.sockets.emit('sensorAn.value', { value : data });
};

// #endregion

process.on('SIGINT', function () {
    rpio.spiEnd();
    process.exit();
});


setInterval(test, 1000);
httpserver.listen(8080);

et le code de la page web console.html

Code : Tout sélectionner

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Console Datalogger</title>
</head>
<body>

    <div id="console"></div>

    <script src="/socket.io/socket.io.js"></script>
    <script src="/jquery/jquery.js"></script>

    <script>

        var io = io.connect(location.host);
        var sensorAn;

        io.on('sensorAn.value', function (data) {
            
            $("#console").text("");
            for (var i = 0; i < data.value.length; i++) {
                $("#console").append(
                    sensorAn.Channel[i].Name + " : " +
                    data.value[i] + " " +
                    sensorAn.Channel[i].Unite + "<br />"
                );
            }
        });
                
        io.on('sensorAn', function (data) {
            sensorAn = data.sensorAn;
        });
       
    </script>

</body>
</html>

Fichiers joints
consolehtml.png
consolehtml.png (51.75 Kio) Vu 6341 fois
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

Répondre

Retourner vers « Tutoriels »