Désireux de connaitre les conditions météo sur un site distant, j'y ai depuis quelques mois installé une station météo CONRAD WH1080. Je me suis cependant vite aperçu des limitations de ce système, tant sur le plan mécanique qu'électronique.

Cette station nécessite en effet un PC sur lequel elle est raccordée via un port USB. La transmission des données sur un site Web - en l'occurence Wunderground - oblige de laisser ce PC allumé 24/24 et d'y faire tourner un logiciel comme "Cumulus".

Mon  projet "Apaguard" étant bloqué en attendant la réalisation des plateaux des balances, je me suis lancé dans la réalisation d'une station météo autonome.

 

Cahier des charges

  • Fonctionnement autonome 24/24
  • Utilisation de capteurs filaires
  • Mesure de la température, humidité, pression atmosphérique, pluviométrie, direction et vitesse du vent.
  • Transfert par connexion Ethernet.
  • Utilisation du site Wunderground
  • Horloge en temps réel pour horodatage des données
  • Respect autant que possible de la note technique n°39 de météo France

Hardware et capteurs

Disposant depuis plusieurs années d'un anémomètre et d'une girouette de qualité professionnelle et ne souhaitant pas dépareiller la station WH1080 que je possède déjà, j'ai décidé d'utiliser ces nouveaux capteurs.
Le matériel utilisé se compose donc de :

  • Arduino UNO v3
  • Shield Ethernet à base de W5100
  • Anémomètre THIES
  • Girouette THIES
  • Pluviomètre à godets référence MS-WH-SP-RG
  • Capteur de température, pression atmosphérique et humidité : capteur Bosch BME280
  • Horloge RTC à base de DS3231

Le fonctionnement étant assuré à partir d'une alimentation secteur, le problème de la consommation d'énergie ne se pose pas vraiment. Par ailleurs, le chip W5100 utilisé sur les shields Ethernet est assez gourmand et les mesures devant s'effectuer toutes les secondes, le fonctionnement sur batterie seul est exclu.

Le capteur de température et de pression atmosphérique fonctionne avec le protocole i2c, ce qui limite la distance entre le capteur et l'Arduino à quelques mètres au mieux. Les autres capteurs de par leurs principes de fonctionnement, permettraient une longueur de câbles plus importante.

Pour ces raisons, j'ai prévu d'installer l'Arduino et le shield Ethernet dans un boîtier plastique étanche à l'extérieur et à proximité des capteurs et d'alimenter le tout par PoE. ( à voir )

Fonctionnement général

Les divers capteurs transmettent en permanence les valeurs mesurées  au module Arduino qui les interprète et les met au format compatible avec le site distant choisi, ici Wunderground. La transmission s'effectue à intervalles réguliers toutes les 5 minutes vers le site distant.

Horloge temps réel

Afin de permettre un horodatage précis en toutes circonstances, j'ai utlisé un module à base de DS3231.  En plus de l'alimentation 3,3 Volts, 2 fils raccordent le bus i2C aux pins A5 et A4 de l'Arduino.
Les librairies i2c et ds3231 permettent l'utilisation de ce module.

Température/pression/humidité

Le capteur de température, pression et humidité BOSCH BME280 est relié au module Arduino sur le bus i2c. La librairie BME280 MOD-1022 est utilisée pour gérer ce capteur. La précision obtenue est plus que correcte pour ce capteur si petit !
A part la conversion des valeurs issues du capteur en système métrique vers le système impérial requis par le site Wunderground, il n'y a pas de traitement particulier.

Précipitation

Le pluviomètre est un modèle largement utilisé dans de nombreuses stations météo et disponible comme pièce détachée sur Aliexpress à un prix dérisoire ( rechercher MS-WH-SP-RG ). Il s'agit d'un pluviomètre à godets qui fournit une impulsion lorsqu'un des 2 godets bascule lorsqu'il est plein. Chaque godet contient 0,011 pouce ( inch ) de pluie. ( à vérifier )
Le comptage des impulsions s'effectue par l'intermédiaire de l'interruption INT0 de l'Arduino et une routine anti-rebond est incluse dans le logiciel pour éviter les rebonds du contact du pluviomètre.
Le logiciel compte le nombre d'impulsions pendant chaque minute, pendant les 60 dernières minutes ainsi que le total par jour. 
Afin de conserver le total journalier en cas de RAZ de la carte Arduino, cette variable est sauvegardée à chaque changement dans la mémoire EEPROM de l'Arduino. Cette variable et la valeur dans l'EEPROM sont remis à zéro à minuit .

Vitesse vent

L'anémomètre utilisé délivre des signaux carrés de 2 Hz à 573 Hz pour une vitesse de vent de 0,5 à 50 m/s sur la sortie d'un optocoupleur. Vérification faite, la forme des signaux est parfaite et ne nécessite pas de remise en forme avant traitement par l'Arduino.
Il suffit de mesurer cette fréquence en Hz et une formule fournie par le constructeur permet la conversion en vitesse en m/s.  Cette mesure est faite sur une période de 1 seconde.
Le logiciel effectue cette mesure à l'aide d'une librairie fréquencemètre sur l'entrée D5 de l'Arduino. Une moyenne de la vitesse est calculée sur les 2 dernières minutes. Cette moyenne s'effectue en stockant les 120 dernières mesures dans un tableau, la moyenne peut donc être calculée sur ces 120 dernières valeurs à tout moment.

Le logiciel conserve également la valeur maximale de la vitesse du vent (rafale) sur les 10 dernière minutes.
La transmission de ces 3 valeurs s'effectue toutes les 5 minutes.

Direction vent

La girouette de la marque THIES donne la direction du vent en 16 quartiers sur 4 fils de sortie au format GRAY 4 bits. Les sorties se font sur des optocoupleurs qui malheureusement délivrent des tensions positives de la valeur de la tension d'alimentation des diodes LED ( 12 - 20V) . Il faut donc effectuer une adaptation de niveau compatible avec la tension d'alimentation de l' Arduino.
Ces 4 bits sont reliés à 4 pins de l'Arduino, il suffit ensuite de décoder ce code GRAY pour obtenir ces 16 quartiers.
La direction du vent fluctuant en permanence, une moyenne est faite sur les 60 dernières secondes à l'aide d'un tableau à 60 valeurs. La fonction calculant cette moyenne utilise le principe des vecteurs après avoir fait une conversion des angles en radians. Cette méthode est nécessaire pour les angles variant autour du Nord, les angles ayant alors des valeurs fluctuant autour de 360° et 1°, ce qui interdit de faire simplement une moyenne de la valeur des angles en degrès.
Le logiciel conserve également la direction du vent lors de la rafale détectée durant les 2 dernières minutes.
Le logiciel transmet la valeur instantanée de la direction, la moyenne sur 2 minutes et la direction de la dernière rafale.

Watchdog

La station météo étant prévue pour fonctionner sur un site distant sans surveillance et afin de palier à d'éventuels plantages du logiciel, l'utilisation du watchdog de l'Arduino est donc incluse. Une boucle de calcul dure un peu plus d'une seconde. Le watchdog est programmé à 8 secondes et est remis à zéro à chaque boucle, soit toutes les secondes.

A l'usage, il est apparu que le shield Ethernet présentait quelques dysfonctionnements et pour une raison encore inconnue, cessait de transmettre... J'ai donc rajouté un second watchdog qui effectue une RAZ hard en mettant la pin RESET à la masse à l'aide d'une pin digitale programmée en sortie.

Ce watchdog est basé sur un timer qui est incrémenté à chaque transfert réussi. En cas de transmission qui n'aboutit pas pour une raison ou une autre, le logiciel effectue une RAZ hard.

Transfert sur Internet

Toutes les 5 minutes, le logiciel effectue un transfert des données sur le site distant (pour le moment Wunderground) à l'aide d'une requête GET.
La fonction uploadWU convertit les données et la date au format impérial réclamé par Wunderground. Elle établit la connexion et transmet le tout.

Elle lit la réponse du site qui doit transmettre le mot "success" en cas de succès et retourne TRUE. Si la connexion ne s'établit pas, elle retourne FALSE.

Dans la boucle principale, en cas d'échec, 3 nouvelles tentatives sont effectuées à 15 s d'intervalle. Si la requête n'aboutit toujours pas, le compteur du watchdog n'étant pas incrémenté  dépasse la valeur limité fixée à 7 min, le logiciel effectue une RAZ hard de l'Arduino.
Le watchdog de l'Arduino est désactivé pendant l'appel de la fonction d'upload, cette dernière pouvant attendre une réponse du serveur pendant un maximum de 10s.

Photo du prototype en cours de développement.
Le shield Ethernet est enfiché sur l'Arduino.

Le logiciel

Il s'agit ici de mon 2ème sketch écrit pour l'Arduino. Avant ces applications, je ne connaissais rien au C++ qui est utilisé par l'IDE Arduino...
Je vais essayer d'expliquer le fonctionnement des fonctions les plus importantes du programme...

La fonction uploadWU()

Cette fonction prend en charge le transfert des données collectées vers un site Web, en l'occurence Wunderground - afin que ce dernier affiche les conditions météo en temps réel et sous différents formats. ( graphiques, visuels, tableau ). 
Je n'exclue cependant pas de créer ma propre page météo à l'aide des outils déjà utilisés dans mon  Projet "Apaguard" . La fonction retourne TRUE en cas de succès de la requête et FALSE dans le cas contraire.

L'interface réseau est un shield Ethernet à base du chip WS5100 qui utilise la librairie Ethernet.h standard.

Chronologiquement :

1) La valeur de millis() est stockée dans une variable uploadTimer qui permettra d'une part d'afficher le temps pris par le transfert  (pour débugage) et d'autre part de gérer un timeout en cas de problème dans le transfert.

2) L'horodatage issu de l'horloge temps réel (RTC) est formatée et stockée dans un buffer à l'aide de la fonction sprintf.

3) La connexion avec le serveur est tentée avec la commande client.connect de la librairie. Cette commande retourne une variable du type INT qui permet de faire des tests et de connaître la raison de l'échec. J'ai constaté qu'un simple test if ( client.connect() ) ne suffit pas à bien tester la connexion. J'ai donc mis if ( client.connect() == 1 )
Les autres test sont 1=SUCCESS, -1 = TIMED_OUT, -2 = INVALID_SERVER, -3 = TRUNCATED, - 4 = INVALID_RESPONSE, -5 = ( à confirmer)

4) Si la connexion est établie, on envoie les données collectées avec la commande client.print. C'est également ici que  les conversions du système métrique vers le système impérial sont effectuées.
A noter que, contrairement à de nombreux exemples, j'ai dû rajouter les commandes suivantes à la fin pour gaantir les transfert vers les différents serveurs testés. Sinon, j'obtenais des erreurs 403 ou aucune réponse du serveur .
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(WU_server);
client.println("User-Agent: arduino-ethernet");
client.println("Connection: close");

5) En cas d'échec de la connexion au serveur, on affiche un message d'erreur et retourne FALSE et on ferme la connexion avec client.stop()

6) On lit les données retournées par le serveur en lisant caractères par caractères avec la commande client.read(). On recherche la chaine de caractères "success" qui indique que la commande est bien passée.

7) Si ce n'est pas le cas et que le timer dépasse la valeur de 10s, on retourne FALSE.

8) Si tout se passe bien, on retourne TRUE et on réinitialise la variable LastUploadTime qui sert pour le watchdog HARD.

La fonction IncPluviometre()

Cette fonction est appellée par la détection d'une interruption provenant du pluviomètre. Elle effecture un "nettoyage" du signal venant du contact du pluviomètre à l'aide d'un anti-rebond logiciel. Si d'autres signaux proviennent du contact AVANT la délai de 100ms, ils ne sont pas pris en compte.
Elle incrémente les variables contenant la quantité de pluie tombée pendant la dernière minute et dans la journée et sauvegarde cette dernière valeur dans l'EEPROM de l'AVR afin qu'elle soit retrouvée en cas de RAZ de l'Arduino. 

// ===============================================================================
// fonction pour capturer les interruptions venant du pluviomètre
// debouncing par soft
// ===============================================================================
void IncPluviometre() {
    static unsigned long last_interrupt_time = 0;
    unsigned long interrupt_time = millis();
  if (interrupt_time - last_interrupt_time > 100)                 // si interruption suivante arrive avant 100ms, on suppose que c'est un rebond et on ignore
  {
       cumulPluie_24h += contenancePluviometre ;                  // cumul sur une journée de la quantité de pluie tombée
       cumulPluie_1m[minutes] += contenancePluviometre;           // cumul de la pluviometrie sur 1 min
       EEPROM.update(eepromAdresseCumulPluie_24h,cumulPluie_24h); // TEST maj valeur cumul 24h dans EEPROM
      
      last_interrupt_time = interrupt_time;                       // on attend la prochaine interruption
  }
}   // fin IncPluviometre

La fonction calculPluvio()

Cette fonction calcule la quantité de pluie tombée durant les 60 dernières minutes. On a stocké les 60 dernières valeurs (une / minute) lues dans un tableau, on lit donc ces 60 valeurs à l'aide d'une boucle et on en fait la somme dans la variable rainin.

// =============================================================================
// calcul pluviométrie
// calcul de la pluviométrie cumulée des 60 dernière minutes
//=============================================================================
void calculPluvio() {
rainin = 0;                                           // RAZ valeur cumul pluie dernières 60 min
for(byte i = 0 ; i < 60 ; i++)                        // lecture tableau des dernières 60 mesures (1/min)
rainin += cumulPluie_1m[i];                           // calcul cumul pluie sur 60 min
#ifdef DEBUG      
 Serial.print(F("rainin = "));
 Serial.println((float) rainin / 1000);
#endif      
} // fin calculPluvio

La fonction readVitesseVent()

Cette fonction lit la fréquence des signaux issus de l'anémomètre. La sortie de ce dernier délivre des signaux avec des fronts très propres qui ne nécessitent donc pas de mise en forme avant d'être injectés dans l'Arduino.
La librairie utilisée est très simple d'utilisation et semble fiable. Pour plus de renseignements, lire l'article suivant.
Le temps de comptage a été fixé à 1s, ce qui évite des calculs inutiles.

//=============================================================================
// lecture de la vitesse du vent
// entrée de l'anémomètre sur pin 5 et utilisation en fréquencemètre
// une mesure dure 1 s
//=============================================================================
void readVitesseVent() {
      long int frq;
      //FreqCounter::f_comp= 8;                 // Set compensation to 12
      FreqCounter::start(1000);                 // Start counting with gatetime of 1000 ms
      while (FreqCounter::f_ready == 0)         // wait until counter ready
      
      frq=FreqCounter::f_freq;                  // read result
      vitesseVent = (frq * 0.08669) + 0.32;     // conversion fréquence en Hz en m/s  V(m/s) = 0.08669 * f(Hz) + 0.32 

      if ( vitesseVent < 0.4 || vitesseVent > 50) {   // limites validité. > 50 pour supprimer 1ère mesure erronée !
        vitesseVent = 0;
      }

      #ifdef DEBUG      
            Serial.print(F("Frequence (Hz) = "));
            Serial.println(frq);                // print result
            Serial.print(F("Vitesse Vent (m/s) = "));
            Serial.println(vitesseVent);
      #endif      
} // FIN readVitesseVent

La fonction readDirectionVent()

Cette fonction convertit les 4 bits issus de la girouette. 4 bits signifie donc 16 cadrans de direction du vent.
Ces 4 bits sont au format GRAY et nécessitent donc un décodage pour retrouver les angles. J'ai utilisé une valeur moyenne de l'angle pour chaque cadran. Le décodage s'effectue par des OU EXCLUSIFS sur chaque bit.

//=============================================================================
// decodage direction vent et calcul moyenne retourne directionVent
// girouette code Gray 4 bits sur pins analogiques A0 à A3
//=============================================================================
int readDirectionVent() {
      const int decode[16] = {
          10, 100, 190, 280,
          55, 145, 235, 325,
          35, 125, 215, 305,
          80, 170, 260, 350};
          
          // décodage code Gray par OU exclusifs
byte codeGray = digitalRead(PINBIT0) | digitalRead(PINBIT1) <<1 | digitalRead(PINBIT2) <<2 | digitalRead(PINBIT3) <<3;
return directionvent = decode[codeGray];
} // fin