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 UnoESP32
pin voltage5 V3.3 V – an input must not get more
analog inputA0, values 0–1023GPIO34 (ADC1), values 0–4095
sensor supply5V3V3 (the capacitive sensor v1.2 also works at 3.3 V)
relaya normal 5 V modulea module that works reliably with a 3.3 V signal
networknoneWi-Fi and Bluetooth
Why GPIO34? ADC2 pins cannot be used for analog readings while Wi-Fi is on. GPIO32–39 belong to ADC1 and also work with Wi-Fi. GPIO34 is an input-only pin – ideal for a sensor.

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_MS has passed since it started;
  • a new portion is allowed only when SOAK_MS has 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).
Security: everyone in your Wi-Fi network can see the page. For a school project that is fine, but do not “open” the device to the internet without password protection.

Practice tasks

  1. Add the number of waterings since power-on to the page.
  2. 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.
  3. Add a “Stop” button to the page that switches the pump off immediately.
  4. 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?