ESP32 et Arduino

Capteur de niveau de cuve d'eau avec ESP32 et MQTT : pourcentage de remplissage et alertes de niveau bas

Mesurez le niveau d'une cuve avec un capteur à ultrasons étanche, convertissez la distance en litres et en pourcentage sur un ESP32, puis publiez des mesures MQTT retenues et des alertes de niveau bas anti-rebond.

Montage d'un capteur de niveau de cuve d'eau ESP32 avec une sonde à ultrasons sur le couvercle envoyant le taux de remplissage via MQTT
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

Un capteur de niveau de cuve d'eau à base d'ESP32 est l'un des premiers projets IoT les plus utiles : il répond à la question « combien d'eau reste-t-il ? » depuis n'importe où et vous prévient avant qu'une pompe ne tourne à sec. Dans ce tutoriel, vous allez câbler un capteur à ultrasons étanche, convertir sa mesure de distance en pourcentage de remplissage et en litres, puis publier ces deux valeurs sur un broker MQTT, accompagnées d'une alerte de niveau bas anti-rebond.

Principe de la mesure de niveau par ultrasons

Un capteur à ultrasons fixé sur le couvercle de la cuve envoie une brève impulsion sonore vers le bas et mesure le temps que met l'écho à revenir depuis la surface de l'eau. La distance correspond à la moitié du temps d'aller-retour multipliée par la vitesse du son. Le niveau d'eau est alors simplement la profondeur de la cuve moins cette distance.

        sensor (lid)
   ---------[S]---------   <- distance d measured by the sensor
   |         |         |
   |         v         |
   |~~~~~~~~~~~~~~~~~~~|   <- water surface
   |                   |   level = tank_depth - (d - mount_offset)
   |                   |
   ---------------------   <- tank bottom

La mesure par ultrasons se fait sans contact : rien ne trempe dans l'eau, et elle fonctionne avec tout liquide non moussant. Ses points faibles sont une portée minimale (une « zone aveugle » près du capteur), la condensation sur le transducteur et les échos parasites renvoyés par les parois ou les tuyaux dans les cuves étroites. Ces trois problèmes se maîtrisent avec un bon positionnement et un filtrage adapté.

Liste du matériel

Composant Rôle
ESP32 DevKit (toute variante avec Wi-Fi) Lit le capteur et publie via MQTT
Capteur à ultrasons étanche JSN-SR04T ou AJ-SR04M Mesure la distance jusqu'à la surface de l'eau
Résistances de 1 kΩ et 2 kΩ Pont diviseur pour le signal echo en 5 V
Alimentation 5 V Alimente le capteur et l'ESP32 via la broche 5V
Un court tronçon de tube PVC (facultatif) Tube de tranquillisation qui réduit les échos et les vaguelettes

Le HC-SR04, moins cher, convient pour un essai sur table, mais il n'est pas étanche et se corrode rapidement dans l'air humide au-dessus d'une cuve. Une sonde scellée montée sur câble est le bon choix pour une installation réelle.

Câblage du capteur sur l'ESP32

Broche JSN-SR04T Broche ESP32 Remarques
5V 5V (VIN) Le capteur a besoin de 5 V pour une portée fiable
GND GND Masse commune
Trig GPIO 5 Une impulsion de déclenchement en 3,3 V est acceptée
Echo GPIO 18 via le pont diviseur 1 kΩ en série, 2 kΩ vers GND : environ 3,3 V sur la broche

Ne branchez pas la broche echo directement : les GPIO de l'ESP32 ne tolèrent pas le 5 V. Le pont diviseur se monte en quelques secondes et vous épargne une carte grillée.

Étalonner votre cuve

Avant d'écrire le code, mesurez trois valeurs au mètre ruban :

  1. Décalage de montage : distance entre la face du capteur et la ligne d'eau « pleine ». Gardez-la supérieure à la zone aveugle du capteur, généralement 20 à 25 cm pour le JSN-SR04T.
  2. Profondeur utile : distance entre la ligne « pleine » et la sortie, ou le niveau le plus bas qui vous intéresse.
  3. Capacité en litres à la ligne « pleine ». Pour un cylindre vertical, le nombre de litres est égal à la surface de section en décimètres carrés multipliée par la profondeur en décimètres ; pour une cuve de forme irrégulière, utilisez la valeur du fabricant.

Le pourcentage de remplissage vaut alors (usable_depth - (distance - mount_offset)) / usable_depth * 100, borné entre 0 et 100.

Le sketch ESP32 pour le niveau de cuve d'eau

Le sketch effectue sept mesures, retient la médiane pour éliminer les échos parasites, calcule le niveau, le pourcentage et les litres, puis publie un message JSON toutes les 30 secondes. Installez d'abord PubSubClient depuis le gestionnaire de bibliothèques.

Topic Retenu Exemple de payload
connectmq/demo/tank-91c2/level oui {"distance_cm":63.4,"level_cm":96.6,"percent":64.4,"litres":644}
connectmq/demo/tank-91c2/alert oui LOW ou OK
connectmq/demo/tank-91c2/status oui online / offline (Last Will)

Remplacez tank-91c2 par votre propre identifiant aléatoire : le sketch se connecte au serveur MQTT public gratuit broker.connectmq.com, partagé et lisible par tous.

#include <WiFi.h>
#include <PubSubClient.h>

const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST     = "broker.connectmq.com";
const uint16_t MQTT_PORT  = 1883;
const char* CLIENT_ID     = "tank-91c2-esp32";
const char* TOPIC_LEVEL   = "connectmq/demo/tank-91c2/level";
const char* TOPIC_ALERT   = "connectmq/demo/tank-91c2/alert";
const char* TOPIC_STATUS  = "connectmq/demo/tank-91c2/status";

const int TRIG_PIN = 5;
const int ECHO_PIN = 18;

// Étalonnage de la cuve (centimètres / litres)
const float MOUNT_OFFSET_CM = 25.0;   // face du capteur jusqu'à la ligne pleine
const float USABLE_DEPTH_CM = 150.0;  // ligne pleine jusqu'à la ligne vide
const float CAPACITY_L      = 1000.0;

// Hystérésis de l'alerte : alarme sous 20 %, levée au-dessus de 25 %
const float LOW_ON_PCT  = 20.0;
const float LOW_OFF_PCT = 25.0;

const uint32_t PUBLISH_INTERVAL_MS = 30000;
const float SOUND_CM_PER_US = 0.0343;  // à environ 20 degrés C

WiFiClient net;
PubSubClient mqtt(net);
uint32_t lastPublish = 0;
bool lowAlarm = false;
bool alarmPublished = false;

float readDistanceOnce() {
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(5);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(20);              // le JSN-SR04T préfère un déclenchement plus long
  digitalWrite(TRIG_PIN, LOW);
  unsigned long us = pulseIn(ECHO_PIN, HIGH, 30000);  // délai d'attente de 30 ms
  if (us == 0) return NAN;
  return us * SOUND_CM_PER_US / 2.0;
}

float readDistanceMedian() {
  const int N = 7;
  float v[N];
  int n = 0;
  for (int i = 0; i < N; i++) {
    float d = readDistanceOnce();
    if (!isnan(d)) v[n++] = d;
    delay(60);                        // laisser les échos s'éteindre
  }
  if (n < 3) return NAN;
  for (int i = 1; i < n; i++) {       // tri par insertion
    float key = v[i];
    int j = i - 1;
    while (j >= 0 && v[j] > key) { v[j + 1] = v[j]; j--; }
    v[j + 1] = key;
  }
  return v[n / 2];
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) delay(500);
}

void connectMqtt() {
  while (!mqtt.connected()) {
    if (mqtt.connect(CLIENT_ID, TOPIC_STATUS, 1, true, "offline")) {
      mqtt.publish(TOPIC_STATUS, "online", true);
      alarmPublished = false;         // republier l'état de l'alerte après reconnexion
    } else {
      delay(5000);
    }
  }
}

void publishLevel() {
  float d = readDistanceMedian();
  if (isnan(d)) {
    mqtt.publish(TOPIC_LEVEL, "{\"error\":\"no_echo\"}", true);
    return;
  }
  float level = USABLE_DEPTH_CM - (d - MOUNT_OFFSET_CM);
  level = constrain(level, 0.0f, USABLE_DEPTH_CM);
  float pct = level / USABLE_DEPTH_CM * 100.0;
  float litres = CAPACITY_L * pct / 100.0;

  char json[160];
  snprintf(json, sizeof(json),
           "{\"distance_cm\":%.1f,\"level_cm\":%.1f,\"percent\":%.1f,\"litres\":%.0f,\"rssi\":%d}",
           d, level, pct, litres, WiFi.RSSI());
  mqtt.publish(TOPIC_LEVEL, json, true);
  Serial.println(json);

  bool prev = lowAlarm;
  if (!lowAlarm && pct < LOW_ON_PCT) lowAlarm = true;
  if (lowAlarm && pct > LOW_OFF_PCT) lowAlarm = false;
  if (lowAlarm != prev || !alarmPublished) {
    mqtt.publish(TOPIC_ALERT, lowAlarm ? "LOW" : "OK", true);
    alarmPublished = true;
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  connectWifi();
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  connectMqtt();
  publishLevel();
  lastPublish = millis();
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) connectWifi();
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();
  if (millis() - lastPublish >= PUBLISH_INTERVAL_MS) {
    lastPublish = millis();
    publishLevel();
  }
}

Suivez les mesures depuis votre ordinateur :

mosquitto_sub -h broker.connectmq.com -p 1883 -i tank-91c2-viewer -t "connectmq/demo/tank-91c2/#" -v

Comme les topics de niveau, d'alerte et d'état sont retenus, un tableau de bord sur téléphone ou le contrôleur de votre maison connectée (Home Assistant, Jeedom et Domoticz savent tous communiquer en MQTT) qui se connecte à n'importe quel moment affiche immédiatement l'état actuel, au lieu d'attendre jusqu'à 30 secondes. Si le flag retained et le Last Will sont nouveaux pour vous, l'article ESP32 MQTT Last Will explique les deux.

Pourquoi l'alerte utilise une hystérésis

La surface de l'eau ondule, surtout lorsqu'une pompe remplit ou puise. Avec un seuil unique à 20 %, une mesure qui oscille entre 19,8 et 20,3 ferait basculer l'alerte à chaque cycle et inonderait tous les abonnés de messages. Deux seuils (activation sous 20, désactivation au-dessus de 25) créent une zone morte qui absorbe ce bruit. Le sketch ne publie en outre l'alerte que lorsque l'état change, plus une fois après chaque reconnexion, ce qui limite le trafic et garde la valeur retenue correcte.

Vous pouvez ajouter un second niveau de contrôle sans toucher au firmware. Dans le portail ConnectMQ, des règles d'alerte à seuil surveillent un topic et ajoutent une entrée dans le journal des alertes lorsqu'une valeur franchit votre limite, ce qui constitue une vérification indépendante bien pratique de la logique embarquée.

Conseils de précision pour un capteur de niveau de cuve d'eau sur ESP32

  1. Montez le capteur parfaitement à la verticale et loin de l'arrivée d'eau, afin que l'eau qui tombe et les vaguelettes ne perturbent pas le faisceau.
  2. Utilisez un tube de tranquillisation dans les cuves étroites ou agitées : un tube PVC allant du couvercle au fond, percé d'un petit trou près de la base, offre une surface calme et un trajet d'écho propre.
  3. Respectez la zone aveugle. Si l'eau peut monter dans les 20 à 25 premiers centimètres, surélevez le capteur avec une courte rehausse en tube.
  4. Compensez la température si votre cuve se trouve dans un grenier surchauffé ou un abri glacial. La vitesse du son augmente avec la température de l'air, d'environ 0,6 m/s par degré Celsius ; un DS18B20 placé à côté du capteur permet de corriger cet effet.
  5. Surveillez la condensation. Des gouttelettes sur le transducteur provoquent des mesures « sans écho ». Un petit trou d'aération dans le couvercle aide.
  6. Validez avec une mesure manuelle à la jauge à plusieurs niveaux et ajustez MOUNT_OFFSET_CM jusqu'à ce que les valeurs concordent.

Cuves alimentées sur batterie

Les cuves isolées n'ont souvent pas accès au secteur. Le sketch ci-dessus garde le Wi-Fi et MQTT connectés en permanence, ce qui vide rapidement une batterie. Pour un nœud solaire ou sur batterie, restructurez-le : réveil, mesure, connexion, publication du niveau et de l'alerte retenus, puis deep sleep pendant 10 ou 15 minutes. Les messages retenus comptent encore davantage dans cette conception, car l'appareil est hors ligne la plupart du temps. Le guide ESP32 deep sleep MQTT présente le schéma complet, et vous pouvez alimenter le capteur à travers un MOSFET pour qu'il ne consomme rien pendant que l'ESP32 dort.

Passer en production

Le serveur MQTT public gratuit broker.connectmq.com est parfait pour le développement et les prototypes, mais tout ce qui s'y trouve est lisible par quiconque devine votre topic. Avant d'installer le capteur de façon permanente :

  • Créez un broker privé et copiez depuis le portail le nom d'utilisateur, le mot de passe et le client ID générés.
  • Passez à WiFiClientSecure, au port 8883 et au certificat d'autorité ISRG Root X1.
  • Connectez-vous avec mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", TOPIC_STATUS, 1, true, "offline").

Exploiter votre propre broker pour quelques cuves vaut rarement l'effort de maintenance, c'est pourquoi un broker MQTT managé est le choix habituel. Si vous souhaitez d'abord comparer les options d'hébergement, lisez l'article sur l'approche du broker MQTT dans le cloud ; pour des payloads structurés, le guide ArduinoJson MQTT est une alternative plus propre à snprintf. D'autres projets de capteurs sont réunis dans le tutoriel IoT ESP32.

Démarrer avec ConnectMQ

Votre surveillance de cuve mérite un point de connexion privé et chiffré. ConnectMQ réunit dans un seul portail le TLS sur le port 8883, des identifiants générés, un explorateur de topics en direct et des règles d'alerte à seuil. Créez un broker Serverless gratuit sur app.connectmq.com, sans carte bancaire, et modifiez trois lignes du sketch.

Questions fréquentes

Quel capteur à ultrasons choisir pour une cuve d'eau ?

Une sonde scellée et étanche comme le JSN-SR04T ou l'AJ-SR04M. Le HC-SR04 convient pour des essais sur table mais ne résiste pas longtemps à l'humidité au-dessus d'une cuve.

Pourquoi mon capteur indique-t-il une cuve pleine alors qu'elle ne l'est pas ?

L'eau ou un obstacle se trouve dans la zone aveugle, ou l'écho rebondit sur un tuyau ou une paroi. Surélevez le capteur, orientez-le vers une zone dégagée ou ajoutez un tube de tranquillisation.

À quelle fréquence l'ESP32 doit-il publier le niveau de la cuve ?

Toutes les 30 à 60 secondes suffisent pour la plupart des cuves domestiques ; les niveaux varient lentement. Sur batterie, toutes les 10 à 15 minutes avec deep sleep est un bon compromis.

Puis-je utiliser ce montage pour du gazole ou d'autres liquides ?

La mesure par ultrasons fonctionne sur la plupart des liquides non moussants, mais les cuves de carburant sont soumises à des règles de sécurité concernant l'électronique en zone dangereuse. Vérifiez la réglementation et utilisez du matériel certifié lorsque c'est exigé.

Articles liés

Exemple MQTT ESP-IDF : construire un client esp-mqtt à partir de zéro

Construisez un projet ESP-IDF à partir d'un dossier vide avec le composant esp-mqtt : configuration du Wi-Fi en mode station, gestionnaire d'événements MQTT, publication et abonnement, propriétés MQTT 5 et TLS avec le bundle de certificats.

9 min de lecture

Horodatage NTP ESP32 MQTT : ajouter une heure précise à chaque message

Synchronisez l'horloge de l'ESP32 avec NTP, formatez des horodatages ISO 8601 et epoch en millisecondes, et joignez-les aux payloads MQTT pour que les mesures conservent leur véritable heure de capture même lorsqu'elles sont publiées en retard.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.