Top 5 ESP32 Garden & Plant Monitoring Projects (With Code & Circuit)



Top 5 ESP32 Garden & Plant Monitoring Projects (With Code & Circuit)

Gardening and electronics make a surprisingly powerful combination. The ESP32's WiFi, deep-sleep mode for battery life, and analog inputs make it ideal for outdoor monitoring and automation. These 5 projects range from a simple moisture alert to a full greenhouse controller — all battery-friendly, all cloud-connected.

Project 1 — Soil Moisture Alert to Phone

Project Description

This ultra-simple project reads a capacitive soil moisture sensor every hour (using ESP32 deep sleep to save battery), and sends a WhatsApp or Telegram message when the soil is dry. It runs for weeks on a small LiPo battery, making it perfect for potted plants indoors or on a balcony.

Components: ESP32, capacitive soil moisture sensor v1.2, 18650 LiPo battery + holder, TP4056 charging module.

Circuit Description

ComponentPinESP32 Pin
Soil SensorAOUTGPIO 34
Soil SensorVCCGPIO 32 (switched power)
Soil SensorGNDGND

Power the sensor from GPIO 32 so you can turn it off before deep sleep — sensors draw current even when idle and will drain the battery overnight if left powered.

Code

#include <WiFi.h>
#include <HTTPClient.h>

#define SOIL_PIN        34
#define SENSOR_POWER    32
#define DRY_THRESHOLD   2700

const char* ssid      = "YOUR_WIFI_SSID";
const char* password  = "YOUR_WIFI_PASSWORD";
const char* botToken  = "YOUR_TELEGRAM_BOT_TOKEN";
const char* chatID    = "YOUR_CHAT_ID";

// Deep sleep for 1 hour between readings
#define SLEEP_SECONDS 3600
RTC_DATA_ATTR int bootCount = 0;

void sendTelegram(String msg) {
  HTTPClient http;
  String url = "https://api.telegram.org/bot" + String(botToken) +
               "/sendMessage?chat_id=" + chatID + "&text=" + msg;
  http.begin(url); http.GET(); http.end();
}

void setup() {
  Serial.begin(115200);
  bootCount++;

  // Power up sensor, wait for it to stabilise
  pinMode(SENSOR_POWER, OUTPUT);
  digitalWrite(SENSOR_POWER, HIGH);
  delay(500);

  int moisture = analogRead(SOIL_PIN);
  digitalWrite(SENSOR_POWER, LOW); // power down sensor

  Serial.printf("Boot %d | Moisture: %d\n", bootCount, moisture);

  if (moisture > DRY_THRESHOLD) {
    WiFi.begin(ssid, password);
    int wait = 0;
    while (WiFi.status() != WL_CONNECTED && wait < 20) {
      delay(500); wait++;
    }
    if (WiFi.status() == WL_CONNECTED) {
      sendTelegram("🌵 Your plant needs water! Moisture level: " + String(moisture));
    }
  }

  Serial.println("Going to deep sleep for 1 hour...");
  esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_SECONDS * 1000000ULL);
  esp_deep_sleep_start();
}

void loop() {} // never reached with deep sleep

Conclusion

Deep sleep + sensor power switching is the key to battery-powered IoT — the same technique is used in commercial wireless sensor nodes. Next step: add a DHT22 for temperature and humidity, log every reading to a Google Sheet, and build a 2-week trend chart.

Project 2 — Multi-Zone Automated Irrigation

Project Description

This system manages 4 garden zones independently. Each zone has a soil moisture sensor; when a zone's soil dries out, its pump (via relay) runs for a set number of seconds. A web interface served from the ESP32 shows all sensor readings and lets you trigger any zone manually from your phone. Perfect for herb boxes, raised beds, or pots on a patio.

Components: ESP32, 4 × capacitive soil moisture sensors, 4-channel relay module, 4 × 5V mini water pumps, 4 water tubes, external 5V power supply for pumps, breadboard, jumper wires.

Circuit Description

ZoneSensor AOUTRelay IN Pin
Zone 1GPIO 34GPIO 26
Zone 2GPIO 35GPIO 27
Zone 3GPIO 32GPIO 14
Zone 4GPIO 33GPIO 12

Power all 4 pumps from an external 5V 2A supply, not from the ESP32's Vin pin. Connect each pump through the relay's NO (Normally Open) and COM terminals.

Code

#include <WiFi.h>
#include <WebServer.h>

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
WebServer server(80);

const int sensorPins[] = {34, 35, 32, 33};
const int relayPins[]  = {26, 27, 14, 12};
const int DRY = 2800;
const int WATER_SECONDS = 5;

void waterZone(int zone) {
  Serial.println("Watering zone " + String(zone + 1));
  digitalWrite(relayPins[zone], HIGH);
  delay(WATER_SECONDS * 1000);
  digitalWrite(relayPins[zone], LOW);
}

void handleRoot() {
  String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>";
  html += "<meta name='viewport' content='width=device-width,initial-scale=1'>";
  html += "<title>Garden Control</title>";
  html += "<style>body{font-family:sans-serif;max-width:500px;margin:auto;padding:20px;}";
  html += "button{padding:10px 20px;margin:5px;background:#4CAF50;color:#fff;border:none;border-radius:6px;cursor:pointer;}</style></head><body>";
  html += "<h2>🌱 Garden Irrigation</h2>";

  for (int i = 0; i < 4; i++) {
    int m = analogRead(sensorPins[i]);
    String status = (m > DRY) ? "DRY" : "OK";
    html += "<p>Zone " + String(i+1) + ": " + String(m) + " (" + status + ")";
    html += " <a href='/water?zone=" + String(i) + "'><button>Water Now</button></a></p>";
  }
  html += "</body></html>";
  server.send(200, "text/html", html);
}

void handleWater() {
  int zone = server.arg("zone").toInt();
  if (zone >= 0 && zone < 4) waterZone(zone);
  server.sendHeader("Location", "/"); server.send(303);
}

void setup() {
  Serial.begin(115200);
  for (int i = 0; i < 4; i++) {
    pinMode(relayPins[i], OUTPUT);
    digitalWrite(relayPins[i], LOW);
  }
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println("\nIP: " + WiFi.localIP().toString());
  server.on("/",       handleRoot);
  server.on("/water",  handleWater);
  server.begin();
}

void loop() {
  server.handleClient();

  // Auto-water check every 10 minutes
  static unsigned long lastCheck = 0;
  if (millis() - lastCheck > 600000UL) {
    lastCheck = millis();
    for (int i = 0; i < 4; i++) {
      if (analogRead(sensorPins[i]) > DRY) waterZone(i);
    }
  }
}

Conclusion

Multi-zone irrigation with a web control panel covers the basics of embedded web servers, GPIO control, and sensor-driven automation. Next step: add a real-time clock (DS3231) to schedule watering times, or connect to Home Assistant via MQTT for integration with your full smart home.

Project 3 — Outdoor Weather Logger with SD Card

Project Description

Log outdoor temperature, humidity, and light level every 15 minutes to a CSV file on an SD card. The file can be opened in Excel or Google Sheets to plot trends over weeks or months. A solar panel + LiPo battery makes this self-powered and maintenance-free.

Components: ESP32, DHT22, LDR (light dependent resistor), SD card module, DS3231 RTC module, 10kΩ resistor (LDR divider), micro SD card.

Circuit Description

ComponentPinESP32 Pin
DHT22DATAGPIO 4
LDR + 10kΩ dividermidpointGPIO 34
SD ModuleMOSIGPIO 23
SD ModuleMISOGPIO 19
SD ModuleSCKGPIO 18
SD ModuleCSGPIO 5
DS3231SDAGPIO 21
DS3231SCLGPIO 22

Code

#include <DHT.h>
#include <SD.h>
#include <SPI.h>
#include <RTClib.h>

#define DHTPIN  4
#define LDR_PIN 34
#define SD_CS   5
DHT dht(DHTPIN, DHT22);
RTC_DS3231 rtc;

void setup() {
  Serial.begin(115200);
  dht.begin();
  rtc.begin();

  if (!SD.begin(SD_CS)) {
    Serial.println("SD card init failed!"); return;
  }
  // Write CSV header if file is new
  if (!SD.exists("/log.csv")) {
    File f = SD.open("/log.csv", FILE_WRITE);
    f.println("datetime,temperature_c,humidity_pct,light_raw");
    f.close();
  }
  Serial.println("Logger ready.");
}

void loop() {
  DateTime now  = rtc.now();
  float temp    = dht.readTemperature();
  float hum     = dht.readHumidity();
  int   light   = analogRead(LDR_PIN);

  String row = String(now.year()) + "-" +
               String(now.month())  + "-" +
               String(now.day())    + " " +
               String(now.hour())   + ":" +
               String(now.minute()) + "," +
               String(temp, 1) + "," +
               String(hum, 1)  + "," +
               String(light);

  File f = SD.open("/log.csv", FILE_APPEND);
  if (f) { f.println(row); f.close(); }
  Serial.println(row);

  // Sleep for 15 minutes
  esp_sleep_enable_timer_wakeup(15ULL * 60 * 1000000);
  esp_deep_sleep_start();
}

Conclusion

Data logging to SD card is one of the most practical skills in embedded systems — it's how black boxes, environmental monitors, and scientific instruments work. Next step: parse the CSV on a Raspberry Pi to generate automatic weekly reports, or add WiFi syncing to upload logs to Google Drive.

Project 4 — Greenhouse Temperature & Humidity Controller

Project Description

Keep a small greenhouse in the ideal growing range automatically. The ESP32 reads temperature and humidity, activates a ventilation fan when too hot, a heater relay when too cold, and a humidifier when too dry. All thresholds are configurable via a web interface. Sends daily summary reports via Telegram.

Components: ESP32, DHT22, 3-channel relay module, 12V PC fan (via transistor or relay), 12V seedling heat mat, USB ultrasonic humidifier.

Circuit Description

DeviceControl PinThreshold
Ventilation fan relayGPIO 26Temp > 28°C
Heater relayGPIO 27Temp < 15°C
Humidifier relayGPIO 14Humidity < 60%
DHT22 DATAGPIO 4—

Code

#include <DHT.h>
#include <WiFi.h>
#include <HTTPClient.h>

#define DHTPIN       4
#define FAN_RELAY    26
#define HEAT_RELAY   27
#define HUMID_RELAY  14
DHT dht(DHTPIN, DHT22);

const char* ssid     = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* botToken = "YOUR_BOT_TOKEN";
const char* chatID   = "YOUR_CHAT_ID";

float TEMP_MAX = 28.0, TEMP_MIN = 15.0, HUM_MIN = 60.0;

void sendTelegram(String msg) {
  HTTPClient http;
  String url = "https://api.telegram.org/bot" + String(botToken) +
               "/sendMessage?chat_id=" + chatID + "&text=" + msg;
  http.begin(url); http.GET(); http.end();
}

void setup() {
  Serial.begin(115200);
  dht.begin();
  pinMode(FAN_RELAY, OUTPUT); pinMode(HEAT_RELAY, OUTPUT); pinMode(HUMID_RELAY, OUTPUT);
  digitalWrite(FAN_RELAY, LOW); digitalWrite(HEAT_RELAY, LOW); digitalWrite(HUMID_RELAY, LOW);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); }
  Serial.println("Greenhouse controller online.");
}

void loop() {
  float temp = dht.readTemperature();
  float hum  = dht.readHumidity();

  digitalWrite(FAN_RELAY,   temp > TEMP_MAX ? HIGH : LOW);
  digitalWrite(HEAT_RELAY,  temp < TEMP_MIN ? HIGH : LOW);
  digitalWrite(HUMID_RELAY, hum  < HUM_MIN  ? HIGH : LOW);

  Serial.printf("Temp: %.1fC | Hum: %.1f%% | Fan:%d Heat:%d Humid:%d\n",
    temp, hum,
    digitalRead(FAN_RELAY), digitalRead(HEAT_RELAY), digitalRead(HUMID_RELAY));

  // Send daily Telegram report at midnight
  static int lastHour = -1;
  struct tm t; getLocalTime(&t);
  if (t.tm_hour == 0 && lastHour != 0) {
    sendTelegram("🌿 Greenhouse daily report:\nTemp: " + String(temp,1) +
                 "°C | Hum: " + String(hum,1) + "%");
    lastHour = 0;
  } else if (t.tm_hour != 0) lastHour = t.tm_hour;

  delay(30000); // check every 30 seconds
}

Conclusion

This project covers multi-actuator control with threshold hysteresis — the logic that runs every real HVAC system. Next step: add a BME280 for pressure readings, or integrate with Home Assistant for visual dashboards and historical graphs.

Project 5 — Plant Health Dashboard on Web Browser

Project Description

A beautiful real-time dashboard served directly from the ESP32 shows soil moisture, temperature, humidity, and light level for up to 3 plants. The page auto-refreshes every 5 seconds and uses colour-coded status indicators (green/yellow/red). No app needed — just open a browser on your phone or laptop.

Components: ESP32, 3 × capacitive soil moisture sensors, DHT22, LDR, 3 × 10kΩ resistors, breadboard, jumper wires.

Circuit Description

SensorESP32 Pin
Plant 1 soil sensorGPIO 34
Plant 2 soil sensorGPIO 35
Plant 3 soil sensorGPIO 32
DHT22 DATAGPIO 4
LDR (via 10kΩ divider)GPIO 33

Code

#include <WiFi.h>
#include <WebServer.h>
#include <DHT.h>

const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const int soilPins[] = {34, 35, 32};
const char* plantNames[] = {"Basil", "Mint", "Tomato"};
#define DHTPIN 4
#define LDR    33
DHT dht(DHTPIN, DHT22);
WebServer server(80);

String statusColor(int val, int dryThresh) {
  if (val > dryThresh) return "#e74c3c"; // red = dry
  if (val > dryThresh - 400) return "#f39c12"; // orange = slightly dry
  return "#27ae60"; // green = good
}

void handleRoot() {
  float temp = dht.readTemperature();
  float hum  = dht.readHumidity();
  int   light = analogRead(LDR);

  String h = "";
  h += "";
  h += "";
  h += "Plant Dashboard";
  h += "";
  h += "

🌿 Plant Health Dashboard

"; for (int i = 0; i < 3; i++) { int m = analogRead(soilPins[i]); String col = statusColor(m, 2700); h += "
" + String(m) + "
"; h += "
" + String(plantNames[i]) + " moisture
"; } h += "
" + String(temp,1) + "°C
Temperature
"; h += "
" + String(hum,1) + "%
Humidity
"; h += "
" + String(light) + "
Light level
"; h += "
"; server.send(200, "text/html", h); } void setup() { Serial.begin(115200); dht.begin(); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } Serial.println("Dashboard: http://" + WiFi.localIP().toString()); server.on("/", handleRoot); server.begin(); } void loop() { server.handleClient(); }

Conclusion

Building a web dashboard directly on a microcontroller — no Raspberry Pi, no cloud — is an impressive and practical skill. Next step: replace the HTML with a WebSocket connection so readings update instantly without page refresh, or add a chart library to show 24-hour trends.


More ESP32 & Arduino Project Guides

Post a Comment

0 Comments