Header Ads

Pi-Vigilance - Un premier Noeud Escalve

Introduction
Après nos deux premiers articles sur Pi-Vigilance article 2article 1, il étant temps de réaliser un premier noeud esclave capable collecter des données.

Concept Pi-Vigilance - par MCHobby.be

Cela tombe bien, nous avons justement des Intel Galileo sous la main.
Intel Galiléo (Certifié Arduino) - Disponible chez MCHobby

Comme ils disposent d'une interface Ethernet, ce sera l'occasion de faire un premier Noeud Ethener Filaire type Arduino auquel votre Pi pourra accéder facilement.
Intel Galileo (Certifié Arduino) et quelques senseurs....


Les senseurs
Nous allons, utiliser quelques senseurs de tests....
Voyez la liste complète du matériel en fin d'article :-) 
Montage
Pour cet exemple, voici le montage réalisé sur base de Galileo.
Montage du premier Noeud Esclave du projet Pi-Vigilance
Basé sur un Arduino Galileo Cliquer pour agrandir


WebServeur_BasicNode.ino
Voici donc un premier projet Arduino pour les noeuds esclaves du projet Pi-Vigilance. Ce Noeud Esclave fonctionnera en HTTP Filaire.
Utilise un Web Serveur sur le port 80 à l'adresse fixe 192.168.1.177 

Ce premier prototype met en place les éléments de base pour produire une réponse pour différentes requêtes.
Voici les requête supportés par la version 0.1
  • http://192.168.1.177/help
    retourne une page d'aide
  • http://192.168.1.177/analog
    retourne les valeurs des entrée analogiques au format html
  • http://192.168.1.177/xread
    retourne des informations attendues au format xml...nettement plus approprié pour le projet Pi-Vigilance

Voici le résultat obtenu dans un navigateur Internet... vous pouvez constater par vous même que le format XML de l'action xread pourrait facilement être interprété par du code Python sur votre Raspberry... ou n'importe quel autre système.

Retour HTML de l'action analog , cette action démontre juste que le retour html est possible.
L'action xread est plus intéressante parce qu'elle retourne les informations sous le format XML.

Les valeurs "raw" représentent les valeurs brutes lue directement via un fonction Arduino digitalRead() ou analogRead(). Les autres informations parlent d'elles-même.

Concernant le Senseur PIR:
Pour le senseur PIR, ce dernier reste actif pendant 6 secondes lorsqu'il est activé par le mouvement d'une personne.
Cela signifie que la valeur <pir-sensor raw="1" /> ne sera visible que pendant 6 secondes (soit le temps d'activation du senseur et de l'entrée digitale 2 sur le Galileo)
Comme il est du ressort de votre Pi de venir lire l'état des senseurs (sur l'action xread), il est assez facile de rater l’événement raw=1 :-/
Nous avons donc ajouté un compteur d'activation pour le senseur PIR (la valeur de "activation-counter") est incrémentée à chaque fois que le senseur PIR est activé.
Il suffit de surveiller le changement de la valeur "activation-counter" pour savoir si quelqu'un est passé par là depuis la dernière consultation de donnée :-)

Un petit coup d'oeil sur le code Arduino
Le programme WebServeur_BasicNode.ino est relativement long du fait du traitement des requêtes entrantes.
Cependant les parties intéressantes sont assez petite (en terme de code), il est donc facile d'attendre le programme pour qu'il réponde à vos besoins.

fonction setup() 
//--- Sensors --------------------------------------------------
//
//--------------------------------------------------------------
const int pinPot = A0;   // Potentiomète 10K linéaire
const int pinTMP36 = A1; // Senseur de température TMP36
const int pinLDR   = A2; // Photo Résistance 

const int pinPIR = 2;    // Senseur PIR (proximité/mouvement)
int pirState = LOW;
int pirActivationCounter = 0;
//--- Setup() --------------------------------------------------
//
//-------------------------------------------------------------
void setup() {
  // Démarre la communication serie pour le debugging
  Serial.begin(9600);
  DEBUG_PRINTLN( "Setuping...." );

  // Réserver la taille du buffer
  currentLine.reserve( CURRENT_LINE_BUFF_SIZE+1 ); // 128 caractères + NULL
  requestAction.reserve( REQUEST_ACTION_BUFF_SIZE+1 ); // 6 caractère max + NULL
  
  //-- Init des PinMode --------------------------
  
  pinMode( pinPIR, INPUT );
  
  // pas nécessaire pour des analog reads
  //----------------------------------------------
  
  // Demarre la connexion Ethernet et le serveur:
  Ethernet.begin(mac, ip);
  server.begin();
  
  DEBUG_PRINT("server is at ");
  DEBUG_PRINTLN(Ethernet.localIP());  
}

Cette fonction (et les constantes) permettent de d'activer les broches d'entrée/sortie des senseurs et d'activer le serveur web.
L'adresse IP et l'adresse MAC sont définit plus avant dans le code

byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192,168,1,177);

La fonction loop()
Cette fonction qui est exécutée continuellement et permet de prendre en charge la gestion des senseurs et celle des requêtes entrantes.

void loop() {
  //-- Gestion des senseurs --
  // Senseur PIR avec déparasitage logiciel
  // http://arduino103.blogspot.be/2011/05/entree-bouton-resistance-pull-up-pull.html
  //
  int currentState = digitalRead( pinPIR );
  // DEBUG_PRINTLN( currentState );
  // Changement d'etat?
  if( currentState != pirState ) {
    delay( 10 ); // déparasitage + nouvel état stable?
    if( digitalRead(pinPIR) == currentState ){ 
      // Nouvel état confirmer :-)
      
      // Incrémenter le compteur (seulement si passe de niveau bas vers Niveau Haut
      if( (currentState == HIGH) && (pirState == LOW ) ){
         pirActivationCounter++; 
      }
      
      // Se souvenir du nouvel état
      pirState = currentState;
    }
  
  }
  
  //-- Gestion des connexion entrantes --
  manageRequests();
}

Une partie non négligeable surveille le changement d'état du senseur PIR.
Le but étant d'incrémenter le compteur d'activation pirActivationCounter (et appliquant une méthode de déparasitage logiciel pour éviter les détections de type "faux positifs").

La dernière fonction appelée est manageRequests(), elle prend en charge la gestion des requêtes entrantes. Elle doit être appelée aussi souvent que possible.

fonction manageRequests()
Cette fonction est relativement complexe. La lire ne vous apportera probablement pas grand chose d'autre qu'un mal de crâne.
Par contre, une fois la requête acceptée, la génération de la réponse finale est déléguée auprès de la fonction
processRequest( EthernetClient client, byte RequestMethod, String RequestAction ).

Fonction processRequest()
Si vous voulez ajouter de nouvelles actions ou modifier le code de réponse, c'est ici que les choses se passent.
A la lecture du code ci-dessous, vous comprendrez assez vite comment insérer une nouvelle action (attention: 6 lettres max et en minuscule).
N'oubliez pas de terminer cette section de code avec le bloc "else" final qui retourne une erreur (en contenu html pour le moment).

void processRequest( EthernetClient client, byte RequestMethod, String RequestAction ){
    if( RequestMethod != REQUEST_METHOD_GET ){
      sendError( client, HTTP_ERR_MethodNotAllowed, "Method Not Allowed" );
      return;
    }
    // détection de l'action et du traitement à faire.
    if( RequestAction.equals( "help" ) ){
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.print( "NODE_ID (" );
          client.print( NODE_ID );
          client.println( ")<br />" );         
          client.println( "Voici la liste des actions disponibles:<br />" );
          client.println("help - HTML, affiche ce message d aide<br />" );
          client.println("analog- HTML, etat de toutes les entrees analogiques<br />" );
          client.println("xread  - XML, lecture des senseurs<br />" );
          client.println("</html>");      
    }
    else 
    if( RequestAction.equals( "analog" ) ){
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.print( "NODE_ID (" );
          client.print( NODE_ID );
          client.println( ")<br />" );         
          // add a meta refresh tag, so the browser pulls again every 5 seconds:
          //** client.println("<meta http-equiv=\"refresh\" content=\"5\">");
          // output the value of each analog input pin
          for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
            int sensorReading = analogRead(analogChannel);
            client.print("analog input -- ");
            client.print(analogChannel);
            client.print(" is ");
            client.print(sensorReading);
            client.println("<br />");       
          }
          client.println("</html>");
    }
    else 
    if( RequestAction.equals( "xread" ) ){
       // send a standard http response header
       client.println("HTTP/1.1 200 OK");
       client.println("Content-Type: text/xml"); //retourne du XML
       client.println("Connection: close");
       client.println();
       client.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
       
       client.print("<node id=\"");client.print( NODE_ID ); client.print("\" version=\"" ); client.println("\">" );
       
       // écriture du noeud PIR
       client.print("<pir-sensor raw=\"");
       if( digitalRead( pinPIR ) )
         client.print( 1 );
       else
         client.print( 0 );
       client.print( "\" activation-counter=\"" );
       client.print( pirActivationCounter );
       client.println( "\" />");
       
       // écriture de la valeur du potentiomètre
       client.print("<pot raw=\"");
       client.print( analogRead( pinPot ) );
       client.println( "\" />");
       
       // écriture de la température
       //  http://wiki.mchobby.be/index.php?title=Senseur_Temp%C3%A9rature
       int raw = analogRead( pinTMP36 );
       float temp = ( ((raw * 5.0)/1024)  - 0.5) * 100;
       client.print("<tmp36 raw=\"");
       client.print(  raw );
       client.println( "\" temperature=\"" );
       client.print( temp );
       client.println( "\"/>" );
       
       // Photo-Résistance
       // écriture de la valeur du potentiomètre
       client.print("<ldr raw=\"");
       client.print( analogRead( pinLDR ) );
       client.println( "\" />");
       
       // client.print("<TEMP>";
       // client.print(tm,DEC);
       // client.print("</TEMP>";
       client.println("</node>");      
    }
    
    // ELSE final... quand le nom de l'action (actionRequest) est inconnu!
    else {
      String errMsg = String( "Action inconnue (" );
      errMsg.concat( RequestAction );
      errMsg.concat( ")" );
      sendError( client, HTTP_ERR_NotFound, errMsg );
      return;    
    }
}

Le mot de la fin
Le prochain article mettra l'accent sur l'acquisition des données produites par xread depuis un code Python fonctionnant sur un Raspberry-Pi.

Je passerais ensuite a une version Aruino-Ethernet du code proposé ici.

Un dernier volet s'attardera sur une version WiFi sur base d'un shield CC3000 de ce projet.

Où acheter
Voici la liste complète du matériel de cet exemple