Draimo
Gallery

HardwareKenyaMade with Draimo Deep Think

A solar drip-irrigation controller for a small farm

An ESP32 that reads soil moisture and runs a 12V pump: parts with Nairobi prices, wiring, complete firmware, and a simulator running Draimo's own numbers.

Run the controller: dry the soil and watch the pump decide.

70%

Starts below 35%Stops at 65%

Pump off

Pump off

GPIO26 LOW

Controller: Soil is fine: wait. Idle, it checks every 180s; watering, every second. Shown 60x faster.

The prompt

Design a hardware prototype: an ESP32 soil-moisture drip-irrigation controller for a small farm in Kenya, powered by a 12V solar battery, driving a 12V pump through a relay. Give: a one-paragraph overview; a bill of materials as a table with part, quantity and an approximate price in KES; a wiring table (from pin, to pin, note); the complete Arduino firmware for the ESP32 with the thresholds as named constants (while the pump runs, keep reading the sensor every second and stop as soon as the soil reaches the stop threshold or the safety timeout fires, whichever comes first; when idle, check every few minutes); and how to calibrate the sensor. Then, at the very end, a ```json code block with exactly this shape and the same numbers your firmware uses: {"sensorPin": number, "relayPin": number, "dryRaw": number, "wetRaw": number, "startBelowPercent": number, "stopAbovePercent": number, "maxPumpSeconds": number, "checkEverySeconds": number}.

Try this prompt

Made by the Draimo team on Draimo Pro, 13 October 2026, exactly as shown.

Draimo's design, in full

ESP32 Solar Drip-Irrigation Controller

Overview

This prototype is a self-contained, solar-powered irrigation brain for a small Kenyan farm (roughly a quarter to one acre of vegetables or a kitchen garden). A 12 V solar panel charges a 12 V battery through a charge controller; the battery feeds a 12 V DC pump directly through a relay, and also powers an ESP32 dev board through a small buck converter (12 V down to 5 V). The ESP32 reads a capacitive soil-moisture probe buried at root depth, converts the raw ADC reading into a percentage of available moisture, and when the soil dries below a set threshold it energises the relay to switch the pump on. While the pump runs, the firmware keeps sampling the sensor once a second and stops the pump the moment the soil reaches the "wet enough" threshold, or when a hard safety timeout fires, whichever comes first. When idle, it only wakes to check every few minutes. Water flows through drip lines to the beds, and a mandatory rest period between cycles stops the pump from short-cycling and protects both the battery and the pump motor.


Bill of Materials

PartQtyApprox. price (KES)
ESP32 DevKit V1 (38-pin)11,200
Capacitive soil-moisture sensor v1.2 (corrosion-resistant)1450
1-channel 5 V relay module, 10 A contacts (opto-isolated)1350
12 V DC diaphragm pump, 1.5 to 3 A12,500
12 V 20 W solar panel12,800
12 V 7 Ah sealed lead-acid (SLA) battery12,200
10 A PWM solar charge controller1900
LM2596 buck converter (12 V to 5 V, 3 A)1250
1N4007 flyback diode120
Inline fuse holder + 5 A blade fuse1150
Drip line kit (16 mm line, 30 emitters, connectors)11,800
IP65 ABS enclosure, 200 x 120 x 75 mm1700
Cable glands, 2.5 mm² DC cable, terminal blocks, heat shrinklot600
Total≈ 13,920

Prices are indicative Nairobi retail (River Road / Luthuli Avenue electronics shops) and will move with the shilling and import duty.


Wiring Table

FromToNote
Solar panel +Charge controller PV +Panel is the only source; keep leads short
Solar panel −Charge controller PV −
Charge controller BAT +Battery + (via 5 A fuse)Fuse as close to the battery as possible
Charge controller BAT −Battery −
Battery +Relay COMThis is the switched 12 V rail to the pump
Relay NOPump +Pump only runs when relay is energised
Pump −Battery −Common ground
Battery +LM2596 IN +Buck converter input
Battery −LM2596 IN −
LM2596 OUT + (set to 5.0 V)ESP32 VIN (5 V pin)Set the trimpot to exactly 5.0 V before connecting the ESP32
LM2596 OUT −ESP32 GND
ESP32 GPIO 34Soil sensor AOUTADC1 channel, input-only pin, no internal pull-up
ESP32 3V3Soil sensor VCCPower the probe at 3.3 V so its output never exceeds ADC range
ESP32 GNDSoil sensor GND
ESP32 GPIO 26Relay INActive-LOW module logic assumed in firmware
ESP32 5VRelay VCC
ESP32 GNDRelay GND
1N4007 cathode (banded end)Relay COM / Pump +Flyback diode across the pump
1N4007 anodePump −Clamps the inductive kick when the pump switches off

Two safety notes: never power the pump from the ESP32's 5 V rail, and always put the flyback diode across the pump, not across the relay coil.


Firmware

cpp
/*
 * ESP32 Solar Drip-Irrigation Controller
 * --------------------------------------
 * Reads a capacitive soil-moisture probe and drives a 12 V pump
 * through a relay. Runs the pump until the soil is wet enough or
 * the safety timeout fires, whichever comes first.
 *
 * Board: ESP32 DevKit V1 (Arduino core for ESP32)
 */

// ---------------- Thresholds and timing (named constants) ----------------

// Raw ADC values from the probe. dryRaw = probe in air, wetRaw = probe in water.
// Calibrate these for YOUR probe (see calibration section below).
const int DRY_RAW = 3200;   // reading when probe is bone dry
const int WET_RAW = 1300;   // reading when probe is in a glass of water

// Moisture thresholds expressed as a percentage of the dry-to-wet span.
const int START_BELOW_PERCENT = 35;   // pump starts when soil drops below this
const int STOP_ABOVE_PERCENT  = 65;   // pump stops once soil reaches this

// Safety and timing
const unsigned long MAX_PUMP_SECONDS   = 300;  // hard cap: 5 minutes of pumping
const unsigned long CHECK_EVERY_SECONDS = 180; // idle poll: every 3 minutes
const unsigned long PUMP_SAMPLE_MS      = 1000; // sample every 1 s while pumping
const unsigned long REST_AFTER_PUMP_SECONDS = 60; // cooldown between cycles

// ---------------- Pin map ----------------
const int SENSOR_PIN = 34;  // ADC1_CH6, input-only
const int RELAY_PIN  = 26;  // drives relay module IN

// Relay module is active-LOW: LOW = pump ON, HIGH = pump OFF.
const int RELAY_ON  = LOW;
const int RELAY_OFF = HIGH;

// ---------------- Globals ----------------
unsigned long pumpStartedAt = 0;
unsigned long lastPumpStoppedAt = 0;
bool pumpRunning = false;

// ---------------- Helpers ----------------

// Average several ADC samples to smooth out pump/electrical noise.
int readRawAveraged() {
  const int samples = 15;
  long total = 0;
  for (int i = 0; i < samples; i++) {
    total += analogRead(SENSOR_PIN);
    delay(5);
  }
  return (int)(total / samples);
}

// Map a raw reading to 0-100 % moisture, clamped.
// Note: raw DECREASES as soil gets wetter, so the mapping is inverted.
int rawToPercent(int raw) {
  long pct = (long)(DRY_RAW - raw) * 100L / (long)(DRY_RAW - WET_RAW);
  if (pct < 0)   pct = 0;
  if (pct > 100) pct = 100;
  return (int)pct;
}

void pumpOn() {
  digitalWrite(RELAY_PIN, RELAY_ON);
  pumpRunning = true;
  pumpStartedAt = millis();
  Serial.println("[PUMP] ON");
}

void pumpOff() {
  digitalWrite(RELAY_PIN, RELAY_OFF);
  pumpRunning = false;
  lastPumpStoppedAt = millis();
  Serial.println("[PUMP] OFF");
}

// ---------------- Setup ----------------
void setup() {
  Serial.begin(115200);
  delay(300);

  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, RELAY_OFF);  // fail-safe: pump off at boot

  analogReadResolution(12);            // 0-4095
  analogSetPinAttenuation(SENSOR_PIN, ADC_11db); // full ~0-3.3 V range

  Serial.println("ESP32 drip-irrigation controller booted.");
  Serial.printf("Start below %d%%, stop above %d%%, max pump %lu s, idle check %lu s\n",
                START_BELOW_PERCENT, STOP_ABOVE_PERCENT,
                MAX_PUMP_SECONDS, CHECK_EVERY_SECONDS);
}

// ---------------- Main loop ----------------
void loop() {
  int raw = readRawAveraged();
  int pct = rawToPercent(raw);

  if (!pumpRunning) {
    // ---- IDLE: check every CHECK_EVERY_SECONDS ----
    Serial.printf("[IDLE] raw=%d  moisture=%d%%\n", raw, pct);

    bool rested = (millis() - lastPumpStoppedAt) >= (REST_AFTER_PUMP_SECONDS * 1000UL);

    if (pct < START_BELOW_PERCENT && rested) {
      pumpOn();
    } else {
      delay(CHECK_EVERY_SECONDS * 1000UL);
    }
  } else {
    // ---- PUMPING: sample every second, stop on target or timeout ----
    unsigned long elapsed = (millis() - pumpStartedAt) / 1000UL;
    Serial.printf("[PUMP] raw=%d  moisture=%d%%  elapsed=%lus\n", raw, pct, elapsed);

    if (pct >= STOP_ABOVE_PERCENT) {
      Serial.println("[STOP] Target moisture reached.");
      pumpOff();
    } else if (elapsed >= MAX_PUMP_SECONDS) {
      Serial.println("[STOP] Safety timeout fired.");
      pumpOff();
    } else {
      delay(PUMP_SAMPLE_MS);
    }
  }
}

Calibrating the sensor

The two constants DRY_RAW and WET_RAW are the whole calibration. Do this once per probe, and re-check it every few months because capacitive probes drift.

  1. Dry point. Leave the probe in open air (not touching soil or your hand) for a few minutes, then read the raw value. On a 12-bit ADC with 11 dB attenuation, a typical capacitive v1.2 probe reads roughly 3000 to 3400 in air. Note your number and set DRY_RAW to it.
  2. Wet point. Immerse the probe up to the line marked on its body (never past it, the electronics are not waterproof) in a glass of water for about a minute, then read the raw value. Typical is 1200 to 1500. Set WET_RAW to it.
  3. Verify the direction. Raw must fall as moisture rises. If yours rises instead, you have a resistive probe or a wiring fault, and the mapping in rawToPercent() needs inverting.
  4. Field-tune the thresholds. Insert the probe at root depth in a bed you have just watered and note the percentage. That is your practical "full" reading. Then let a bed dry out and note the percentage at the point the plants just start to look thirsty. Set STOP_ABOVE_PERCENT a little below your full reading and START_BELOW_PERCENT a little above your thirsty reading, so the pump has a clear band to work in.
  5. Sanity-check the ADC. Feed the ESP32 a known voltage (for example a 1.65 V divider) and confirm analogRead returns about 2048. If it is wildly off, the attenuation setting is wrong.

A quick calibration sketch you can flash first:

cpp
void setup() { Serial.begin(115200); analogReadResolution(12);
  analogSetPinAttenuation(34, ADC_11db); }
void loop() {
  long t = 0; for (int i = 0; i < 20; i++) { t += analogRead(34); delay(50); }
  Serial.printf("raw avg = %ld  ->  %d%%\n", t / 20,
    constrain((long)(3200 - (t/20)) * 100L / (3200 - 1300), 0, 100));
  delay(1000);
}

One thing worth knowing before you build: the ESP32's ADC is famously non-linear near the top of its range, so if your dry reading sits above about 3400 you will lose resolution exactly where you need it. A single 100 nF capacitor from GPIO 34 to GND, plus the 15-sample average already in the firmware, cleans that up nicely. Want me to add a soil-temperature-compensated version, or a deep-sleep build that stretches a 7 Ah battery past a week of cloudy weather?

More made with Draimo