Gesture-Controlled Lamp with Arduino and the APDS-9960
A light switch is already convenient, but waving a lamp off while your hands are full is a genuinely nicer trick. This project uses the APDS-9960 gesture sensor — the same family of chip found in older phones’ “swipe over the screen” detection — to recognize four wave directions and control an LED lamp’s brightness over PWM.
How it works
- The APDS-9960 has four infrared photodiodes in its corners and detects the order in which reflected light changes as your hand passes over it, inferring a direction: UP, DOWN, LEFT, or RIGHT.
- The Arduino reads gesture events over I2C using the
SparkFun_APDS9960library. - UP/DOWN raises or lowers the lamp’s brightness by a fixed step (PWM on the pin driving a MOSFET).
- LEFT or RIGHT toggles the lamp fully on or off (turning back on restores the last saved brightness).
Parts list
- Arduino Uno/Nano (or any 5V/3.3V board with I2C)
- APDS-9960 gesture sensor breakout (most cheap breakouts already include a 3.3V regulator and level shifting)
- N-channel MOSFET (e.g. IRLZ44N — a low gate threshold that switches reliably straight from an Arduino pin)
- 12V LED strip (or a 12V LED bulb) + a separate 12V supply
- 10kΩ pull-down resistor on the MOSFET gate
- Flyback diode (optional, protects against any inductive load)
Sample code
#include <Wire.h>
#include <SparkFun_APDS9960.h>
SparkFun_APDS9960 apds = SparkFun_APDS9960();
const int LED_PIN = 9; // PWM pin wired to the MOSFET gate
const int BRIGHTNESS_STEP = 25;
const int MAX_BRIGHTNESS = 255;
int brightness = 0;
int lastBrightness = 150; // brightness remembered when turning back on
bool lampOn = false;
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
if (apds.init()) {
Serial.println("APDS-9960 initialized");
} else {
Serial.println("Failed to initialize APDS-9960");
}
if (apds.enableGestureSensor(true)) {
Serial.println("Gesture sensor enabled");
}
}
void applyBrightness() {
analogWrite(LED_PIN, lampOn ? brightness : 0);
}
void loop() {
if (apds.isGestureAvailable()) {
int gesture = apds.readGesture();
switch (gesture) {
case DIR_UP:
if (!lampOn) lampOn = true;
brightness = min(brightness + BRIGHTNESS_STEP, MAX_BRIGHTNESS);
lastBrightness = brightness;
Serial.println("Brightness up");
break;
case DIR_DOWN:
brightness = max(brightness - BRIGHTNESS_STEP, 0);
lastBrightness = brightness;
Serial.println("Brightness down");
break;
case DIR_LEFT:
case DIR_RIGHT:
lampOn = !lampOn;
if (lampOn && brightness == 0) {
brightness = lastBrightness > 0 ? lastBrightness : 150;
}
Serial.println(lampOn ? "Lamp on" : "Lamp off");
break;
default:
break;
}
applyBrightness();
}
}
The library’s default gesture thresholds can be too twitchy or too sluggish depending on your mounting distance — call
apds.setGestureGain(...)or tuneGGAIN/GPTHRin the library’s config header if gestures are being missed or misfired repeatedly.
Common pitfalls
- Mounting the sensor near a strong light source (ceiling light, window) floods the APDS-9960 with infrared background noise — mount it somewhere shielded from direct light, or add a small enclosure.
- Undersized or unheatsinked MOSFET when driving a long LED strip — check the MOSFET’s continuous
Idrating against the strip’s actual current draw, and add a small heatsink above roughly 2A. - Skipping the gate pull-down resistor: if the Arduino resets or loses signal, a floating gate can leave the lamp flickering or stuck full-on.
- The APDS-9960’s effective wave range is only about 10-15cm — place it somewhere a hand can reach naturally, like a desk edge or under a nightstand shelf.
Where to go from here
Swap in an ESP32 to publish lamp state over MQTT and integrate with Home Assistant, or use the same chip’s NEAR/FAR proximity gesture to turn the lamp on automatically when someone approaches, without needing a wave at all.
Comments