A soil-moisture sensor, a relay and a Wi-Fi board make a demo. Making it survive a season outdoors is a different project — one where the capacitive sensor matters more than the code, the pump needs its own supply, and the firmware has to assume the Wi-Fi will disappear.
This is the full build log for the eARgle smart irrigation node: bill of materials with real part choices, the wiring that avoids the ESP8266 boot traps, calibration procedure, the complete firmware with fail-safes, and what actually went wrong across eight weeks of running it on a rooftop garden in Vellore.
- Board ESP8266 (NodeMCU)
- Sensor Capacitive v2.0
- Pump 12 V, 5 W
- Build time ~3 hours
- Runtime 8 weeks tested
- Level Intermediate
Contents
What the node has to do
Written as requirements before any hardware was bought — which is the step most project builds skip:
- Water a 2 m × 1 m raised bed when the soil dries past a set point, not on a clock.
- Never run the pump for more than 90 seconds in one go, and never more than twice an hour, even if the sensor fails.
- Keep working with no internet: Wi-Fi is for telemetry and manual override, not for the control loop.
- Report moisture, pump runtime and uptime so a dead sensor is visible remotely.
- Survive 40 °C afternoons, monsoon humidity and the occasional direct splash.
The control loop runs locally. Every cloud-first irrigation project we have seen eventually floods a bed or kills a plant when the Wi-Fi drops mid-cycle. The node decides; the cloud only watches and can request.
Bill of materials — and the two parts not to cheap out on
| Part | Choice | Notes |
|---|---|---|
| Controller | NodeMCU ESP8266 (CP2102 version) | Mains-powered node, single Wi-Fi connection, one analog input needed — the ESP8266’s limits do not bite here |
| Moisture sensor | Capacitive v1.2 / v2.0 | Not the cheap two-prong resistive probe. See the warning below |
| Relay | 5 V single-channel with opto-isolation | Non-isolated relay boards couple pump kickback straight into the GPIO |
| Pump | 12 V DC diaphragm, ~5 W, with check valve | Self-priming matters if the tank sits below the bed |
| Supply | 12 V 2 A adapter + LM2596 buck to 5 V | One adapter, two rails. Do not run the pump from the board’s 5 V |
| Protection | 1N5408 flyback diode across the pump, 470 µF on the 5 V rail | Two components that stop most mystery reboots |
| Enclosure | IP65 ABS box, cable glands, sensor on a 1 m lead | Electronics out of the weather; only the probe in the soil |
| Optional | DHT22 for air temperature and humidity | Useful context in the telemetry; also feeds evaporation estimates |
The two-prong resistive probe that ships in most starter kits passes DC current through the soil. Within two to six weeks the exposed traces corrode, the readings drift upward, and the node concludes the soil is permanently dry — so it waters permanently. Capacitive probes measure through an insulated PCB with no exposed copper, and ours read consistently through eight weeks buried. This is the single highest-value substitution in the whole build.
Wiring and power architecture
| Signal | ESP8266 pin | Why this pin |
|---|---|---|
| Moisture sensor AOUT | A0 | The ESP8266’s only ADC. NodeMCU’s on-board divider gives a 0–3.3 V range |
| Relay IN | D5 (GPIO14) | Free of boot-strapping duty and idles LOW, so the pump cannot latch on at power-up |
| DHT22 DATA | D6 (GPIO12) | Also boot-safe; 10 kΩ pull-up to 3V3 |
| Status LED | D4 (GPIO2, on-board) | Already fitted, inverted logic |
| Manual override button | D7 (GPIO13) to GND | Internal pull-up, debounced in software |
GPIO0 (D3), GPIO2 (D4) and GPIO15 (D8) set the boot mode. Anything that pulls GPIO0 low or GPIO15 high at power-up stops the board from booting your sketch — a relay module’s input pin is a classic offender. Keep relays and sensors on D1, D2, D5, D6 and D7.
Power: the 12 V adapter feeds the pump side directly and an LM2596 buck module steps it to 5 V for the NodeMCU’s VIN. Grounds are joined at one point next to the buck module, not daisy-chained through the relay board. The flyback diode goes across the pump terminals, banded end to +12 V. With this arrangement the pump’s switch-off spike stops reaching the ESP8266 at all; without it, expect a reset every few cycles and hours of blaming the firmware.
Calibrating the moisture sensor properly
A capacitive sensor gives you an arbitrary ADC number, not a percentage. Calibration is two readings and takes five minutes — and without it your threshold is a guess:
- Dry readingHold the probe in air, or in a cup of bone-dry soil. Log the raw ADC value. On our NodeMCU + capacitive v2.0:
~790. - Wet readingInsert the probe into a glass of water up to the marked line only — never past it, the top of the board is not waterproof. Ours:
~370. - Map, remembering the sensor is invertedHigher ADC means drier.
moisture% = map(raw, dry, wet, 0, 100). - Find the real set point in the bedWater thoroughly, then log moisture every 30 minutes for two days. The percentage at which the plants first look thirsty is your threshold — for our loam-and-compost mix it was 38 %, not the 50 % we had assumed.
Firmware, with the fail-safes that matter
The sketch below is the control core, trimmed of telemetry specifics so you can point it at whatever backend you use. The important parts are not the watering logic — they are the four guards: sensor sanity, maximum run time, minimum interval, and daily cap.
ESP8266 · irrigation control core
#include <ESP8266WiFi.h>
// ---- calibration (from the steps above) ----
const int RAW_DRY = 790;
const int RAW_WET = 370;
// ---- policy ----
const int THRESHOLD_PCT = 38; // water below this
const int TARGET_PCT = 55; // stop above this
const unsigned long MAX_RUN_MS = 90UL * 1000UL;
const unsigned long MIN_GAP_MS = 30UL * 60UL * 1000UL;
const uint8_t MAX_CYCLES_DAY = 6;
const uint8_t PIN_RELAY = 14; // D5
const uint8_t PIN_SENSE = A0;
bool pumping = false;
unsigned long pumpStart = 0;
unsigned long lastPumpEnd = 0;
uint8_t cyclesToday = 0;
unsigned long dayStart = 0;
int readMoisturePct() {
long sum = 0; // 8 samples, 20 ms apart
for (uint8_t i = 0; i < 8; i++) { sum += analogRead(PIN_SENSE); delay(20); }
int raw = sum / 8;
if (raw < 150 || raw > 1000) return -1; // open circuit / shorted probe
int pct = map(raw, RAW_DRY, RAW_WET, 0, 100);
return constrain(pct, 0, 100);
}
void pumpOn() { digitalWrite(PIN_RELAY, HIGH); pumping = true; pumpStart = millis(); cyclesToday++; }
void pumpOff() { digitalWrite(PIN_RELAY, LOW); pumping = false; lastPumpEnd = millis(); }
void setup() {
pinMode(PIN_RELAY, OUTPUT);
digitalWrite(PIN_RELAY, LOW); // pump off before anything else
Serial.begin(115200);
dayStart = millis();
// WiFi.begin(...) — telemetry only; the loop below never waits for it
}
void loop() {
if (millis() - dayStart >= 24UL * 3600UL * 1000UL) { dayStart = millis(); cyclesToday = 0; }
int pct = readMoisturePct();
// GUARD 1 — a failed sensor must never water
if (pct < 0) {
if (pumping) pumpOff();
Serial.println(F("SENSOR FAULT - pump inhibited"));
delay(5000);
return;
}
if (pumping) {
// GUARD 2 — hard ceiling on a single run
if (millis() - pumpStart >= MAX_RUN_MS) { pumpOff(); Serial.println(F("max run reached")); }
else if (pct >= TARGET_PCT) { pumpOff(); Serial.println(F("target reached")); }
} else {
bool dry = pct < THRESHOLD_PCT;
bool rested = (lastPumpEnd == 0) || (millis() - lastPumpEnd >= MIN_GAP_MS); // GUARD 3
bool quotaOk = cyclesToday < MAX_CYCLES_DAY; // GUARD 4
if (dry && rested && quotaOk) pumpOn();
}
Serial.printf("moisture %d%% pump %s cycles %u\n", pct, pumping ? "ON" : "off", cyclesToday);
delay(2000);
}
On power-up, GPIO float briefly before setup() runs. With an active-HIGH relay that is a short click — harmless. With an active-LOW relay board (many are) it is the pump running from the instant power is applied until your sketch takes over, and again on every crash-reboot. Check which polarity your module is, write the safe state first, and add a pull resistor that holds the relay off with the ESP absent.
Eight weeks of field results
| What happened | Cause | Fix applied |
|---|---|---|
| Node rebooted on almost every pump switch-off in week 1 | No flyback diode; inductive spike through the shared 5 V rail | 1N5408 across the pump, 470 µF on the 5 V rail. Zero reboots afterwards |
| Moisture readings drifted 6–8 % over an afternoon | Capacitive sensors are temperature-sensitive; the enclosure hit 48 °C | Moved the box into shade; added DHT22 temperature to the telemetry so drift is visible rather than mysterious |
| One 90-second cycle at 2 a.m. every night during monsoon | Soil near the surface dried while the root zone stayed wet — probe sat too shallow | Buried the probe to root depth (~15 cm) and angled it; night cycles stopped |
| Wi-Fi dropped for 11 hours on day 26 | Router firmware update at the house | Nothing needed — the local control loop watered correctly throughout, which was the point of the architecture |
| Telemetry gaps whenever the pump ran | Blocking delay() plus TLS handshake during the pump cycle | Buffered readings and posted them after the cycle rather than during it |
Water use over the eight weeks came out roughly 30–40 % below the previous fixed-timer schedule for the same bed, almost entirely because the node skips watering after rain — the case a timer can never handle.
What we would change next
- Move to an ESP32-C3 and a battery. The node is mains-powered only because the ESP8266 dev board’s sleep current makes battery operation pointless. A C3 sleeping at microamps between 15-minute checks would run for a season on a cell — see the board comparison for the numbers.
- Two probes, not one. One at 5 cm and one at 15 cm tells you whether water is reaching the roots. That needs a second ADC channel — an ADS1115, or simply the ESP32.
- A flow sensor on the outlet. Runtime is a proxy for water delivered; a blocked dripper or an empty tank looks identical to a healthy cycle without one.
- Rain forecast as a veto. Cheap to add once telemetry exists, and the safest kind of cloud dependency: it can only prevent watering, never cause it.
Frequently asked questions
Can I run the pump straight from the NodeMCU’s 5 V pin?
No. A small 12 V diaphragm pump draws several hundred milliamps to over an amp at start-up, well past what the board’s regulator and USB supply can provide. The rail collapses, the ESP8266 browns out and reboots, and the cycle repeats. Give the pump its own supply, switch it with an opto-isolated relay, and join the grounds at a single point.
How long do capacitive soil sensors last buried?
Ours read consistently through eight weeks, and the usual failure mode is not the sensing area but water creeping into the header pins at the top of the board. Seal the top 2 cm and the cable entry with hot glue or heat-shrink and epoxy, keep the marked waterline above soil level, and a season is realistic. Resistive probes, by contrast, degrade in weeks.
Do I need the cloud for this project at all?
Not for control, and designing it that way is the main lesson of this build. The cloud earns its place for three things: seeing that a sensor has failed while you are away, a manual “water now” button, and vetoing a cycle when rain is forecast. All three are optional extras on top of a loop that works offline.
What moisture threshold should I use?
There is no universal number — it depends on your soil mix, the plant and the probe depth. Calibrate the sensor’s dry and wet ends, then log moisture for two days after a thorough watering and note the percentage at which the plants first show stress. Set the threshold a few points above that. Ours landed at 38 % for a loam-and-compost bed; sandy soil will sit far lower.
Will this work with a 220 V AC mains pump?
Electrically yes, with a relay rated for the load — but mains wiring is not a hobby-bench task. Use a properly enclosed, earthed contactor or a commercial smart plug switched by the ESP over Wi-Fi, keep all mains wiring inside a sealed enclosure, and have it checked by a qualified electrician. The low-voltage side of the design stays exactly as described above.
Build it with the kit
Every part in the bill of materials — capacitive sensor, opto-isolated relay, pump and wiring — ships in the eARgle irrigation kit, with the course module that walks through it.
Get the irrigation kit More project builds
1 Comment.
Comments are closed.