Variables

Déclaration des variables dans arduino

variables globales, locales, static

Variables globales

Ce sont des variables déclarées au début du programme, en dehors de toute fonction. Ces variables ont une portée globale, c’est à dire que chaque fonction constituant le programme peut les utiliser, les manipuler.

Avantages :

  • On déclare les variables une fois pour toute, on n’a plus à y revenir…
  • Comme les variables sont accessibles à toutes les fonctions, on peut faire l’économie de passage de paramètres.

Inconvénients :

  • Les variables mobilisent l’espace mémoire durant toute la vie du programme, on peut ainsi saturer la mémoire vive avec des variables qui n’ont servi qu’une fois (à l’initialisation par exemple)
  • Si on utilise les mêmes variables dans diverses fonctions, il se peut qu’une fonction modifie la valeur d’une manière qui n’était pas attendue par une autre fonction qui utilisait la même variable, c’est un source importante de bugs…

On évite donc le plus possible d’utiliser les variables globales et en particulier pour des compteurs de boucles.

Variables locales

Ce sont des variables déclarées au sein d’une fonction. Ces variables ont une portée locale, c’est-à-dire qu’une fois sortie de la fonction elles sont détruites libérant ainsi de l’espace mémoire. Ce sont les variables internes à une fonction, nécessaire à son fonctionnement.

Avantage :

  • Variables uniquement utilisables par la fonction à laquelle elles appartiennent, les autres fonctions ne peuvent pas interférer.
  • Des fonctions diverses peuvent avoir des variables portant le même nom sans risque d’interférence.

Inconvénients :

  • On doit utiliser un passage paramètre pour renvoyer une variable de ce genre à une autre fonction.
  • Les variables sont détruites à la sortie de la fonction, il n’y a pas de mémorisation de la dernière valeur de la variable.

Variables statiques

Ce sont des variables déclarées au sein d’une fonction, mais qui ne sont pas détruites à la sortie de cette dernière, de cette manière au prochain rappel de la fonction cette variable n’est pas recrée, mais elle est toujours là avec la dernière valeur obtenue. Ces variables associent le coté permanent des variables globales et le fait que cette variable est isolée des autres fonctions.

Variables volatiles

Ce sont des variables globales amenées à être modifiées par un processus extérieur au programme principal. Le mot volatile est une indication donnée au compilateur de ne pas simplifier son code et supprimer cette variable même si elle semble inutile.

Par exemple Routine d’interruption
Les routines d’interruptions utilisent souvent des variables testées dans le programme principal. Par exemple, l’interruption d’un port série en réception : le programme d’interruption stocke et teste les caractères reçus, signalant la fin du message par une variable globale au programme principal :

int End_Message = FALSE; // variable globale
void main ( void )
{
    ....
        while (! End_Message) 
/* mal réglé le compilateur va supprimer ce test car pour lui 
!End_Message= !FALSE= TRUE et comme While(TRUE) est toujours vrai le compilateur simplifie en supprimant ce test « inutile », or End_Message est modifié dans sous programme d’interruption ce test ne doit donc pas être simplifié */
        {
        .....
        }
}
interrupt void Isr_Receive ( void )
        {
            .....
                if ( RX_CHAR == ETX ) { End_Message = TRUE;}
                .....
                    .....
        }

 

Si l’optimisation du compilateur est dé-validée, ce programme fonctionne correctement. Par contre n’importe quel compilateur “décent” avec l’optimisation validée, va supprimer le code après la boucle while dans le main(), la condition ( ! End_Message) étant toujours vraie pour lui : en effet le compilateur n’a pas idée que la variable End_Message est modifiée par la routine d’interruption !
Dans ce cas-là et pour que tout rentre dans l’ordre il suffit de déclarer la variable End_Message avec l’attribut volatile.

bibliothèque Ethernet et Udp

Fonctions de bases nécessaires à l’utilisation du protocole UDP

L’utilisation de la carte réseau avec Arduino est subordonnée à l’utilisation de la bibliothèque Ethernet. Nous passerons en revue les fonctions nécessaire à l’utilisation du protocole UDP.

Bibliothèques nécessaires :

La communication entre la carte Ethernet et l’Arduino s’appuie sur la liaison série synchrone (SPI) , suivant la version de Arduino il peut être nécessaire d’inclure la bibliothèque SPI.h. Les autres fichiers d’entête à inclure au projet sont Ethernet.h et Udp.h

Le début de notre programme contiendra donc au minimum les entêtes suivants :

#include <SPI.h>         
#include <Ethernet.h>
#include <EthernetUdp.h>

 Bibliothèque ethernet

Un objet « Ethernet » est auto-instancié lors de l’inclusion de la bibliothèque Ethernet. Pour utiliser le protocole UDP, une seule fonction de la bibliothèque Ethernet est nécessaire :

Ethernet.begin(mac,IP,gateway,subnet);

Cette fonction initialise la bibliothèque et les paramètres du réseau. Les paramètres sont les suivants :

  • mac: l’adresse MAC (Media Access Control) pour le composant (tableau de 6 octets) : c’est l’adresse Ethernet matérielle de votre module. Pour de nombreux composants Ethernet, cette adresse est prédéfinie par le fabricant. Pour le module Ethernet Arduino, vous devez choisir votre propre adresse MAC.
  • ip: l’adresse IP du composant (tableau de 4 octets).
  • gateway: l’adresse IP de la passerelle du réseau (tableau de 4 octets). Optionnel: la valeur par défaut est l’adresse IP de la carte avec le dernier octet à 1.
  • subnet: le masque du sous-réseau du réseau (tableau de 4 octets). Optionnel : la valeur par défaut est 255.255.255.0.

pour plus d’informations et les autres fonctions exposées, voir la documentation en français ici : http://www.mon-club-elec.fr/pmwiki_reference_arduino/pmwiki.php?n=Main.BibliothèqueEthernet#toc2

bibliothèque UDP

Un objet Udp s’instancie de la manière suivante :

EthernetUDP Udp;

On peut alors utiliser les méthodes suivantes :

  • Udp.begin(localPort);cette fonction initialise le protocole UDP et démarre l’écoute sur le port désigné
  • Udp.stop(); cette fonction stoppe l’écoute du port. Utile si le traitement d’un paquet est long. Cela évite le débordement du buffer d’entrée de la carte Ethernet. (cette fonction a été rajoutée dans la bibliothèque Eiffel)
  • Udp.parsePacket(); cette fonction retourne la taille d’un paquet en octets ou 0 si aucun paquet n’est disponible
  • Udp.available(); cette fonction permet de savoir s’il y a des données dans le buffer de reception. Elle retourne un entier (int) qui vaut 0 si le buffer est vide et le nombre d’octets disponible s autrement.
  • Udp.read(unsigned char* buffer, size_t len); cette fonction permet de lire le paquet reçu et de le placer dans buffer (dont la longueur maximum est spcécifiée par buflen).
    Avant d’appeler cette fonction, il faut s’assurer que le buffer de reception n’est pas vide , il faut donc faire un test avec la fonction Udp.parsePacket()
    La fonction renvoie un entier (int) qui correspond au nombre d’octets lus. Si le nombre d’octets contenus dans le paquet est supérieur à la longueur du buffer, il y a troncature, le nombre renvoyé est alors négatif.
  • Udp.beginPacket(IPAddress ip, uint16_t port); permet d’initier l’envoi d’un paquet vers l’hôte distant dont on spécifie l’adresse IP et le port. La fonction retourne 1 en cas de succès et 0 en cas d’échec.
  • Udp.beginPacket(const char *host, uint16_t port); autre version en passant en argument le nom d’hôte au lieu de l’adresse IP.
  • Udp.endPacket(); ferme le paquet et l’envoie à l’hôte distant.
  • Udp.write(byte data); écrit un octet unique dans le paquet à envoyer.
  • Udp.write(const byte *buffer, size_t taille); Envoie « taille » octets du buffer vers le paquet.

exemple

UDPSendReceiveString.ino

#include <SPI.h>         // needed for Arduino versions later than 0018
#include <Ethernet.h>
#include <EthernetUdp.h>         // UDP library from: bjoern@cs.stanford.edu 12/30/2008


// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {
    0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 1, 177);

unsigned int localPort = 8888;      // local port to listen on

// buffers for receiving and sending data
char packetBuffer[UDP_TX_PACKET_MAX_SIZE];  //buffer to hold incoming packet,
char  ReplyBuffer[] = "acknowledged";       // a string to send back

// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup() {
    // start the Ethernet and UDP:
    Ethernet.begin(mac, ip);
    Udp.begin(localPort);

    Serial.begin(9600);
}

void loop() {
    // if there's data available, read a packet
    int packetSize = Udp.parsePacket();
    if (packetSize) {
        Serial.print("Received packet of size ");
        Serial.println(packetSize);
        Serial.print("From ");
        IPAddress remote = Udp.remoteIP();
        for (int i = 0; i < 4; i++) {
            Serial.print(remote[i], DEC);
            if (i < 3) {
                Serial.print(".");
            }
        }
        Serial.print(", port ");
        Serial.println(Udp.remotePort());

        // read the packet into packetBufffer
        Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE);
        Serial.println("Contents:");
        Serial.println(packetBuffer);

        // send a reply to the IP address and port that sent us the packet we received
        Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
        Udp.write(ReplyBuffer);
        Udp.endPacket();
    }
    delay(10);
}

 

 

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

MQTT – utilisation avec Arduino

Pour arduino il existe une bibliothèque qui permet de créer un client permettant de publier et de souscrire à un topic.
Le code suivant permet de souscrire auprès d’un broker public gratuit : broker.hivemq.com

/*
  Basic MQTT example
  This sketch demonstrates the basic capabilities of the library.
  It connects to an MQTT server then:
  - publishes "Arduino Connected" to the topic "eiffel/texte"
  - subscribes to the topic "eiffel/texte", printing out any messages
  it receives. NB - it assumes the received payloads are strings not binary
  It will reconnect to the server if the connection is lost using a blocking
  reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
  achieve the same result without blocking the main loop. */
#include 
#include 
#include 
byte mac[] = { 0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
char server[] = "broker.hivemq.com";
void callback(char* topic, byte* payload, unsigned int length) {
	Serial.print("Message arrived [");
	Serial.print(topic);
	Serial.print("] ");
	for (int i = 0; i < length; i++) {
		Serial.print((char)payload[i]);
	}
	Serial.println();
}
EthernetClient ethClient;
PubSubClient client(ethClient);
void reconnect() {
	// Loop until we're reconnected
	while (!client.connected()) {
		Serial.print("Attempting MQTT connection...");
		// Attempt to connect
		// rename "arduinoClient" with an unique Id
		// like "arduinoClient1524ztSDeRT"
		if (client.connect("arduinoClient")) {
			Serial.println("connected");
			// Once connected, publish an announcement...
			client.publish("eiffel/texte", "Arduino connected");
			// ... and resubscribe
			client.subscribe("eiffel/texte");
		}
		else {
			Serial.print("failed, rc=");
			Serial.print(client.state());
			Serial.println(" try again in 5 seconds");
			// Wait 5 seconds before retrying
			delay(5000);
		}
	}
}
void setup()
{
	Serial.begin(115200);
	client.setServer(server, 1883);
	client.setCallback(callback);
	Ethernet.begin(mac); // get IP via DHCP
	// Allow the hardware to sort itself out
	delay(1500);
}
void loop()
{
	if (!client.connected()) {
		reconnect();
	}
	client.loop();
}

 

Pour tester le programme, il faut que la carte réseau de l’Arduino soit connectée à un réseau ayant accès à internet (adresse IP et passerelle renseignée) le programme utilise un paramétrage automatique DHCP.
Hivemq met à disposition une interface de test : http://www.hivemq.com/demos/websocket-client/
Le paramétrage est simple :
Cliquer sur le bouton Connect (laisser les paramètres par défaut)

Une fois connecté, déplier le bandeau Subscriptions et cliquer sur Add New Topic Subscription

Saisir alors le Topic du programme Arduino : eiffel/texte

Provoquer un reset de la carte Arduino, lancer le moniteur Série et observer la page hiveMQ, le message “Arduino connected” apparait, ce qui signifie que Arduino a publié ce message sur le topic eiffel/texte et que cela fonctionne !

Taper un message dans la fenêtre Publish du site HiveMQ et cliquer sur Publish, On voit normalement arriver le message dans la console du moniteur Arduino.

Si la carte Arduino tente de se reconnecter en permanence, c’est que l’Identifiant de connexion est déjà utilisé par un autre appareil. il faut modifier la ligne 37
[cpp ]
if (client.connect(“arduinoClient”)) {
[/cpp]
et remplacer “arduinoClient” par un identifiant unique
[cpp”]
if (client.connect(“mon_identifiant_perso_1872AZT123p”)) {
[/cpp]

MQTT – utilisation de node-red

Pour le test nous allons utiliser une version de node-red en ligne : https://fred.sensetecnic.com/

Si ce n’est déjà fait, il faut créer un compte (gratuit) afin d’arriver sur l’interface de node-red en ligne.

Une fois le compte créé et validé (vérification par émail), on peut accéder à l’interface de création de flux node-red.

A gauche (repère 1) on trouve le tableau de bord de notre instance FRED, on peut redémarrer l’instance, rajouter des nœuds…

Le repère 2 montre la liste des nœuds disponibles dans notre instance, il y a des nœuds d’entrée (input), de sortie (output), des nœuds de fonction (function)…

Le repère 3 est l’espace de composition des flux (flow dans le jargon node-red) , on y fait glisser des nœuds, que l’on paramètre en double-cliquant dessus. On relie des nœuds entre-eux à l’aide de connexions.

Le repère 4 possède 2 onglets : info et debug. Info donne les informations d’un nœud quand on le sélectionne, c’est une aide en ligne très pratique. L’onglet debug permet d’afficher les information de débogage du flux en utilisant le nœud debug

flux  MQTT très simple:

Faire glisser un nœud inject (dans input) et un nœud mqtt (dans output), relier les deux nœuds.

Paramétrer le nœud mqtt : double cliquer dessus. Cliquer alors sur le stylo à droite de la liste déroulante « Add new mqtt broker… »

Saisir alors broker.hivemq.com dans la boite de texte , puis cliquer sur Add

De retour à la configuration de la connexion MQTT compléter le topic avec eiffel/texte

laisser la liste déroulante QoS sur 2, puis cliquer sur Done

Publier le flux en cliquant sur

Dans une autre fenêtre de navigateur, ouvrir une page sur http://www.hivemq.com/demos/websocket-client/ et se connecter. Comme précédemment, créer une souscription à eiffel/texte.
dans la fenètre node-red, cliquer sur le bouton du nœud timestamp : une valeur numérique apparait dans la fenêtre Messages du site HiveMQ.

Node-red envoie correctement une donnée au Broker HiveMQ.

Dans le sens inverse il est aussi possible d’utiliser un flux en tant que souscripteur et d’afficher les messages reçus.

Flux plus complexe

Ce flux est composé à l’aide de :

  • Inject (input)
  • Text input (ui)
  • Button (ui)
  • Function (function)
  • Mqtt (input)
  • Mqtt (output)
  • Text (ui)

le code du flux complet est le suivant :

[ { "id": "953bb49c.e4ad38", "type": "legacy_ui_text_input", "z": "5230c72.fadcf38", "tab": "84499b6b.8ac208", "mode": "text", "delay": 300, "name": "saisie", "topic": "texte", "group": "Publication", "order": 1, "x": 389, "y": 168, "wires": [ [ "ced9018e.3a8a78" ] ] }, { "id": "9fd6a3b6.540028", "type": "mqtt out", "z": "5230c72.fadcf38", "name": "", "topic": "eiffel/texte", "qos": "", "retain": "", "broker": "ac60999.b095968", "x": 792, "y": 193, "wires": [] }, { "id": "ced9018e.3a8a78", "type": "function", "z": "5230c72.fadcf38", "name": "", "func": "switch (msg.topic)\n{\n case \"texte\" :\n global.set(\"texte\",msg.payload);\n break;\n case \"btn01\" :\n msg.payload=global.get(\"texte\");\n return msg;\n}\n", "outputs": 1, "noerr": 0, "x": 592, "y": 190, "wires": [ [ "9fd6a3b6.540028" ] ] }, { "id": "7204c371.c12724", "type": "legacy_ui_button", "z": "5230c72.fadcf38", "tab": "84499b6b.8ac208", "name": "Envoi", "payload": "btn01", "topic": "btn01", "group": "Publication", "order": 1, "x": 389, "y": 224, "wires": [ [ "ced9018e.3a8a78" ] ] }, { "id": "da2dd664.a80928", "type": "mqtt in", "z": "5230c72.fadcf38", "name": "", "topic": "eiffel/texte", "qos": "2", "broker": "ac60999.b095968", "x": 385, "y": 297, "wires": [ [ "5f6550ac.8a541" ] ] }, { "id": "5f6550ac.8a541", "type": "legacy_ui_text", "z": "5230c72.fadcf38", "tab": "84499b6b.8ac208", "name": "reçu :", "group": "Souscription", "order": 1, "format": "{{msg.payload}}", "x": 786, "y": 302, "wires": [] }, { "id": "84499b6b.8ac208", "type": "legacy_ui_tab", "z": "5230c72.fadcf38", "name": "Home", "icon": "dashboard", "order": "1" }, { "id": "ac60999.b095968", "type": "mqtt-broker", "z": "5230c72.fadcf38", "broker": "broker.hivemq.com", "port": "1883", "clientid": "", "usetls": false, "compatmode": true, "keepalive": "60", "cleansession": true, "willTopic": "", "willQos": "0", "willPayload": "", "birthTopic": "", "birthQos": "0", "birthPayload": "" } ]

Copier/ coller ce texte dans la fenêtre popup qui s’ouvre si on suit Menu import ClipBoard

Le flux complet apparait à l’écran et peut être placé où l’on veut.

La fonction (nœud function) possède le code suivant :

switch (msg.topic)
    {
      case "texte":
          global.set("texte", msg.payload);
          break;
      case "btn01":
          msg.payload = global.get("texte");
          return msg;
     }

 

Msg.topic permet d’identifier le nœud qui a provoqué l’appel de la fonction, si c’est la boite de texte, on mémorise le texte dans une variable du contexte global. Si l’appel a été provoqué par le clic sur le bouton, on récupère la variable texte du contexte global et on l’affecte à msg.payload, et on retourne msg, de ce fait le texte ressort de la fonction.

Qualité de service QoS [1]:

Le niveau de qualité de service le plus simple est le service non confirmé (Unacknowledged Service). Ce niveau utilise une séquence de paquets PUBLISH : l’éditeur envoie un message une seule fois au broker et ce dernier transmet ce message une seule fois aux abonnés. Aucun mécanisme ne garantit la réception du message et le broker ne l’enregistre pas non plus. Ce niveau de qualité de service est également appelé « QoS niveau 0 » ou QoS0, ou encore « At most once » (au plus une fois).

Le deuxième niveau de service est le service confirmé (Acknowledged Service). Ce niveau utilise une séquence de paquets PUBLISH/PUBACK (Publish Acknowledge) entre l’éditeur et son broker, ainsi qu’entre le broker et les abonnés.

Un paquet de confirmation vérifie que le contenu a été reçu et un mécanisme de renvoi du contenu d’origine est déclenché si l’accusé de réception n’est pas reçu en temps voulu. Cela signifie que l’abonné peut recevoir plusieurs copies du même message. Ce niveau de qualité de service est également appelé « QoS niveau 1 » ou QoS1, ou encore « At least once » (au moins une fois).

Le troisième niveau de QoS est le service garanti (Assured Service). Ce niveau délivre le message avec deux paires de paquets. La première est appelée PUBLISH/PUBREC et la seconde, PUBREL/PUBCOMP. Les deux paires s’assurent que quel que soit le nombre de tentatives, le message ne sera délivré qu’une seule fois. Ce niveau de qualité de service est également appelé « QoS niveau 2 » ou QoS2, ou encore « Exactly once » (exactement une fois).

  1. http://www.lemagit.fr/conseil/Internet-des-Objets-bien-comprendre-MQTT