How to Build a Plant Disco Guardian with ESP32

A plant monitor that celebrates watering with lights and sound

ESP32GardenBeginner35 minutes3 components

Updated

How to Build a Plant Disco Guardian with ESP32
For illustrative purposes only
On this page

What you'll build

This guide shows you how to build a Plant Disco Guardian -- an ESP32-based soil moisture monitor that not only tells you when your plant is thirsty but throws a full disco celebration when you water it. A capacitive soil moisture sensor continuously reads the hydration level, and when it drops below a configurable threshold, a WS2812B LED ring glows a warning red and the buzzer emits a gentle periodic chirp. The moment you water the plant and the sensor detects rising moisture, the guardian erupts into a disco light show with rainbow chasing patterns, strobing colors, and a triumphant victory melody. It is absurd, it is delightful, and it actually makes you better at remembering to water your plants.

Under the hood you will learn how capacitive soil moisture sensors work and why they are preferred over resistive probes for long-term use, how to read analog values from the ESP32's ADC and smooth them with a running average to avoid false triggers from sensor noise, and how to drive WS2812B LEDs through the FastLED library with buttery-smooth animations. The disco celebration routine teaches you how to chain multiple animation patterns into a sequenced light show using non-blocking timers, while the alert system demonstrates hysteresis-based threshold logic that prevents the guardian from flickering between alert and celebration states when moisture sits near the boundary.

The completed guardian is a conversation starter that sits in any pot and runs for weeks on a USB power supply. The code is structured so you can adjust moisture thresholds for different soil types, customize the disco playlist with your own LED patterns and melodies, or add Wi-Fi to push moisture data to a dashboard or send phone notifications. Pair it with multiple sensors to monitor an entire windowsill garden from a single ESP32. It is the most fun you will ever have learning about analog-to-digital conversion and threshold-based alerting. If you want to explore more WS2812B animation techniques, the gesture-controlled mood lamp builds on the same FastLED skills with hand-wave interaction.

Wiring diagram

Wiring diagram

Interactive wiring diagram

Components needed

ComponentTypeQtyBuy
Capacitive Soil Moisture Sensorsensor1€7.40
WS2812B LED Ringactuator1
Piezo Buzzeractuator1€4.75

Prices and availability are indicative and may have been updated by the supplier. Schematik may earn a commission from purchases made through affiliate links.

Assembly

1

Connect the soil moisture sensor

Wire capacitive soil moisture sensor VCC to 3.3V, GND to ground, and analog output to GPIO34.

2

Wire the LED ring

Connect WS2812B 5V to USB 5V, GND to ESP32 GND, and DIN to GPIO4. Add a 330 Ω resistor on the DIN line.

3

Add the buzzer

Wire piezo buzzer signal to GPIO26 with the other lead to GND.

4

Calibrate and deploy

Flash the sketch and open Serial Monitor at 115200 baud. Read the moisture values when the soil is dry and wet, then adjust DRY_THRESHOLD and WET_THRESHOLD in the code. Stick the sensor in your plant pot and enjoy the disco.

Pin assignments

PinConnectionType
3V3soil-sensor-1 VCCPOWER
GNDsoil-sensor-1 GNDGROUND
GPIO 34soil-sensor-1 SIGANALOG
5Vplant-led-ring-1 5VPOWER
GNDplant-led-ring-1 GNDGROUND
GPIO 4plant-led-ring-1 DINDATA
GNDplant-buzzer-1 GNDGROUND
GPIO 26plant-buzzer-1 SIGPWM

Code

#include <FastLED.h>

#define SOIL_PIN 34
#define LED_PIN 4
#define NUM_LEDS 12
#define BUZZER_PIN 26

CRGB leds[NUM_LEDS];

const int DRY_THRESHOLD = 1700;
const int WET_THRESHOLD = 2200;

bool wasDry = false;
unsigned long discoEndMs = 0;
const unsigned long DISCO_DURATION = 5000;

int smoothRead(int pin, int samples = 8) {
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(pin);
    delay(2);
  }
  return sum / samples;
}

void discoPattern() {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CHSV((millis() / 6 + i * 21) % 255, 255, 200);
  }
}

void warningPattern() {
  uint8_t pulse = beatsin8(30, 60, 200);
  fill_solid(leds, NUM_LEDS, CRGB(pulse, 0, 0));
}

void idlePattern() {
  uint8_t glow = beatsin8(15, 40, 120);
  fill_solid(leds, NUM_LEDS, CRGB(0, glow, glow / 3));
}

void setup() {
  Serial.begin(115200);
  delay(100);
  FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(140);
  pinMode(BUZZER_PIN, OUTPUT);
  Serial.println("Plant Disco Guardian ready");
}

void loop() {
  int moisture = smoothRead(SOIL_PIN);
  bool isDry = moisture < DRY_THRESHOLD;
  bool isWet = moisture > WET_THRESHOLD;

  if (wasDry && isWet) {
    discoEndMs = millis() + DISCO_DURATION;
    tone(BUZZER_PIN, 1800, 100);
    delay(110);
    tone(BUZZER_PIN, 2200, 100);
    delay(110);
    tone(BUZZER_PIN, 2600, 150);
    Serial.println("DISCO TIME!");
  }

  bool discoMode = millis() < discoEndMs;

  if (discoMode) {
    discoPattern();
  } else if (isDry) {
    warningPattern();
    if ((millis() / 3000) % 2 == 0) {
      tone(BUZZER_PIN, 900, 80);
    }
  } else {
    idlePattern();
  }

  FastLED.show();
  wasDry = isDry;

  Serial.printf("Moisture: %d | %s\n", moisture,
    discoMode ? "DISCO" : isDry ? "DRY" : "OK");
  delay(discoMode ? 30 : 200);
}

// Run this and build other cool things at schematik.io
Libraries: FastLED