
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.
- Read raw analogue moisture values from the Capacitive Soil Moisture Sensor on GPIO 34 and compare them against two configurable thresholds.
- 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.
- Sound a short periodic chirp on the Piezo Buzzer when the dry alert is active.
- Trigger a rainbow chase animation and a short victory melody when moisture recovers above the wet threshold, running the celebration for
DISCO_DURATIONmilliseconds before returning to idle. - 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_THRESHOLDmay be set too high for your sensor. Water the soil thoroughly, note the Serial Monitor reading, and setWET_THRESHOLDabout 50 counts below that value. - Dry alert triggers even with wet soil. The
DRY_THRESHOLDis set too high. Run the probe in freshly watered soil, note the reading, and setDRY_THRESHOLDabout 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_PINin 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
Components needed
| Component | Type | Qty | Buy |
|---|---|---|---|
| Adafruit Simple Soil Moisture Sensor | sensor | 1 | $3.00 |
| NeoPixel Ring - 16 x 5050 RGB LED with Integrated Drivers | actuator | 1 | $9.95 |
| Piezo Buzzer | actuator | 1 | $1.50 |
Supplier links, prices, and availability are shown as a guide and may change. Schematik may earn a commission from purchases made through affiliate links.
Assembly
Connect the soil moisture sensor
Wire capacitive soil moisture sensor VCC to 3.3V, GND to ground, and analog output to GPIO34.
- Use a capacitive sensor (not resistive) for longer life in wet soil.
- GPIO34 is input-only — this is fine for analog reads.
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.
- A diffuser (frosted cup or paper) around the ring makes the glow more even.
Add the buzzer
Wire piezo buzzer signal to GPIO26 with the other lead to GND.
- A passive buzzer lets you control pitch; an active buzzer only beeps at one frequency.
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.
- Different soil types and sensors produce different ranges — always calibrate.
Pin assignments
| Pin | Connection | Type |
|---|---|---|
| 3V3 | soil-sensor-1 VCC | POWER |
| GND | soil-sensor-1 GND | GROUND |
| GPIO 34 | soil-sensor-1 SIG | ANALOG |
| 5V | plant-led-ring-1 5V | POWER |
| GND | plant-led-ring-1 GND | GROUND |
| GPIO 4 | plant-led-ring-1 DIN | DATA |
| GND | plant-buzzer-1 GND | GROUND |
| GPIO 26 | plant-buzzer-1 SIG | PWM |
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.ioReady to build this?
Open this project in Schematik to get the full wiring diagram, pin assignments, and deployable code for the Plant Disco Guardian.
Open in Schematik →Related guides
How to Build a Gesture-Controlled Mood Lamp with ESP32
ESP32 · Beginner
How to Build a Cosmic Critter Pet with Raspberry Pi Pico
Raspberry Pi Pico · Beginner
How to Build a Rocket Launch Alarm Clock with ESP32
ESP32 · Beginner
How to Build a Laser Harp Synth with ESP32
ESP32 · Beginner