Community project

Automatische Pflanzenbewässerungskontrolle

Arduino
Photo of Automatische Pflanzenbewässerungskontrolle
Generated with AI

michael.hasenberg

Published September 22, 2026

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

Wiring diagram for Automatische Pflanzenbewässerungskontrolle

Gather all the parts

QtyComponent
1

Kapazitiver Moisture Sensor V1.2

V1.2

Der Sensor misst die Feuchte der Erde ohne freiliegende Metallkontakte.

1

HC-SR04

5 V

Ultrasonic distance measurement sensor

1

SH1106 OLED

128×64, 0x3C

1.3 inch 128x64 OLED display with I2C interface using the SH1106 controller

1

DS18B20

wasserdicht

Digital temperature sensor using OneWire protocol

1

DS3231 Echtzeituhr

mit Backup-Batterie

Die batteriegestützte Uhr trennt Tag- und Nachttemperatur zeitlich.

1

10kΩ Potentiometer

10 kΩ

A 3-terminal passive resistive voltage divider with a total resistance of 10kΩ. One end connects to 5V, the other to GND, and the wiper outputs a variable voltage between 0V and 5V.

1

10kΩ Potentiometer

10 kΩ

A 3-terminal passive resistive voltage divider with a total resistance of 10kΩ. One end connects to 5V, the other to GND, and the wiper outputs a variable voltage between 0V and 5V.

1

10kΩ Potentiometer

10 kΩ

A 3-terminal passive resistive voltage divider with a total resistance of 10kΩ. One end connects to 5V, the other to GND, and the wiper outputs a variable voltage between 0V and 5V.

1

10-kΩ-Potentiometer für Nacht-Temperatur

10 kΩ

Der Drehknopf stellt die gewünschte Nachttemperatur ein und speist den zusätzlichen Analogwandler mit 3,3 V.

1

ADS1115 16-Bit ADC Module

I²C, Adresse 0x48

16-bit four-channel I2C analog-to-digital converter with programmable gain amplifier. Common ADS1115 breakouts expose MCU-facing SDA/SCL pins and four analog inputs for single-ended or differential measurements.

1

2-Kanal-5-V-Relaismodul, optoentkoppelt, 250 VAC/10 A

Kontakte mindestens 250 VAC / 10 A

Der fertige Relaisbaustein schaltet die 12-V-Pumpe und die getrennt geführte 230-V-Heizung.

1

12V DC Submersible Mini Water Pump

12 V

Two-wire brushed DC submersible water pump for small irrigation or circulation projects. It is an external load and must be driven through a relay, MOSFET, or motor driver; do not connect directly to an MCU GPIO.

1

12V Barrel-Jack Adapter

12 V / mindestens 2 A

12 V / 2 A wall adapter with a 5.5 mm / 2.1 mm barrel jack. Used to power motor drivers, LED strips, or boards that need a higher rail.

1

LM2596-Abwärtswandler

auf 5,0 V eingestellt

Der einstellbare Wandler erzeugt aus 12 V eine stabile 5-V-Versorgung für Arduino und Kleinspannungsteile.

1

4,7-kΩ-Widerstand für DS18B20

4,7 kΩ

Der Widerstand hält die Datenleitung des Temperaturfühlers zuverlässig auf 3,3 V.

1

230-V-Heizgerät mit Schutzleiteranschluss

230 V, Leistung innerhalb Relaisfreigabe

Das fertig isolierte Heizgerät erwärmt die Umgebung und wird nur über die Phase geschaltet.

1

Berührungsgeschützter 230-V-Netzanschluss mit Sicherung

230 V, abgesichert und berührungsgeschützt

Der geschlossene Netzanschluss führt Phase, Neutralleiter und Schutzleiter sicher zur Heizung und zum Relaiskontakt.

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"

Functionpower_supplyArduino
power+12VLM2596-Abwärtswandler VIN+EXT
power+12V2-Kanal-5-V-Relaismodul, optoentkoppelt, 250 VAC/10 A COM1EXT
groundGNDLM2596-Abwärtswandler VIN-EXT
groundGND12V DC Submersible Mini Water Pump PUMP-EXT

2. Connections between "buck_converter" and "Arduino"

Functionbuck_converterArduino
powerVOUT+5V
groundVOUT-GND

3. Connections between "soil_sensor" and "Arduino"

Functionsoil_sensorArduino
powerVCC3V3
groundGNDGND
analogAOUTGPIO 14

4. Connections between "water_level_sensor" and "Arduino"

Functionwater_level_sensorArduino
powerVCC5V
groundGNDGND
digitalTRIGGPIO 9
digitalECHOGPIO 10

5. Connections between "oled" and "Arduino"

FunctionoledArduino
powerVCC3V3
groundGNDGND
i2cSDAGPIO 18
i2cSCLGPIO 19

6. Connections between "rtc" and "Arduino"

FunctionrtcArduino
powerVIN3V3
groundGNDGND
i2cSDAGPIO 18
i2cSCLGPIO 19

7. Connections between "temperature_sensor" and "Arduino"

Functiontemperature_sensorArduino
powerVCC3V3
groundGNDGND
dataDATA4,7-kΩ-Widerstand für DS18B20 DATA_ENDEXT

8. Connections between "ds18b20_pullup" and "Arduino"

Functionds18b20_pullupArduino
powerVCC_END3V3
dataDATA_ENDGPIO 2

9. Connections between "moisture_min_pot" and "Arduino"

Functionmoisture_min_potArduino
powerEnd1 (VCC side)5V
analogWiper (middle)GPIO 15
groundEnd2 (GND side)GND

10. Connections between "moisture_max_pot" and "Arduino"

Functionmoisture_max_potArduino
powerEnd1 (VCC side)5V
analogWiper (middle)GPIO 16
groundEnd2 (GND side)GND

11. Connections between "day_temp_pot" and "Arduino"

Functionday_temp_potArduino
powerEnd1 (VCC side)5V
analogWiper (middle)GPIO 17
groundEnd2 (GND side)GND

12. Connections between "night_temp_pot" and "Arduino"

Functionnight_temp_potArduino
powerEnd1 (3V3 side)3V3
analogWiper (middle)ADS1115 16-Bit ADC Module AIN0EXT
groundEnd2 (GND side)GND

13. Connections between "adc" and "Arduino"

FunctionadcArduino
powerVDD3V3
groundGNDGND
i2cSDAGPIO 18
i2cSCLGPIO 19

14. Connections between "relay_module" and "Arduino"

Functionrelay_moduleArduino
powerVCC5V
groundGNDGND
digitalIN1GPIO 6
digitalIN2GPIO 7
dataNO112V DC Submersible Mini Water Pump PUMP+EXT
dataNO2230-V-Heizgerät mit Schutzleiteranschluss LEXT

15. Connections between "mains_terminal" and "Arduino"

Functionmains_terminalArduino
powerL2-Kanal-5-V-Relaismodul, optoentkoppelt, 250 VAC/10 A COM2EXT
groundN230-V-Heizgerät mit Schutzleiteranschluss NEXT
groundPE230-V-Heizgerät mit Schutzleiteranschluss PEEXT

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.

Open in Schematik