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

  • Arduino Uno (or Nano with I/O remap)
  • Capacitive soil moisture sensor v1.2 (analog output)
  • SSD1306 128×64 OLED (I²C)
  • 5 V peristaltic pump + silicone tubing + water jar
  • Logic-level N-channel MOSFET (IRLZ44N or AO3400)
  • 1N5819 flyback diode
  • 10 kΩ resistor (gate pull-down)
  • Momentary button + 10 kΩ resistor (prime input)
  • External 5 V supply for pump (1 A) + shared ground

Wiring Map

Wiring map: Hydro Guardian Mini module pins and their Arduino connections.
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

  1. Dry reading. With the probe in air, confirm the OLED shows ~0–5 % and the serial log prints a raw value near the MOIST_DRY constant.
  2. Prime cycle. Press the button; the pump should spin immediately, water moves, and the MOSFET stays cool (warm is acceptable).
  3. Automatic watering. Remove the probe from wet soil to drop below target; the pump runs for exactly 6 s once, then refuses to run again until the rest timer expires.
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.