Automatic garden watering with ESP32 and Wi-Fi
In the lesson automatic watering with Arduino the garden watered itself, but we could see the state only in the Serial Monitor. The ESP32 has built-in Wi-Fi, so it can create a small web page: on your phone you see the soil moisture and whether the pump is running, and you can start it with one tap. You already know the pump and the relay from the lesson water pump with ESP32.
Differences from the Arduino Uno
| Arduino Uno | ESP32 | |
|---|---|---|
| pin voltage | 5 V | 3.3 V – an input must not get more |
| analog input | A0, values 0–1023 | GPIO34 (ADC1), values 0–4095 |
| sensor supply | 5V | 3V3 (the capacitive sensor v1.2 also works at 3.3 V) |
| relay | a normal 5 V module | a module that works reliably with a 3.3 V signal |
| network | none | Wi-Fi and Bluetooth |
Wiring
MOISTURE SENSOR ESP32
VCC --------------> 3V3
GND --------------> GND
AOUT --------------> GPIO34
RELAY MODULE ESP32
VCC --------------> VIN (5 V from USB)
GND --------------> GND
IN --------------> GPIO23
BATTERY (+) -> relay COM, relay NO -> pump (+), pump (-) -> BATTERY (-)
The relay module is powered from the VIN pin (5 V from the USB cable) and controlled by a 3.3 V signal from GPIO23. If the relay clicks unreliably, you need a module designed for 3.3 V logic.
Calibration
As with the Arduino, first measure the DRY value (sensor in air) and the WET value (sensor in a glass of water). On the ESP32 the numbers are larger because the range is 0–4095 – for example about 3000 in air and about 1300 in water. To measure them you can use the program from the LDR sensor lesson – just keep it on GPIO34.
Program
Write the name of your Wi-Fi network and its password into the program, together with your calibration values. After uploading, open the Serial Monitor (115200): the ESP32 prints an address such as http://192.168.1.45. Open it on a phone or computer in the same Wi-Fi network.
// ESP32 – automatic watering with a web page (Wi-Fi)
#include <WiFi.h>
#include <WebServer.h>
const char* SSID = "NETWORK_NAME"; // write the name of your Wi-Fi network
const char* PASSWORD = "PASSWORD"; // and its password
const int SENSOR_PIN = 34; // analog input (ADC1), input only
const int RELAY_PIN = 23;
const int RELAY_ON = HIGH; // for a LOW-trigger relay swap HIGH and LOW
const int RELAY_OFF = LOW;
const int DRY = 3000; // calibration: sensor in air (0–4095)
const int WET = 1300; // calibration: sensor in a glass of water
const int THRESHOLD = 35; // below 35 % the soil is dry
const unsigned long PORTION_MS = 3000; // the pump runs for 3 s
const unsigned long SOAK_MS = 30000; // pause for soaking 30 s
WebServer server(80);
int moisture = 0;
bool pumpOn = false;
bool automatic = true;
unsigned long pumpStarted = 0;
unsigned long wateringEnded = 0;
unsigned long lastReading = 0;
int readMoisture() {
long sum = 0;
for (int i = 0; i < 10; i++) {
sum += analogRead(SENSOR_PIN);
delay(2);
}
int percent = map(sum / 10, DRY, WET, 0, 100);
return constrain(percent, 0, 100);
}
void pump(bool on) {
digitalWrite(RELAY_PIN, on ? RELAY_ON : RELAY_OFF);
pumpOn = on;
if (on) pumpStarted = millis();
else wateringEnded = millis();
}
String page() {
String s = "<!DOCTYPE html><html><head><meta charset='utf-8'>";
s += "<meta name='viewport' content='width=device-width'>";
s += "<meta http-equiv='refresh' content='5'><title>Garden</title></head><body>";
s += "<h1>Smart garden</h1>";
s += "<p>Soil moisture: <b>" + String(moisture) + " %</b></p>";
s += "<p>Pump: <b>" + String(pumpOn ? "running" : "off") + "</b></p>";
s += "<p>Automatic mode: <b>" + String(automatic ? "on" : "off") + "</b></p>";
s += "<p><a href='/water'>Water now</a> | <a href='/auto'>Change mode</a></p>";
s += "</body></html>";
return s;
}
void backToHome() {
server.sendHeader("Location", "/");
server.send(303, "text/plain", "");
}
void setup() {
Serial.begin(115200);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, RELAY_OFF);
WiFi.begin(SSID, PASSWORD);
Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("Open in the browser: http://");
Serial.println(WiFi.localIP());
server.on("/", []() { server.send(200, "text/html", page()); });
server.on("/water", []() { if (!pumpOn) pump(true); backToHome(); });
server.on("/auto", []() { automatic = !automatic; backToHome(); });
server.begin();
}
void loop() {
server.handleClient(); // answer the browser
if (millis() - lastReading >= 1000) { // one reading per second
lastReading = millis();
moisture = readMoisture();
Serial.println(moisture);
}
// the pump switches itself off after one portion
if (pumpOn && millis() - pumpStarted >= PORTION_MS) {
pump(false);
}
// automatic mode: dry soil and the soaking time has passed
if (automatic && !pumpOn && moisture < THRESHOLD &&
millis() - wateringEnded >= SOAK_MS) {
pump(true);
}
}
Why is there no delay(30000) here?
The web server must keep answering the browser – that is what server.handleClient() in the loop does. If we wrote delay(30000) as in the Arduino version, the page would not open for 30 seconds. That is why we measure time with millis() (milliseconds since power-on) and only check “has enough time passed”:
- the pump switches off when
PORTION_MShas passed since it started; - a new portion is allowed only when
SOAK_MShas passed since the last watering; - the moisture is measured once per second.
The web page
server.on("/", …)– at the address/it sends the page with the moisture and the pump state. The page refreshes itself every 5 seconds (meta refresh)./water– starts one watering portion and returns to the home page./auto– switches the automatic mode on or off (e.g. while you repot the plant).
Practice tasks
- Add the number of waterings since power-on to the page.
- Add the protection from the Arduino version: after 5 unsuccessful portions in a row, switch off the automatic mode and show a warning on the page.
- Add a “Stop” button to the page that switches the pump off immediately.
- Explore: how could the ESP32 send the moisture to an IoT service so that it can be seen away from home? (see the lessons about the Internet of Things, in Serbian)
Check your knowledge
Open the quiz
Quiz: watering with ESP32
1. Why is the sensor connected to GPIO34?
2. What range does analogRead() return on the ESP32?
3. Why does the program not use delay(30000)?
4. Who can open the garden web page?
