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 :
- 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.
- Profondeur utile : distance entre la ligne « pleine » et la sortie, ou le niveau le plus bas qui vous intéresse.
- 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
- 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.
- 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.
- 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.
- 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.
- Surveillez la condensation. Des gouttelettes sur le transducteur provoquent des mesures « sans écho ». Un petit trou d'aération dans le couvercle aide.
- Validez avec une mesure manuelle à la jauge à plusieurs niveaux et ajustez
MOUNT_OFFSET_CMjusqu'à 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é.



