Build Project

IoT Projects

ESP32 Smart Irrigation System

Build a calibrated ESP32 soil-moisture controller with a documented active-HIGH relay, low-voltage pump wiring, hysteresis and a latched watering timeout.

BeginnerAges 12+90-150 minUnder $35Parent Safe
ESP32 smart irrigation system with soil moisture sensor, pump relay, and plant pot
Jump to a section

What You Will Build

A supervised plant-watering prototype: calibrate the probe with the pump disabled, then use two moisture thresholds to control a normally-open relay contact. Startup is OFF, each watering run is limited, and an out-of-range reading or run timeout latches OFF until reset. It is not an unattended irrigation or plant-safety guarantee.

Before You Start

Classic ESP32-WROOM DevKit with the named pins exposed; Arduino IDE 2.x and Arduino-ESP32 3.3.2 review baseline, not a latest-release claim. Other chip families require their own pin review. Serial Monitor: 115200 baud. Documentation/code review does not establish compilation or hardware operation; neither has been performed for these changes. Use a DFRobot SEN0193 capacitive probe at 3.3 V and Adafruit STEMMA non-latching relay #4409 at external 5 V. Its input is active HIGH and accepts this 3.3 V command. A generic probe or active-LOW relay is not a drop-in substitute. Selected load: Adafruit #4547 submersible pump at a separate regulated 3 V, with the inlet submerged while running. Its listed 100 mA operating draw is not a startup/stall rating. Verify worst-case current, fuse and relay DC motor-switching suitability before powering it; otherwise stop at the contact-only test. This is not for drinking water or long-term installations.

Parts

  • Classic ESP32-WROOM DevKitUSB data cable; GPIO34 ADC1 and GPIO26 output.
  • DFRobot SEN0193 probeSupply from 3V3; manufacturer specifies analog output 0–3.0 V. Identify labelled terminals.
  • Adafruit STEMMA relay #4409External regulated 5 V control supply; 10 kΩ Signal-to-GND pull-down.
  • Adafruit #4547 3 V submersible pump, tubing and separate regulated 3 V supplyListed operating draw 100 mA is not a stall rating; verify startup/stall load, DC contact rating, fuse, leads and flyback diode before use.
  • 10 kΩ resistor and suitable terminalsKeep pump current off breadboard/GPIO wiring.

Wiring

SEN0193 VCC goes to 3V3, GND to ESP32 GND and OUT to GPIO34. STEMMA Signal goes to GPIO26 with 10 kΩ to GND; its Vin uses external regulated 5 V, with control supply negative joined to ESP32 GND. Keep external positive separate from USB. In the isolated contact circuit, pump supply positive goes through an appropriate fuse to COM; NO goes to pump positive; pump negative returns to pump supply negative. Fit a flyback diode directly across the brushed pump, cathode/stripe at pump positive and anode at pump negative; select forward/surge current and reverse voltage ratings for the motor/supply. Contact isolation means the separate pump return need not join control ground. Read labels, not illustration positions.

Logical SEN0193 and STEMMA irrigation circuit with separate pump supply and flyback diode; not physical pin positions

Open wiring diagram full size

  1. 1

    Disconnect all power. Wire and test the sensor and relay control side without pump power.

  2. 2

    Upload the default calibration-only sketch. Observe stable raw readings with the same soil, insertion depth and supply at a dry condition and after watering/draining. Do not immerse the upper electronics.

  3. 3

    Set DRY_RAW and WET_RAW from your own observations; this build requires dry counts greater than wet counts. Set CALIBRATED true only after reviewing the measured range.

  4. 4

    With the pump supply still unplugged, verify LOW=OFF and HIGH=ON at COM/NO. Unknown relay polarity needs a different reviewed interface, not a blind code inversion.

  5. 5

    Verify pump/contact/supply ratings and suppression, wire the contact circuit, then supervise a short test with the disconnect reachable.

Wiring Reference

SignalESP32 PinDirectionNotes
SEN0193 VCC / GND3V3 / GNDPowerSelected probe only; never infer unknown 5 V module output safety
SEN0193 analog OUTGPIO34Input ADC13.0 V maximum documented output; raw counts are not water-content percentage
STEMMA SignalGPIO26OutputActive HIGH; 10 kΩ to control GND
STEMMA Vin / GNDExternal +5 V / shared GNDPowerDo not join external +5 V to USB-powered board positive
Relay COM → NOPump supply + → pump +Switched loadNC unused; pump − returns to its own supply −; diode across pump

Libraries and Setup

  • Arduino-ESP32 3.3.2 built-in ADC and GPIO APIs; no third-party library.

Configure the Example

Default CALIBRATED=false keeps the pump OFF while printing raw counts. Record dry and watered/drained soil readings in the actual pot; air/water endpoints alone do not establish a plant watering threshold. Replace both zero constants, then set CALIBRATED=true. The code requires 100 < WET_RAW < DRY_RAW < 4000 and a span of at least 200 counts; these are illustrative fault guards, not sensor specifications. It switches ON below 30% and OFF above 55% of that relative calibrated span. Those percentages are not volumetric water content. Tune them for your plant before any supervised pump run. MAX_RUN_MS=10000 and SOAK_MS=60000 are bench limits, not validated watering amounts; shorter runs may be necessary.

Control and Recovery Behavior

ConditionResult
Startup / uncalibratedRelay command LOW. Calibration-only mode never enables the pump. Configured mode waits at least 60 seconds.
Dry / wetStart at relative ≤30%; stop at ≥55%; after a normal stop wait at least 60 seconds before another run.
Run limit / invalid readingCommand OFF and latch until reset. Diagnose with all load power disconnected before resetting.
Undetected faultA disconnected or damaged sensor may still produce plausible counts. There is no flow, tank-level, current or independent watchdog feedback.

Code

Install the libraries above and match the configuration and wiring before uploading.

smart-irrigation-system.ino
#include <Arduino.h>
constexpr uint8_t SOIL_PIN = 34, RELAY_PIN = 26;
constexpr bool CALIBRATED = false; // Enable only after measuring your own soil.
constexpr int DRY_RAW = 0, WET_RAW = 0; // Replace both; dry must be higher.
constexpr uint32_t SAMPLE_MS = 1000, MAX_RUN_MS = 10000, SOAK_MS = 60000;
bool pumpOn = false, fault = false, calibrationOK = false;
uint32_t lastSample = 0, runStarted = 0, stoppedAt = 0;

void stopPump() {
  digitalWrite(RELAY_PIN, LOW); // Supported STEMMA 4409 only: LOW is OFF.
  pumpOn = false;
  stoppedAt = millis();
}
void failOff(const char* reason) {
  stopPump(); fault = true;
  Serial.print("LATCHED OFF: "); Serial.println(reason);
}
void setup() {
  digitalWrite(RELAY_PIN, LOW);
  pinMode(RELAY_PIN, OUTPUT); // External 10k pull-down covers high-impedance reset.
  stopPump();
  Serial.begin(115200);
  analogReadResolution(12);
  analogSetPinAttenuation(SOIL_PIN, ADC_11db);
  calibrationOK = WET_RAW > 100 && DRY_RAW < 4000 && DRY_RAW-WET_RAW >= 200;
  if (CALIBRATED && !calibrationOK) failOff("invalid calibration constants");
  Serial.println(CALIBRATED ? "Configured; startup soak before watering" :
    "CALIBRATION ONLY: pump OFF; record raw values");
}
void loop() {
  uint32_t now = millis();
  if (pumpOn && now-runStarted >= MAX_RUN_MS) failOff("watering timeout; inspect before reset");
  if (now-lastSample < SAMPLE_MS) return;
  lastSample = now;
  int raw = analogRead(SOIL_PIN);
  Serial.printf("Soil raw: %d", raw);
  if (!CALIBRATED) { Serial.println(" | calibration only, OFF"); return; }
  if (fault) { Serial.println(" | LATCHED OFF"); return; }
  if (raw < 100 || raw > 4000 || raw < WET_RAW-100 || raw > DRY_RAW+100) {
    Serial.println(); failOff("reading outside configured guard band"); return;
  }
  float relative = 100.0f * (DRY_RAW-raw) / (DRY_RAW-WET_RAW);
  relative = constrain(relative, 0.0f, 100.0f);
  if (pumpOn && relative >= 55.0f) stopPump();
  else if (!pumpOn && relative <= 30.0f && now-stoppedAt >= SOAK_MS) {
    digitalWrite(RELAY_PIN, HIGH); pumpOn = true; runStarted = now;
  }
  Serial.printf(" | relative %.1f %% | Pump %s\n", relative, pumpOn ? "ON" : "OFF");
}

How the Code Works

The sketch explicitly selects 12-bit ADC1 raw counts and attenuation. Calibration produces a relative scale, then two thresholds provide hysteresis. Timeout is checked every loop; sensor checks run nominally once a second. Faults latch, so the software does not repeatedly restart a timed-out pump. No status LED is driven. Hardware contacts, water flow and soil health are not measured.

Expected Output

Initially Serial prints raw counts and calibration only, OFF. With measured constants and calibration enabled, a supervised dry condition can start the relay after the startup soak. A wet threshold stops it. A ten-second run without reaching the wet threshold produces LATCHED OFF; reset only after diagnosis. These are expected checks, not recorded hardware results.

Troubleshooting

  • Pump starts during reset Disconnect load power. Confirm exact active-HIGH relay, Signal pull-down and COM/NO. An active-LOW module is outside this circuit.
  • Raw counts barely change or run backwards Check selected sensor, 3.3 V supply, depth and soil contact. Measure endpoints again; do not reverse thresholds to hide an unknown variant.
  • LATCHED OFF Read the reason. Check calibration and watering response with pump power off; verify time/flow suitability before a manual reset.
  • Relay clicks but pump does not run With power off inspect COM/NO, supply, fuse, polarity and motor ratings. GPIO does not supply pump power.
  • Controller resets or water continues after OFF Disconnect pump supply immediately. Inspect supply noise, suppression, welded contacts and siphoning; software OFF is not physical isolation.

Checks to Perform on Your Build

  • Verify the default sketch never commands the relay ON.
  • Record endpoints with pump power disconnected.
  • Check relay reset/OFF/ON using contact continuity before attaching the pump.
  • In a supervised test verify hysteresis, soak delay and timeout latch with suitable spill containment.
  • Power off before simulating a missing sensor; a floating input may not be detected.

Practical Design Notes

A probe reports an electrical response affected by soil, salinity, depth and installation. Relative calibration is useful for a pot experiment, not a universal moisture percentage. The SEN0193 specified output fits this selected ESP32 input configuration; attenuation is not overvoltage protection. Follow the detailed ADC guide for measurement limits. A plausible stuck reading can escape these guards. Use no unattended pump until the whole hydraulic/electrical design has independent fault protection.

Upgrade Ideas

  • Add independently verified reservoir/flow protection before considering unattended operation.

FAQs

Does 50% mean the soil contains 50% water?

No. It is halfway between the two configured raw-count endpoints, not volumetric water content.

Can I use an active-LOW relay?

Not with this wiring and startup assumption. Select a separately reviewed interface and its reset behavior first.

Review, Testing, and References

Author: Abdul Mubeen and the ESP32 Engine editorial team. Last updated: 2026-10-26. Reviewed: wiring logic, Arduino code structure, beginner safety, and learning sequence.

Educational level: Beginner. Estimated completion time: 90-150 min. This project is for learning and prototyping; production or unattended hardware needs additional engineering review.