In diesem Beitrag geht es um einen Füllstandsanzeige für eine Zisterne, Brunnen oder einen Wassertank. Sicher gibt es noch viele weitere Anwendungsgebiete.
Das besondere an diesem Projekt ist, man kann es überall einsetzten wo kein WLAN / Internet zur Verfügung steht. Wie zum Beispiel abgelegene Gartenanlagen oder ähnliches.
Es werden hier 2 ESP8266 Boards benötigt, dass eine Board fungiert als Sender und das andere Board als Empfänger. Die Ausgabe wird über ein Nextion Display 3.5 Zoll realisiert. Als Übertragungprotokoll wird UPD verwendet.
Ich konnte hier Reichweiten von 60-100m erreichen. Es ist hier vom Gelände abhängig wie weite das WLAN reicht. Im Bereich von 90-100m kommt es jedoch schon zu Fehlern bei der Übertragung (Aussetzer)
![]() |
![]() |
Für das Projekt habe ich mir zwei Platinen anfertigen lassen.
![]() |
![]() ![]() ![]() |
![]() |
![]() ![]() |
Das Empfängerboard wird normal mit Stromversorgt. Das Senderboard kann hier mit einem Akku betrieben werden. Beim Lolin D1 mini Pro v2 ist die Ladeelektronik mit auf der Platine. Wenn hier eine Spannungquelle an den USB Port angeschlossen wird, wird der Akku geladen.
Alternativ kann auch der D1 mini Pro v1 verwendet werden. Hier muss dann das Batterie Schield mit genutzt werden.
Was wird für das Projekt benötigt.
1. Installation ESP8266 in der Arduino IDE
Um den EPS8266 in der Arduino IDE verwenden zu können, muss dieser erst installiert werden. Wie das geht habe ich in diesem Blogbeitrag schon einmal erklärt.
Welche Bauteile werden benötigt.
1x Nextion 3.5″ Discovery Touch Display 480×320 HMI – NX4832F035 - https://ap-url.de/nextion35d
1x Zisterne 4.0 Platinen Set - https://arduino-projekte.info/produkt/zisterne-4-0-platine/
1x Ultraschallentfernungsmesser AJ-SR04M / JSN-SR04T - https://ap-url.de/jsnsr04t
2x Externe Antenne für D1 mini Pro Modelle - https://ap-url.de/extantenne
je nach Wahl einen D1 für ich Empfehle
2x Wemos Lolin D1 mini Pro V2.0 - https://ap-url.de/d1miniprov2
Funktionsweise
Der Sender sendet 3 Datenpacket. 1. die Batteriespannung, 2. die Nummer der letzten Messung und 3. die Messdaten vom Ultraschallsensor.
Der Empfänger empfängt die Daten und teilt sie auf zur Weitergabe an das Nextion Display.
Wenn die Externe Antenne verwendet werden soll muss der Widerstand umgelötet werden oder einfach einen Lötklecks über die Pads ziehen.
![]() |
![]() |
Sender
Sketch Um die Batteriefunktion (Messung) nutzen zu können müsst Ihr am D1 mini pro v2.0 oder dem D1 Lipo Battery Shield. Je nachdem was Ihr verwendet das Lotpad auf der Rückseite verbinden.
![]() |
![]() |
Für die Deep Sleep Funktion müsst Ihr auf der Rückseite des D1 mini pro v2.0 das Lötpad Sleep verbinden.
#include <esp8266wifi.h> #include <wifiudp.h> #include <eeprom.h> #ifndef STASSID #define STASSID "WIFI_UPD_R" #define STAPSK "123456789+" #endif IPAddress ip(192, 168, 10, 10); IPAddress gateway(192, 168, 10, 1); IPAddress subnet(255, 255, 255, 0); unsigned int sendPort = 9500; int trigger = 2; int echo = 4; long dauer = 0; int counter = 0; unsigned int raw = 0; float batt = 0.0; long wasser = 0; const int sleepSeconds = 2000; WiFiUDP Udp; void setup() { pinMode(A0, INPUT); Serial.begin(115200); pinMode(trigger, OUTPUT); pinMode(echo, INPUT); WiFi.mode(WIFI_STA); WiFi.config(ip, gateway, subnet); WiFi.begin(STASSID, STAPSK); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(500); } Serial.print("Connected! IP address: "); Serial.println(WiFi.localIP()); Serial.printf("UDP server on port %d\n", sendPort); Udp.begin(sendPort); } void batt_status() { raw = analogRead(A0); batt = raw / 1023.0; batt = batt * 4.2; } void zeit() { counter++; EEPROM.write(1, counter); Serial.println(counter); if (counter >= 21) { counter = 0; EEPROM.write(1, counter); EEPROM.commit(); EEPROM.end(); ESP.reset(); } } void loop() { EEPROM.begin(512); counter = EEPROM.read(1); batt_status(); zeit(); digitalWrite(trigger, LOW); delay(5); digitalWrite(trigger, HIGH); delay(10); digitalWrite(trigger, LOW); dauer = pulseIn(echo, HIGH); wasser = (dauer / 2) / 29.1; for (int i=0; i <= 3; i++){ Udp.beginPacket(gateway, sendPort); Udp.write("Batterie:"); Udp.print(batt); Udp.write(";count:"); Udp.print(counter); Udp.write(";wasser:"); Udp.print(wasser); Udp.endPacket(); delay(1000); } EEPROM.commit(); EEPROM.end(); delay(3000); ESP.deepSleep(sleepSeconds * 1000000); }
Funktionsweise
Als erstes werden die Librarys (Bibliotheken) inkludiert.
#include <ESP8266WiFi.h> #include <WiFiUdp.h> #include <EEPROM.h>
WLAN Daten um auf den Empfänger zugriff zu bekommen. IP Adressen und festlegen auf welchen UDP Port gesendet wird.
#ifndef STASSID #define STASSID "WIFI_UPD_R" #define STAPSK "123456789+" #endif IPAddress ip(192, 168, 10, 10); IPAddress gateway(192, 168, 10, 1); IPAddress subnet(255, 255, 255, 0); unsigned int sendPort = 9500;
Definieren der Signale und Variablen.
// Ultraschallsensor int trigger = 2; int echo = 4; long dauer = 0; // Zähler für die Messreihe int counter = 0; // Variablen für die Batterie und dem Wasserstand unsigned int raw = 0; float batt = 0.0; long wasser = 0; // Zeit für den DeepSleep in Sekunden const int sleepSeconds = 2000; WiFiUDP Udp;
Im Setupteil brauch ich glaub ich nicht viel zu sagen. Hier werden die Ausgangspin, Eingangspins und das WLAN gestartet.
void setup() { pinMode(A0, INPUT); Serial.begin(115200); pinMode(trigger, OUTPUT); pinMode(echo, INPUT); WiFi.mode(WIFI_STA); WiFi.config(ip, gateway, subnet); WiFi.begin(STASSID, STAPSK); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(500); } Serial.print("Connected! IP address: "); Serial.println(WiFi.localIP()); Serial.printf("UDP server on port %d\n", sendPort); Udp.begin(sendPort); }
Routine für das Batterie messen, der Wert wird in "batt" gespeichert.
void batt_status() { raw = analogRead(A0); batt = raw / 1023.0; batt = batt * 4.2; }
Routine für den Zähler. Dieser dient nur dazu um zu pürfen ob die Messung funktioniert hat. Hier kann man sicher auch eine andere Möglichekeit wählen. Der Zähler zählt bis 20 und fängt dann wieder von vorne an. Der Zähler wird im EEPROM gespeichert, damit er nach dem DeepSleep / Ausschalten wieder zur verfügung steht. Der ESP wird nach den 20 Messwerten 1x Resetet.
void zeit() { counter++; EEPROM.write(1, counter); if (counter >= 21) { counter = 0; EEPROM.write(1, counter); EEPROM.commit(); EEPROM.end(); ESP.reset(); } }
Routine für den Zähler. Dieser dient nur dazu um zu pürfen ob die Messung funktioniert hat. Hier kann man sicher auch eine andere Möglichekeit wählen. Der Zähler zählt bis 20 und fängt dann wieder von vorne an.
void loop() { // EEPROM Starten EEPROM.begin(512); // Zähler aus dem EEPROM lesen counter = EEPROM.read(1); batt_status(); zeit(); // Ultraschallmessung digitalWrite(trigger, LOW); delay(5); digitalWrite(trigger, HIGH); delay(10); digitalWrite(trigger, LOW); dauer = pulseIn(echo, HIGH); wasser = (dauer / 2) / 29.1; // Daten werden gesendet, zur sicherheit 3x im ein Sekundenabstand for (int i=0; i <= 3; i++){ Udp.beginPacket(gateway, sendPort); Udp.write("Batterie:"); Udp.print(batt); Udp.write(";count:"); Udp.print(counter); Udp.write(";wasser:"); Udp.print(wasser); Udp.endPacket(); delay(1000); } // EEPROM schließen EEPROM.commit(); EEPROM.end(); delay(3000); // deepSleep ausführen ESP.deepSleep(sleepSeconds * 1000000); }
Empfänger Sketch
#include <esp8266wifi.h> #include <wifiudp.h> #include <softwareserial.h> #ifndef APSSID #define APSSID "WIFI_UPD_R" #define APPSK "123456789+" #endif SoftwareSerial nextion(12, 13); // RX, TX IPAddress ip(192, 168, 10, 1); IPAddress gateway(192, 168, 10, 10); IPAddress subnet(255, 255, 255, 0); unsigned int recivePort = 9500; // buffers für Empfangene Daten char packetBuffer[UDP_TX_PACKET_MAX_SIZE + 1]; //buffer to hold incoming packet, char * values[3]; char * buf[3]; float batt; unsigned int w_stand_txt; unsigned int w_stand; WiFiUDP Udp; void setup() { nextion.begin(9600); Serial.begin(115200); Serial.println(); Serial.print("Configuring access point..."); WiFi.softAPConfig(ip, gateway, subnet); WiFi.softAP(APSSID, APPSK); delay(500); Serial.print("AP IP address: "); Serial.println(WiFi.softAPIP()); Serial.printf("UDP server on port %d\n", recivePort); Udp.begin(recivePort); nextion.print("p0.pic="); nextion.print(0); sendTOdisplay(); } void sendTOdisplay() { nextion.write(0xFF); nextion.write(0xFF); nextion.write(0xFF); } void loop() { String cmd; cmd += "\""; // sind Daten vorhanden, lese ein packet int packetSize = Udp.parsePacket(); if (packetSize) { // lese das packet in packetBufffer int n = Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); packetBuffer[n] = 0; //Empfangene Daten zerlegen char* ptr = strtok(packetBuffer, ";"); for (byte i = 0; i < sizeof(values) / sizeof(values[0]); i++) { values[i] = ptr; ptr = strtok(NULL, ";"); } buf[0] = strtok(values[0], ":"); batt = atof(strtok(NULL, ":")); //Batterie Wert buf[1] = strtok(values[1], ":"); buf[2] = strtok(NULL, ":"); // Counter buf[3] = strtok(values[2], ":"); w_stand_txt = atof(strtok(NULL, ":")); // Messwert Ultraschallsensor //Daten an das Display senden nextion.print("batterie.txt=" + cmd + batt); nextion.print(" V" + cmd); sendTOdisplay(); nextion.print("zeit.txt=" + cmd + buf[2] + cmd); sendTOdisplay(); nextion.print("w_stand.txt=" + cmd + w_stand_txt + cmd); sendTOdisplay(); w_stand = w_stand_txt * 0.1666667; // Berechnung für die Seule w_stand = 100 - w_stand; // Berechnung für die Seule nextion.print("wstand.val="); nextion.print(w_stand); sendTOdisplay(); if (batt >= 3.8 && batt <= 4.1) { nextion.print("p0.pic="); nextion.print(4); sendTOdisplay(); } if (batt >= 3.5 && batt <= 3.8) { nextion.print("p0.pic="); nextion.print(3); sendTOdisplay(); } if (batt >= 3.3 && batt <= 3.5) { nextion.print("p0.pic="); nextion.print(2); sendTOdisplay(); } if (batt >= 0 && batt <= 3.3) { nextion.print("p0.pic="); nextion.print(1); sendTOdisplay(); } delay(100); } }
Funktionsweise
Als erstes werden die Librarys (Bibliotheken) inkludiert.
#include <esp8266wifi.h> #include <wifiudp.h> #include <softwareserial.h>
WLAN Daten für den eigenen Access Point. IP Adressen und festlegen auf welchen UDP Port empfangen wird. Der SoftwareSerial legt fest wo die RX/TX Leitungen für das Display liegen.
#ifndef APSSID #define APSSID "WIFI_UPD_R" #define APPSK "123456789+" #endif SoftwareSerial nextion(12, 13); // RX, TX IPAddress ip(192, 168, 10, 1); IPAddress gateway(192, 168, 10, 10); IPAddress subnet(255, 255, 255, 0); unsigned int recivePort = 9500;
Definieren der Variablen.
// buffers für Empfangene Daten char packetBuffer[UDP_TX_PACKET_MAX_SIZE + 1]; //buffer to hold incoming packet, char * values[3]; char * buf[3]; float batt; unsigned int w_stand_txt; unsigned int w_stand; WiFiUDP Udp;
Serielle Schnittstellen, WLAN und das UDP werden gestartet.
void setup() { nextion.begin(9600); Serial.begin(115200); Serial.println(); Serial.print("Configuring access point..."); WiFi.softAPConfig(ip, gateway, subnet); WiFi.softAP(APSSID, APPSK); delay(500); Serial.print("AP IP address: "); Serial.println(WiFi.softAPIP()); Serial.printf("UDP server on port %d\n", recivePort); Udp.begin(recivePort); // Nextion Display Batterie Bild auf leer setzen. nextion.print("p0.pic="); nextion.print(0); sendTOdisplay(); }
Im LOOP Teil werden hier die Datenpakete empfangen und in 3 Werte zerlegt.
// sind Daten vorhanden, lese ein packet int packetSize = Udp.parsePacket(); if (packetSize) { // lese das packet in packetBufffer int n = Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); packetBuffer[n] = 0; //Empfangene Daten zerlegen char* ptr = strtok(packetBuffer, ";"); for (byte i = 0; i < sizeof(values) / sizeof(values[0]); i++) { values[i] = ptr; ptr = strtok(NULL, ";"); } buf[0] = strtok(values[0], ":"); batt = atof(strtok(NULL, ":")); //Batterie Wert buf[1] = strtok(values[1], ":"); buf[2] = strtok(NULL, ":"); // Counter buf[3] = strtok(values[2], ":"); w_stand_txt = atof(strtok(NULL, ":")); // Messwert Ultraschallsensor
Im Anschluss werden die Werte an das Display übertragen.
//Daten an das Display senden nextion.print("batterie.txt=" + cmd + batt); nextion.print(" V" + cmd); sendTOdisplay(); nextion.print("zeit.txt=" + cmd + buf[2] + cmd); sendTOdisplay(); nextion.print("w_stand.txt=" + cmd + w_stand_txt + cmd); sendTOdisplay(); w_stand = w_stand_txt * 0.1666667; // Berechnung für die Seule w_stand = 100 - w_stand; // Berechnung für die Seule nextion.print("wstand.val="); nextion.print(w_stand); sendTOdisplay(); if (batt >= 3.8 && batt <= 4.1) { nextion.print("p0.pic="); nextion.print(4); sendTOdisplay(); } if (batt >= 3.5 && batt <= 3.8) { nextion.print("p0.pic="); nextion.print(3); sendTOdisplay(); } if (batt >= 3.3 && batt <= 3.5) { nextion.print("p0.pic="); nextion.print(2); sendTOdisplay(); } if (batt >= 0 && batt <= 3.3) { nextion.print("p0.pic="); nextion.print(1); sendTOdisplay(); } delay(100); }
Wie wird das Programm auf das Nextion Display geladen?
Ihr packt die brunnen.tft Datei auf eine Micro SD Karte und steckt diese in das Display. Dann das Display mit Spannung versorgen und der Upload wird gestartet. Nach dem Upload das Display wieder von der Spannung trennen und nach dem nächsten Verbinden ist das Programm aktiv.
Hier könnt Ihr euch den Sketch runterladen.
Download Sketch + Display Daten (230kb .zip)
Wenn Ihr hier mit einem Akku arbeitet dann nach dem ausfstecken noch einmal den Reset Knopft drücken.
Sicherlich kann die ein oder andere Stelle noch verbessert werden. Ich hoffe es ist alles soweit verständich. Wenn Ihr fragen habe schickt mir einfach ein Mail. Ich versuche Sie zeitnah zu beantworden.
Folgende Bauteile wurden verwendet:
1x Nextion 3.5″ Discovery Touch Display 480×320 HMI – NX4832F035 - https://ap-url.de/nextion35d
1x Zisterne 4.0 Platinen Set - https://arduino-projekte.info/produkt/zisterne-4-0-platine/
1x Ultraschallentfernungsmesser AJ-SR04M / JSN-SR04T - https://ap-url.de/jsnsr04t
2x Externe Antenne für D1 mini Pro Modelle - https://ap-url.de/extantenne
je nach Wahl einen D1 für ich Empfehle
2x Wemos Lolin D1 mini Pro V2.0 - https://ap-url.de/d1miniprov2
10 Kommentare
Hallo Hellmut,
vielen dank für die Info, ich werde mal nach dem Sensor schauen und eventeull im Shop aufnehmen.
Gruß
Tobias
Danke für die ausführliche Darstellung. Ich bin gerade dabei, die Anordnung für die Tankanzeige in meinem Wohnmobil zu adaptieren. Leider ergibt sich gerade das Problem, dass ich die Binärdatei ‘brunnen.tft’ nicht auf das Nextion-Display, welches ich hier im Shop gekauft habe, flashen kann. Bei dem Versuch erscheint nur die folgende Fehlermeldung auf dem Display:
Model does not match:
Device Model:
NX4832F035_011R
Auf dem Display ist als Modellbezeichnung NX4832F035_011 angegeben (d. h. ohne das abschließende ‘R’).
Da ich keinen Windows-PC besitze, auf dem ich die Sache mit der Nextion-Software ggf. selbst beheben könnte (auf meinem M1-Mac ist mit der Software nichts zu machen), wäre ich über einen Tipp sehr froh, der mir hilft, das Problem zu lösen.
======
Alles zurück:
Ich konnte die Nextion-Software nun doch auf einem alten Mac zum Laufen bringen (mit Virtual Box). Damit habe ich den Typ des Displays umstellen können. Bei dieser Gelegenheit habe ich auch die Orientierung der Anzeige um 180° gedreht (das war nötig, weil das Display in ein Handgehäuse eingebaut wird und der Anschluss des Displays mit dem Batteriekasten kollidiert). Nun ist alles gut.
Übrigens: Als Sensor verwende ich einen kapazitiven Wassersensor von Votronic. Der hat eine eigene
C-U-Umsetzung und liefert eine Spannung zwischen 0 und ca. 2,3 V, die proportional zum Wasserstand ist. Da entfallen die Zeitmessungen und es genügt, die Spannung an einem Analogeingang abzufragen.
Hallo,
man kann alle Nextion Displays nehmen. Also wenn das Nextion 3,5 Basic NX4832T035 nicht verfügbar ist, gehen auch die Nextion 3,5 Discovery NX4832F035 und das Nextion 3,5 Enhanced NX4832K035. Man muss dann nur in der Nextion Software den Display Typ ändern. Das geht oben Rechts bei Device.
Wenn es da Fragen gibt einfach melden.
Gruß
Tobias
Hallo,
Was nimmt man den für ein Display wenn das NX4832T035 Ausverkauft ist? Geht da auch was anderes?
Ich möchte das Projekt gern nachbauen und habe aber von Arduino noch nicht so die Erfahrung.
Danke
Hallo Winfried,
hast du dir das Video hier im Blogbeitrag mal angesehen, da wird erklärt wie die Daten zwischen den Modulen gesendet wird. https://www.youtube.com/watch?v=1p2S4k-Otdg
Gruß
Tobias