Project, Automatic Plant Watering

Draft. This tutorial has not been checked against the physical product yet. Confirm wiring, pin assignments and voltage limits against the product documentation before building it.
DifficultyAdvanced
CategoryProjects
Supported boardsArduino Uno R3 and compatible ATmega328P boards
Time needed150 minutes
PrerequisitesDriving a Submersible Water Pump
Tested boardNot yet verified
Tested onNot yet verified
Arduino IDE versionNot yet verified
LibraryNot yet verified

What you will build

A plant that waters itself. A probe in the pot, a pump in a bucket, and a sketch that decides when the soil is dry enough to do something about it.

It is the first project in this library that can do damage while behaving exactly as written. The sensor is a conductivity probe whose numbers mean nothing in absolute terms, the pump is destroyed by running dry, and the thing you are controlling takes minutes to respond to what you did. Get the loop wrong and you come home to a flooded room or a dead pump.

So this project is really about one idea: how to control something that answers slowly, when being wrong is expensive. Everything else is two sensors and a relay.

Before you begin

Parts

Part Siratek product Its own tutorial
Arduino Uno 2219 Getting Started with Arduino Uno
Soil moisture probe 2841 Reading Soil Moisture
Submersible pump 2808 Driving a Submersible Water Pump
Relay module 3308 Switching Bigger Loads with a Relay
Water level sensor 2923 Water Level and Rain Detection
Breadboard and jumper wires Using a Breadboard

Also needed, and not from the parts list: a 12 V supply able to give at least 500 mA, silicone or PVC tube to fit the pump outlet, a bucket or tank, a 1N4007 diode, and a measuring jug.

Read this first: three ways this project destroys something

Each of these is covered on a component page. Together they are the reason the sketch looks more careful than the task sounds.

The pump dies if it runs dry. It is cooled and lubricated by the water it moves. A few minutes in an empty tank can ruin the bearing. Driving a Submersible Water Pump says this plainly, and it is why the water level sensor is in the parts list rather than the “making it yours” section.

The room floods if the pump runs long. 240 litres an hour is four litres a minute at zero lift. A stuck-dry probe and an unlimited loop will empty the bucket onto the floor.

The plant drowns if the loop is impatient. This is the subtle one, and the next section is about it.

The feedback loop is slow, and that decides the design

Water the pot and the probe does not change its mind straight away. The water has to soak through the soil and reach the electrodes, and depending on the pot and the compost that takes anywhere from one to ten minutes.

A loop that waters, reads, sees “still dry” and waters again will keep watering until the pot overflows. It will have done precisely what it was told.

So the sketch below never reads the probe and acts on it in one breath. After every watering it enters a soak period and refuses to even consider watering again until that has passed. The soak time is not a refinement; it is the thing that makes the project safe.

This is the general shape of controlling anything with a lag — a heater, a fan, a motor driving a load — and a plant pot is a forgiving place to learn it.

Numbers you must measure, not copy

Your own dry and wet thresholds. The probe measures conductivity, not water, so its numbers mean something only against other numbers from the same probe in the same pot. Reading Soil Moisture explains why a value copied from a tutorial, this one included, is worthless.

Your pump’s actual flow. The label says 240 L/h at zero lift, and lifting water a metre through a narrow tube can easily halve that. The pump page has a ten-second measuring sketch; run it with the tube and the height your build actually uses.

From those two you get the watering time. If you measure 400 ml in ten seconds and the pot takes 200 ml, water for five seconds.

Where the probe goes

Push it into the soil near the plant, roughly half-way between the stem and the pot wall, deep enough to reach the root zone. Keep the outlet tube away from it: dribbling water straight onto the probe reads as a watered pot when nothing has reached the roots.

Mark the depth, because pulling the probe out and pushing it back somewhere else changes every threshold you measured.

How it works

Four jobs, none of which blocks any other:

Job How often Why
Read the soil probe Every 10 minutes Soil does not change faster; the probe corrodes if left powered
Read the tank level Just before watering It only matters at that moment
Water At most once per soak period The lag described above
Count the day’s waterings Rolling 24 hours A cap nothing can argue with

The decision to water needs all of these to be true:

  1. The soil reading is above the dry threshold.
  2. The soak period since the last watering has expired.
  3. The tank has water.
  4. Fewer than the daily maximum waterings have happened.

And while the pump runs, one further rule applies that consults none of the above: the pump stops after its run time, whatever anything else thinks. That check lives on its own in loop() and is the last line of defence if a condition above is computed wrongly.

The state machine is three states, which is as simple as this can honestly be:

State What it means What ends it
IDLE Watching the soil A dry reading with all conditions met
WATERING Pump on The run time expires
SOAKING Waiting for the water to reach the probe The soak time expires

Readings continue in all three states. Only the decision is suspended.

Build it in stages

Do not wire the pump until stage 3. Every flooded bench in the history of this project came from starting at the end.

Stage 1: the probe, on its own

Wire the soil probe as in its own tutorial, power from a digital pin rather than the 5V rail, and run its example. Write down two numbers:

  • The reading with the probe in dry soil, the state you want to water at.
  • The reading just after you water by hand, when the pot is as wet as you want it.

Those become DRY_ABOVE and WET_BELOW. Leave a wide gap between them.

Then do the slow test that matters: water the pot by hand and watch the reading in the serial monitor. Time how long it takes to fall. That measurement is your soak period, with a generous margin added.

Stage 2: the tank sensor

Add the water level sensor and find the reading that corresponds to “enough water to pump”. The sensor need not be precise here; it answers one question, which is whether the pump’s inlet is submerged. Mount it so it reads dry before the pump’s inlet is uncovered, not after.

Stage 3: the pump, in a bucket, on the floor

Wire the relay and the pump as in the pump tutorial, with the diode, with a separate 12 V supply, with the grounds joined. Run the pump page’s ten-second measuring sketch and work out millilitres per second.

Keep everything in a washing-up bowl for this stage.

Stage 4: put it together

The complete sketch below. Run it first with MAX_RUN set to one second and the soak period set to one minute, so you can watch a full cycle in a few minutes rather than overnight. Then set the real values.

Pin reference

Device Pin Connect to Notes
Soil probe VCC Uno D4 Switched, so the probe is only live while reading
Soil probe GND Uno GND
Soil probe AO Uno A0
Tank sensor + Uno D5 Switched, for the same reason
Tank sensor - Uno GND
Tank sensor S Uno A1
Relay VCC Uno 5V
Relay GND Uno GND
Relay IN Uno D8 LOW energises on this board
Relay COM 12 V supply + Load side
Relay NO Pump red Off when unpowered
Pump black 12 V supply -
Diode across the pump Banded end to red 1N4007
12 V supply - Also Uno GND Shared reference
An automatic watering system with a soil probe and a tank level sensor both powered from switched digital pins, a relay driving a submersible pump from its own 12 volt supply, and the soak period that stops the pot being watered twice
The pot answers in minutes, not seconds. The soak period is what stops a slow sensor becoming a flood.

The complete sketch

// Siratek: automatic plant watering.
// Four conditions must agree before the pump runs, and one limit
// stops it regardless of what the other three decided.

const int soilPowerPin = 4;
const int soilPin       = A0;
const int tankPowerPin  = 5;
const int tankPin       = A1;
const int pumpPin       = 8;
const int faultLedPin   = 13;      // the onboard LED

// Relay polarity: see Switching Bigger Loads with a Relay.
const int PUMP_ON  = LOW;
const int PUMP_OFF = HIGH;

// ---- Measured on YOUR pot and YOUR pump. See stages 1 and 3. ----
const int DRY_ABOVE = 700;         // water above this
const int WET_BELOW = 450;         // satisfied below this
const int TANK_EMPTY_BELOW = 300;  // below this, do not run the pump

const unsigned long MAX_RUN   = 6000UL;        // 6 seconds of pumping
const unsigned long SOAK_TIME = 900000UL;      // 15 minutes to reach the probe
const unsigned long CHECK_EVERY = 600000UL;    // read the soil every 10 minutes

const int MAX_PER_DAY = 4;
const unsigned long DAY = 86400000UL;
// -----------------------------------------------------------------

enum State { IDLE, WATERING, SOAKING };
State state = IDLE;

unsigned long stateSince = 0;
unsigned long lastCheck = 0;

int soil = 0;
int tank = 0;

// A rolling 24 hours of watering times.
unsigned long wateredAt[8];
int waterings = 0;

bool faulted = false;

int readSwitched(int powerPin, int analogPin) {
  digitalWrite(powerPin, HIGH);
  delay(200);                       // let the reading settle

  long total = 0;
  for (int i = 0; i < 8; i++) {
    total += analogRead(analogPin);
    delay(10);
  }

  digitalWrite(powerPin, LOW);
  return total / 8;
}

// How many waterings in the last 24 hours, discarding older ones.
int wateringsToday() {
  int kept = 0;
  for (int i = 0; i < waterings; i++) {
    if (millis() - wateredAt[i] < DAY) {
      wateredAt[kept] = wateredAt[i];
      kept++;
    }
  }
  waterings = kept;
  return kept;
}

void enter(State next) {
  state = next;
  stateSince = millis();
}

void pumpOff() {
  digitalWrite(pumpPin, PUMP_OFF);
}

void setup() {
  digitalWrite(pumpPin, PUMP_OFF);   // before pinMode, so it cannot pulse on
  pinMode(pumpPin, OUTPUT);

  pinMode(soilPowerPin, OUTPUT);
  pinMode(tankPowerPin, OUTPUT);
  pinMode(faultLedPin, OUTPUT);
  digitalWrite(soilPowerPin, LOW);
  digitalWrite(tankPowerPin, LOW);

  Serial.begin(9600);
  Serial.println(F("Siratek: plant watering."));

  lastCheck = millis() - CHECK_EVERY;   // read once at startup
  enter(IDLE);
}

void loop() {
  // 1. The limit that consults nothing. If the pump is on and its time
  //    is up, it goes off, whatever any condition below believes.
  if (state == WATERING && millis() - stateSince >= MAX_RUN) {
    pumpOff();
    Serial.println(F("pump off, run time reached"));
    enter(SOAKING);
  }

  // 2. Readings continue in every state.
  if (millis() - lastCheck >= CHECK_EVERY) {
    lastCheck = millis();
    soil = readSwitched(soilPowerPin, soilPin);

    Serial.print(F("soil = "));
    Serial.print(soil);
    Serial.print(F("  state = "));
    Serial.print(state == IDLE ? F("idle") : state == WATERING ? F("watering") : F("soaking"));
    Serial.print(F("  waterings today = "));
    Serial.println(wateringsToday());
  }

  // 3. The soak period has to expire before anything else is considered.
  if (state == SOAKING && millis() - stateSince >= SOAK_TIME) {
    Serial.println(F("soak finished"));
    enter(IDLE);
  }

  // 4. The decision, which only happens from IDLE.
  if (state != IDLE) {
    return;
  }

  if (soil <= DRY_ABOVE) {
    return;                          // not dry enough
  }

  if (wateringsToday() >= MAX_PER_DAY) {
    if (!faulted) {
      faulted = true;
      digitalWrite(faultLedPin, HIGH);
      Serial.println(F("DAILY LIMIT REACHED: still dry after four waterings."));
      Serial.println(F("Something is wrong. Check the tube, the probe and the pot."));
    }
    return;
  }

  tank = readSwitched(tankPowerPin, tankPin);
  if (tank < TANK_EMPTY_BELOW) {
    if (!faulted) {
      faulted = true;
      digitalWrite(faultLedPin, HIGH);
      Serial.print(F("TANK EMPTY: "));
      Serial.println(tank);
    }
    return;
  }

  // Everything agrees.
  faulted = false;
  digitalWrite(faultLedPin, LOW);

  wateredAt[waterings % 8] = millis();
  if (waterings < 8) {
    waterings++;
  }

  digitalWrite(pumpPin, PUMP_ON);
  Serial.println(F("pump on"));
  enter(WATERING);
}

Four decisions worth explaining

The run-time check is the first thing in loop(). Not the last, and not inside the decision logic. Whatever else is wrong — a threshold inverted, a sensor stuck, a condition mis-typed — the pump still stops after six seconds, because that check reads only the clock and the state.

The daily limit is a fault, not a schedule. Four waterings and a pot that is still dry means the tube has come off, the probe has moved, or the water is not going where you think. The right response is to stop and light the LED, not to keep pumping. A system that silently keeps trying is how a small problem becomes a big one.

The tank is read only when watering is about to happen. It is the one reading whose age matters: a level taken ten minutes ago tells you nothing useful about whether the inlet is submerged now.

WET_BELOW is in the thresholds but not in the decision. It is there for the hysteresis you will want when you extend this — a “satisfied” state that has to be properly reached before dryness counts again. Left as written, the soak period plus the daily cap already prevent flapping, and one mechanism doing the job clearly beats two doing it partly.

Testing it

Work down this list before leaving it alone with a plant. The last two are the ones that matter.

Try this It should
Lift the probe into the air Read very dry, and water once, then soak
Water the pot by hand during a soak Nothing happens until the soak expires
Pull the tube off the pump outlet Pump runs, nothing reaches the pot, four waterings then the fault LED
Lift the pump out of the bucket Tank reads empty, pump refuses to run, fault LED on
Set MAX_RUN to 60000 and start a watering, then reason about it It cannot: the limit is checked first
Unplug the probe entirely Reading goes to one extreme; see which, and whether that is safe
Let it run for a full day At most four waterings, logged with their times

The fourth test is the one that proves the project. A watering system that keeps pumping into an empty bucket destroys the pump, and the only way to know yours will not is to take the pump out and watch it refuse.

The sixth test deserves a thought too. An unplugged probe floats to a reading that may look dry, which would water a pot that needs nothing. Decide what your build should do about that — a plausibility window around the reading is the usual answer, and Making a Project Survive Unattended has the general case.

Making it yours

  • Log every watering. The SD card module and the DS3231 give you a dated line per watering, and a month of those tells you more about your plant than any single reading. Project, a Standalone Data Logger is the pattern.
  • Show the state. An I2C LCD or OLED displaying the soil reading, the state and the waterings today makes the whole thing legible without a serial cable.
  • Two pots. The two-channel relay drives two pumps, with the interlock that page describes so only one runs at a time. The decision logic stays as it is, once per pot.
  • Put it on the network. The ESP32 MQTT node publishes the reading and the waterings, so a failed tube becomes a message on your phone rather than a dead plant.
  • A float switch instead of the level sensor. More reliable, mechanically, than a resistive level board, and it answers the same single question.

What not to do: water on a timer alone, with no probe. It works until the weather changes.

Troubleshooting

Symptom Likely cause Check or fix
Pump never runs Soil reading below DRY_ABOVE Print the reading; thresholds are yours to measure
Pump never runs, soil clearly dry Tank reading low, or daily cap hit Watch the serial output for which
Pump runs and will not stop Relay polarity inverted PUMP_OFF must be the resting state
Pot overflowed Soak period too short, or no tube The probe answers in minutes
Watered twice in a minute Soak period not being entered Watering must always lead to SOAKING
Pump hot, no water moved Ran dry Stop; the tank check exists for this
Soil reading drifts over weeks Probe corroding It is only powered while reading; check D4
Soil reading never changes Probe VCC on the 5V rail It must be on the switched pin
Reading high after watering Water has not reached the probe yet That is the whole point of the soak
Arduino resets when the pump starts Shared supply Separate 12 V, grounds joined
Fault LED on and nothing happens A fault, as designed Read the serial message; it says which
Everything works on the bench, not in the pot Thresholds measured in the wrong soil Measure in the pot it will live in

Sources and attribution

All text, wiring guidance and code in this project are original Siratek work.

  • Component behaviour is as described in Reading Soil Moisture, Driving a Submersible Water Pump, Switching Bigger Loads with a Relay and Water Level and Rain Detection, including the probe’s conductivity measurement and corrosion, the switched-power reading, the pump’s 350 mA at 12 V and its need to stay wet, and the relay’s low-level trigger.
  • The soak period, the daily cap and the placement of the run-time check are Siratek’s own design decisions for this project, with the reasoning given rather than asserted.

Thresholds, flow rate and soak time are specific to one pot, one pump and one plant, so the tutorial gives procedures for measuring each rather than values to copy. This project has not yet been built and run on hardware; the figures in the sketch are starting points, and the Testing section is written to be worked through rather than read. No third-party tutorial text, diagrams, photographs or code has been reproduced here.

Product used in this tutorial: Submersible Brushless Mini Water Pump 12v
Shop
Sidebar
0 Wishlist
0 Cart