Schematik build

How to Build a Plant Disco Guardian with ESP32

A plant monitor that celebrates watering with lights and sound

Schematik

Published April 9, 2026 · Updated June 12, 2026

ESP32Beginner35 minutes3 components4 assembly steps
Remix this project
Photo of How to Build a Plant Disco Guardian with ESP32

What you'll build

This guide shows you how to build a Plant Disco Guardian -- an ESP32-based soil moisture monitor that alerts you when your plant is thirsty and runs a brief light and sound celebration when you water it. A capacitive soil moisture sensor continuously reads the hydration level. When it drops below a configurable threshold, a WS2812B LED ring glows red and the buzzer emits a periodic chirp. When you water the plant and moisture rises back above the threshold, the guardian runs a rainbow chasing animation and a short victory melody before returning to its calm idle glow.

The project teaches you how to read an analogue capacitive sensor on the ESP32's ADC, apply hysteresis-based threshold logic to avoid false triggers when moisture hovers near the boundary, and drive a 12-LED WS2812B ring through the FastLED library. The disco celebration sequence chains multiple animation patterns using non-blocking timers so the main loop stays responsive throughout.

When the build is complete you have a self-contained monitor that runs from any USB supply. The threshold constants are easy to tune for different soil types and pot sizes, and the code is structured so you can add new animation routines or extend it with Wi-Fi logging. If you want to explore more FastLED animation techniques, the gesture-controlled mood lamp builds on the same library with hand-wave interaction.

What you are building

This build has a specific scope. Knowing what is and is not included helps you decide whether to extend it later.

  1. Read raw analogue moisture values from the Capacitive Soil Moisture Sensor on GPIO 34 and compare them against two configurable thresholds.
  2. Drive the WS2812B LED Ring with a calm idle colour when moisture is normal, switch to a solid red alert when the soil is dry.
  3. Sound a short periodic chirp on the Piezo Buzzer when the dry alert is active.
  4. Trigger a rainbow chase animation and a short victory melody when moisture recovers above the wet threshold, running the celebration for DISCO_DURATION milliseconds before returning to idle.
  5. Print moisture readings and state changes to Serial Monitor at 115200 baud for calibration and debugging.

Out of scope: Wi-Fi, push notifications, moisture data logging, multiple sensor support, and battery power management. These are reasonable extensions but are not part of this guide.

Upload and calibrate

Install libraries in the Arduino IDE Library Manager before opening the sketch:

  • FastLED (by Daniel Garcia)

Pin and threshold constants are near the top of the sketch:

#define SOIL_PIN        34
#define LED_PIN          4
#define BUZZER_PIN      26
#define NUM_LEDS        12
#define DRY_THRESHOLD 1700   // below this value = dry alert
#define WET_THRESHOLD 2200   // above this value = watered, trigger disco
#define DISCO_DURATION 5000  // celebration length in milliseconds

The sensor produces lower ADC values in wet soil and higher values in dry soil. DRY_THRESHOLD and WET_THRESHOLD are starting points; calibrate them for your specific sensor and soil mix by watching the Serial Monitor readings with the probe in dry soil, then in freshly watered soil, and adjusting the constants to sit comfortably between those two readings.

Upload: select your ESP32 board and port, then click Upload. Open the Serial Monitor at 115200 baud.

Expected Serial Monitor output:

Plant Disco Guardian ready
Moisture: 1580  State: DRY
Moisture: 1574  State: DRY
Moisture: 2310  State: DISCO
Moisture: 2295  State: IDLE

After the "Plant Disco Guardian ready" message, readings appear once per second. Dry soil typically gives readings below 1700; submerged or very wet soil reads above 2200. If readings look inverted (dry soil gives low numbers rather than high), your sensor module may have a different output polarity — swap the threshold comparison operators in the sketch.

Troubleshooting

  • LED ring stays off or flickers randomly. Confirm the ring's 5V wire goes to the USB 5V (VIN) pin, not 3V3. Also check that the 330 Ω resistor is in the DIN line and that the GND wire is connected.
  • Moisture readings are stuck at 4095 or 0. GPIO 34 is not connected to the sensor SIG pin, or the sensor VCC is not powered. Verify all three sensor wires (VCC, GND, SIG) are seated firmly.
  • Disco never triggers after watering. The WET_THRESHOLD may be set too high for your sensor. Water the soil thoroughly, note the Serial Monitor reading, and set WET_THRESHOLD about 50 counts below that value.
  • Dry alert triggers even with wet soil. The DRY_THRESHOLD is set too high. Run the probe in freshly watered soil, note the reading, and set DRY_THRESHOLD about 50 counts above that value to leave a comfortable gap.
  • Buzzer makes no sound. GPIO 26 is not a PWM-capable pin on all ESP32 board variants. If you hear nothing, try moving the buzzer wire to GPIO 25 and updating BUZZER_PIN in the sketch.
  • Serial Monitor shows garbled characters. The baud rate is not set to 115200. Change it in the Serial Monitor dropdown and press the reset button on the ESP32.

Going further

The most practical extension is replacing the single threshold chirp with a graduated alert. Because SOIL_PIN gives a continuous analogue value, you can map moisture level to LED colour on a gradient -- green for well-watered, yellow for borderline, red for dry -- without adding any hardware. The FastLED CHSV colour model makes this straightforward: map the raw ADC reading to a hue value and call fill_solid.

A second direction is adding Wi-Fi so the ESP32 posts readings to a home automation system whenever the state changes. The ESP32's built-in Wi-Fi can send an HTTP POST to Home Assistant, a simple MQTT broker, or a webhook in a few dozen lines alongside the existing sensor and LED code. Because the state machine is already cleanly separated in the sketch, the networking layer drops in without restructuring the core logic.

Wiring diagram

Interactive · read-only

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Parts list

Bill of materials
ComponentQtyNotes
Capacitive Soil Moisture Sensor1Measures water level in plant soil.
WS2812B LED Ring1Disco celebration or warning colors.
Piezo Buzzer1Dryness alerts and celebration chirps.

Assembly

4 steps
  1. Connect the soil moisture sensor

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

    • Tip: Use a capacitive sensor (not resistive) for longer life in wet soil.
    • GPIO34 is input-only — this is fine for analog reads.
  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.

    • Tip: A diffuser (frosted cup or paper) around the ring makes the glow more even.
  3. Add the buzzer

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

    • Tip: A passive buzzer lets you control pitch; an active buzzer only beeps at one frequency.
  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.

    • Tip: Different soil types and sensors produce different ranges — always calibrate.

Pin assignments

Board wiring reference
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

Firmware

ESP32
schematik_esp32.inoDeploy to device
#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);
}

“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik