Niveau d’eau dans la citerne
par
popularité : 100%

Voici une petite réalisation destinée à répondre à la question :
“Combien d’eau reste-t-il dans ma citerne à eau de pluie ?”
Le printemps étant là, et les mois plus secs vont bientôt arriver et la question risque de se poser à nouveau. Et comme la cuve est enterrée, ce n’est pas tous les jours que l’on va soulever le couvercle en béton pour sonder.
La première version a été décrite ici.
Voici le seconde version, qui contient juste une amélioration : l’affichage directement sur le boitier, du pourcentage de remplissage et du niveau d’eau dans la cuve.
Plusieurs solutions
J’ai envisagé plusieurs solutions :
- Utiliser une série de contacts, espacés verticalement, et commandés par un flotteur => Pas forcément très précis.
- Utiliser un détecteur de distance ultrasonore pour mesurer la distance de la flottaison par rapport au haut de la cuve.
- Mesurer la pression au fond de la cuve.
Ensuite, l’environnement à l’intérieur de la citerne présente une très forte humidité. Cet environnement est, le moins que l’on puisse dire, hostile à tout ce qui est électricité et électronique. Une solution “sans fils” serait donc souhaitable. Ici, par “sans fils”, il faut comprendre sans fils électrique ni composants électroniques. On oublie donc les 2 premières solutions, reste à trouver le moyen de mesurer une pression sans mettre le capteur sous l’eau.
La solution est dans l’air ;-)
La solution théorique retenue consiste à souffler de l’air dans un tuyau qui descend jusqu’au fond de la cuve et à mesurer la pression dans ce tuyau. A partir du moment où l’air va s’échapper du tuyau, la pression dans le tuyau ne va plus augmenter.
Cette solution, je ne l’ai pas inventée, elle est présentée sur le site arduino.cc. Par contre, sa mise en œuvre m’a posé quelques problèmes.
La pompe
Ne sachant pas initialement où j’allais l’installer, je voulais un modèle basse tension, si possible 5 Volt, comme l’Arduino, sinon, 12 Volt.
Ne trouvant aucun modèle 5 Volt, j’ai dû essayer plusieurs modèles avant de trouver une pompe capable de souffler de l’air sous au moins 2 mètres d’eau.
La première à fonctionner correctement (photo ci-dessus) demandait beaucoup de puissance, était très bruyante et vibrait énormément. Elle fonctionne bien avec une batterie automobile, mais ce genre de batterie s’intègre très mal dans un tableau électrique. Finalement, j’ai trouvé et conservé celle-ci. Petite et pas trop bruyante.
Lire la pression
Le capteur utilisé est le Freescale MPX5050DP qui permet de mesurer jusqu’à des pressions équivalentes à une hauteur d’eau de 5 mètres.
Il fonctionne à merveille. Par contre, le placer près de la pompe n’est vraiment pas une bonne idée, surtout avec la première pompe utilisée, car des ondes de pression sont générées par la pompe, ce qui donne des valeurs très erratiques.
Positionner le capteur près de la cuve semblait résoudre le problème. En partie seulement, car cette fois ci c’est l’échappement des bulles qui perturbe la lecture.
Mais ayant décidé de ne pas mettre d’électronique dehors, j’ai dû me résoudre à utiliser un second tuyau. Un tuyau pour l’aller, et un autre pour le retour. Et là, vu la longueur du tuyau (40 mètres aller + 40 mètres retour) les parasites ont pratiquement disparus, amortis par la distance !
Les composants nécessaires
L’ensemble a été installé dans une petite armoire électrique d’une rangée de 13 modules. Et toute la rangée va être utilisée.
Liste des composants utilisés :
- Un disjoncteur comme celui-ci qui fait principalement office de coupe circuit.
- Une alimentation 5 Volt pour alimenter l’Arduino et le relai. Disponible également ici.
- Une carte Arduino Uno comme celle-ci ou celle-la.
- Un shield Ethernet comme celui-ci ou celui-la.
- Un relai pour déclencher la pompe.
- Une alimentation 12 Volt pour alimenter la pompe à air. Disponible également ici.
- Une pompe à air pas trop bruyante et pas trop gourmande en énergie.
- Un capteur de pression Freescale MPX5050DP.
- Une LED, dont la fréquence de clignotement donnera une indication sur le fonctionnement de l’ensemble.
- Un petit écran monochrome oled affichant 128x64 pixels de 0.96" de diagonale via une interface i2c. C’est petit mais suffisant !
- Un coffret électrique 13 modules de ce type pour emballer le tout.
- Deux fois 50 mètres de tuyau 4 mm pour faire l’aller et le retour. Il existe en 15 m et 25 m, mais j’avais besoin d’au moins 30 mètres. De plus, la longueur facilite la mesure.
- Des pièces imprimées en 3D qui servent de support à différents composants pour les fixer sur le rail DIN.
L’ensemble revient quand même à prêt de 110 €
Le plan de montage
- Le secteur alimente le disjoncteur qui sert en fait de coupe circuit général.
- Le disjoncteur alimente l’alimentation 5 Volt, ainsi que l’alimentation 12 Volt, par l’intermédiaire du relai.
- L’Arduino coordonne l’ensemble des opérations :
- Commander le relai pour alimenter la pompe.
- Lire la mesure de la pression.
- Envoyer la mesure réalisée vers un serveur de datalogging.
- Afficher la dernière mesure ou les opérations en cours.
- Interroger un serveur NTP pour savoir quand effectuer les mesures.
- Le relai est commandé par l’Arduino, pour mettre l’alimentation 12 Volt sous tension afin de faire fonctionner la pompe à air
- L’alimentation 12 Volt pour la pompe à air.
- Il y a donc 2 tuyaux qui vont jusqu’à la citerne :
- Un pour l’aller qui part de la pompe à air jusqu’à l’entrée de la citerne. Il sert à mettre l’ensemble en pression au moment de la mesure.
- Un pour le retour, qui va de l’entrée de la citerne au capteur de pression, et qui va transmettre la pression en atténuant toutes les ondes parasites.
- Enfin, un T permet de relier le tuyau “aller”, au tuyau “retour”, ainsi qu’au tuyau qui plonge au fond de la citerne. Pour maintenir l’extrémité du tuyau au fond de la citerne, j’ai percé un petit bloc de schiste de plusieurs trous de 6 mm dans lesquels j’ai fait passer le tuyau en zigzag.
Les pièces 3D
Il y a 6 pièces imprimée en 3D afin de peaufiner et de fixer l’ensemble. Ces pièces sont disponibles sur GitHub
- Support de la carte Arduino.
- Support du relai.
- Support de la pompe à air
- Support du capteur de pression
- Cache transparent qui recouvre l’Arduino et qui permet de voir les LED
- Cache opaque à placer devant la pompe et le capteur de pression, mais avec une fenêtre et un support pour y placer l’écran d’affichage.
Support de l’Arduino
Cette pièce et son utilisation sont décrites ici.
Le fichier source est disponible ici.
Support du relai
Le fichier source est disponible ici.
Il est conçu pour qu’on puisse y glisser le relai, et que les borniers restent accessibles.
Support de la pompe à air
Le fichier source est disponible ici.
La pompe est fixée avec 2 petits colliers de serrage qui sont passées dans des tunnels prévus à cet effet.
Support du capteur de pression
Le fichier source est disponible ici.
Le capteur est fixé avec 2 vis M4. Un emplacement pour les écrous est prévu à l’arrière.
Cache transparent
Il recouvre l’Arduino et qui permet de voir les LED.
Le fichier source est disponible ici.
Le fichier .scad est également disponible. Cela peut permettre d’imprimer des caches de différentes tailles.
Cache opaque
Il recouvre la pompe et le capteur de pression et sert de support à l’écran d’affichage.
L’écran est simplement clipser au cache grâce à 4 cylindres prévus à l’emplacement des trous de fixation.
Le fichier source est disponible ici.
Le fichier .scad est également disponible.
Mesurer la pression
Pour prendre la mesure de la pression, lire juste une fois la tension aux bornes du capteur ne suffit pas. La précision n’est pas au rendez-vous.
Pour améliorer la mesure, la solution adoptée est la classique série de mesure, dont on ne garde que la moyenne.
Ensuite, quand faut-il mesurer ?
En effet, lorsque la pompe se met en marche, on constate une augmentation de la pression. Puis, en fonction de la hauteur d’eau, la pression diminue, jusqu’à se stabiliser.
Afficher le taux de remplissage
Plutôt que d’afficher la hauteur d’eau mesurée, qui ne signifie pas grans chose en elle-même, j’ai préféré afficher le taux de remplissage, sous la forme d’un pourcentage, allant d’une valeur négative à 100%.
Ce pourcentage est affiché en gros, visible de loin.
Mais en s’approchant, il est quand même possible de lire la hauteur d’eau.
Pour gérer l’affichage, j’utilise la librairie U8glib. Celle-ci est disponible ici.
Câblage
Et voici le schéma de câblage général.
L’ensemble est isolable du secteur 220 Volt AC par un disjoncteur coupe-circuit. Le 220 volt alimente un bloc d’alimentation 5 Volt, ainsi qu’un bloc d’alimentation 12 Volt via un relai commandé par la broche 2 de l’Arduino.
Le bloc d’alimentation 5 Volt alimente directement :
- L’Arduino via ses broches +5v et GND.
- Le relai, par ailleurs commandé par la broche 2 de l’Arduino
- Le capteur de pression, dont la mesure est lue par la broche A0 de l’Arduino
La pompe à air est directement alimentée par le bloc d’alimentation 12 Volt
Une LED est commandée par la broche 5.
L’afficheur est alimentée par la broche 3.3V de l’Arduino
Le programme
Voici le code, expurgé des commentaires du début.
L’intégralité est disponible à la fin de cet article sous forme de fichier archive téléchargeable.
Egalement disponible sur GitHub.
Ce programme effectue deux mesures par jour, entre 00h00 et 00h05, ainsi que entre 12h00 et 12h05. Afin de connaitre l’heure, un server NTP est interrogé régulièrement.
Il est aussi possible de provoquer une mesure à la demande, grâce à l’ordre ReadWaterLevel. 2 autres codes sont aussi implémentés : AbortProcess pour stopper un processus de lecture et Reset pour réinitialiser la carte Arduino.
Toutes les mesures sont envoyées vers le serveur de datalogging.
Les broches suivantes sont utilisées :
- A0 : Capteur de pression
- 2 : Relais alimentation électrique de la pompe
- 4 : carte Ethernet - SS for SD card
- 5 : LED
- 10 : carte Ethernet - SS for ethernet controller / LCD - Backlit control (broche pliée)
- 11 : carte Ethernet - SPI bus : MOSI
- 12 : carte Ethernet - SPI bus : MISO
- 13 : carte Ethernet - SPI bus : SCK
Le programme :
Attention
Avec l’utilisation des polices, le croquis dépasse la taille maximale s’il est généré avec une version récente du logiciel Arduino. Avec la version 1.6.5, la taille du croquis est de 32 118 octets. Pour rappel, le maximum est de 32 256 octets.
- /*************************************************************
- * Includes
- */
- #include <avr/wdt.h> // Pour le WatchDog
- #include <SPI.h> // Pour la carte Ethernet
- #include <Ethernet.h> // Pour la carte Ethernet
- #include <EthernetServer.h> // Pour la communication Ethernet
- #include <EthernetClient.h> // Pour la communication Ethernet
- #include <EthernetUdp.h> // Pour le protocole NTP qui permet de récupérer l'heure
- #include <U8glib.h> // Pour l'afficheur oLed
- /*************************************************************
- * Defines & constantes
- */
- const int printPause = 50; // Pause après envoi sur le terminal série, pour laisser le temps au destinataire de traiter l'information
- const int pinRelayPump = 2; // Broche du relai d'alimentation électrique de la pompe à air
- const int pinPressure = A0; // Broche reliée au capteur de pression
- const int pinInternalLED = 13; // Led interne sur la carte Arduino - Ne pas utiliser avec le shield ethernet
- const int pinLED = 5; // Led du montage
- const int relayOn = 0;
- const int relayOff = 255;
- const int httpMaxChar = 25; // Nombre de caractère maxi à traiter
- //const long millisDay = 86400000L; // Nombre de millisecondes dans une journée
- const int nbConsReads = 5; // Nombre de mesures consécutives à observer pour obtenir un résultat stable
- const int nLoop = 100; // Nombre de lectures à effectuer pour avoir une mesure
- const int maxCycles = 100; // Nombre de cycle maxi avant mise en défaut
- const int ntpEveryMinutes = 10; // Demande NTP (heure) toutes les 10 minutes
- const unsigned long seventyYears = 2208988800UL;
- // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
- // Déclaration des états
- const int statusUnknown = 0; // Etat inconnu ou indéfini
- const int statusWaiting = 1; // En attende de déclencher une lecture de pression
- const int statusOperatingPhase1 = 2; // Une lecture de pression est en cours, en phase 1
- const int statusOperatingPhase2 = 3; // Une lecture de pression est en cours, en phase 2
- const int statusDefault = 99; // Mise en défaut car hauteur d'eau non lue correctement
- const int ntpPacketSize = 48; // NTP time stamp is in the first 48 bytes of the message
- /*************************************************************
- * Déclaration des variables globales
- */
- int loopStatus = statusUnknown; // Etat en cours
- int loopLastStatus = statusUnknown; // Etat précédent
- int ledStatus = LOW;
- float waterLevel = 0;
- float reads[nbConsReads-1]; // Enregistrement des lectures successives
- int cycles = 0; // Nombre de cycle depuis le démarrage de la pompe
- // Déclaration relatives à la config réseau
- byte netMac[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}; // Adresse MAC de la carte ethernet arduino
- byte netIp[4] = { 192, 168, 1, 2 }; // Adresse IP de la carte Arduino
- byte netGateway[4] = { 192, 168, 1, 200 }; // Adresse IP de la passerelle
- byte netMask[4] = { 255, 255, 255, 0 }; // Masque sous-réseau
- byte sqlServer[4] = { 192, 168, 1, 1 }; // Adresse IP vers laquelle envoyer les donn�es
- unsigned int sqlPort = 8080; // Port HTTP sur lequel écoute le server SQL
- unsigned int udpPort = 8888; // Port pour écouter les packets UDP
- char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server
- //char timeServer[] = "192.168.2.200"; // server sur le réseau local
- byte packetBuffer[ ntpPacketSize]; // buffer to hold incoming and outgoing packets
- unsigned long nextNtp; // millis() à atteindre pour demander l'heure
- int ntpHour = -1;
- int ntpMinute = -1;
- int ntpSecond = -1;
- /*************************************************************
- * Déclaration des objets
- */
- EthernetServer httpServer(80); // on crée un objet serveur utilisant le port 80 (port HTTP par défaut)
- EthernetClient sqlClient; // objet client pour envoyer des données
- EthernetUDP udp;
- //U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE); // Afficheur sur broches SCL & SDA
- U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NO_ACK); // Afficheur sur broches A4 & A5
- const u8g_fntpgm_uint8_t *smallFont = u8g_font_courR10r; // Petite police
- const u8g_fntpgm_uint8_t *hugeFont = u8g_font_fub49n; // Police géante
- /**************************************************************************
- **
- ** setup() ==> Fonction obligatoire d'initialisation
- **
- **************************************************************************/
- void setup() {
- // Initialisation du WatchDog
- wdt_enable(WDTO_8S);
- // Initialisation de la communication série
- Serial.begin(115200);
- Serial.println("Starting V 1.0 ...");
- // Test de l'afficheur
- oledDrawText("Setup");
- delay(printPause);
- // Déclarer les broches comme "output" ou "input"
- pinMode(pinLED, OUTPUT);
- pinMode(pinRelayPump, OUTPUT);
- pinMode(pinPressure, INPUT);
- // Initialisation des broches pour que les relais ne soient pas actifs au reset
- digitalWrite(pinRelayPump, relayOff);
- Serial.println("Pins initialized.");
- delay(printPause);
- // Initialisation de la connexion Ethernet avec l'adresse MAC, l'adresse IP et le masque
- Ethernet.begin(netMac, netIp, netGateway, netMask);
- Serial.print("Ethernet initialized on IP ");
- for(int i = 0; i < 4; i++) {
- if (i > 0) {
- Serial.print(".");
- }
- Serial.print(netIp[i]);
- }
- udp.begin(udpPort);
- Serial.println("");
- delay(printPause);
- // Initialisation des variables
- for(int i=0; i<nbConsReads; i++) {
- reads[i] = NULL;
- }
- //getNtpTime();
- nextNtp = 0;
- // Initialisation du serveur interne, et commence à écouter les clients
- httpServer.begin();
- flushSerial(); // Pour vider le port série des ordres ventuellement arriv lors de l'initialisation
- loopStatus = statusWaiting;
- Serial.println("Setup finish.");
- delay(printPause);
- }
- /**************************************************************************
- **
- ** loop() ==> Fonction obligatoire. Boucle infinie de fonctionnement
- **
- **************************************************************************/
- void loop() {
- // Reset du watchDog : C'est reparti pour 8 secondes
- wdt_reset();
- // Lire les éventuelles commandes reçues sur les ports série ou réseau et y répondre
- serialCommandProcess();
- httpCommandProcess();
- // Gérer la mesure de pression
- if (isOperating()) {
- oledDrawText("Operate");
- float p = pressureRead(pinPressure);
- addPressure(p);
- Serial.print(p, 2);
- Serial.println(" kPa");
- delay(printPause);
- int e = pressureCheckEvolution();
- if (e > 0) {
- // La pile est pleine, la phase 1 a bien commencée
- if ( (loopStatus == statusOperatingPhase1) && (e == 2) ) {
- // Phase 1 ET que des diminutions
- loopStatusChange(statusOperatingPhase2);
- }
- if ( (loopStatus == statusOperatingPhase2) && (e == 1) ) {
- // Phase 2 ET une augmentation : On a trouvé une valeur
- airPumpStop(statusWaiting);
- }
- }
- cycles++;
- if (cycles >= maxCycles) {
- airPumpStop(statusDefault);
- }
- }
- // Gérer le temps - Toutes les 10 minutes environ, récupérer l'heure
- if ( (millis() > nextNtp) && !isOperating() ) {
- getNtpTime();
- nextNtp = calcNextNtp(nextNtp);
- if (checkTime()) {
- airPumpStart();
- }
- }
- // Faire clignoter la LED, en fonction de loopStatus
- toggleLedStatus();
- // Si pas en cours de lecture, faire une longue pause
- if (!isOperating()) {
- // Si en défaut, quelques éclats supplémentaires
- if (loopStatus == statusDefault) {
- oledDrawText("Default");
- for (int i=0; i<7; i++) {
- toggleLedStatus();
- delay(100);
- }
- delay(800);
- }
- else {
- delay(1500);
- }
- }
- }
- /**************************************************************************
- **
- ** Fonctions d'affichage oLed et calcul du pourcentage
- **
- **************************************************************************/
- // A partir du niveau d'eau en mm, calcul le pourcentage de remplissage de la cuve
- float perCentFill() {
- // 1600 mm ou plus => 100% (plein)
- // 200 mm ou moins => 0% (vide)
- const float fillMax = 1600;
- const float fillMin = 200;
- return( (waterLevel - fillMin) / (fillMax - fillMin) * 100 );
- }
- // Affiche le texte passé en paramètre
- void oledDrawText(String label) {
- u8g.firstPage();
- do {
- u8g.setFont(smallFont);
- u8g.setFontPosTop();
- u8g.setScale2x2();
- u8g.drawStr( 0, 10, label.c_str());
- u8g.undoScale();
- } while( u8g.nextPage() );
- }
- // Affiche un pourcentage en gros
- void oledDrawPCentLevel(float pc, float level) {
- int top = 0;
- char str[10];
- u8g.firstPage();
- do {
- u8g.setColorIndex(1);
- u8g.drawBox(0, 0, 127, 63);
- u8g.setColorIndex(0);
- u8g.setFont(hugeFont); // OK
- u8g.setFontPosTop();
- // Cas du chiffre 100
- if (pc > 99.5) {
- u8g.drawStr(-7, top, "1");
- u8g.drawStr(24, top, "00");
- }
- else {
- if (pc > 9.5) {
- u8g.drawStr(20, top, dtostrf(pc, 2, 0, str));
- }
- else {
- if (pc < 0) {
- if (pc <= -9.5) {
- u8g.drawStr(0, top, dtostrf(pc, 3, 0, str));
- }
- else {
- u8g.drawStr(37, top, dtostrf(pc, 3, 0, str));
- }
- }
- else {
- u8g.drawStr(52, top, dtostrf(pc, 2, 0, str));
- }
- }
- }
- // Le pourcentage
- int xe = 100;
- u8g.drawStr(xe, top, "/");
- u8g.drawFilledEllipse(xe+8, top+6, 6, 8, U8G_DRAW_ALL);
- u8g.drawFilledEllipse(xe+20, top+45, 6, 8, U8G_DRAW_ALL);
- u8g.setFont(smallFont);
- u8g.setFontPosTop();
- //u8g.drawStr(104, top, "0");
- //u8g.drawStr(119, top + 42, "0");
- // le niveau
- int top2 = 52;
- u8g.drawStr(50, top2, dtostrf((int)level, 4, 0, str));
- u8g.drawStr(90, top2, "mm");
- u8g.setColorIndex(1);
- u8g.drawFilledEllipse(xe+8, top+6, 2, 3, U8G_DRAW_ALL);
- u8g.drawFilledEllipse(xe+20, top+45, 2, 3, U8G_DRAW_ALL);
- } while( u8g.nextPage() );
- }
- /**************************************************************************
- **
- ** Fonctions diverses
- **
- **************************************************************************/
- // Vérifie si c'est l'heure de lire la hauteur d'eau
- boolean checkTime() {
- // Entre respectivement 11h00 et 23h00
- if ( (ntpHour == 11) || (ntpHour == 23) ) {
- // et 11h10 et 23h10
- if (ntpMinute < ntpEveryMinutes) {
- return(true);
- }
- }
- return(false);
- }
- // Change l'état de la LED
- void toggleLedStatus(void) {
- if (ledStatus == LOW) {
- ledStatus = HIGH;
- }
- else {
- ledStatus = LOW;
- }
- digitalWrite(pinLED, ledStatus);
- }
- // Vérifie si c'est une phase d'activité
- boolean isOperating() {
- return ( (loopStatus == statusOperatingPhase1) || (loopStatus == statusOperatingPhase2) );
- }
- // Changer le status courant
- void loopStatusChange(int newStatus) {
- loopLastStatus = loopStatus;
- loopStatus = newStatus;
- Serial.print("loopStatus=");
- Serial.println(textStatus(loopStatus));
- }
- // Envoi des données vers le server de dataLogging
- void ethernetSendData() {
- // Envoyer le rapport vers le server
- if (sqlClient.connect(sqlServer, sqlPort)) {
- sqlClient.print("GET /data/");
- sqlClient.print("WaterTank");
- sqlClient.print("/status=");
- sqlClient.print(textStatus(loopStatus));
- sqlClient.print("&level=");
- sqlClient.print(waterLevel, 0);
- sqlClient.println(" HTTP/1.0");
- sqlClient.println();
- delay(2);
- sqlClient.stop();
- }
- }
- // Envoie des données de lecture sur le port série
- void serialSendData(void) {
- Serial.print("{\"status\":\"");
- Serial.print(textStatus(loopStatus));
- Serial.print("\"");
- jsonPrint("level", waterLevel, 0);
- Serial.println("}");
- }
- // Chaine du statut
- String textStatus(int currentStatus) {
- if (currentStatus == statusWaiting) {
- return("Waiting");
- }
- else if (currentStatus == statusOperatingPhase1) {
- return("OperatingPhase1");
- }
- else if (currentStatus == statusOperatingPhase2) {
- return("OperatingPhase2");
- }
- else if (currentStatus == statusDefault) {
- return("Default");
- }
- return("Unknown");
- }
- // Ajout d'une valeur au document jSon, après vérification
- void jsonPrint(String label, float val, int precis) {
- if ( !isinf(val) && !isnan(val) && (val <= 4294967040.0) && (val >= -4294967040.0) ) {
- Serial.print(",\"");
- Serial.print(label);
- Serial.print("\":");
- Serial.print(val, precis);
- }
- }
- /**************************************************************************
- **
- ** Gestion des commandes reçues
- **
- **************************************************************************/
- // Lire les éventuelles commandes reçues sur le port série et y répondre
- void serialCommandProcess(void) {
- String commandText = String("");
- char byteInSerial;
- while (Serial.available() > 0) {
- byteInSerial = Serial.read();
- if ( (byteInSerial == 10) || (byteInSerial == 13) ) {
- //Serial.println("*");
- commandProcess(commandText);
- }
- else {
- //Serial.println("+");
- commandText += String(byteInSerial);
- }
- }
- if (commandText.length() > 0) {
- //Serial.println("-");
- commandProcess(commandText);
- }
- }
- void commandProcess(String commandTxt) {
- Serial.println("commandProcess");
- // Test les différentes commandes possibles
- if (commandTxt.startsWith("Reset")) {
- commandReset();
- }
- if (commandTxt.startsWith("ReadWaterLevel")) {
- commandInitOperate();
- }
- if (commandTxt.startsWith("AbortProcess")) {
- commandAbortProcess();
- }
- else {
- commandUnknown(commandTxt);
- }
- }
- // Commande de reset
- void commandReset() {
- Serial.println("commandReset");
- delay(printPause);
- // Demander au watchdog de ne pas attendre plus que 15 millisecondes avant d'agir
- wdt_enable(WDTO_15MS);
- // Demander une longue attente pour déclencher le watchdog
- delay(10000);
- }
- // Commande d'initialisation de la lecture du niveau d'eau dans la cuve
- void commandInitOperate(void) {
- Serial.println("commandInitOperate");
- delay(printPause);
- airPumpStart();
- }
- // Commande AbortProcess
- void commandAbortProcess(void) {
- Serial.println("commandAbortProcess");
- delay(printPause);
- digitalWrite(pinRelayPump, relayOff);
- loopStatusChange(statusWaiting);
- }
- // Commande inconnue !
- void commandUnknown(String commandTxt) {
- Serial.print("commandUnknown:");
- Serial.println(commandTxt);
- delay(printPause);
- }
- // Démarrage de la pompe à air - Démarrage du processus de lecture du niveau d'eau
- void airPumpStart(void) {
- digitalWrite(pinRelayPump, relayOn);
- loopStatusChange(statusOperatingPhase1);
- cycles = 0;
- }
- // Arret de la pompe à air - Fin du processus de lecture du niveau d'eau
- // Envoi des données de lecture
- void airPumpStop(int newStatus) {
- waterLevel = pressureKpaToMm(reads[nbConsReads-1]);
- digitalWrite(pinRelayPump, relayOff);
- loopStatusChange(newStatus);
- serialSendData();
- oledDrawPCentLevel(perCentFill(), waterLevel);
- ethernetSendData();
- }
- // Pour vider le port série
- void flushSerial(void) {
- while(1) {
- if (Serial.available() == 0) {
- break;
- }
- else {
- Serial.read();
- }
- }
- }
- /**************************************************************************
- **
- ** Gestion du réseau
- **
- **************************************************************************/
- // Lire les éventuelles commandes reçues sur le port ethernet et y répondre
- void httpCommandProcess(void) {
- // Exemple d'envoi de commande : http://192.168.1.2/ReadWaterLevel
- // Déclaration des variables
- String chaineRecue = ""; // Pour y mettre la chaine reçue
- int comptChar = 0; // Pour compter les caractères reçus
- // crée un objet client basé sur le client connecté au serveur
- EthernetClient httpClient = httpServer.available();
- if (httpClient) { // si l'objet client n'est pas vide
- // Initialisation des variables utilisées pour l'échange serveur/client
- chaineRecue = ""; // Vide le String de reception
- comptChar = 0; // Compteur de caractères en réception à 0
- if (httpClient.connected()) { // Si que le client est connecté
- while (httpClient.available()) { // Tant que des octets sont disponibles en lecture
- char c = httpClient.read(); // Lit l'octet suivant reçu du client (pour vider le buffer au fur à mesure !)
- comptChar++; // Incrémente le compteur de caractère reçus
- if (comptChar <= httpMaxChar) { // Les 25 premiers caractères sont suffisants pour analyses la requête
- chaineRecue += c; // Ajoute le caractère reçu au String pour les N premiers caractères
- }
- }
- // Si la chaine recue fait au moins 25 caractères ET qu'elle commence par "GET"
- if ( (chaineRecue.length() >= httpMaxChar) && (chaineRecue.startsWith("GET")) ) {
- // Extrait à partir du 6ème caractère
- httpSend(httpClient,"OK");
- commandProcess(chaineRecue.substring(5));
- }
- } // Fin while client connected
- delay(10); // On donne au navigateur le temps de recevoir les données
- httpClient.stop(); // Fermeture de la connexion avec le client
- }
- }
- void httpSend(EthernetClient httpClient, String txt) {
- // Envoi d'une entete standard de réponse http
- httpClient.print("HTTP/1.1 20");
- if (txt.length() == 0) {
- //httpClient.println("HTTP/1.1 204 OK");
- httpClient.print("4");
- }
- else {
- //httpClient.println("HTTP/1.1 200 OK");
- httpClient.print("0");
- }
- httpClient.println(" OK");
- //httpClient.println("HTTP/1.1 200 OK");
- httpClient.println("Content-Type: text/html");
- httpClient.println("Connection: close");
- httpClient.println();
- // Affiche chaines caractères simples
- if (txt.length() > 0) {
- httpClient.println(txt);
- }
- delay(200);
- }
- // Envoi une demande au server NTP dont l'adresse est en paramètre
- unsigned long sendNtpPacket(char* address) {
- // Initialise le buffer à 0 par défaut
- memset(packetBuffer, 0, ntpPacketSize);
- // Initialisée la demande NTP
- packetBuffer[0] = 0b11100011; // LI, Version, Mode
- packetBuffer[1] = 0; // Stratum, or type of clock
- packetBuffer[2] = 6; // Polling Interval
- packetBuffer[3] = 0xEC; // Peer Clock Precision
- // 8 bytes of zero for Root Delay & Root Dispersion
- packetBuffer[12] = 49;
- packetBuffer[13] = 0x4E;
- packetBuffer[14] = 49;
- packetBuffer[15] = 52;
- // Envoi du paquet IP de demande NTP
- udp.beginPacket(address, 123); // Les requetes NTP se font sur le port 123
- udp.write(packetBuffer, ntpPacketSize);
- udp.endPacket();
- }
- void getNtpTime(void) {
- // Envoyer un packet Ntp au server
- sendNtpPacket(timeServer);
- // attendre la réponse du server NTP. 100 ms pour un server local est suffisant. Prévoir plus pour un server Internet
- delay(100);
- if (udp.parsePacket()) {
- // Le server NTP a répondu.
- // Lire lire données reçues dans le buffer
- udp.read(packetBuffer, ntpPacketSize);
- // l(heure se trouve codée sur 4 octets à partir de l'octet 40
- // soit 2 'mots' à extraire
- unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
- unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
- // Combiner les 4 octets (2 'mots') dans un entier long pour avoir l'heure NTP (le nombre de secondes depuis le 1er janvier 1900)
- unsigned long secsSince1900 = highWord << 16 | lowWord;
- // Convertir l'heure NTP en temps usuel. Soustraire 70 ans pour avoir l'"epoch" (le temps UNIX)
- unsigned long epoch = secsSince1900 - seventyYears;
- // Récupérer Heures, Minutes et Secondes
- ntpHour = (epoch % 86400L) / 3600; // Il y a 86400 secondes par jour
- ntpMinute = (epoch % 3600) / 60; // il y a 3600 secondes par minute
- ntpSecond = epoch % 60;
- // Envoi de l'heure au terminal série
- Serial.print("l'heure GMT est "); // GMT (Greenwich Meridian Time) ou heure TU, ou encore UTC
- Serial.print(ntpHour); // print the hour (86400 equals secs per day)
- Serial.print(':');
- if (ntpMinute < 10) {
- // Pour les 10 premières minutes, il faut plcer d'abord un "0"
- Serial.print('0');
- }
- Serial.print(ntpMinute);
- Serial.print(':');
- if (ntpSecond < 10) {
- // Pour les 10 premières secondes, il faut plcer d'abord un "0"
- Serial.print('0');
- }
- Serial.println(ntpSecond);
- }
- }
- // Prochaine lecture de l'heure
- unsigned long calcNextNtp(unsigned long currentMillis) {
- return(currentMillis + (ntpEveryMinutes * 60L * 1000L));
- }
- /**************************************************************************
- **
- ** Lecture de la pression
- **
- **************************************************************************/
- /**************************************************************************
- **
- ** Lecture de la pression du composant Freescale MPX5050DP
- ** Valeur d'isopression : 32 => O kPa
- ** Valeur max : 1016 => 50 kPa
- ** Valeur min : 11 => dépression
- **
- ** Max 3 mètres d'eau, soit 30 kPa
- **
- ** 1 bar = 1OO kPa
- **
- **************************************************************************/
- const float pressureBitMin = 32.0; // Valeur brute en isopression
- const float pressureBitMax = 1016.0; // Valeur brute maxi
- const float pressureKPaMin = 0.0; // kPa en isopression
- const float pressureKPaMax = 50.0; // kPa maxi
- const float pressureMmPerKpa = 100.0; // Coefficient à appliquer aux kPa pour avoir des millimètres d'eau
- // Vérifie l'évolution des mesures
- // Renvoi:
- // 0 -> pas assez de mesure
- // 1 -> il y a au moins une augmentation dans la série
- // 2 -> toutes les mesures montrent une diminution
- int pressureCheckEvolution(void) {
- if (reads[nbConsReads-1] == NULL) {
- //Serial.println(reads[nbConsReads-1]);
- return(0);
- }
- for (int i=1; i<nbConsReads-1; i++) {
- if (reads[i-1] < reads[i]) {
- //Serial.println(i);
- //Serial.print(reads[i-1]);
- //Serial.print(" < ");
- //Serial.println(reads[i]);
- return(1);
- }
- }
- //Serial.println("OK");
- return(2);
- }
- // Ajoute une mesure, faite sur la broche passée en paramètre, dans la pile des mesures
- void addPressure(float p) {
- // Dépiler si la pile est pleine
- if (reads[nbConsReads-1] != NULL) {
- for (int i=1; i<nbConsReads; i++) {
- reads[i-1] = reads[i];
- }
- reads[nbConsReads-1] = NULL;
- }
- // Placer la valeur dans la première case vide
- for (int i=0; i<nbConsReads; i++) {
- if (reads[i] == NULL) {
- reads[i] = p;
- break;
- }
- }
- }
- // Effectue une mesure sur la broche passée en paramètre et renvoi une valeur moyenne exprimée en kPa
- float pressureRead(int pin) {
- long sumReads = 0;
- float avgReads = 0;
- for (int i=0; i<nLoop; i++) {
- sumReads += pressureReadAnalogPin(pin);
- delay(5);
- }
- avgReads = (float)sumReads / (float)nLoop;
- //Serial.println(avgReads, 2);
- return (pressureRawToKpa(avgReads));
- }
- // Lit la broche analogique passée en paramètre et renvoi une valeur brute
- int pressureReadAnalogPin(int pin) {
- int r = analogRead(pin);
- //float p = (r - pressureBitMin) / (pressureBitMax - pressureBitMin) * (pressureKPaMax - pressureKPaMin);
- //return p;
- return (r);
- }
- // Convertir une valeur brute lue sur une broche en kPa
- float pressureRawToKpa(float avgR) {
- return (avgR - pressureBitMin) / (pressureBitMax - pressureBitMin) * (pressureKPaMax - pressureKPaMin);
- }
- //Transforme les kPa en millimètres d'eau
- float pressureKpaToMm(float kpa) {
- return kpa * pressureMmPerKpa;
- }
- /**************************************************************************
- **
- ** MPX5050DP DataSheet
- ** Pressure range : From 0 to 50 kPa
- ** Supply voltage (Vs) : 5 V
- ** Min pressure offset : 0.2 V
- ** Full scale ouput : 4.7 V
- ** Full scale span : 4.5 V
- ** Sensitivity : 90 mV / kPa
- ** Transfert function : Vout = Vs * (P * 0.0018 + 0.04)
- ** Valeur isopression : 32
- **
- **************************************************************************/
- /**************************************************************************
- **
- ** MPX5500DP DataSheet
- ** Pressure range : From 0 to 500 kPa
- ** Supply voltage (Vs) : 5 V
- ** Supply current (Io) : 10 mA max
- ** Min pressure offset : 0.2 V
- ** Full scale ouput : 4.7 V
- ** Full scale span : 4.5 V
- ** Sensitivity : 9 mV / kPa
- ** Transfert function : Vout = Vs * (P * 0.0018 + 0.04) ?
- ** Valeur isopression : 37
- **
- **************************************************************************/
Conclusion
Voici un petit montage qui fonctionne tout seul depuis 6 mois, avec l’inconvénient de devoir aller chercher l’info.
Désormais, je la vois chaque fois que je passe dans le garage.
Commentaires forum ferme