[TUTO] Application web dynamique

Proposer ou rechercher un tutoriel concernant le Raspberry Pi

Modérateur : Francois

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 19:38

Decidedement, si créer une appli datalogger de toutes pièce avec un PI est une partie de plaisir, il n'en va pas de meme pour poster des articles ici. Ca n'a jamais été bien pratique, mais depuis la dernière mise a jour, c'est carrément vrai calvaire :( ...

Les résultats des requêtes http du post du dessus :
httpresult.png
httpresult.png (202.7 Kio) Vu 6821 fois
j'ai oublié de préciser dans le post précédent que j'ai intégrer au serveur web l'authentification du client conformément au cahier des charges. Pour le descativer en phase de dev il suffit de mettre la ligne app.use(basicauth('bud', 'spencer')); en commentaire.
bud est évidement le nom d'utilisateur et spencer le mot de passe mais tout ca a déjà été expliquer sur un autre post du tuto
Modifié en dernier par Bud Spencer le dim. 25 mars 2018 10:35, 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 » dim. 25 mars 2018 10:13

Maintenant qu’on a un serveur web qui sait échanger des datas en temps réel avec ses client, on peut s’amuser a plein de chose très facilement. Ce matin, je vous ai fait ça entre 2 tartines :
manometer.png
manometer.png (188.02 Kio) Vu 6810 fois
Ce n’est rien d’autre que l’équivalent de la page de la leçon n°4 avec juste un petit tour de magie pour créer dynamiquement les manomètres en utilisant les paramètres des capteurs qui sont reçu sur le socket à la connexion et la mise à jour se fait a chaque lecture des capteurs.

Coté code, à part créer la nouvelle page web (que l’on pourrait qualifier de ‘vue’) que j'ai mis dans le rep. templates, j’ai juste ajouté une route qui lui est destinée dans l’index.js.

Code : Tout sélectionner

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

Pour utiliser cette page, il faut récupérer le fichier gauge.min.js et le rep. fonts qui se trouve dans le zip de la leçon 4 et les mettre dans le répertoire ‘public’. Voila à quoi ressemble l'arborescence du projet :
abrprj.png
abrprj.png (14.59 Kio) Vu 6809 fois
manometre.html

Code : Tout sélectionner

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title><"Manomètre Datalogger"></title>   
</head>
<body bgcolor="#D1D1D1">
    <div> 
        <canvas id="jauge0"></canvas>
        <canvas id="jauge1"></canvas>
        <canvas id="jauge2"></canvas>
        <canvas id="jauge3"></canvas>
    </div>
    <div>
        <canvas id="jauge4"></canvas>
        <canvas id="jauge5"></canvas>
        <canvas id="jauge6"></canvas>
        <canvas id="jauge7"></canvas>
    </div>

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

        var io = io.connect(location.host);
        var sensorAn;
        var jauge = [];

        io.on('sensorAn.value', function (data) {
            $("#console").text("");
            for (var i = 0; i < data.value.length; i++) {
                jauge[i].setValue(data.value[i]);               
            }
        });

        io.on('sensorAn', function (data) {
            sensorAn = data.sensorAn;
            for (var i = 0; i < sensorAn.Channel.length; i++) {              
                drawgauge(sensorAn.Channel[i]);
            }
        });

        function drawgauge(sensor) {
            var ticks = ((sensor.Vmax - sensor.Vmin) ) / 5 ;
            var majorticks = [];
            for (var i = 0; i < 6; i++)
            {
                majorticks[i] = ((i * ticks)+sensor.Vmin).toFixed(0).toString();
            }
            jauge[sensor.Iden] = new Gauge({
                renderTo: 'jauge' + sensor.Iden,
                width: 200,height: 200,glow: true,
                valueFormat: { int: 1, dec: sensor.Dec },
                units: sensor.Unite,title: sensor.Name,minValue: sensor.Vmin,
                maxValue: sensor.Vmax,majorTicks: majorticks, 
                minorTicks: 10,strokeTicks: true,highlights: true,
                colors: {
                    plate: '#000000',majorTicks: '#00ff00',minorTicks: '#ddd',
                    title: '#00ff00',units: '#ccc',numbers: '#00ff00',
                    needle: { start: 'rgba(240, 128, 128, 1)', end: 'rgba(255, 160, 122, .9)' }
                }
            });
            jauge[sensor.Iden].draw();
        }

    </script>
</body>
</html>
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

spourre
Raspinaute
Messages : 735
Enregistré le : lun. 22 déc. 2014 16:50
Localisation : 67380 LINGOLSHEIM

Re: [TUTO] Application web dynamique

Message par spourre » dim. 25 mars 2018 10:59

Bud Spencer a écrit :
ven. 23 mars 2018 19:13
....
snip plein de choses passionnantes
....

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
C'est un point de détail mais, personnellement, je mettrais une résistance talon pour être certain de ne pas dépasser la tension max supportée par un des ports analogique (cf. datasheet, entre VSS et Vref, soit 5V dans noter cas).

Avec un seul potentiomètre, le risque est trop grand de mettre le curseur trop proche de Vmax.

Sylvain

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

Re: [TUTO] Application web dynamique

Message par Bud Spencer » lun. 26 mars 2018 11:36

C’est sur qu’il ne faut pas faire n’importe quoi et appliquer des tensions trop élevées sur les entrées d’adc. Perso, pour ce genre de chose, je ne prends pas de risque. Je règle mes potentiomètres à l’ohmmètre en me basant sur la valeur totale mesurée et sur la valeur divisée au niveau du curseur. Ensuite je mets une pointe de vernis sur la vis et je marque le rapport et le point froid dessus au stylo feutre.
Il y a une multitude de façon de protéger les entrées des adc. Après, la méthode peut différer suivant si on construit un data logger ‘universel’ ou si le montage est destiné à usage spécifique. Dans tous les cas, même si on se l’autorise en phase de développement, c’est toujours une mauvaise pratique que de vouloir raccorder des capteurs directement sur les entrées. En ce qui me concerne, Cela fait bientôt 30 ans que je fais de la conversion ad avec des composants de toutes sortes et je n’ai jamais grillé une entrée.
Juste pour la petite histoire, une de mes premières applications qui utilisait un ADC était une carte d’interface multimode (Fax/SSTV/CW/RTTY) pour l’Amstrad cpc et de mémoire le composant utilisé était un adc804. C’était du temps ou il fallait réfléchir un peu et ou le ‘DIY’ ne se résumait à acheter et câbler des chinoiseries toutes faites 😉
Je ferais peut-être une petite note sur la partie hard quand j’en aurait fini avec le soft.
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. 26 mars 2018 20:50

Allez, on se fait vite fait les alarmes. N’ayant pas de détails, je fais suivant mon interprétation, à savoir détecter quand une valeur de capteur dépasse un seuil critique.

Pour ça, c’est juste quelques lignes de codes pis tant qu’a élever un peu le niveau, on va utiliser de l’évènementiel (déjà présenté dans l’exemple DynIP).

J’ai ajouté le package ‘events’ (npm install events) dans le fichier sensor.js, déclaré une variable d’évènement et ajouter un évènement AlarmeChange à la class Analog. Pis comme maintenant on a des évènements, on peut les utiliser aussi pour le reste donc j’ai aussi ajouté un évènement ‘Change’. Du coup la fonction Read() de la class analog ne retourne plus rien, elle lève des évènements. (J’ai viré la partie qui permettait de n‘interroger qu’un capteur et qui était juste à titre démonstratif).
sensorevent.png
sensorevent.png (44.28 Kio) Vu 6774 fois
Coté code principal, il suffit, juste de s’abonner à ces évènements en écrivant les fonctions requissent. Du coup la fonction test peut se résumer a uniquement appeler la fonction sensorAn.Read() et tout le reste est traité dans les fonctions d’évènement.
La fonction ‘Change' est levée à chaque lecture des capteurs alors que la fonction ‘AlarmeChange’ n’est levée que quand un état d’alarme d’un capteur change.
indexevent.png
indexevent.png (23.43 Kio) Vu 6774 fois
Bien sûr, il faut bien définir une valeur critique pour chaque capteur et pour ça, il suffit juste de rajouter une variable à chaque capteur dans le fichier sensor.json (je l’ai appelé Valm (pour Valeur d’alarme)).
sensjsonevent.png
sensjsonevent.png (22.71 Kio) Vu 6774 fois
Il me reste encore une bonne heure pour tracer un graph et relier physiquement les alarmes, normalement ça devrait le faire :P
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. 27 mars 2018 21:25

Aller hop, un petit coup de graphique. Pour ça, il existe des milliers de scripts et de lib sur le net (chart javascript dans google) ou on peut aussi le faire soit meme en traçant dans un canvas suivant le modèle de la leçon 3. Il y en a un que j’aime bien et que j’utilise assez fréquemment, c’est le smoothie.js. C’est un slide shart très simple qui contrairement à beaucoup d’autres est bien adapté pour le suivie en temps réel. Il est d’ailleurs utilisé dans la console Dashboard Windows 10 IoT pour le tableau de bord des ressources.

Pour l’exemple j’ai gardé la meme maquette sauf que j’ai remplacé le potard du canal 3 par un signal bf sinusoïdal d’1/2 Hertz avec une tension de 3.5v d'amplitude et un offset de 0.5 volts et j'ai réduit le setInteval d'appel de la fonction test a 100ms dans le fichier index.js. Les 8 graphs sont parfaitement synchronisé et très fluide meme sur un vieux smartphone androïd 4.0. Les petites led sont écrite en css et indiquent l’état d’alarme des capteurs. On pourrait chiader le truc en mettant des couleurs différentes pour chaque capteur, il suffirait juste pour ça d’ajouter une valeur a cet effet dans le fichier sensor.json.

Pour utiliser l'exemple, il faut enregistrer la page graphique. html dansle rep. templates, mettre le fichier smoothie.js dans le rep public et bien sur ajouter la route 'graphique' dans le fichier index.js
graphique.png
graphique.png (72.98 Kio) Vu 6752 fois
graphique.html

Code : Tout sélectionner


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

        div.smoothie-chart-tooltip {
            background: #444;
            padding: 1em;
            margin-top: 20px;
            font-family: consolas;
            color: white;
            font-size: 10px;
            pointer-events: none;
        }

        table {
            background-color: lightgrey;
            margin-left: auto;
            margin-right: auto;
        }

        td {
            background: #444;
            padding: 5px;
            margin-top: 10px;
            font-family: consolas;
            color: white;
            font-size: 10px;
            font-weight: bold;
            text-align: center;
        }

        .led-on {
            margin: auto;
            width: 12px;
            height: 12px;
            background-color: #F00;
            border-radius: 50%;
            box-shadow: #000 0 -1px 7px 1px, inset #F00 0 -1px 9px, #F00 0 2px 12px;
        }

        .led-off {
            margin: auto;
            width: 12px;
            height: 12px;
            background-color: #808080;
            border-radius: 50%;
            box-shadow: #000 0 -1px 7px 1px, inset #808080 0 -1px 9px, #808080 0 2px 12px;
        }
    </style>
</head>

<body bgcolor="#444" >
    <br/>
    <table>
        <tr>
            <td colspan="2"><canvas id="chart0" width="250" height="150"> </canvas></td>
            <td colspan="2"><canvas id="chart1" width="250" height="150"> </canvas></td>
            <td colspan="2"><canvas id="chart2" width="250" height="150"> </canvas></td>
            <td colspan="2"><canvas id="chart3" width="250" height="150"> </canvas></td>
        </tr>
        <tr>
            <td id="name0"></td><td><div id="led0" class="led-off"></div></td>
            <td id="name1"></td><td><div id="led1" class="led-off"></div></td>
            <td id="name2"></td><td><div id="led2" class="led-off"></div></td>
            <td id="name3"></td><td><div id="led3" class="led-off"></div></td>
        </tr>
        <tr>
            <td colspan="2"><canvas id="chart4" width="250" height="150"> </canvas></td>
            <td colspan="2"><canvas id="chart5" width="250" height="150"> </canvas></td>
            <td colspan="2"><canvas id="chart6" width="250" height="150"> </canvas></td>
            <td colspan="2"><canvas id="chart7" width="250" height="150"> </canvas></td>
        </tr>
        <tr>
            <td id="name4"></td><td><div id="led4" class="led-off"></div></td>
            <td id="name5"></td><td><div id="led5" class="led-off"></div></td>
            <td id="name6"></td><td><div id="led6" class="led-off"></div></td>
            <td id="name7"></td><td><div id="led7" class="led-off"></div></td>
        </tr>
    </table>

    <script src="/jquery/jquery.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script src="/public/smoothie.js"></script>
    <script>
        var io = io.connect(location.host);

        var sensorAn;
        var chart = [];
        var line = [];
        var memo = [];
        var led = [];

        io.on('sensorAn', function (data) {
            sensorAn = data.sensorAn;
            for (var i = 0; i < sensorAn.Channel.length; i++) {
                drawchart(sensorAn.Channel[i]);
            }
        });

        function drawchart(sensor) {

                line[sensor.Iden] = new TimeSeries();
                chart[sensor.Iden] = new SmoothieChart(
                    {
                        grid: {
                            strokeStyle: 'rgb(255, 255, 255)',
                            fillStyle: 'rgb(0, 0, 0)',
                            lineWidth: 1, millisPerLine: 500,
                            verticalSections: 10
                        },
                        maxValue: sensor.Vmax,
                        minValue: sensor.Vmin,
                        tooltip: true,
                        interpolation: 'linear'
                    });

                chart[sensor.Iden].addTimeSeries(line[sensor.Iden],
                    {
                        strokeStyle: 'rgb(255, 128, 128)',
                        fillStyle: 'rgba(255, 128, 128,0.3)',
                        lineWidth: 2
                    });
            let c = document.getElementById("chart" + sensor.Iden);
                chart[sensor.Iden].streamTo(c);
                memo[sensor.Iden] = document.getElementById("name" + sensor.Iden);
                led[sensor.Iden] = document.getElementById("led" + sensor.Iden);            
        }

        io.on('sensorAn.value', function (data) {
            $("#console").text("");
            var d = new Date().getTime();
            for (var i = 0; i < data.value.length; i++) {
                sensorAn.Channel[i].Value = data.value[i];
                let c = sensorAn.Channel[i];
                line[i].append(d, c.Value);
                memo[i].innerHTML = c.Name + " : " + c.Value + " " + c.Unite;
                if (c.Value >= c.Valm) {
                    $('#led' + i).removeClass('led-off').addClass('led-on');
                }
                else {
                    $('#led' + i).removeClass('led-on').addClass('led-off');
                }
            }          
        });
       
    </script>
</body>
</html >



Comme je me suis pas mal amusé avec les css, j'ai pratiquement atteint les 3h00 de code que j'avais prévue. D'un autre coté, j'ai aussi pas mal brodé pour pouvoir expliquer différentes choses, donc je suis dans les clous. Il me reste a chainer physiquement les alarmes, mais ca devrait être résolue rapidement vue que tout est déjà prêt. Je ferais peut être ca demain soir ...
Modifié en dernier par Bud Spencer le mer. 28 mars 2018 21:12, 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 » mer. 28 mars 2018 21:11

Il restait à relier physiquement les alarmes. J’avais plusieurs possibilités pour ça. Soit ajouter un composant sur la SPI (genre un mcp23s08) ou alors me contenter des GPIO du Pi. J’ai choisi la seconde en considérant que tout est déjà expliqué sur le tuto pour quelqu’un qui choisirait la première.

La mise en œuvre de ces alarmes physiques se résume à quelques lignes. La première chose à faire, c’est d’ajouter une valeur à chaque capteur pour indiquer quelle pin du gpio sera rattaché à son alarme et si on en veut pas, on met 0 (j’ai appelé cette variable GpioAlm)
gpioalm.png
gpioalm.png (21.07 Kio) Vu 6731 fois
Ensuite, initialiser ces GPIO en sortie juste après la déclaration de l’objet analogAn dans le fichier index.js (l’idéal serait de le faire depuis le fichier de capteur.js ) et aussi penser à les libérer sans le SIGINT . Et pour finir tout simplement rajouter le pilotage des sorties dans l’évent d’alarme.
indexgpioalm.png
indexgpioalm.png (30.59 Kio) Vu 6731 fois
Voilà, c’est fini. J’ai désormais un dataloguer quasi universel 8 pistes avec alarme programmable et serveur web d’application temps réel. Je pourrais lui apporter beaucoup d’amélioration comme des entrés logiques, un système de sauvegarde des données locale ou distant, une page web de configuration pour les capteurs et tout un tas d’autres chose …

Les codes :

index.js

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

for (var i = 0; i < sensorAn.Channel.length; i++) {
    console.log(i);
    if (sensorAn.Channel[i].PinAlm !== 0) {
        rpio.open(sensorAn.Channel[i].GpioAlm, rpio.OUTPUT, rpio.LOW);
    }
};


// #endregion 

// #region Routes

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

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

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

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

// #endregion

// #region socket.io

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

// #endregion

// #region Sensor Events

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

sensorAn.Event.on('Alarme', function (channel) {
    var c = sensorAn.Channel[channel];
    if (c.GpioAlm !== 0) {
        rpio.write(c.GpioAlm, c.Alarme ? rpio.HIGH : rpio.LOW);
    }   
});


// #endregio

process.on('SIGINT', function () {
    rpio.spiEnd();
    for (var i = 0; i < sensorAn.Channel.length; i++) {
        if (sensorAn.Channel[i].GpioAlm !== 0) {
            rpio.close(sensorAn.Channel[i].PinAlm);
        }
    }
    process.exit();
});

var test = function () {
    sensorAn.Read();
};


setInterval(test, 100);
httpserver.listen(8080);
spi_io

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;
sensor.json

Code : Tout sélectionner

{
  "ANALOG": [
    {
      "Iden": 0,
      "Name": "Voltmetre 0",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3,
      "Valm": 5.0,
      "GpioAlm": 0
    },
    {
      "Iden": 1,
      "Name": "Courant",
      "Unite": "Amp",
      "Vmin": -25.0,
      "Vmax": 25.0,
      "Dec": 1,
      "Valm": 20.0,
      "GpioAlm": 0
    },
    {
      "Iden": 2,
      "Name": "Tension",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 25,
      "Dec": 3,
      "Valm": 20.0,
      "GpioAlm": 0
    },
    {
      "Iden": 3,
      "Name": "Température",
      "Unite": "°C",
      "Vmin": -40,
      "Vmax": 125,
      "Dec": 0,
      "Valm": 80.0,
      "GpioAlm": 18
    },
    {
      "Iden": 4,
      "Name": "Voltmetre 4",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3,
      "Valm": 5.0,
      "GpioAlm": 0
    },
    {
      "Iden": 5,
      "Name": "Voltmetre 5",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3,
      "Valm": 5.0,
      "GpioAlm": 0
    },
    {
      "Iden": 6,
      "Name": "Voltmetre 6",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3,
      "Valm": 5.0,
      "GpioAlm": 0
    },
    {
      "Iden": 7,
      "Name": "Voltmetre 7",
      "Unite": "Volt",
      "Vmin": 0.0,
      "Vmax": 5.0,
      "Dec": 3,
      "Valm": 20.0,
      "GpioAlm": 0
    }
  ]
}
sensor.js

Code : Tout sélectionner

var events = require('events');
var Sensor = {

    ANALOG: function (sensorfile, adc) {

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

        for (var i = 0; i < ch.length; i++) {
            ch[i].Coef = (ch[i].Vmax - ch[i].Vmin) / dmax;
            ch[i].Value = 0.0; 
            ch[i].Alarme = false;
           
        }
      
        this.Read = function () {
            let v = adc.ReadAll();
            for (var i = 0; i < ch.length; i++) {
                let alarme = ch[i].Alarme;
                ch[i].Value = (ch[i].Coef * v[i] + ch[i].Vmin).toFixed(ch[i].Dec);                   
                ch[i].Alarme = ch[i].Value > ch[i].Valm; 
                if (alarme !== ch[i].Alarme) {
                    chEvent.emit("Alarme", i);
                }
            }  
            chEvent.emit("Result");
        };

        this.Channel = ch;
        this.Event = chEvent;
    }
};
module.exports = Sensor;
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. 2 avr. 2018 20:13

Franchement ? … j’étais ennuyé. Cette histoire d’utiliser les gpio du pi par facilité alors que l’on venait de mettre en place une liaison SPI pour avoir des canaux analogique, ça faisait un peu trop noobs et en rester là après tout le temps que j’ai passé à rédiger ce tuto, pour moi, ça le faisait pas.
Donc hier soir au lieu de m’abrutir devant la téloch, je me suis dit qu’il serait bien plus rigolo de reprendre un peu le code de ce truc. Ma première idée était de câbler un mpc23s08 (expander 8 i/o spi) pour remplacer les GPIO utilisés dans le précédent code pour les alarmes. Pas de bol, je n’avais plus ce composant dans mes tiroir et pas trop l’envi de vampiriser un montage existant. Du coup, je me suis rabattu sur un mcp23S17 en remplacement (expander 16 i/o spi). Pis une fois que j’ai eu programmé mes 8 sorties, je me suis dit que c’était idiot d’utiliser seulement la moitié du composant, donc j’ai recodé un petit peu et j’ai ajouté 8 entrés ‘tout ou rien’ au datalogger.

J’ai maintenant un datalogger 8 entrés analogique avec sortie d’alarme programmable et 8 entrés ‘tout ou rien’.

Pis comme il était encore trop tôt pour aller au dodo, je me suis amusé un peu avec les css de la page graphique. J’ai aussi ajouté un petit monitor qui affiche en scrolling les derniers changements d’alarme et les connexions client. Pour résumer, les graphs affichent les valeurs de capteurs analogiques, les led rouge représentent les alarme des analogiques respectifs et les led vertes les états des entrés booléenne J’ai pas la prétention d’être un expert en web design, mais en écran de supervision pour salle de torture dans Quake 2, ça pourrait le faire :lol: :mrgreen:
bndlg0001.png
bndlg0001.png (457.05 Kio) Vu 6665 fois
Pour la partie ‘technique’, c’est toujours la meme maquette, mais avec un mcp23s17 en plus sur la meme spi. Par défaut, tous les analogiques sont initialisé de 0 à 5 v avec l’alarme programmée à 2.5v et toujours mon signal d’1/2 hz sur l’analog 3. Les valeurs de port d’entrés sont inversées (registre IPOL sur le 23s17) de façon à avoir ‘true’ quand une entrés est mise à la masse. J’ai raccordé l’entrée 5 à la masse et la sortie d’alarme de l’analog 3 a l’entrée 3 (ce qui me permet de contrôler le bon fonctionnement des entrès/sortie).
Si j’ai le temps dans la semaine, je dessinerais la partie hardware nécessaire et je mettrais tous les codes dans un zip.
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. 10 avr. 2018 23:18

Je voulais publier le code de la GUI du post précédent, mais en le contrôlant, je me suis dit que c’était ballot d’en rester là. J’ai donc ajouté quelques options :mrgreen: .

Déjà, exit le contrôle smoothie.js. J’ai codé mon propre graphique défilant (qui est basé sur le meme modèle de tracé que la leçon n°3). Il n’est pas aussi joli, mais il permet d’avoir les courbes des 8 entrées analogiques sur un meme graphique en consommant infiniment moins de ressource que le smoothie; ce qui rend la GUI directement exploitable dans le navigateur du PI. Evidemment, pour différentier chaque courbe, j’ai ajouté un paramètre de couleur pour chaque capteur dans le fichier de config sensor.json.
Je me suis dit que ça pouvait aussi être pratique de masquer une ou plusieurs courbes ainsi que les évènements de changement d’état de certaines I/O dans le monitor, donc j’ai ajouté l’option. Sur l’image, on peut voir que les Analog 5 et 6 et les Input 1 et 2 sont désactivés (grisés). Toutes ces données sont bien sur toujours activent mais sont masquée sur le graph et le monitor. Ces possibilités sont activable/désactivable par simple clic sur le nom des capteurs ou entrées.

Jusque-là, j’affichais des données, mais pas moyen de les récupérer, du coup j’ai codé un petit buffer tournant qui permet le stockage en mémoire des dernières données. Sur l’image on voit que la taille du buffer est de 72000 lignes, (2 heure de stockage à raison de 10 acquisitions complètes par seconde). La taille du fichier est de ~ 2.5Mo/heure. Si on regarde bien, ces 72000 lignes de données, c’est 576000 valeurs analogiques et 1152000 état d’i/o numérique et tout ça sans écrire le moindre octet sur la SD... Bien sûr pas la peine d’attendre que le buffer soit plein pour en profiter, on peut le télécharger au format csv ou le vider à n’importe quel instant sans perturber l’acquisition. J’aurais pus ajouter un double buffer pour externaliser les données en automatique, mais ça imposerait de choisir un modèle de persistance. C’est en tous cas une idée à garder sous le coude si on à besoin de loger moins vite et sur des périodes plus longues (genre usage domotique)

Hormis ça, quelques bricole pour meubler, comme les éphémérides du Pi (mémoire et température CPU) qui défilent en bas de la GUI ainsi qu’un bouton de reboot et un de shutdown.
gui2.jpg
gui2.jpg (150.58 Kio) Vu 6577 fois
Aperçu du buffer au format csv dans LO directement sur le pi
csv_lo.jpg
csv_lo.jpg (134.49 Kio) Vu 6577 fois


Du coup, je n’ai pas fait le schéma de l’interface avec l’add du mcp23s17 et j’ai encore 1 ou 2 trucs sympa à ajouter avant de vous livrer tout ça ;)
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 » jeu. 12 avr. 2018 20:32

Bon, j’ai fait un gros effort aujourd’hui. Je vous ai dessiné la maquette incluant le mcp23s17. En fait j’ai juste ajouter le composant au dessin précédent .Les plus attentifs noteront que je me suis quand meme fendu de remettre les n° et noms de pin dans le sens du dessin :lol: .

Fondamentalement la partie câblage avec le pi se résume à faire les bons raccordements pour la liaison SPI. La pin /Reset est câblée au + et les pins A0, A1 et A2 d’adressage sont misent à la masse (adresse 0). L’interfaçage 5v du composant avec le 3.3v des I/O du PI se suffit du pont R1/R2 déjà mis en place pour le 3208. Pour l’exemple, j’ai utilisé le second /CS dispo sur le PI (GPIO7) mais le montage fonctionne aussi en utilisant un seul CS pour les 2 composants. J’ai câblé une led de contrôle sur le port B3 (que le code utilise en sortie d’alarme pour l’analog 3) et un simple poussoir sur le port A5 que le code utilise en entré. La liaison GPA3<->GPB3 permet de suivre l’état de changement des I/O dans la GUI sans avoir à appuyer sur un bouton (fainéantise pratique et du plus bel effet vue que quand l’alarme de l’analog3 passe à Off, l’input3 passe à On ). Dans mon monde réel, il y a juste le potentiomètre P1 que j’ai remplacé par un générateur BF (par fainéantise de tourner le bouton) ce qui me vaut ces belles courbe sinusoïdale sur le graph de test.
datalogger_spi.gif
datalogger_spi.gif (62.17 Kio) Vu 6532 fois
Pour les petites améliorations qu’il me reste à apporter au code, c’est très simple. Je veux juste ajouter quelques boutons pour pouvoir modifier la vitesse d’acquisition depuis la gui ainsi que la vitesse de défilement locale du graphique.
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 »