Station météo Ethernet avec Arduino

a marqué ce sujet comme résolu.

Bonjour à tous !

Les questions concernant la programmation se trouvent à la fin et ne nécessitent pas forcément de tout lire

Je travaille actuellement sur une station météo Arduino dont le but final est d’être intégrée à un système de domotique que je souhaite développer (pour mon futur chez-moi :D). Une discussion autour de l’architecture générale est également en cours sur ce post pour ceux que ça intéresse.

Pour faire court, plusieurs modules arduino sont connectés sur un réseau local via des cables Ethernet. Un PC fera office de "cerveau" domotique et collectera les infos des modules et leur enverra des ordres.

La station météo est le premier de ces modules que je développe. Voici les points clés de ce module :

Matériel :

  • Carte arduino UNO
  • Shield Ethernet
  • Carte SD 2Go (dans le shield)
  • girouette fait maison
  • anemomètre fait maison
  • capteur de pluie du genre Lmk:47
  • un capteur de luminosité ADA1384
  • un capteur pression/température/humidité ADA2652

Petites précisions sur l’anémomètre et la girouette :

Ces éléments seront imprimés en 3D. Leur fonctionnement se base sur des aimants et des capteurs à effet hall.

Pour l’anémomètre, rien de bien sorcier, on a un capteur sur le stator, un nombre connu d’aimant sur le rotor et on compte le nombre de tours en un temps donné. On convertit ensuite les RPM en vitesse de vent.

Principe de fonctionnement de l’anémomètre

Pour la girouette c’est un peu plus compliqué. On a 4 capteurs disposés à 90°. un secteur aimanté est positionné sur le rotor. Selon les capteurs actifs, on sait d’où vient le vent avec une précision de +/- 22,5° (comme un codeur 8 positions).

Fonctionnement de la girouette

Je pense que ces précisions sont utiles si vous vous plongez dans le code pour essayer de comprendre le cheminement de ma pensée tordue :p

Le fonctionnement attendu

Cette station météo devra… relever la météo !!!

Plus précisément, chaque capteur est relevé selon un intervalle qui peut être défini de manière spécifique (dans mon code, toutes les 10s pour la pluie et le vent, toutes les 5 min pour les autres).

Le tout est inséré dans une trame sous la forme : valeur_capteur1=valeur_relevée1&valeur_capteur2=valeurrelevée2& etc…

on essaie ensuite d’envoyer la trame via la liaison ethernet. Si on y arrive pas, on écrit la trame dans un fichier texte qui se trouve sur la carte SD (pour ne pas perdre de données).

Comme la boucle tourne plus vite que le délai de mesure minimum, on profite de ces cycles sans mesures pour envoyer des trames stockées sur la SD (en commençant pas la plus récente).

Simple ! :p

Le code

Et voila le code dans son état actuel ! Il n’est pas encore terminé mais déjà pas mal de boulot dessus. (le code ne compile pas, c’est tout à fait normal, certaines infos sont manquantes et marquées "XXX")

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// inclut les elements ADA2652
#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME280 bme;
//Liste des pins
const char pluviometre = X;
const char anemometre = 2;
const char ILS1 = X;
const char ILS2 = X;
const char ILS3 = X;
const char ILS4 = X;
const char luxmetre = X;

// la communication
byte mac[]={ 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(XXX,XXX,X,XXX);
EthernetClient client;

char serveur[] = "XXXXXXXXXX";
unsigned int port = XX;

// les intervals de mesures
const unsigned long rainInterval = 10000; // temps entre 2 mesure de pluie
const unsigned long windInterval = 10000; // temps entre 2 mesures de vent
const unsigned long lightInterval = 300000; // temps entre 2 mesures de luminosite
const unsigned long pressureInterval = 300000; // temps entre 2 mesures de pression atm
const unsigned long hygroInterval = 300000; // temps entre 2 mesures d'hygrometrie
const unsigned long tempInterval = 300000; // temps entre 2 mesures de temperature

// les dernieres mesures
unsigned long lastRainUpdate = 0; //derniere mesure de pluie
unsigned long lastWindUpdate = 0; // derniere mesure de vent
unsigned long lastLightUpdate = 0; // derniere mesure de luminosité
unsigned long lastPressureUpdate =0; // derniere mesure de pression atm
unsigned long lastHygroUpdate = 0; // derniere mesure d'hygrometrie
unsigned long lastTempUpdate = 0; // derniere mesure de temperature

//body de la trame ethernet
String trame = ("");

// structure du datalog
unsigned long ligneActive = 0;
unsigned char longueurTrame = 66;

// variable gestion du temps
unsigned long time = 0; // variable millis

//variables anemometre
volatile unsigned char rpmCount = 0; // compte le nombre de passage d'aimant devant le capteur

void setup() {

  // parametrage des I/O
  pinMode(pluviometre, INPUT);
  pinMode(anemometre, INPUT);
  pinMode(ILS1, INPUT);
  pinMode(ILS2, INPUT);
  pinMode(ILS3, INPUT);
  pinMode(ILS4, INPUT);
  pinMode(luxmetre, INPUT);

  attachInterrupt(digitalPinToInterrupt(anemometre), RPMCounter, RISING); // met en place l'interruption RPMCounter sur un front montant de l'entrée anemometre

  //demarage de la voie série pour debuguer
  Serial.begin(9600);

  // démarage du capteur ADA2652
  if (!bme.begin());
  {
    Serial.println("capteur ADA2652 non trouve");
    while (1);
  }
  Serial.println("capteur ADA2652 trouve");
  
  // demarage de la SD
  Serial.println("Demarage de la SD");
  if(!SD.begin(4))
  {
    Serial.println("probleme de connexion SD");
    while(1);
    return;
  }

  Serial.println("SD initialisee, reprise du fil de lecture");
  // determination de la ligne active
  File datalog = SD.open("datalog.txt", FILE_READ);

  while (datalog.peek() == '0')
  {
    ligneActive ++;
    datalog.seek(longueurTrame * ligneActive);
  }

  datalog.close();

  // demarrage du shield ethernet
  Serial.println("Parametrage ip fixe");
  Ethernet.begin(mac,ip);

  Serial.println("Initialisation");
  delay(1000);

  Serial.println("Pret");
}

void loop() {

  //routine de mesure : on ne lance la mesure que si l'interval défini est dépassé
  time = millis();

  if ((time - lastRainUpdate) >= rainInterval)
  {
    trame = ("p=");
    trame = trame + relevePluie(pluviometre);
    trame = trame + ("&");
    
    lastRainUpdate = time;
  }

  if ((time - lastWindUpdate) >= windInterval)
  {
    trame = trame + ("v=");
    trame = trame + releveVitesseVent();
    trame = trame + ("&d=");
    trame = trame + releveDirVent(ILS1, ILS2, ILS3, ILS4);
    trame = trame + ("&");

    lastWindUpdate = time;
  }

  if ((time - lastLightUpdate) >= lightInterval)
  {
    trame = trame + ("l=");
    trame = trame + releveLumiere();
    trame = trame + ("&");

    lastLightUpdate = time;
  }

  if ((time - lastPressureUpdate) >= pressureInterval)
  {
    trame = trame + ("b=");
    trame = trame + relevePression();
    trame = trame + ("&");

    lastPressureUpdate = time;
  }

  if ((time-lastHygroUpdate) >= hygroInterval)
  {
    trame = trame + ("h=");
    trame = trame + releveHygro();
    trame = trame + ("&");

    lastHygroUpdate = time;
  }

  if ((time-lastTempUpdate) >= tempInterval)
  {
    trame = trame + ("t=");
    trame = trame + releveTemp();
    trame = trame + ("&");

    lastTempUpdate = time;
  }

  Serial.println("tentative de connexion Ethernet");
  // si on arrive a se connecter, on tente d'envoyer une trame
  if(client.connect(serveur,port))
  {
    // Si la trame de la boucle est vide, on récupère la dernière trame stockée
    if (trame =="")
    {
      Serial.println("trame vide, récupération à partir du datalog");
      trame = FromFile(ligneActive, longueurTrame, trame);
    }

    if(trame != "")
    {
      Serial.println("Evoi trame");
      SendEthernet(trame);
    }
    else
    {
      Serial.println("Pas de trame a envoyer");
    }
    
  }

  // Si la connection echoue, on ecrit la trame dans le fichier
  else
  {
    Serial.println("Echec de la connection ethernet, ecriture dans le datalog");
    ligneActive = WriteInDatalog(ligneActive, longueurTrame, trame);
  }
  //reset de la trame
  trame="";

}

void RPMCounter() // incremente le compteur de rpm à chaque interruption
{
  rpmCount ++;
}

bool relevePluie(const char pluviometre)
{
  bool pluie = false;
  
    if (digitalRead(pluviometre) == LOW)
    {
      pluie = true;
    }
    
  return pluie;
}

unsigned char releveVitesseVent()
{
  unsigned int RPM = 0;
  unsigned char vitesseVent = 0;
  
  //calcul de la vitesse de rotation
  RPM = rpmCount / (time - lastWindUpdate); //vitesse de rotation en tr/ms
  vitesseVent = fct(RPM);

  return vitesseVent;
}

// relevé de la direction du vent
// ILS 1,2,3,4 dans le sens horaire, ILS1 vers le nord
String releveDirVent(const char ILS1, const char ILS2, const char ILS3, const char ILS4)
{
  String dir;

  if (digitalRead(ILS1) == HIGH && digitalRead(ILS2) == LOW && digitalRead(ILS3) == LOW && digitalRead(ILS4) == LOW)
  {
    dir = "NN";
  }

  else if (digitalRead(ILS1) == LOW && digitalRead(ILS2) == HIGH && digitalRead(ILS3) == LOW && digitalRead(ILS4) == LOW)
  {
    dir = "OO";
  }

  else if (digitalRead(ILS1) == LOW && digitalRead(ILS2) == LOW && digitalRead(ILS3) == HIGH && digitalRead(ILS4) == LOW)
  {
    dir = "SS";
  }

  else if (digitalRead(ILS1) == LOW && digitalRead(ILS2) == LOW && digitalRead(ILS3) == LOW && digitalRead(ILS4) == HIGH)
  {
    dir = "EE";
  }

  else if (digitalRead(ILS1) == HIGH && digitalRead(ILS2) == HIGH && digitalRead(ILS3) == LOW && digitalRead(ILS4) == LOW)
  {
    dir = "NO";
  }
  else if (digitalRead(ILS1) == LOW && digitalRead(ILS2) == HIGH && digitalRead(ILS3) == HIGH && digitalRead(ILS4) == LOW)
  {
    dir = "SO";
  }

  else if (digitalRead(ILS1) == LOW && digitalRead(ILS2) == LOW && digitalRead(ILS3) == HIGH && digitalRead(ILS4) == HIGH)
  {
    dir = "SE";
  }

  else if (digitalRead(ILS1) == HIGH && digitalRead(ILS2) == LOW && digitalRead(ILS3) == LOW && digitalRead(ILS4) == HIGH)
  {
    dir = "NE";
  }

  else
  {
    dir = "NA";
  }

  return dir;

}

unsigned int releveLumiere () // avec un capteur ADA1384
{
  int lux = 0;

  lux = map(analogRead(luxmetre),0,1023,3,55000);
  
  return lux;
}

float relevePression ()
{
  float pression = 0;

  pression = bme.readPressure()/100;

  return pression;
}

unsigned char releveHygro ()
{
  float hygrometrie = 0;

  hygrometrie = bme.readHumidity();

  return hygrometrie;
}

char releveTemp ()
{
  char temperature = 0;
  
  temperature = bme.readTemperature();
  
  return temperature;
}

String FromFile(unsigned long ligneActive, unsigned char longueurTrame, String trame)
{
  Serial.println("ouverture du datalog");
  File datalog = SD.open("datalog.txt",FILE_WRITE);
  if (!datalog)
  {
    Serial.println("Impossible d'ouvrir le fichier datalog");
    while(1);
  }

  datalog.seek(ligneActive * longueurTrame);

  if(datalog.peek() == "0")
  {
    datalog.print("1");
    
    while(datalog.peek() != 'x' && datalog.peek() != '\n')
    {
      trame = trame + datalog.read();
    }
  }
  
  return (trame);

}

void SendEthernet(String trame)
{
  client.println("POST / HTTP/1.1");
  client.println("Host: XXXXXXXXXXX");
  client.println("Connection: Close");
  client.println("Content-type: application/x-www-form-urlencoded");
  client.print("Content-Length: ");
  client.println();
  client.println();
  client.print(trame);
}

unsigned long WriteInDatalog(unsigned long ligneActive, unsigned char longueurTrame, String trame)
{  
  //ajoute 0 comme indicateur pour le read : 0 non envoyé, 1 envoyé
  trame = "0" + trame;
  // complete la trame pour qu'elle est le nombre de caractere standard et l'indicateur d'envoi
  while (trame.length()!= longueurTrame)
  {
    trame = trame + "x";
  }
  
  Serial.println("ouverture du datalog");
  File datalog = SD.open("datalog.txt",FILE_WRITE);
  if (!datalog)
  {
    Serial.println("Impossible d'ouvrir le fichier datalog");
    while(1);
  }

  datalog.seek(longueurTrame * ligneActive);

  datalog.println(trame);

  datalog.close();

  ligneActive ++;

  return (ligneActive);
  
}

Ce qu’il reste à faire :

  • Intégrer un horodatage de chaque mesure
  • Configurer les pins E/S et la liaison Ethernet
  • Fabriquer et étalonner l’anémomètre pour écrire la fonction qui calcule la vitesse du vent (actuellement marquée fct(RPM))
  • Tester :)

I need you !

Tout ce post pour finalement en arriver là !

C’est mon premier projet arduino (et codage en général, mis a part quelques délires en VBA sur Excel :lol: ) donc si vous voyez des choses horribles dans mon code, n’hésitez pas à m’en faire part pour que je puisse m’améliorer !

Je cherche également comment réaliser la fonction d’horodatage. Je souhaite récupérer l’heure du PC qui fera le cerveau du système via ethernet pour initialiser le temps et ensuite utiliser l’horloge interne de l’arduino pour mettre à jour cette date. Cette action se répèterais régulièrement pour compenser le décalage provoqué par l’imprécision naturelle de la fonction millis (j’ai lu quelque part une personne qui perdait 5s par jour, ce n’est pas énorme mais ce décalage existe) et l’utilisation d’une interruption (qui stoppe la fonction millis le temps de l’exécution).

J’ai vu qu’un protocole spécifique existait : le NTP. En recherchant quelques exemple pour arduino, j’ai pu voir que l’heure était récupérée sur un serveur dédié sur internet. Or mon réseau sera (au moins dans un premier temps) uniquement local. En lisant rapidement la page wikipedia du protocole NTP, j’ai pu voir qu’il existait une synchronisation horizontale entre machines d’un même réseau. Serait-il possible d’utiliser ce système pour régler mon problème ?

Si vous êtes arrivés jusque là bravo ! Et merci d’avance pour votre participation ;)

Edit 1 : petite mise à jour du code pour gérer la situation où il n’y aurais rien à envoyer du tout (pas de mesures effectuées et pas de trame non envoyées dans le datalog)

+0 -0

Bonjour tout le monde,

Après avoir farfouillé un moment sur internet, j’ai fini par me décider pour utiliser la bibliothèque NTPClient pour récupérer la date et l’heure. En faisant quelques tests, j’ai réussi à adapter le code fournie en exemple (avec un shield wifi) à mon shield ethernet. J’arrive à récupérer l’heure provenant du serveur ntp.unice.fr. J’ai ensuite essayé de modifier la bibliothèque en ajoutant la fonction getFullFormatedTime tel que proposée dans cette discussion. J’ai donc modifié mon NTPClient.h pour ajouter le prototype et le Time.h et copié collé le code suivant dans mon NTPClient.ccp :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
String NTPClient::getFullFormattedTime() {
   time_t rawtime = this->getEpochTime();
   struct tm * ti;
   ti = localtime (&rawtime);

   uint16_t year = ti->tm_year + 1900;
   String yearStr = String(year);

   uint8_t month = ti->tm_mon + 1;
   String monthStr = month < 10 ? "0" + String(month) : String(month);

   uint8_t day = ti->tm_mday;
   String dayStr = day < 10 ? "0" + String(day) : String(day);

   uint8_t hours = ti->tm_hour;
   String hoursStr = hours < 10 ? "0" + String(hours) : String(hours);

   uint8_t minutes = ti->tm_min;
   String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes);

   uint8_t seconds = ti->tm_sec;
   String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds);

   return yearStr + "-" + monthStr + "-" + dayStr + " " +
          hoursStr + ":" + minuteStr + ":" + secondStr;
}

Lorsque je teste mon code, j’obtiens bien la date et l’heure mais l’année est 2048. Quelqu’un a une idée de pourquoi ?

J’obtiens 148

Edit : j’ai également essayé avec un autre serveur NTP et le résultat est identique

Edit 2 : La fonction getEpochTime renvoie un timestamp correct, testé avec le convertisseur http://www.timestamp.fr/? . C’est donc bien le calcul à partir du timestamp qui est faux. D’ailleurs si je force un timestamp défini directement dans la fonction getFullFormatedTime, le résultat renvoyé est faux. Exemple : le timestamp 667318515 (23/02/1991 15:15:15) retourne la date 22/02/2021 14:15:15 (le 14h est normal, je n’ai pas mis de time offset).

+0 -0

Bonsoir tout le monde,

Après un petit moment sans avancer sur le projet, j’ai repris le développement. Pour l’histoire de la conversion du timestamp je n’ai pas trouvé d’explication… du coup je mettrais le timestamp tel quel dans la trame et le traitement sera fait coté serveur. Dans le même temps, je me suis débarrassé de la bibliothèque NTPClient, cette partie étant en fait prise en charge plus ou moins nativement dans la bibliothèque time (en reprenant l’exemple NTP fourni avec la lib).

Maintenant que ceci est posé, j’ai poursuivi mes tests en simulant la partie SD avec une routine de mesure qui renvoie des valeurs fixes et en envoyant la trame sur le port série. Tout fonctionne parfaitement.

J’ai ensuite fait quelques essais sur le code NTP d’exemple de la bibliothèque time pour utiliser l’adresse (allias ?) d’un serveur NTP plutôt que son IP. Ca aussi ça fonctionne parfaitement (et la conversion vers la date et l’heure se fait correctement accessoirement).

J’ai ensuite voulu assembler les 2 codes et là… c’est le drame. Le shield Ethernet ne veut plus s’initialiser (mais le SD oui). J’ai cherché sur internet d’où pouvait venir le problème et le seul indice que j’ai trouvé serait que comme les 2 systèmes utilisent la communication SPI (ethernet DP10 et SD DP4) il faut "désactiver" le système auquel on ne s’adresse pas. Mais je n’ai pas trouvé d’exemple concret qui fonctionne chez moi. Par exemple, certains disent qu’il suffit de mettre le pin 10 en sortie à l’état haut. J’ai testé cela sans succes.

Ci dessous vous trouverez le setup de mon code. Le moniteur série termine sur le message "Demarage du shield ethernet echec"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void setup() {

  pinMode(boutontest, INPUT);

  //demarage de la voie série pour debuguer
  Serial.begin(9600);

  Serial.print("IP number assigned by DHCP is ");
  Serial.println(Ethernet.localIP());

  // demarage de la SD
  Serial.println("Demarage de la SD");
  if(!SD.begin(4))
  {
    Serial.println("probleme de connexion SD");
    while(1);
    return;
  }

  Serial.println("SD initialisee, reprise du fil de lecture");
  // determination de la ligne active
  File datalog = SD.open("datalog.txt", FILE_READ);

  while (datalog.peek() == '0')
  {
    ligneActive ++;
    datalog.seek((longueurTrame+2) * ligneActive);
  }

  Serial.println(ligneActive);

  datalog.close();

  // demarrage du shield ethernet
  Serial.println("Demarage du shield ethernet");
  if(Ethernet.begin(mac)==0);
  {
    Serial.println("Echec");
    while(1);
  }

  // NTP
  Udp.begin(localPort);
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
}

Avez-vous une idée de ce qu’il faudrait faire ?

Merci d’avance.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte