Hydro Guardian Mini

An Arduino Uno, capacitive moisture probe, and tiny peristaltic pump cooperate to keep one plant on target. The OLED dashboard shows live moisture %, 24-hour min/max, and pump runtime while a push button lets you prime the lines.

Bill of Materials

Wiring Map

Module Pin Arduino Notes
OLED VCC 5V Display tolerates 3.3–5 V.
OLED GND GND Common ground for whole system.
OLED SDA A4 I²C data.
OLED SCL A5 I²C clock.
Moisture sensor VCC 5V Keep probe traces away from pump wiring.
Moisture sensor GND GND Shielded cable helps in noisy rooms.
Moisture sensor OUT A0 Analog read (0–1023).
Prime button One side 5V Use 0.1 µF to ground if you see bouncing.
Prime button Other side D4 10 kΩ pull-down to GND.
MOSFET gate D7 10 kΩ gate pull-down keeps pump off on reset.
MOSFET drain Pump - Diode from pump - to + (stripe to +).
Pump + External 5V + Do not draw pump current from USB.

Build Steps

  1. Stage power domains. Tie the external 5 V supply ground to the Arduino GND. Route the pump positive wire straight to the external supply; run the negative wire to the MOSFET drain, source to ground, and clamp with the diode.
  2. Install the probe. Push the capacitive sensor halfway into the soil. Route the cable along the pot and secure it to relieve strain so the PCB traces never flex.
  3. Mount the display. Angle the SSD1306 so you can see it while working with the plant. Use short wires to A4/A5 and add a blob of hot glue over the solder joints.
  4. Button and enclosure. Wire the prime button to 5 V and D4 (with 10 kΩ to GND). Mount it where accidental presses are unlikely; priming runs the pump for a few seconds.
  5. Calibrate the probe. Before uploading the final sketch, open the serial plotter and record the dry reading (probe in air) and the saturated reading (probe in wet soil). You will plug these into the constants in the code.
  6. Upload and monitor. Flash the sketch, keep the plant nearby, and watch the OLED trendline. The system waters for 6 seconds whenever moisture drops below the setpoint, then enforces a cooldown period.

Sketch

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

constexpr uint8_t OLED_WIDTH = 128;
constexpr uint8_t OLED_HEIGHT = 64;
constexpr uint8_t OLED_ADDR = 0x3C;
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);

constexpr uint8_t MOIST_PIN = A0;
constexpr uint8_t PUMP_PIN = 7;
constexpr uint8_t PRIME_PIN = 4;

constexpr int MOIST_DRY = 880;   // Update with your calibration
constexpr int MOIST_WET = 420;   // Update with your calibration
constexpr int TARGET_PERCENT = 55;
constexpr unsigned long WATER_MS = 6000;
// Cooldown between watering cycles (15 minutes). Increase for larger pots/cool rooms,
// decrease for small pots/hot, fast-drying conditions.
constexpr unsigned long REST_MS = 15UL * 60UL * 1000UL;

unsigned long lastWater = 0;
unsigned long lastSample = 0;
int min24 = 101;
int max24 = -1;

void setup() {
  pinMode(PUMP_PIN, OUTPUT);
  digitalWrite(PUMP_PIN, LOW);
  pinMode(PRIME_PIN, INPUT);
  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
}

void loop() {
  if (millis() - lastSample > 5000) {
    lastSample = millis();
    int percent = moisturePercent();
    min24 = min(min24, percent);
    max24 = max(max24, percent);
    drawScreen(percent);
    if (shouldWater(percent)) runPump(WATER_MS);
  }

  if (digitalRead(PRIME_PIN) == HIGH) {
    drawMessage("Priming pump");
    runPump(3000);
  }
}

int moisturePercent() {
  int raw = analogRead(MOIST_PIN);
  int percent = map(raw, MOIST_DRY, MOIST_WET, 0, 100);
  percent = constrain(percent, 0, 100);
  return percent;
}

bool shouldWater(int percent) {
  if (percent < TARGET_PERCENT && millis() - lastWater > REST_MS) {
    lastWater = millis();
    return true;
  }
  return false;
}

void runPump(unsigned long duration) {
  digitalWrite(PUMP_PIN, HIGH);
  unsigned long start = millis();
  while (millis() - start < duration) {
    delay(10);
  }
  digitalWrite(PUMP_PIN, LOW);
}

void drawScreen(int percent) {
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.print("Soil ");
  display.print(percent);
  display.println("%");
  display.setTextSize(1);
  display.print("24h ");
  display.print(min24);
  display.print("% / ");
  display.print(max24);
  display.println("%");
  display.print("Last water: ");
  if (lastWater == 0) display.println("never");
  else {
    unsigned long ago = (millis() - lastWater) / 60000UL;
    display.print(ago);
    display.println(" min ago");
  }
  display.display();
}

void drawMessage(const char* msg) {
  display.clearDisplay();
  display.setTextSize(2);
  display.setCursor(0, 16);
  display.println(msg);
  display.display();
}

Checkpoints

Troubleshooting. If the OLED flickers when the pump starts, add a 100 µF capacitor across the display’s VCC/GND. Noisy readings usually mean the sensor cable is too close to the pump wires—twist the sensor leads or add a ferrite bead.