Skyduino:~#
Articles
arduino, Corrigé, programmation

[Arduino] Systéme de traitement online / offline

Bonjour tout le monde !

Il y a 4-5 jours je vous avait montré un morceau de code d’exemple pour faire un système de traitement fichier par fichier.
Et je vous avais précisé que le but final était de faire un système de traitement online / offline.

Le sujet m’a bien inspiré et j’ai finalement aidé le membre en question (du forum arduino) à finir son code complet.

Le code est fini et plutôt bien fourni :
- comptage des impulsions (d’un compteur d’eau ou autre par exemple) par interruption
- horodatage en interne avec la librairie arduino Time
- synchronisation automatique du temps local via NTP
- envoi des "rapports" (date + valeur) au serveur de traitement en HTTP
- stockage en local sur une carte SD des rapports en attente d’envoi (en cas d’erreur de connexion TCP)

Cette fois ci tout y est et le code marche (enfin j’ai pas testé par moi même mais le membre du forum m’as dis que tout marchait bien).
Si vous voulez faire un système de suivi de consommation d’un compteur d’eau ou autre, qui possède une sortie par impulsion (lumineuse souvent) vous allez être content le code est prêt à l’emploi :)

Le code, avec tout plein de commentaires comme d’habitude :

#include <SD.h>          /* Pour la carte SD */
#include <Ethernet.h>    /* Pour la shield ethernet */
#include <SPI.h>         /* Pour la communication bas niveau (carte SD et ethernet) */
#include <EthernetUdp.h> /* Pour la communication UDP */
#include <Time.h>        /* Pour la gestion du temps */

/** Taille du buffer pour la lecture du fichier texte et l'envoi de la requete HTTP */
static const byte BUFFER_SIZE = 50;

/** Taille d'un paquet NTP */
static const byte NTP_PACKET_SIZE = 48;

/** Temps d'attente entre deux écritures (en ms) */
static const unsigned long PERIOD_WRITE_DATA  =  120000UL;

/** Broche CS de la carte SD */
static const byte PIN_SD_CS = 4;

/** Adresse MAC de l'ethernet shield */
static byte localMacAddress[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF };

/** Port d'écoute local */
static const unsigned int listenPort = 8888;

/** Adresse IP de l'ethernet shield */
static IPAddress localIpAddress(192, 168, 0, 35); 

/** Adresse IP de time.nist.gov */
static IPAddress timeServer1(192, 43, 244, 18); 

/** Adresse IP de time.nist.gov */
static IPAddress timeServer2(129, 6, 15, 28); 

/** Adresse IP serveur perso PHP */
static IPAddress serverIpAddress(192, 168, 0, 135);

/** Compteur d'impulsion */
static volatile unsigned long pulseCounter = 0;

/** Instance de EthernetUDP permettant l'échange de données via UDP */
static EthernetUDP Udp;

/** Zone horaire pour NTP */
static const byte timeZoneOffset = 1; // GMT zone

/** Objet File pointant sur le répertoire root de la carte SD */
static File root;

/** Fonction d'initialisation hardware */
void setup() {

  /* Initialisation du port série */
  Serial.begin(115200);
  
  /* Initialisation de l'interruption INT0 (comptage des impulsions) */
  attachInterrupt(0, handlePulse, RISING);
  
  /* Initialisation de la shield ethernet et UDP */
  Ethernet.begin(localMacAddress, localIpAddress);
  Udp.begin(listenPort);
  
  /* Mise à l'heure via NTP */
  Serial.print(F("Synchronisation NTP ... "));
  setSyncProvider(getNtpSync);
  while (timeStatus() == timeNotSet);
  Serial.println(F("Ok !"));
  
  /* Initialisation de la carte SD */
  Serial.print(F("Initialisation de la carte SD ... "));
  pinMode(10, OUTPUT); /* Obligatoire pour que la communication SPI fonctionne (10 UNO, 53 Mega)*/
  if (!SD.begin(PIN_SD_CS)) {
    Serial.println(F("Echec !"));
    for (;;);
  } 
  Serial.println(F("Ok !"));
  
  /* Ouverture du dossier "root" */
  root = SD.open("/");
  if (!root) {
    Serial.println(F("Erreur lors de l'ouverture de /"));
    for (;;);
  }
  
  /* Message de fin de setup */ 
  Serial.println(F("Setup termine !"));
}

/** Routine de traitement */
void loop() {

  /* Variables persistantes */
  static unsigned long last_write_time = 0;
  static byte currentDay = day();
  
  /* Test s'il y a eu changement de jour */
  if (currentDay != day()) {
    
	/* Remise à zéro du compteur et du jour en cours */
    pulseCounter = 0;
    currentDay = day();
	
	/* Message de debug */
    Serial.println(F("Reset compteur (changement de jour)"));
  }

  /* Test s'il est temps de faire un rapport */
  if ((millis() - last_write_time) > PERIOD_WRITE_DATA) {
  
    /* Envoi du rapport */
    sendReport(pulseCounter);
	
	/* Sauvegarde du temps courant */
    last_write_time = millis();
  }
}

/** Routines de gestion des impulsions */
void handlePulse() {

  /* Incrémente le compteur */
  ++pulseCounter;
}

/** Fonction d'envoi d'un rapport */
void sendReport(unsigned long nbPulses) {

  /* Temps actuel */
  int years = year();
  byte months = month();
  byte days = day();
  byte hours = hour();
  byte minutes = minute();
  byte seconds = second();

  /* Tentative d'envoi du rapport */
  if (sendData(years, months, days, hours, minutes, seconds, nbPulses)) { /* Réussite */
    
	/* Message de debug */
    Serial.println(F("Envoi ok, tentative d'envoi des rapports en attente ..."));
	
	/* Tentative d'envoi des rapports en attente */
	sendWaitingReports();
	
  } else { /* Echec */
	
	/* Message de debug */
    Serial.println(F("Echec envoi, mise en attente du rapport ..."));
	
	/* Mise en attente du rapport */
	createWaitingReport(years, months, days, hours, minutes, seconds, nbPulses);
  }
}

/** Synchronise le temps courant via NTP */
unsigned long getNtpSync() {

  /* Temps NTP courant */
  unsigned long currentTime = 0;
  
  /* Message de debug */
  Serial.println(F("Tentative de synchronisation NTP ..."));
  
  /* Tentative de synchronisation avec le serveur NTP 1 */
  if ((currentTime = getNtpTime(timeServer1)) == 0) {
    Serial.println(F("Erreur serveur NTP 1 !"));
    
	/* Tentative de synchronisation avec le serveur NTP 2 */
	if ((currentTime = getNtpTime(timeServer2)) == 0) {
      Serial.println(F("Erreur serveur NTP 2 !"));
    }
  }
  
  /* Retourne le temps NTP courant */
  return currentTime;
}

/** Retourne le temps courant via NTP */
unsigned long getNtpTime(IPAddress& ntpServer) {

  /* Buffer contenant le paquet UDP NTP */
  byte buffer[NTP_PACKET_SIZE] = { 0 };

  /* Création de la requéte NTP */
  buffer[0] = 0b11100011; // LI, Version, Mode
  buffer[1] = 0;          // Stratum, or type of clock
  buffer[2] = 6;          // Polling Interval
  buffer[3] = 0xEC;       // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  buffer[12] = 49; 
  buffer[13] = 0x4E;
  buffer[14] = 49;
  buffer[15] = 52;

  /* Envoi d'une requete NTP au serveur spécifié */    
  Udp.beginPacket(ntpServer, 123);
  Udp.write(buffer, NTP_PACKET_SIZE);
  Udp.endPacket(); 
  delay(1000);
  
  /* Lecture de la réponse */
  if (Udp.parsePacket()) {  
  
    /* Mise en buffer du paquet UDP */
    Udp.read(buffer, NTP_PACKET_SIZE);

    /* Lecture des données binaire */
    unsigned int highWord = word(buffer[40], buffer[41]);
    unsigned int lowWord = word(buffer[42], buffer[43]);
	
    /* Calcul du timestamp (nombre de secondes depuis 1 janvier 1900) */
    unsigned long timestamp = ((unsigned long) highWord << 16) | lowWord; 
	
    /* Calcul du temps courant en fonction de la zone */
    unsigned long currentTime = timestamp - 2208988800UL + (timeZoneOffset * 3600L);
    
    /* Retourne le temps NTP courant */
    return currentTime;
  }
  
  /* Erreur */
  return 0;
}

/** Création d'un fichier de rapport en attente */
void createWaitingReport(int years, int months, int days, int hours, int minutes, int seconds, unsigned long nbPulses) {
  
  /* Buffer temporaire pour le nom et le contenu du fichier */
  char buffer[BUFFER_SIZE];
  
  /* Calcul du nom du fichier (format 8.3) */
  sprintf(buffer, "%02d%02d%02d%02d.txt", days, hours, minutes, seconds);
  
  /* Message de debug */
  Serial.print(F("Creation du fichier d'attente : "));
  Serial.println(buffer);
  
  /* Création d'un nouveau fichier en écriture */
  File outfile = SD.open(buffer, FILE_WRITE);
  
  /* Calcul du contenu du fichier */
  sprintf(buffer, "%d %d %d %d %d %d %lu", years, months, days, hours, minutes, seconds, nbPulses);
  
  /* Ecriture du fichier */
  outfile.println(buffer);
  
  /* Fermeture du fichier */
  outfile.close();
}

/** Envoi des fichiers de rapport en attente */
void sendWaitingReports() {

  /* Buffer temporaire pour la lecture du fichier */
  char buffer[BUFFER_SIZE];

  /* Variables contenant les résultats de la lecture du fichier texte */
  int years, months, days, hours, minutes, seconds;
  unsigned long nbPulses;

  /* Ouverture du premier fichier disponible */
  root.rewindDirectory();
  File entry =  root.openNextFile();
  
  /* Tant qu'un fichier est disponible */
  while (entry) {
  
    /* Test pour savoir s'il s'agit d'un répertoire */
    if (!entry.isDirectory()) {

      /* Affichage du nom du fichier en cours (pour debug) */
      Serial.print(F("Traitement du fichier : "));
      Serial.println(entry.name());

      /* Lecture des données du fichier */
      byte i = 0;
      while (entry.available() > 0) { /* Jusqu'a la fin du fichier */
        if ((buffer[i] = entry.read()) == '\n') /* Lecture d'une ligne compléte */
		  break;

        /* Test anti buffer-overflow */
        if (++i == BUFFER_SIZE) {
          Serial.println(F("Fichier trop gros !"));
		  --i;
          break;
        }
      }
	  buffer[i] = '\0';

      /* Extraction des champs */
      if(sscanf(buffer, "%d %d %d %d %d %d %lu", &years, &months, &days, &hours, &minutes, &seconds, &nbPulses) == 7) {
        
		/* Envoi de la requéte */
        if(sendData(years, months, days, hours, minutes, seconds, nbPulses)) {
		
		  /* Message de debug */
		  Serial.print(F("Rapport en attente envoye !"));
		  
		  /* Suppression du fichier d'attente */
          SD.remove(entry.name());
		} else {
		
		  /* Message de debug */
		  Serial.print(F("Rapport toujours en attente ..."));
		}
      }
	}

	/* Fermeture du fichier */
    entry.close();
	  
	/* Ouverture du prochain fichier disponible sur la carte SD */
    entry =  root.openNextFile();
  }
}

/** Fonction d'envoi au serveur PHP */
boolean sendData(int years, int months, int days, int hours, int minutes, int seconds, unsigned long nbPulses) {
  
  /* Buffer contenant une partie de la requete HTTP */
  char buffer[BUFFER_SIZE];
  
  /* Ouverture de la connexion TCP */
  EthernetClient client;
  Serial.print(F("Connexion au serveur ... "));
  if (!client.connect(serverIpAddress, 80)) {
  
    /* Erreur */
    Serial.println(F("Echec !"));
    return false;
  }
  Serial.println(F("Ok !"));

  /* Envoi de la requete HTTP */
  sprintf(buffer, "%04d%02d%02d%02d%02d%02d&val=%05lu", years, months, days, hours, minutes, seconds, nbPulses);
  client.print(F("GET /traitement.php?date="));
  client.print(buffer);
  client.println(F(" HTTP/1.0"));
  client.println();

  /* Fin de la requete HTTP */
  Serial.println(F("Fin envoi ..."));
  delay(10); /* Permet au serveur distant de recevoir les données avant de fermer la connexion */
  client.stop();
  
  /* Pas d'erreur */
  return true;
}

Ps: et bonne année 2013 ! :)
J’espère que vous avez passé un bon réveillon de la Saint-Sylvestre, moi perso j’ai mangé comme un porc.
(les repas de +10h avec 3 entrées, 2 plats et 3 desserts c’est aussi ça d’être auvergnat ! :))

About these ads

Discussion

23 réflexions sur “[Arduino] Systéme de traitement online / offline

  1. Super boulot !
    Merci pour le partage, je compte mettre en place une solution de comptage de consommation d’eau justement :)

    Publié par bNj | 2 janvier 2013, 13 h 20 min
  2. Peux tu me dire ou je peux trouver la librairie time.h ?

    Merci pour ton aide.

    Publié par bNj | 2 janvier 2013, 13 h 30 min
  3. Tous mes voeux à toi skywodd, que l’année te réussisse autant pour la santé que pour tes études et la passion qui t’anime!

    Tous mes voeux aussi à tous tes lecteurs (fans ?)

    Publié par jjnoui | 2 janvier 2013, 20 h 17 min
  4. bonne année à tous et uste une question, il a besoins d’autre chose à pars l’arduino pour pouvoir se connecter et lire les données le programme ?

    Publié par Fabien | 4 janvier 2013, 15 h 10 min
  5. mais c’est quelle ip pour se co au shield??? et un capteur infrarouge en générateur d’impulsions c’est bon ?

    Publié par Fabien | 5 janvier 2013, 14 h 43 min
    • C’est pas toi qui te connecte à la shield, c’est la shield qui se connecte à un serveur web !

      Pour les IPs voir le code :

      /** Adresse IP de l'ethernet shield */
      static IPAddress localIpAddress(192, 168, 0, 35); 
      
      /** Adresse IP serveur perso PHP */
      static IPAddress serverIpAddress(192, 168, 0, 135);
      

      Pour le capteur si il sort des niveaux logiques 0-5v c’est bon.

      Publié par skywodd | 5 janvier 2013, 15 h 16 min
      • "Pour le capteur si il sort des niveaux logiques 0-5v c’est bon." au pire c’est pas bien grave si c’est pas du 0-5V, un mosfet peut generalement s’occuper du "level-shiffting" au cas ou sa sortirait du 0-12V par exemple, meme si selon moi tu peux perdre en précision

        Publié par Zilators | 5 janvier 2013, 15 h 35 min
  6. encore un peu et tu me fait 80% du projet pour l’école la :D

    Publié par Zilators | 5 janvier 2013, 15 h 28 min
  7. Salut SkyWodd!
    Merci pour tes codes et pour ta newsletter, je n’ai aucun doute que ton IUT sera une formalité pour toi!

    Je me suis approprié tel quel ton code pour la synchronisation NTP, et ca marche nickel!
    Cependant, j’ai remarqué que la synchronisation, chez moi en tout cas, ne se faisait pas tous les nouveaux
    jours, mais plutôt toutes les 5-6 minutes… T’aurais une idée?

    Publié par Damien | 31 janvier 2013, 20 h 11 min
    • >> Cependant, j’ai remarqué que la synchronisation, chez moi en tout cas, ne se faisait pas tous les nouveaux
      jours, mais plutôt toutes les 5-6 minutes… T’aurais une idée?

      La synchronisation NTP ou le reset du compteur ?

      La synchronisation NTP c’est la librairie Time qui s’en charge … et je sait pas trop comment.
      Il me semble que c’est à chaque appel de now() que la synchronisation via syncProvider ce fait.

      Publié par skywodd | 31 janvier 2013, 20 h 15 min
  8. Alors je n’ai pas de compteur qui s’incrémente, et je suis d’accord, c’est bien la librairie Time qui s’en charge!
    D’ailleurs, j’utilise le même bout de code pour lancer une mesure sur mon convertisseur ADC:

    if(minuteActuelle != minute()){
    minuteActuelle = minute();
    faireXMesure();
    }

    et il fonctionne parfaitement! Par contre, pour moi (et je vais creuser dans la librairie Time), celui là se met
    à jour toutes les 6 minutes environ, un peu comme si day() me renvoyait une donnée fausse

    if (currentDay != day()) {
    currentDay = day();
    setSyncProvider(getNtpSync);
    while (timeStatus() == timeNotSet);
    Serial.println("Remise à l’heure");
    }

    Publié par Damien | 2 février 2013, 10 h 35 min
    • Bien le bonsoir!
      Finalement, j’ai trouvé le pourquoi du comment! ou du moins une solution à mon
      interrogation, qui explique pourquoi dans ton sketch, la mise à jour se fait une seule
      fois par jour. Comme tu l’as deviné/le savais, c’est l’appel à now() qui déclenche la
      MAJ. Et bien même si je ne comprends pas encore pourquoi chez moi la synchro se
      fait toutes les 6 minutes, la fonction
      setSyncInterval(interval); // set the number of seconds between re-sync
      permet donc de définir l’ intervalle a une valeur souhaitée, et ca fonctionne!
      @+

      Publié par Damien dit Levaillant | 13 février 2013, 1 h 13 min
  9. Bonjour,
    Excellent travail, c’est ce que je cherche exactement pour mon projet. Je veux juste que vous m’expliquer ces 3 lignes.
    — sprintf(buffer, "%04d%02d%02d%02d%02d%02d&val=%05lu", years, months, days, hours, minutes, seconds, nbPulses);
    client.print(F("GET /traitement.php?date="));
    client.print(buffer); —
    Donnez moi un exemple de l’URL pour savoir il sera sur quel forme c’est à dire" traitement.php?date=VALEURdeDATE&years=VALEURdeYEARS…"
    Merci d’avance

    Publié par Trimèche Chaïma | 22 juillet 2013, 14 h 27 min
  10. Bonjour, je découvre ce code que j’essaye de comprendre, mais ça bloque dés le départ.
    j’ai modifié : pinMode(53, OUTPUT); pour ma carte MEGA
    et j’obtiens :
    Erreur serveur NTP 1 !
    Erreur serveur NTP 2 ! et plus rien.

    Publié par Jérôme PERRIN | 4 août 2013, 11 h 36 min
    • Je teste avec le code example "TimeNTP" et ça ne fonctionne pas avec la Mega,
      pour que cela fonctionne je doit ajouter dans le setup :
      pinMode(4,OUTPUT);
      digitalWrite(4,HIGH);
      j’ai trouvé ça "Pour déselectionner la SD carte, mettre la pin 4 en ‘output’ à l’état haut (écrire un 1)"

      mais ça ne donne rien pour ton code…

      Publié par Jérôme PERRIN | 4 août 2013, 17 h 33 min
      • Change rien au code, le problème est bien plus simple ; les deux serveurs NTP que j’utilisais à l’époque semblent morts.

        Publié par skywodd | 5 août 2013, 16 h 57 min
  11. comment je peux faire la communication entre arduino Uno et Arduino Wifi shield et je veux savoir comment je peux transférer les donner vers le Pc Client

    Publié par Med | 8 mars 2014, 1 h 13 min
  12. super boulot

    Publié par carre | 25 juin 2014, 16 h 08 min

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

Archives

Wow. Dogecoin. Amaze.

Laissez un tip en Dogecoin

DMMNFk6WBVTpx2Wu1Z35GL61QXSd6r6WQx

Suivre

Recevez les nouvelles publications par mail.

Rejoignez 715 autres abonnés