How to Build a Gesture-Controlled Mood Lamp with ESP32

Wave to cycle colors, hold to dim, and save your favorite lighting scenes

ESP32Smart HomeBeginner45 minutes2 components

Updated

How to Build a Gesture-Controlled Mood Lamp with ESP32
For illustrative purposes only
On this page

What you'll build

Build a gesture-controlled mood lamp using an ESP32, an APDS-9960 Gesture Sensor, and a WS2812B LED Strip. Swipe left or right to shift the colour, swipe up or down to adjust brightness. The current colour and brightness save automatically to flash five seconds after your last gesture, so the lamp restores its state after a power cycle.

The APDS-9960 combines gesture detection, proximity sensing, and ambient light measurement in a single I2C breakout. The ESP32 reads gesture interrupts in the main loop and passes the result to FastLED. There is no app, no Wi-Fi setup, and no buttons — the lamp responds only to hand movements held 5–10 cm above the sensor.

The firmware has four jobs: initialise the APDS-9960 over I2C and enable gesture detection; translate left/right gestures to hue changes and up/down gestures to brightness changes; drive the WS2812B LED Strip with FastLED using the current hue and brightness; and save colour and brightness to the ESP32's non-volatile Preferences storage after five seconds of inactivity.

Upload and calibrate

Flash the sketch from Schematik and open Serial Monitor at 115200 baud. On boot you should see Mood Lamp ready. The LEDs light at the last saved colour and brightness, or a default colour on first boot.

Swipe left or right horizontally over the sensor to shift the hue. Swipe up or down to adjust brightness. Each swipe shifts hue by 24 steps on a 256-step wheel, and brightness by 15 steps on a 255-step scale. After five seconds of no gesture the current state saves to Preferences. Cycle the power and confirm the lamp restores the same colour.

The default strip length is 16 LEDs. If your strip is longer or shorter, update NUM_LEDS in the firmware and re-flash.

Troubleshooting

  • APDS-9960 init fails on boot (APDS-9960 init failed in Serial). Confirm SDA is GPIO 21 and SCL is GPIO 22, and that the sensor VCC is on 3.3 V. The default I2C address for APDS-9960 is 0x39 — some breakout versions differ, though this is uncommon.
  • Gestures not detected or misfiring. Hold your hand 5–10 cm above the sensor. Bright direct light from above can swamp the photodiodes; shade the sensor or reduce ambient light. Confirm INT is connected to GPIO 27 with INPUT_PULLUP.
  • LEDs flicker or show wrong colours. Check that GND is shared between the ESP32 and the 5 V supply. A missing common ground is the most common cause of WS2812B data errors. Also verify the 330 Ω series resistor is on the DIN line.
  • First LED burns out. Power-on inrush without the 470 µF capacitor can damage the first LED in a strip. Replace the LED and add the capacitor before powering up again.
  • State does not save. Preferences writes happen five seconds after the last gesture. If you power off immediately after a gesture, the new colour has not saved yet. Wait for the five-second window before cycling power.

Going further

With gesture control and persistent state in place, a straightforward next step is adding Wi-Fi so you can also set colour from a browser. The Voice-Controlled Robot Face shows the pattern for hosting a small web page on the ESP32 that accepts GET requests to change state. For a multi-lamp setup, each ESP32 could subscribe to an MQTT topic and change colour together, turning the gesture on one lamp into a group scene change across a room.

Wiring diagram

Loading diagram…
Interactive wiring diagram

Components needed

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

1

Connect the gesture sensor

Wire APDS-9960 VCC to 3.3V, GND to ground, SDA to GPIO21, SCL to GPIO22, and INT to GPIO27.

2

Wire the LED strip

Connect WS2812B 5V and GND to a stable 5V power source, and DIN to GPIO4. Share ground with the ESP32.

3

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.

Pin assignments

PinConnectionType
3V3apds9960-1 VCCPOWER
GNDapds9960-1 GNDGROUND
GPIO 21apds9960-1 SDAI2C
GPIO 22apds9960-1 SCLI2C
GPIO 27apds9960-1 INTDIGITAL
5Vws2812b-1 5VPOWER
GNDws2812b-1 GNDGROUND
GPIO 4ws2812b-1 DINDATA

Code

Arduino C++
#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.io
Libraries: Adafruit APDS9960 Library, Adafruit BusIO, FastLED