Ein Wassertank-Füllstandssensor mit ESP32 ist eines der nützlichsten ersten IoT-Projekte: Er beantwortet die Frage „Wie viel Wasser ist noch da?“ von überall aus und warnt Sie, bevor eine Pumpe trocken läuft. In diesem Tutorial schließen Sie einen wasserdichten Ultraschallsensor an, rechnen seinen Distanzwert in Prozent Füllgrad und Liter um und veröffentlichen beides zusammen mit einer entprellten Warnung bei niedrigem Pegel an einen MQTT-Broker.
So funktioniert die Füllstandsmessung per Ultraschall
Ein Ultraschallsensor im Tankdeckel sendet einen kurzen Schallimpuls nach unten und misst, wie lange das Echo von der Wasseroberfläche zurück braucht. Die Distanz ergibt sich aus der halben Laufzeit multipliziert mit der Schallgeschwindigkeit. Der Wasserstand ist dann einfach die Tanktiefe minus diese Distanz.
sensor (lid)
---------[S]--------- <- distance d measured by the sensor
| | |
| v |
|~~~~~~~~~~~~~~~~~~~| <- water surface
| | level = tank_depth - (d - mount_offset)
| |
--------------------- <- tank bottom
Die Ultraschallmessung arbeitet berührungslos, es befindet sich also nichts im Wasser, und sie funktioniert mit jeder nicht schäumenden Flüssigkeit. Ihre Schwachstellen sind eine Mindestreichweite (eine „Blindzone“ direkt vor dem Sensor), Kondenswasser auf dem Schallwandler und Echos von Tankwänden oder Rohren in schmalen Tanks. Alle drei lassen sich mit guter Platzierung und Filterung beherrschen.
Stückliste
| Bauteil | Zweck |
|---|---|
| ESP32 DevKit (jede Variante mit WLAN) | Liest den Sensor aus und veröffentlicht per MQTT |
| Wasserdichter Ultraschallsensor JSN-SR04T oder AJ-SR04M | Misst die Distanz zur Wasseroberfläche |
| Widerstände 1 kΩ und 2 kΩ | Spannungsteiler für das 5-V-Echosignal |
| 5-V-Netzteil | Versorgt den Sensor und den ESP32 über den 5V-Pin |
| Kurzes Stück PVC-Rohr (optional) | Beruhigungsrohr, das Echos und Wellen reduziert |
Der günstigere HC-SR04 reicht für einen Test auf dem Labortisch, ist aber nicht wasserdicht und korrodiert in der feuchten Luft über einem Tank schnell. Für eine echte Installation ist eine abgedichtete Sonde am Kabel die richtige Wahl.
Den Sensor mit dem ESP32 verdrahten
| JSN-SR04T-Pin | ESP32-Pin | Hinweise |
|---|---|---|
| 5V | 5V (VIN) | Der Sensor benötigt 5 V für eine zuverlässige Reichweite |
| GND | GND | Gemeinsame Masse |
| Trig | GPIO 5 | Ein 3,3-V-Triggerimpuls wird akzeptiert |
| Echo | GPIO 18 über Spannungsteiler | 1 kΩ in Reihe, 2 kΩ gegen GND: etwa 3,3 V am Pin |
Schließen Sie den Echo-Pin nicht direkt an: Die GPIOs des ESP32 sind nicht 5-V-tolerant. Der Spannungsteiler ist in wenigen Sekunden aufgebaut und rettet ein Board.
Ihren Tank kalibrieren
Bevor Sie Code schreiben, messen Sie mit einem Maßband drei Werte:
- Montageabstand: Distanz von der Sensorfläche bis zur „Voll“-Linie des Wassers. Halten Sie ihn größer als die Blindzone des Sensors, beim JSN-SR04T typischerweise 20 bis 25 cm.
- Nutzbare Tiefe: Distanz von der Voll-Linie bis zum Auslass oder zum niedrigsten Pegel, der für Sie relevant ist.
- Fassungsvermögen in Litern an der Voll-Linie. Bei einem stehenden Zylinder entsprechen die Liter der Querschnittsfläche in Quadratdezimetern multipliziert mit der Tiefe in Dezimetern; bei unregelmäßigen Tanks verwenden Sie die Angabe des Herstellers.
Der Füllgrad in Prozent ist dann (usable_depth - (distance - mount_offset)) / usable_depth * 100, begrenzt auf 0 bis 100.
Der ESP32-Sketch für den Wassertank-Füllstand
Der Sketch nimmt sieben Messungen auf, verwendet den Median, um Streuechos zu verwerfen, berechnet Pegel, Prozent und Liter und veröffentlicht alle 30 Sekunden eine JSON-Nachricht. Installieren Sie zuerst PubSubClient über den Bibliotheksverwalter.
| Topic | Retained | Beispiel-Payload |
|---|---|---|
connectmq/demo/tank-91c2/level |
ja | {"distance_cm":63.4,"level_cm":96.6,"percent":64.4,"litres":644} |
connectmq/demo/tank-91c2/alert |
ja | LOW oder OK |
connectmq/demo/tank-91c2/status |
ja | online / offline (Last Will) |
Ersetzen Sie tank-91c2 durch Ihre eigene zufällige Kennung. Überwachen Sie Tanks in einem Betrieb, etwa Brauch- oder Kühlwasser in einer Produktionshalle, ordnen Sie sie später in den Unified Namespace (UNS) des Standorts ein, dessen Topics der ISA-95-Hierarchie folgen, zum Beispiel acme/werk-muenchen/versorgung/wasser/tank-2/level.
#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;
// Tankkalibrierung (Zentimeter / Liter)
const float MOUNT_OFFSET_CM = 25.0; // Sensorfläche bis Voll-Linie
const float USABLE_DEPTH_CM = 150.0; // Voll-Linie bis Leer-Linie
const float CAPACITY_L = 1000.0;
// Alarm-Hysterese: Alarm unter 20 %, Aufhebung über 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; // bei etwa 20 Grad 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); // JSN-SR04T bevorzugt einen längeren Trigger
digitalWrite(TRIG_PIN, LOW);
unsigned long us = pulseIn(ECHO_PIN, HIGH, 30000); // 30 ms Timeout
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); // Echos abklingen lassen
}
if (n < 3) return NAN;
for (int i = 1; i < n; i++) { // Insertion Sort
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; // Alarmzustand nach Reconnect erneut senden
} 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();
}
}
Verfolgen Sie die Messwerte von Ihrem Rechner aus:
mosquitto_sub -h broker.connectmq.com -p 1883 -i tank-91c2-viewer -t "connectmq/demo/tank-91c2/#" -v
Da die Topics für Pegel, Alarm und Status retained sind, zeigt ein Smartphone-Dashboard, das sich zu einem beliebigen Zeitpunkt verbindet, sofort den aktuellen Zustand an, statt bis zu 30 Sekunden zu warten. Wenn Retained-Flag und Last Will für Sie neu sind, erklärt ESP32 MQTT Last Will beides.
Warum der Alarm eine Hysterese nutzt
Wasseroberflächen kräuseln sich, besonders während eine Pumpe befüllt oder entnimmt. Mit einer einzigen Schwelle bei 20 Prozent würde ein Messwert, der zwischen 19,8 und 20,3 pendelt, den Alarm in jedem Zyklus ein- und ausschalten und alle Subscriber mit Nachrichten überfluten. Zwei Schwellen (ein unter 20, aus über 25) schaffen eine Totzone, die dieses Rauschen schluckt. Der Sketch veröffentlicht den Alarm außerdem nur bei einer Zustandsänderung sowie einmal nach jedem Reconnect. Das hält den Datenverkehr gering und den Retained-Wert korrekt.
Eine zweite Ebene können Sie ganz ohne Firmware-Änderung ergänzen. Im ConnectMQ-Portal überwachen Schwellwert-Alarmregeln ein Topic und schreiben einen Eintrag ins Alarmprotokoll, sobald ein Wert Ihr Limit überschreitet. Das ist praktisch als unabhängige Kontrolle der Gerätelogik.
Tipps für einen genauen Wassertank-Füllstandssensor mit ESP32
- Montieren Sie den Sensor exakt senkrecht und abseits des Zulaufs, damit fallendes Wasser und Wellen nicht in den Schallkegel geraten.
- Verwenden Sie ein Beruhigungsrohr in schmalen oder turbulenten Tanks: Ein PVC-Rohr vom Deckel bis zum Boden mit einem kleinen Loch nahe dem Boden sorgt für eine ruhige Oberfläche und einen sauberen Echopfad.
- Beachten Sie die Blindzone. Wenn das Wasser bis in die ersten 20 bis 25 cm steigen kann, setzen Sie den Sensor auf ein kurzes Aufsatzrohr.
- Kompensieren Sie die Temperatur, wenn Ihr Tank auf einem heißen Dachboden oder in einem frostigen Schuppen steht. Die Schallgeschwindigkeit steigt mit der Lufttemperatur um rund 0,6 m/s pro Grad Celsius; ein DS18B20 neben dem Sensor ermöglicht die Korrektur.
- Achten Sie auf Kondenswasser. Tropfen auf dem Schallwandler führen zu „No Echo“-Messungen. Ein kleines Lüftungsloch im Deckel hilft.
- Gleichen Sie mit einer manuellen Peilung bei mehreren Füllständen ab und passen Sie
MOUNT_OFFSET_CMan, bis die Werte übereinstimmen.
Batteriebetriebene Tanks
Abgelegene Tanks haben oft keinen Netzanschluss. Der obige Sketch hält WLAN und MQTT dauerhaft verbunden, was Batterien schnell leert. Für einen Solar- oder Batterieknoten bauen Sie ihn um: aufwachen, messen, verbinden, den Retained-Pegel und den Alarm veröffentlichen und dann für 10 oder 15 Minuten in den Deep Sleep wechseln. Retained Messages sind in diesem Aufbau noch wichtiger, weil das Gerät die meiste Zeit offline ist. Der Leitfaden zu ESP32 Deep Sleep mit MQTT zeigt das vollständige Muster, und Sie können den Sensor über einen MOSFET versorgen, sodass er nichts verbraucht, während der ESP32 schläft.
Der Weg in die Produktion
Der öffentliche MQTT-Broker auf broker.connectmq.com ist ideal für Entwicklung und Prototypen, aber alles dort kann jeder lesen, der Ihr Topic errät; für Produktivdaten ist er nicht gedacht. Bevor Sie den Sensor dauerhaft installieren:
- Legen Sie einen privaten Broker an und kopieren Sie den generierten Benutzernamen, das Passwort und die Client-ID aus dem Portal.
- Wechseln Sie zu
WiFiClientSecure, Port 8883 und dem CA-Zertifikat ISRG Root X1. - Verbinden Sie sich mit
mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", TOPIC_STATUS, 1, true, "offline").
Einen eigenen Broker für eine Handvoll Tanks zu betreiben, lohnt den Wartungsaufwand selten. Deshalb ist ein verwalteter MQTT-Broker die übliche Wahl. Wenn Sie zunächst Hosting-Optionen vergleichen möchten, lesen Sie über den Ansatz Cloud-MQTT-Broker, und für strukturierte Payloads ist der ArduinoJson-MQTT-Leitfaden eine sauberere Alternative zu snprintf. Weitere Sensorprojekte finden Sie im ESP32-IoT-Tutorial.
Starten Sie mit ConnectMQ
Ihre Tanküberwachung verdient einen privaten, verschlüsselten Endpunkt. ConnectMQ bietet TLS auf Port 8883, generierte Zugangsdaten, einen Live-Topic-Explorer und Schwellwert-Alarmregeln in einem Portal. Erstellen Sie einen kostenlosen Serverless-Broker unter app.connectmq.com, ganz ohne Kreditkarte, und ändern Sie drei Zeilen im Sketch.
Häufig gestellte Fragen
Welcher Ultraschallsensor eignet sich am besten für einen Wassertank?
Eine abgedichtete, wasserdichte Sonde wie der JSN-SR04T oder der AJ-SR04M. Der HC-SR04 genügt für Tests auf dem Labortisch, übersteht die Feuchtigkeit über einem Tank aber nicht lange.
Warum meldet mein Sensor einen vollen Tank, obwohl er es nicht ist?
Das Wasser oder ein Hindernis befindet sich in der Blindzone, oder das Echo wird von einem Rohr oder einer Wand reflektiert. Setzen Sie den Sensor höher, richten Sie ihn auf einen freien Bereich aus oder ergänzen Sie ein Beruhigungsrohr.
Wie oft sollte der ESP32 den Tankfüllstand veröffentlichen?
Alle 30 bis 60 Sekunden reichen für die meisten Haushaltstanks völlig aus; Füllstände ändern sich langsam. Im Batteriebetrieb ist ein Intervall von 10 bis 15 Minuten mit Deep Sleep ein guter Kompromiss.
Kann ich das auch für Diesel oder andere Flüssigkeiten nutzen?
Die Ultraschallmessung funktioniert bei den meisten nicht schäumenden Flüssigkeiten, für Kraftstofftanks gelten jedoch Sicherheitsvorschriften zu Elektronik in explosionsgefährdeten Bereichen. Prüfen Sie die Vorschriften und setzen Sie wo nötig zertifizierte Geräte ein.



