Community project
Automatische Pflanzenbewässerungskontrolle
This automatic plant watering system monitors soil moisture, water tank level, and temperature to keep plants healthy without manual intervention. Built around an Arduino Uno, it combines a capacitive soil moisture sensor, ultrasonic water level detection, real-time clock, and temperature monitoring with dual relay control for a submersible pump and optional heating element.
The guide provides a complete wiring diagram, detailed parts list, calibrated firmware, and step-by-step assembly instructions. Learn how to set moisture thresholds with potentiometers, configure day and night temperature targets, monitor system status on an OLED display, and safely integrate high-voltage heating equipment. Includes sensor calibration procedures and initial testing protocols.
Wiring diagram

Gather all the parts
Assemble it in 8 steps
1. Stromversorgung sicher vorbereiten
Montiere das 12-V-Netzteil, den 12-auf-5-V-Abwärtswandler und eine Verteilerklemme in einem trockenen Gehäuse. Stelle den Abwärtswandler vor dem Anschluss an den Arduino mit einem Messgerät auf genau 5,0 V ein. Verbinde den 12-V-Ausgang des Netzteils mit dem Eingang des Wandlers; der Pluspol geht an IN+ und der Minuspol an IN−. Lege eine gemeinsame Masseleitung für die gesamte Kleinspannungsseite an.
- Die Pumpe erhält ihre 12 V direkt vom Netzteil. Arduino, Sensoren, Display und Relaismodul erhalten 5 V vom korrekt eingestellten Abwärtswandler.
- Beschrifte 12 V, 5 V und GND mit unterschiedlichen Kabelfarben, zum Beispiel rot, orange und schwarz.
- Schließe den Arduino erst an, nachdem du die 5,0 V am Ausgang des Wandlers gemessen hast — eine falsch eingestellte Spannung kann ihn beschädigen.
- Halte die 230-V-Leitungen vollständig getrennt von allen Kleinspannungsleitungen.
2. Arduino, Display und Uhr anschließen
Verbinde den 5-V-Ausgang mit dem 5V-Pin des Arduino Uno R4 WiFi und GND mit GND. Verbinde am SH1106-Display VCC → 3V3 (power), GND → GND (ground), SDA → SDA/D20 (data) und SCL → SCL/D21 (clock). Verbinde am DS3231-Modul VCC → 3V3 (power), GND → GND (ground), SDA → SDA/D20 (data) und SCL → SCL/D21 (clock).
- SDA und SCL sind gemeinsame Datenleitungen; Display, Uhr und ADS1115 liegen zusammen darauf.
- Setze bei der DS3231-Uhr die Knopfzelle ein, damit Uhrzeit und Tag-/Nacht-Umschaltung nach einem Stromausfall weiterlaufen.
- Vertausche VCC und GND nicht — vertauschte Versorgung kann Display oder Uhr beschädigen.
3. Boden- und Temperaturfühler anschließen
Verbinde den kapazitiven Bodenfühler mit VCC → 3V3 (power), GND → GND (ground) und AOUT → A0 (signal). Verbinde den DS18B20 mit VCC → 5V (power), GND → GND (ground) und DATA → D2 (signal). Setze den 4,7-kΩ-Widerstand zwischen DATA und 5V; er hält die Temperaturleitung auf einem eindeutig lesbaren Pegel.
- Stecke nur die beschichtete Messfläche des Bodenfühlers in die Erde; die Elektronik am oberen Ende bleibt trocken.
- Platziere den Temperaturfühler dort, wo die Heizung tatsächlich regeln soll, nicht direkt im warmen Luftstrom.
- Der Bodenfühler darf nicht dauerhaft unter Wasser stehen — das kann seine Elektronik beschädigen.
4. Vier Drehknöpfe und den Zusatz-ADC anschließen
Verbinde bei den Potentiometern für Feuchte min., Feuchte max. und Tag-Temperatur jeweils einen äußeren Anschluss mit 5V (power), den anderen äußeren Anschluss mit GND (ground) und den mittleren Anschluss mit A1, A2 beziehungsweise A3 (signal). Verbinde beim ADS1115 VDD → 3V3 (power), GND → GND (ground), SDA → SDA/D20 (data) und SCL → SCL/D21 (clock). Beim Nacht-Temperatur-Potentiometer geht ein äußerer Anschluss zu 3V3 (power), der andere zu GND (ground) und der mittlere Anschluss zu A0 des ADS1115 (signal).
- Drehe jeden Knopf nach dem Einschalten einmal langsam von Anschlag zu Anschlag; der angezeigte Wert muss sich dabei stetig ändern.
- Der Nacht-Drehknopf hat absichtlich 3,3 V statt 5 V, weil sein Signal in den ADS1115-Eingang geht.
- Verbinde den mittleren Anschluss des Nacht-Drehknopfs niemals direkt mit einem Arduino-Pin — er gehört an A0 des ADS1115-Moduls.
5. Wasserstandssensor über dem Tank montieren
Befestige den HC-SR04 waagerecht oben im 18-cm-hohen Wasserbehälter, mit den runden Öffnungen nach unten. Verbinde VCC → 5V (power), GND → GND (ground), TRIG → D9 (signal) und ECHO → D10 (signal). Miss den Abstand vom Sensor zur Wasseroberfläche; bei vollem Tank ist er klein, bei leerem Tank ungefähr 18 cm.
- Der Sensor sollte nicht direkt über dem einfallenden Wasserstrahl sitzen, damit Spritzer die Messung nicht stören.
- Sorge für eine freie Sicht zwischen den Sensoröffnungen und der Wasseroberfläche.
- Achte darauf, dass keine Tropfen in die Öffnungen des Ultraschallsensors gelangen — Wasser kann ihn beschädigen.
6. Pumpe und Relaismodul anschließen
Verbinde das Relaismodul mit VCC → 5V (power), GND → GND (ground), IN1 → D6 (signal) und IN2 → D7 (signal). Führe für die 12-V-Pumpe den Pluspol des Netzteils zum COM-Kontakt des ersten Relais und vom NO-Kontakt weiter zum Pluspol der Pumpe. Verbinde den Minuspol der Pumpe direkt mit dem 12-V-Minuspol. So läuft die Pumpe nur, wenn das Relais einschaltet.
- NO bedeutet „normally open“: Im ausgeschalteten Zustand fließt kein Strom zur Pumpe.
- Lege den Pumpenschlauch erst nach erfolgreichem Trockentest in den Behälter.
- Die Pumpe darf nicht trocken laufen — das kann sie zerstören.
- Benutze die Relaiskontakte nur innerhalb der auf dem Relaismodul angegebenen Stromgrenze.
7. 230-V-Heizung ausschließlich fachgerecht anschließen lassen
Lass die 230-V-Zuleitung, Sicherung, Schutzleiter und den zweiten Relaiskanal von einer Elektrofachkraft in ein berührungsgeschütztes Gehäuse einbauen. Die Fachkraft schaltet ausschließlich den Außenleiter über COM und NO des zweiten Relais; Neutralleiter und Schutzleiter werden fachgerecht direkt zur Heizung geführt. Der Arduino steuert diesen Relaiskanal über D7.
- Die Heizung muss eine eigene Zugentlastung und ein geschlossenes, nicht brennbares Gehäuse haben.
- Wähle eine Heizung, deren Nennstrom einschließlich Einschaltstrom unter der Kontaktfreigabe des Relais liegt.
- 230 V können tödlich sein und Brände verursachen. Keine offene Netzverdrahtung, keine Breadboard-Verbindungen und keine Arbeiten unter Spannung.
- Wenn Leistung oder Einschaltstrom der Heizung unklar sind, darf sie nicht direkt über dieses Relais betrieben werden; dann benötigt sie eine passend dimensionierte Schaltstufe.
8. Ersttest und Kalibrierung durchführen
Prüfe alle Kleinspannungsleitungen noch einmal, schließe den Arduino über USB an und kontrolliere die Anzeige. Stelle Feuchte min. und Feuchte max. mit den beiden Knöpfen ein. Kalibriere den Bodenfühler in trockener Erde und in vollständig feuchter Erde, indem du die beiden Messwerte in der Firmware als trocken beziehungsweise nass einträgst. Teste Pumpe und Heizung zunächst ohne Wasser beziehungsweise ohne angeschlossene 230-V-Last.
- Wähle zwischen Feuchte min. und Feuchte max. mindestens 5 Prozentpunkte Abstand; die Steuerung erzwingt diesen Mindestabstand.
- Die Pumpe läuft in kurzen 2,5-Sekunden-Impulsen und wartet anschließend 30 Sekunden, damit Wasser einsickern kann.
- Nimm den 230-V-Test erst nach fachgerechter Prüfung der Netzseite vor.
- Beobachte die erste Bewässerung; je nach Topf und Pumpe müssen Impulszeit und Bodenfühler-Kalibrierung angepasst werden.
Review all connections
1. Connections between "power_supply" and "Arduino"
2. Connections between "buck_converter" and "Arduino"
3. Connections between "soil_sensor" and "Arduino"
4. Connections between "water_level_sensor" and "Arduino"
5. Connections between "oled" and "Arduino"
6. Connections between "rtc" and "Arduino"
7. Connections between "temperature_sensor" and "Arduino"
8. Connections between "ds18b20_pullup" and "Arduino"
9. Connections between "moisture_min_pot" and "Arduino"
10. Connections between "moisture_max_pot" and "Arduino"
11. Connections between "day_temp_pot" and "Arduino"
12. Connections between "night_temp_pot" and "Arduino"
13. Connections between "adc" and "Arduino"
14. Connections between "relay_module" and "Arduino"
15. Connections between "mains_terminal" and "Arduino"
Deploy the firmware
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <RTClib.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Adafruit_ADS1X15.h>
// Forward declarations
int readAverageAnalog(uint8_t pin);
int clampPercent(int value);
int soilRawToPercent(int raw);
float readWaterDistanceCm();
void updatePump();
void updateHeater();
void readSensors();
void drawDisplay();
constexpr uint8_t SOIL_PIN = A0;
constexpr uint8_t MOISTURE_MIN_PIN = A1;
constexpr uint8_t MOISTURE_MAX_PIN = A2;
constexpr uint8_t DAY_TEMP_PIN = A3;
constexpr uint8_t DS18B20_PIN = 2;
constexpr uint8_t ULTRASONIC_TRIG_PIN = 9;
constexpr uint8_t ULTRASONIC_ECHO_PIN = 10;
constexpr uint8_t PUMP_RELAY_PIN = 6;
constexpr uint8_t HEATER_RELAY_PIN = 7;
constexpr uint8_t OLED_ADDRESS = 0x3C;
constexpr uint8_t ADS1115_ADDRESS = 0x48;
constexpr uint8_t RELAY_ON = LOW; // Typical opto relay modules are active LOW.
constexpr uint8_t RELAY_OFF = HIGH;
constexpr int TANK_HEIGHT_CM = 18;
constexpr float MIN_ALLOWED_TEMPERATURE = 5.0f;
constexpr float MAX_ALLOWED_TEMPERATURE = 35.0f;
constexpr float HEATER_HYSTERESIS_C = 0.5f;
constexpr unsigned long PUMP_PULSE_MS = 2500;
constexpr unsigned long SOAK_TIME_MS = 30000;
constexpr unsigned long SENSOR_INTERVAL_MS = 1000;
constexpr unsigned long DISPLAY_INTERVAL_MS = 1000;
// Calibrate these two values for the actual soil and sensor before normal use.
// Readings fall when the sensor is wetter: dry = larger number, wet = smaller number.
constexpr int SOIL_RAW_WET = 5200;
constexpr int SOIL_RAW_DRY = 11800;
Adafruit_SH1106G display(128, 64, &Wire, -1);
RTC_DS3231 rtc;
OneWire oneWire(DS18B20_PIN);
DallasTemperature temperatureBus(&oneWire);
Adafruit_ADS1115 ads;
bool displayReady = false;
bool rtcReady = false;
bool adsReady = false;
bool pumpRunning = false;
bool heaterRunning = false;
bool pumpLockout = false;
unsigned long pumpStartedAt = 0;
unsigned long pumpStoppedAt = 0;
unsigned long lastSensorAt = 0;
unsigned long lastDisplayAt = 0;
int soilPercent = 0;
int minMoisturePercent = 30;
int maxMoisturePercent = 60;
int waterLevelPercent = 0;
float waterDistanceCm = -1.0f;
float measuredTemperatureC = NAN;
float dayTargetC = 20.0f;
float nightTargetC = 16.0f;
float activeTargetC = 20.0f;
bool isDay = true;
int readAverageAnalog(uint8_t pin) {
long total = 0;
for (uint8_t i = 0; i < 8; ++i) {
total += analogRead(pin);
delay(2);
}
return total / 8;
}
int clampPercent(int value) {
return constrain(value, 0, 100);
}
int soilRawToPercent(int raw) {
long percent = map(raw, SOIL_RAW_DRY, SOIL_RAW_WET, 0, 100);
return clampPercent((int)percent);
}
float readWaterDistanceCm() {
digitalWrite(ULTRASONIC_TRIG_PIN, LOW);
delayMicroseconds(3);
digitalWrite(ULTRASONIC_TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(ULTRASONIC_TRIG_PIN, LOW);
unsigned long duration = pulseIn(ULTRASONIC_ECHO_PIN, HIGH, 30000);
if (duration == 0) return -1.0f;
return duration * 0.0343f / 2.0f;
}
void updatePump() {
unsigned long now = millis();
if (pumpRunning && now - pumpStartedAt >= PUMP_PULSE_MS) {
digitalWrite(PUMP_RELAY_PIN, RELAY_OFF);
pumpRunning = false;
pumpLockout = true;
pumpStoppedAt = now;
}
if (pumpLockout && now - pumpStoppedAt >= SOAK_TIME_MS) {
pumpLockout = false;
}
bool tankHasWater = waterLevelPercent > 5;
if (!pumpRunning && !pumpLockout && tankHasWater && soilPercent < minMoisturePercent) {
digitalWrite(PUMP_RELAY_PIN, RELAY_ON);
pumpRunning = true;
pumpStartedAt = now;
}
// The max value ends irrigation immediately; this is the upper safety limit.
if (pumpRunning && soilPercent >= maxMoisturePercent) {
digitalWrite(PUMP_RELAY_PIN, RELAY_OFF);
pumpRunning = false;
pumpLockout = true;
pumpStoppedAt = now;
}
}
void updateHeater() {
if (isnan(measuredTemperatureC)) {
digitalWrite(HEATER_RELAY_PIN, RELAY_OFF);
heaterRunning = false;
return;
}
if (!heaterRunning && measuredTemperatureC <= activeTargetC - HEATER_HYSTERESIS_C) {
digitalWrite(HEATER_RELAY_PIN, RELAY_ON);
heaterRunning = true;
} else if (heaterRunning && measuredTemperatureC >= activeTargetC + HEATER_HYSTERESIS_C) {
digitalWrite(HEATER_RELAY_PIN, RELAY_OFF);
heaterRunning = false;
}
}
void readSensors() {
int soilRaw = readAverageAnalog(SOIL_PIN);
soilPercent = soilRawToPercent(soilRaw);
minMoisturePercent = clampPercent(map(readAverageAnalog(MOISTURE_MIN_PIN), 0, 16383, 0, 100));
maxMoisturePercent = clampPercent(map(readAverageAnalog(MOISTURE_MAX_PIN), 0, 16383, 0, 100));
if (maxMoisturePercent < minMoisturePercent + 5) {
maxMoisturePercent = min(100, minMoisturePercent + 5);
}
dayTargetC = map(readAverageAnalog(DAY_TEMP_PIN), 0, 16383, 100, 350) / 10.0f;
if (adsReady) {
int16_t nightRaw = ads.readADC_SingleEnded(0);
nightTargetC = map(nightRaw, 0, 17600, 100, 350) / 10.0f;
}
dayTargetC = constrain(dayTargetC, MIN_ALLOWED_TEMPERATURE, MAX_ALLOWED_TEMPERATURE);
nightTargetC = constrain(nightTargetC, MIN_ALLOWED_TEMPERATURE, MAX_ALLOWED_TEMPERATURE);
if (rtcReady) {
DateTime now = rtc.now();
isDay = now.hour() >= 7 && now.hour() < 22;
} else {
isDay = true;
}
activeTargetC = isDay ? dayTargetC : nightTargetC;
temperatureBus.requestTemperatures();
float newTemperature = temperatureBus.getTempCByIndex(0);
measuredTemperatureC = (newTemperature == DEVICE_DISCONNECTED_C) ? NAN : newTemperature;
waterDistanceCm = readWaterDistanceCm();
if (waterDistanceCm >= 0.0f) {
waterLevelPercent = clampPercent((int)round((TANK_HEIGHT_CM - waterDistanceCm) * 100.0f / TANK_HEIGHT_CM));
} else {
waterLevelPercent = 0;
}
}
void drawDisplay() {
if (!displayReady) return;
display.clearDisplay();
display.setTextColor(SH110X_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.print("Feuchte: ");
display.print(soilPercent);
display.println(" %");
display.print("Feuchte min.: ");
display.print(minMoisturePercent);
display.println(" %");
display.print("Feuchte max.: ");
display.print(maxMoisturePercent);
display.println(" % ");
display.print("Wasser: ");
display.print(waterLevelPercent);
display.println(" %");
display.print("Temp ");
if (isnan(measuredTemperatureC)) display.print("--.-");
else display.print(measuredTemperatureC, 1);
display.print("/");
display.print(activeTargetC, 1);
display.println(" C");
display.print(isDay ? "Tag " : "Nacht ");
display.print("P:");
display.print(pumpRunning ? "AN" : "AUS");
display.print(" H:");
display.println(heaterRunning ? "AN" : "AUS");
display.display();
}
void setup() {
pinMode(ULTRASONIC_TRIG_PIN, OUTPUT);
pinMode(ULTRASONIC_ECHO_PIN, INPUT);
pinMode(PUMP_RELAY_PIN, OUTPUT);
pinMode(HEATER_RELAY_PIN, OUTPUT);
digitalWrite(PUMP_RELAY_PIN, RELAY_OFF);
digitalWrite(HEATER_RELAY_PIN, RELAY_OFF);
analogReadResolution(14);
Wire.begin();
temperatureBus.begin();
displayReady = display.begin(OLED_ADDRESS, true);
rtcReady = rtc.begin();
if (rtcReady && rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
adsReady = ads.begin(ADS1115_ADDRESS);
if (adsReady) ads.setGain(GAIN_ONE);
readSensors();
updatePump();
updateHeater();
drawDisplay();
}
void loop() {
unsigned long now = millis();
if (now - lastSensorAt >= SENSOR_INTERVAL_MS) {
lastSensorAt = now;
readSensors();
updateHeater();
}
updatePump();
if (now - lastDisplayAt >= DISPLAY_INTERVAL_MS) {
lastDisplayAt = now;
drawDisplay();
}
}Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.




