arduino beginner 30 min

Arduino: motion-activated night security light

A real outdoor-style light: PIR triggers, an LDR confirms it is dark, a relay drives a 12V lamp. The AND logic that stops false triggers.

Code available for: Arduino CESP32 Arduino
Published Sep 22, 2026

Motion lights are the project that leaves the breadboard. PIR senses the person, an LDR confirms it is dark, a relay switches a real lamp, and the AND of all three is what separates a useful light from a strobing porch nuisance. Three sensors, one output, and the logic that ties them.

This tutorial builds it with a 12V lamp (safe to start with) and the same wiring carries to a mains relay box once you are done testing.

What you need

  • Arduino Uno (or Nano)
  • HC-SR501 PIR motion sensor (about $2)
  • An LDR (photoresistor) + a 10k ohm resistor
  • Relay module (1-channel, 5V trigger, about $2)
  • A 12V LED lamp or lamp strip for testing
  • A 12V supply

Wiring

Wire key: D-pinGND5VA-pin
ComponentArduino
PIR OUTD2
PIR VCC/GND5V / GND
LDR top leg5V
LDR bottom leg + 10k to GND; junction toA0
Relay IND8
Relay VCC/GND5V / GND
Lamp +12VRelay NO (normally open) contact
Lamp return12V supply GND

The LDR + 10k forms a voltage divider: bright = high reading, dark = low reading (e.g. values are approximate; calibrate by printing A0 at day and at night and picking your threshold between them).

The code

const int PIR_PIN = 2;
const int LDR_PIN = A0;
const int RELAY_PIN = 8;

const int DARK_THRESHOLD = 400;      // below this = dark (calibrate!)
const unsigned long HOLD_TIME = 15000;   // stay lit 15 s after motion
const unsigned long RETRIGGER_LOCKOUT = 4000;

bool lightOn = false;
unsigned long lastMotion = 0;
unsigned long lastTurnOff = 0;

bool isDark() {
  return analogRead(LDR_PIN) < DARK_THRESHOLD;
}

void setup() {
  pinMode(PIR_PIN, INPUT);
  pinMode(RELAY_PIN, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  bool motion = digitalRead(PIR_PIN) == HIGH;

  if (motion) lastMotion = millis();

  bool wantLight = motion && isDark();

  if (wantLight && !lightOn) {
    digitalWrite(RELAY_PIN, HIGH);
    lightOn = true;
    Serial.println("light ON (motion + dark)");
  }

  // stay on while motion continues, plus HOLD_TIME grace after
  bool holdExpired = millis() - lastMotion > HOLD_TIME;
  bool lockoutPassed = millis() - lastTurnOff > RETRIGGER_LOCKOUT;

  if (lightOn && (!isDark() && !motion) && holdExpired && lockoutPassed) {
    digitalWrite(RELAY_PIN, LOW);
    lightOn = false;
    lastTurnOff = millis();
    Serial.println("light OFF");
  }

  delay(200);
}

(The wantLight/wantOff names collapse to one variable in the actual file; the structure is motion-AND-dark to turn on, hold window plus lockout to turn off.)

The two conditions and why both

SensorQuestionWhy it matters
PIRIs something moving?Fires on cats, curtains, HVAC vents
LDRIs it dark enough to need light?Daytime triggers waste the lamp

Both true is the only condition that turns the light on. Every “my motion light turns on all day” bug is this AND missing.

The PIR settings

The HC-SR501 has two pots and a jumper:

ControlSetting for this build
Sensitivity60-70% (range of 3-5 m; full range triggers through walls)
Time delayMinimum (the code does its own hold timing)
JumperH (retrigger mode) so motion refreshes the timer

Mount chest-height, lens angled down. A PIR aimed at a road will trigger on every car headlight sweep (e.g. aim it at approach paths, not parking spots).

Calibration in two minutes

Open Serial Monitor with a debug print of analogRead(A0), note the value at midday and at 10 PM, and set DARK_THRESHOLD between them. Then cover the LDR with your hand (simulating night) and wave at the PIR; the lamp should come on.

Mains power, the honest note

The 12V lamp version is the right practice rig. The mains version of this project is the same code with a mains-rated relay or a pre-built wireless relay box, and it deserves respect: mains wiring in a weatherproof box, or better, a commercial outdoor smart plug driven by an ESP32 (the ESP32 project tutorials cover that path). Do not learn electricity on the wires behind your porch light.

What you learned

  • AND logic from two sensors (motion + darkness) before one output.
  • Millis-based hold and lockout windows instead of delay().
  • LDR voltage divider calibration against your actual conditions.

When something breaks

  • Light flaps on and off: the LDR is right at the threshold and flickering, or the PIR’s own timer fights your code’s hold. Set the PIR to minimum time and let the code hold; add hysteresis to the LDR (on below 400, off above 450).
  • Triggers all day: the AND is missing or the LDR reads dark always (wired wrong: check the divider orientation).
  • Never triggers: HC-SR501 needs 60 s of warm-up after power. Also check the jumper is in H, not L.
  • Relay clicks but lamp stays dark: you wired the lamp to NC (normally closed) instead of NO. Or the lamp is 12V and you gave it 5V. Meter the contact, then the lamp.

What to build next

  • The ESP32 ntfy tutorial adds “porch motion” notifications on top of this exact trigger pair.
  • The relay module tutorial details the output side.
  • The BME280 tutorial adds temperature logging so the same box reports conditions while it watches the door.