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
| Component | Pin | ESP32 Pin |
|---|---|---|
| Soil Sensor | AOUT | GPIO 34 |
| Soil Sensor | VCC | GPIO 32 (switched power) |
| Soil Sensor | GND | GND |
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
| Zone | Sensor AOUT | Relay IN Pin |
|---|---|---|
| Zone 1 | GPIO 34 | GPIO 26 |
| Zone 2 | GPIO 35 | GPIO 27 |
| Zone 3 | GPIO 32 | GPIO 14 |
| Zone 4 | GPIO 33 | GPIO 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
| Component | Pin | ESP32 Pin |
|---|---|---|
| DHT22 | DATA | GPIO 4 |
| LDR + 10kΩ divider | midpoint | GPIO 34 |
| SD Module | MOSI | GPIO 23 |
| SD Module | MISO | GPIO 19 |
| SD Module | SCK | GPIO 18 |
| SD Module | CS | GPIO 5 |
| DS3231 | SDA | GPIO 21 |
| DS3231 | SCL | GPIO 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
| Device | Control Pin | Threshold |
|---|---|---|
| Ventilation fan relay | GPIO 26 | Temp > 28°C |
| Heater relay | GPIO 27 | Temp < 15°C |
| Humidifier relay | GPIO 14 | Humidity < 60% |
| DHT22 DATA | GPIO 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
| Sensor | ESP32 Pin |
|---|---|
| Plant 1 soil sensor | GPIO 34 |
| Plant 2 soil sensor | GPIO 35 |
| Plant 3 soil sensor | GPIO 32 |
| DHT22 DATA | GPIO 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) + "°CTemperature";
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.
0 Comments