Einleitung
Der WaterMeV2 – Feuchtigkeitssensor mit ESP8266 und OLED-Display kann die Bodenfeuchtigkeit in Echtzeit überwachen und die Daten direkt per MQTT an dein Smart Home System senden. Dieses Upgrade unseres vorherigen WaterMe Sensors bietet Verbesserungen in der Hardware und Software, eine optimierte Reset-Funktion sowie ein ansprechendes Gehäuse, das du kostenlos auf meinem Cults3D-Account herunterladen kannst (hier klicken).

Vorteile von WaterMev2
- Echtzeitüberwachung der Bodenfeuchtigkeit
- Anbindung an MQTT für Smart Home Integration
- WiFi-Manager für einfache WLAN-Konfiguration
- OLED-Display für lokale Anzeige
- 5-Sekunden-Reset-Taster für eine einfache Neukonfiguration
- Kostenloses 3D-gedrucktes Gehäuse zum Schutz der Hardware
Benötigte Komponenten
Für dieses Projekt benötigst du folgende Komponenten (mit Affiliate-Links):
- ESP8266 NodeMCU – Jetzt kaufen* (bezahlter Link)
- Feuchtigkeitssensor – Jetzt kaufen* (bezahlter Link)
- 0,96 Zoll OLED-Display (I2C) – Jetzt kaufen* (bezahlter Link)
- Taster für Reset-Funktion – Jetzt kaufen* (bezahlter Link)
- Schmelzgewinde – Jetzt kaufen* (bezahlter Link)
Aufbau der Hardware
Der Aufbau ist einfach und erfordert nur wenige Kabelverbindungen:
- ESP8266 mit dem OLED-Display verbinden:
- SDA → D2 (GPIO4)
- SCL → D1 (GPIO5)
- VCC → 3.3V
- GND → GND
- Feuchtigkeitssensor anschließen:
- Signal → A0 (Analog-Pin)
- VCC → 3.3V
- GND → GND
- Taster für den Reset:
- Ein Pin an GPIO0 (D3)
- Der andere Pin an GND
Software – Der Code von WaterMev2
Der folgende Code enthält alle wichtigen Funktionen für den Sensor:
1. Bibliotheken und Variablen
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiManager.h>
#include <ESP8266WebServer.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <SSD1306Wire.h>
#include <LittleFS.h>
#include <ArduinoJson.h>
#define SENSOR_PIN A0
#define RESET_PIN 0
#define RESET_TIME 5000
Hier laden wir alle wichtigen Bibliotheken und definieren die Pins für den Sensor und den Reset-Taster.
2. Reset-Funktion mit 5-Sekunden-Haltezeit
void checkResetButton() {
if (digitalRead(RESET_PIN) == LOW) {
if (!buttonPressed) {
buttonPressStart = millis();
buttonPressed = true;
}
if (millis() - buttonPressStart >= RESET_TIME) {
Serial.println("Reset-Taster 5 Sekunden gehalten! Setze WiFiManager-Einstellungen zurück...");
WiFiManager wifiManager;
wifiManager.resetSettings();
delay(1000);
WiFi.disconnect(true);
delay(1000);
ESP.restart();
}
} else {
buttonPressed = false;
}
}
Diese Funktion sorgt dafür, dass der WiFiManager nur zurückgesetzt wird, wenn der Taster mindestens 5 Sekunden gedrückt wird. Dadurch wird ein versehentliches Zurücksetzen verhindert.
3. Verbindung zum WLAN mit WiFiManager
void setup_wifi() {
WiFiManager wifiManager;
wifiManager.setTimeout(180);
if (!wifiManager.autoConnect("WaterMev2")) {
ESP.restart();
}
}
Der WiFiManager ermöglicht eine einfache Einrichtung des WLANs über einen Access Point.
4. Sensor-Daten auslesen und per MQTT senden
void updateSensorData() {
int sensorValue = analogRead(SENSOR_PIN);
float moisture = map(sensorValue, 1024, 0, 0, 100);
client.publish("WaterMev2/feuchtigkeit", String(moisture).c_str());
}
Hier wird die Feuchtigkeit gemessen und an den MQTT-Server gesendet.
5. Loop-Funktion mit Reset-Check
void loop() {
server.handleClient();
checkResetButton();
updateSensorData();
delay(30000);
}
Die loop()
-Funktion überprüft den Taster und aktualisiert die Sensordaten alle 30 Sekunden.
6. Vollständiger Code
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiManager.h> // WiFiManager Library für AP-Konfiguration
#include <ESP8266WebServer.h>
#include <time.h> // Für configTime()
#include <SSD1306Wire.h> // legacy: #include "SSD1306.h"
#include <LittleFS.h>
#include <ArduinoJson.h>
#define SENSOR_PIN A0 // Sensor an A0 angeschlossen
#define RESET_PIN 0 // GPIO0 (D3 beim ESP8266)
#define RESET_TIME 5000 // Zeit in Millisekunden (5 Sekunden)
unsigned long buttonPressStart = 0;
bool buttonPressed = false;
unsigned long previousMillis = 0; // Speichert den Zeitpunkt des letzten MQTT-Sendevorgangs
const long interval = 30000; // Zeitintervall für das Senden der MQTT-Daten
bool resetActive = false; // verhindert, dass andere Funktionen das Display während Reset stören
char mqtt_server[40] = "";
char mqtt_port[6] = "";
char mqtt_user[40] = "";
char mqtt_pass[40] = "";
char mqtt_topic_prefix[40] = "WaterMe"; // Benutzerdefiniertes Präfix
char ntp_server[40] = "pool.ntp.org"; // Standard NTP Server
WiFiClient espClient;
PubSubClient client(espClient);
ESP8266WebServer server(80);
SSD1306Wire display(0x3c, SDA, SCL);
void saveConfig() {
Serial.println("Speichere Konfiguration in LittleFS...");
File configFile = LittleFS.open("/config.json", "w");
if (!configFile) {
Serial.println("Fehler beim Speichern der Konfiguration!");
return;
}
DynamicJsonDocument doc(128);
doc["mqtt_server"] = mqtt_server;
doc["mqtt_port"] = mqtt_port;
doc["mqtt_user"] = mqtt_user;
doc["mqtt_pass"] = mqtt_pass;
doc["mqtt_topic_prefix"] = mqtt_topic_prefix;
doc["ntp_server"] = ntp_server;
serializeJson(doc, configFile);
configFile.close();
Serial.println("Konfiguration gespeichert.");
}
void loadConfig() {
Serial.println("Lade Konfiguration aus LittleFS...");
File configFile = LittleFS.open("/config.json", "r");
if (!configFile) {
Serial.println("Keine gespeicherte Konfiguration gefunden.");
return;
}
DynamicJsonDocument doc(128);
DeserializationError error = deserializeJson(doc, configFile);
if (error) {
Serial.println("Fehler beim Lesen der Konfigurationsdatei!");
return;
}
strlcpy(mqtt_server, doc["mqtt_server"] | "", sizeof(mqtt_server));
strlcpy(mqtt_port, doc["mqtt_port"] | "", sizeof(mqtt_port));
strlcpy(mqtt_user, doc["mqtt_user"] | "", sizeof(mqtt_user));
strlcpy(mqtt_pass, doc["mqtt_pass"] | "", sizeof(mqtt_pass));
strlcpy(mqtt_topic_prefix, doc["mqtt_topic_prefix"] | "", sizeof(mqtt_topic_prefix));
strlcpy(ntp_server, doc["ntp_server"] | "", sizeof(ntp_server));
Serial.println("Konfiguration geladen.");
}
void setup_wifi() {
Serial.println("Starte WiFiManager...");
uint8_t mac[6];
WiFi.macAddress(mac);
char hostname[32];
snprintf(hostname, sizeof(hostname), "WaterMe_%02X%02X%02X", mac[3], mac[4], mac[5]);
WiFiManager wifiManager;
wifiManager.setTimeout(180); // 3 Minuten warten statt unendlich
//wifiManager.resetSettings();
WiFiManagerParameter custom_mqtt_server("server", "MQTT Server", mqtt_server, 40);
WiFiManagerParameter custom_mqtt_port("port", "MQTT Port", mqtt_port, 6);
WiFiManagerParameter custom_mqtt_topic("topic", "MQTT Topic Prefix", mqtt_topic_prefix, 40);
WiFiManagerParameter custom_ntp_server("ntp_server", "NTP Server", ntp_server, 40);
wifiManager.addParameter(&custom_mqtt_server);
wifiManager.addParameter(&custom_mqtt_port);
wifiManager.addParameter(&custom_mqtt_topic);
wifiManager.addParameter(&custom_ntp_server);
if (!wifiManager.autoConnect(hostname)) {
Serial.println("Fehlgeschlagen! Neustart in 5 Sekunden...");
delay(5000);
ESP.restart();
}
WiFi.hostname(hostname);
strncpy(mqtt_server, custom_mqtt_server.getValue(), sizeof(mqtt_server) - 1);
strncpy(mqtt_port, custom_mqtt_port.getValue(), sizeof(mqtt_port) - 1);
strncpy(mqtt_topic_prefix, custom_mqtt_topic.getValue(), sizeof(mqtt_topic_prefix) - 1);
strncpy(ntp_server, custom_ntp_server.getValue(), sizeof(ntp_server) - 1);
saveConfig();
Serial.println("WLAN verbunden!");
Serial.print("IP-Adresse: ");
Serial.println(WiFi.localIP());
}
void handleRoot() {
Serial.println("Web-Oberfläche aufgerufen");
String htmlContent = "<!DOCTYPE html><html><head><meta charset='UTF-8'><title>WaterMe Einstellungen</title>"
"<style>"
"body { font-family: Arial, sans-serif; margin: 40px; background-color: #333; color: #fff; }"
"h1 { color: #fff; }"
"form { background-color: #222; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px 0 rgba(0,0,0,0.5); }"
"label { display: block; margin-top: 20px; margin-bottom: 5px; color: #ccc; }"
"input[type='text'], input[type='submit'] { width: 100%; padding: 8px; margin-top: 5px; border: 1px solid #555; border-radius: 4px; }"
"input[type='text'] { background-color: #555; color: #ddd; }"
"input[type='submit'] { background-color: #008CBA; color: white; cursor: pointer; }"
"input[type='submit']:hover { background-color: #005f7a; }"
"</style>"
"</head><body>"
"<h1>WaterMe Einstellungen</h1>"
"<form action='/save' method='POST'>"
"<label for='server'>MQTT Server:</label> <input type='text' id='server' name='server' value='" + String(mqtt_server) + "'><br>"
"<label for='port'>MQTT Port:</label> <input type='text' id='port' name='port' value='" + String(mqtt_port) + "'><br>"
"<label for='user'>MQTT Benutzer:</label> <input type='text' id='user' name='user' value='" + String(mqtt_user) + "'><br>"
"<label for='pass'>MQTT Passwort:</label> <input type='password' id='pass' name='pass' value='" + String(mqtt_pass) + "'><br>"
"<label for='topic'>MQTT Topic Prefix:</label> <input type='text' id='topic' name='topic' value='" + String(mqtt_topic_prefix) + "'><br>"
"<label for='ntp_server'>NTP Server:</label> <input type='text' id='ntp_server' name='ntp_server' value='" + String(ntp_server) + "'><br>"
"<input type='submit' value='Speichern'>"
"</form></body></html>";
server.send(200, "text/html", htmlContent);
}
void handleSave() {
Serial.println("Speichere neue Einstellungen...");
if (server.hasArg("server")) strncpy(mqtt_server, server.arg("server").c_str(), sizeof(mqtt_server) - 1);
if (server.hasArg("port")) strncpy(mqtt_port, server.arg("port").c_str(), sizeof(mqtt_port) - 1);
if (server.hasArg("user")) strlcpy(mqtt_user, server.arg("user").c_str(), sizeof(mqtt_user));
if (server.hasArg("pass")) strlcpy(mqtt_pass, server.arg("pass").c_str(), sizeof(mqtt_pass));
if (server.hasArg("topic")) strncpy(mqtt_topic_prefix, server.arg("topic").c_str(), sizeof(mqtt_topic_prefix) - 1);
if (server.hasArg("ntp_server")) strncpy(ntp_server, server.arg("ntp_server").c_str(), sizeof(ntp_server) - 1);
saveConfig();
server.send(200, "text/html", "<html><body><h1>Gespeichert! Neustart...</h1></body></html>");
delay(3000);
ESP.restart();
}
void checkResetButton() {
static int lastSecondsLeft = -1;
if (digitalRead(RESET_PIN) == LOW) {
if (!buttonPressed) {
buttonPressStart = millis();
buttonPressed = true;
resetActive = true;
lastSecondsLeft = -1;
}
unsigned long heldTime = millis() - buttonPressStart;
int secondsLeft = (RESET_TIME - heldTime) / 1000;
if (secondsLeft != lastSecondsLeft && heldTime < RESET_TIME) {
lastSecondsLeft = secondsLeft;
display.clear();
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.drawString(display.getWidth() / 2, 20, "Reset in");
display.setFont(ArialMT_Plain_24);
display.drawString(display.getWidth() / 2, 44, String(secondsLeft));
display.display();
}
if (heldTime >= RESET_TIME) {
display.clear();
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.drawString(display.getWidth() / 2, 28, "RESET!");
display.display();
delay(1000);
Serial.println("Reset-Taster 5 Sekunden gehalten! Setze WiFiManager-Einstellungen zurück...");
WiFiManager wifiManager;
wifiManager.resetSettings();
delay(500);
WiFi.disconnect(true);
delay(500);
ESP.restart();
}
} else {
if (buttonPressed) {
buttonPressed = false;
resetActive = false;
// <<< Hier neu ergänzen:
previousMillis = millis() - interval;
// <<< Dadurch wird beim nächsten loop() direkt aktualisiert!
}
}
}
void setup() {
pinMode(RESET_PIN, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("WaterMe startet...");
if (!LittleFS.begin()) {
Serial.println("LittleFS-Fehler");
} else {
loadConfig();
}
// *** Display direkt am Anfang initialisieren und "AP" anzeigen ***
display.init();
display.flipScreenVertically();
display.clear();
display.setFont(ArialMT_Plain_24);
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.drawString(display.getWidth() / 2, 28, "AP");
display.display();
delay(500); // Kurze Verzögerung (optional), damit man es sicher sieht
setup_wifi();
if (strlen(mqtt_server) > 0 && strlen(mqtt_port) > 0) {
client.setServer(mqtt_server, atoi(mqtt_port));
Serial.println("MQTT-Konfiguration geladen.");
} else {
Serial.println("MQTT-Daten fehlen! Webinterface zur Konfiguration nutzen.");
}
server.on("/", handleRoot);
server.on("/save", HTTP_POST, handleSave);
server.begin();
Serial.println("Webserver gestartet!");
Serial.print("Rufe auf: http://");
Serial.println(WiFi.localIP());
// Sofortiges erstes Update erzwingen
previousMillis = millis() - interval;
handleMQTTAndDisplayUpdate(millis());
// Sommerzeitregel für Mitteleuropa: letzte So im März + Okt
configTime("CET-1CEST,M3.5.0/2,M10.5.0/3", ntp_server);
Serial.print("Warte auf Zeit...");
time_t now = time(nullptr);
while (now < 100000) {
delay(100);
Serial.print(".");
now = time(nullptr);
}
Serial.println(" Zeit empfangen.");
}
void reconnect() {
static unsigned long lastReconnectAttempt = 0;
uint8_t mac[6];
WiFi.macAddress(mac);
char clientId[32];
snprintf(clientId, sizeof(clientId), "WaterMe_%02X%02X%02X", mac[3], mac[4], mac[5]);
if (!client.connected() && millis() - lastReconnectAttempt > 5000) {
lastReconnectAttempt = millis();
Serial.print("Verbindungsversuch mit MQTT-Broker als ");
Serial.println(clientId);
if (client.connect(clientId, mqtt_user, mqtt_pass)) {
Serial.println("MQTT-Verbindung hergestellt!");
} else {
Serial.print("MQTT-Verbindung fehlgeschlagen, Fehlercode: ");
Serial.print(client.state());
Serial.println(" - Neuer Versuch in 5 Sekunden...");
}
}
}
void loop() {
checkResetButton();
if (resetActive) {
return;
}
server.handleClient();
if (!client.connected()) {
reconnect();
}
client.loop();
handleMQTTAndDisplayUpdate(millis());
}
void handleMQTTAndDisplayUpdate(unsigned long currentMillis) {
if (resetActive) return;
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
if (!client.connected()) {
reconnect();
}
updateSensorData();
}
}
void updateSensorData() {
if (resetActive) return;
int sensorValue = analogRead(SENSOR_PIN);
float moisture = map(sensorValue, 1024, 0, 0, 100);
time_t now = time(nullptr);
struct tm *timeinfo = localtime(&now);
char buffer[20];
strftime(buffer, sizeof(buffer), "%H:%M:%S", timeinfo);
String timestamp = String(buffer);
updateDisplay(moisture, timestamp);
sendMQTTData(moisture, timestamp);
}
void updateDisplay(float moisture, String timestamp) {
Serial.println("Aktualisiere Display...");
Serial.print("Feuchtigkeit: "); Serial.println(moisture);
Serial.print("Timestamp: "); Serial.println(timestamp);
String text = String(moisture, 0) + "%";
display.clear();
display.setFont(ArialMT_Plain_24);
display.setTextAlignment(TEXT_ALIGN_CENTER_BOTH);
display.drawString(display.getWidth() / 2, 28, text);
display.drawLine(0, 42, 128, 42);
display.setFont(ArialMT_Plain_16);
display.drawString(display.getWidth() / 2, 54, timestamp);
display.display();
}
void sendMQTTData(float moisture, const String& timestamp) {
char topic[50], message[50];
snprintf(topic, sizeof(topic), "%s/feuchtigkeit", mqtt_topic_prefix);
snprintf(message, sizeof(message), "%.2f", moisture);
client.publish(topic, message);
snprintf(topic, sizeof(topic), "%s/timestamp", mqtt_topic_prefix);
client.publish(topic, timestamp.c_str());
}
Gehäuse zum 3D-Drucken
Damit die Elektronik geschützt ist, kannst du dir ein passendes Gehäuse für WaterMev2 kostenlos auf meinem Cults3D-Account herunterladen: Hier geht’s zum Gehäuse.

Fazit
Mit dem WaterMeV2 – Feuchtigkeitssensor mit ESP8266 und OLED-Display kannst du ganz einfach Werte überwachen und in dein Smart Home System integrieren. Dank der MQTT-Integration kannst du die Daten bequem weiterverarbeiten und mit dem OLED-Display behältst du stets den Überblick. Baue dein eigenes WaterMeV2 jetzt nach und lade dir das passende Gehäuse herunter!
Hast du Fragen oder Verbesserungsvorschläge? Schreib sie in die Kommentare!