How to Build a Gesture-Controlled Mood Lamp with ESP32
Wave to cycle colors, hold to dim, and save your favorite lighting scenes
Updated

What you'll build
In this guide you will build a gesture-controlled mood lamp powered by an ESP32 microcontroller, an APDS-9960 proximity and gesture sensor, and a strip of individually addressable WS2812B LEDs. A simple wave of your hand cycles through color palettes, holding your hand near the sensor smoothly dims or brightens the output, and a double-swipe gesture saves the current color and brightness as a favorite scene you can recall later. The result is an ambient light that feels almost magical to interact with -- no buttons, no app, just natural hand movements.
The APDS-9960 is one of the most versatile low-cost sensors available to makers. It packs gesture detection, ambient light sensing, and proximity measurement into a single breakout board, making it perfect for beginner-friendly projects that still feel polished. You will learn how to initialize the sensor over I2C, interpret its gesture interrupts inside the ESP32's main loop, and translate those events into smooth LED animations using the FastLED library. Along the way you will pick up foundational skills in I2C communication, interrupt-driven programming, and non-blocking animation timing that transfer directly to more advanced ESP32 projects.
By the end of the build you will have a desk-ready lamp that responds to four distinct gestures, stores up to three favorite scenes in the ESP32's non-volatile flash memory, and recovers its last state after a power cycle. The project is an excellent starting point for anyone interested in smart-home hardware because it introduces addressable LED control, persistent storage with Preferences, and sensor fusion concepts you can later extend with Wi-Fi connectivity or voice assistant integration. If you enjoy working with addressable LEDs and the FastLED library, the Plant Disco Guardian is a great next build that adds soil moisture sensing into the mix.
Wiring diagram
Wiring diagram
Components needed
| Component | Type | Qty | Buy |
|---|---|---|---|
| APDS-9960 Gesture Sensor | sensor | 1 | €4.35 |
| WS2812B LED Strip | actuator | 1 |
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
Connect the gesture sensor
Wire APDS-9960 VCC to 3.3V, GND to ground, SDA to GPIO21, SCL to GPIO22, and INT to GPIO27.
- Use 3.3V for the sensor — most APDS-9960 breakouts have an onboard regulator.
Wire the LED strip
Connect WS2812B 5V and GND to a stable 5V power source, and DIN to GPIO4. Share ground with the ESP32.
- Add a 330 Ω resistor on the DIN line and a 470 µF capacitor across LED power for stability.
- Power the LED strip from a separate 5V source when using more than 8 LEDs.
Upload and test gestures
Flash the sketch, open Serial Monitor at 115200 baud. Swipe left/right to change color, up/down to adjust brightness. Settings save automatically after 5 seconds of inactivity.
- Hold your hand 5–10 cm above the APDS-9960 for reliable gesture detection.
Pin assignments
| Pin | Connection | Type |
|---|---|---|
| 3V3 | apds9960-1 VCC | POWER |
| GND | apds9960-1 GND | GROUND |
| GPIO 21 | apds9960-1 SDA | I2C |
| GPIO 22 | apds9960-1 SCL | I2C |
| GPIO 27 | apds9960-1 INT | DIGITAL |
| 5V | ws2812b-1 5V | POWER |
| GND | ws2812b-1 GND | GROUND |
| GPIO 4 | ws2812b-1 DIN | DATA |
Code
#include <Wire.h>
#include <Adafruit_APDS9960.h>
#include <FastLED.h>
#include <Preferences.h>
#define APDS_INT_PIN 27
#define LED_PIN 4
#define NUM_LEDS 16
#define SDA_PIN 21
#define SCL_PIN 22
Adafruit_APDS9960 apds;
CRGB leds[NUM_LEDS];
Preferences prefs;
uint8_t hue = 0;
uint8_t brightness = 140;
uint8_t savedHue = 0;
uint8_t savedBrightness = 140;
unsigned long lastGestureMs = 0;
void setup() {
Serial.begin(115200);
delay(100);
prefs.begin("moodlamp", false);
hue = prefs.getUChar("hue", 0);
brightness = prefs.getUChar("bright", 140);
savedHue = hue;
savedBrightness = brightness;
Wire.begin(SDA_PIN, SCL_PIN);
pinMode(APDS_INT_PIN, INPUT_PULLUP);
FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, NUM_LEDS);
FastLED.setBrightness(brightness);
if (!apds.begin()) {
Serial.println("APDS-9960 init failed");
}
apds.enableProximity(true);
apds.enableGesture(true);
Serial.println("Mood Lamp ready");
}
void loop() {
if (apds.gestureValid()) {
int g = apds.readGesture();
if (g == APDS9960_LEFT) hue += 24;
if (g == APDS9960_RIGHT) hue -= 24;
if (g == APDS9960_UP && brightness < 245) brightness += 15;
if (g == APDS9960_DOWN && brightness > 20) brightness -= 15;
FastLED.setBrightness(brightness);
lastGestureMs = millis();
}
if (millis() - lastGestureMs > 5000 && lastGestureMs > 0) {
if (hue != savedHue || brightness != savedBrightness) {
prefs.putUChar("hue", hue);
prefs.putUChar("bright", brightness);
savedHue = hue;
savedBrightness = brightness;
}
lastGestureMs = 0;
}
fill_rainbow(leds, NUM_LEDS, hue, 4);
FastLED.show();
delay(20);
}
// 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 Gesture-Controlled Mood Lamp.
Open in Schematik →