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.
Starts below 35%Stops at 65%
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 promptMade 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
| Part | Qty | Approx. price (KES) |
|---|---|---|
| ESP32 DevKit V1 (38-pin) | 1 | 1,200 |
| Capacitive soil-moisture sensor v1.2 (corrosion-resistant) | 1 | 450 |
| 1-channel 5 V relay module, 10 A contacts (opto-isolated) | 1 | 350 |
| 12 V DC diaphragm pump, 1.5 to 3 A | 1 | 2,500 |
| 12 V 20 W solar panel | 1 | 2,800 |
| 12 V 7 Ah sealed lead-acid (SLA) battery | 1 | 2,200 |
| 10 A PWM solar charge controller | 1 | 900 |
| LM2596 buck converter (12 V to 5 V, 3 A) | 1 | 250 |
| 1N4007 flyback diode | 1 | 20 |
| Inline fuse holder + 5 A blade fuse | 1 | 150 |
| Drip line kit (16 mm line, 30 emitters, connectors) | 1 | 1,800 |
| IP65 ABS enclosure, 200 x 120 x 75 mm | 1 | 700 |
| Cable glands, 2.5 mm² DC cable, terminal blocks, heat shrink | lot | 600 |
| 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
| From | To | Note |
|---|---|---|
| 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 COM | This is the switched 12 V rail to the pump |
| Relay NO | Pump + | 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 34 | Soil sensor AOUT | ADC1 channel, input-only pin, no internal pull-up |
| ESP32 3V3 | Soil sensor VCC | Power the probe at 3.3 V so its output never exceeds ADC range |
| ESP32 GND | Soil sensor GND | |
| ESP32 GPIO 26 | Relay IN | Active-LOW module logic assumed in firmware |
| ESP32 5V | Relay VCC | |
| ESP32 GND | Relay GND | |
| 1N4007 cathode (banded end) | Relay COM / Pump + | Flyback diode across the pump |
| 1N4007 anode | Pump − | 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
/*
* 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.
- 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_RAWto it. - 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_RAWto it. - 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. - 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_PERCENTa little below your full reading andSTART_BELOW_PERCENTa little above your thirsty reading, so the pump has a clear band to work in. - Sanity-check the ADC. Feed the ESP32 a known voltage (for example a 1.65 V divider) and confirm
analogReadreturns about 2048. If it is wildly off, the attenuation setting is wrong.
A quick calibration sketch you can flash first:
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
WebsiteKenyaKito's Bike Repair, Eldoret: built, then edited twice
A one-page site for a mobile bike mechanic, then two plain-English edits: a new headline, and a price list in shillings.
Made with Draimo @Build
DocumentKenyaA county tender bid: 300 classroom desks for Nakuru ECDE centres
A technical and financial proposal to a Kenyan county under the Public Procurement and Asset Disposal Act: compliance checklist, delivery plan, warranty and a priced bill of quantities.
Made with Draimo documents
3D modelKenyaA solar phone-charging kiosk, designed in 3D
Draimo designed a market kiosk with solar panels, a service window and charging lockers, part by part, in real metres.
Made with Draimo Deep Think