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.
J’ai envisagé plusieurs solutions :
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 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.
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.
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 !
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, de gauche à droite :
L’ensemble revient quand même à prêt de 110 €.
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
Cette pièce et son utilisation sont décrites ici. Le fichier source est disponible ici.
Le fichier source est disponible ici. Il est conçu pour qu'on puisse y glisser le relai, et que les borniers restent accessibles.
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.
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.
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.
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.
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.
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.
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 :
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
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 :
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
**
**************************************************************************/
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.
Publié le 25 mars 2016