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.
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
| Component | Arduino |
|---|---|
| PIR OUT | D2 |
PIR VCC/GND | 5V / GND |
| LDR top leg | 5V |
LDR bottom leg + 10k to GND; junction to | A0 |
| Relay IN | D8 |
Relay VCC/GND | 5V / GND |
| Lamp +12V | Relay NO (normally open) contact |
| Lamp return | 12V 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
| Sensor | Question | Why it matters |
|---|---|---|
| PIR | Is something moving? | Fires on cats, curtains, HVAC vents |
| LDR | Is 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:
| Control | Setting for this build |
|---|---|
| Sensitivity | 60-70% (range of 3-5 m; full range triggers through walls) |
| Time delay | Minimum (the code does its own hold timing) |
| Jumper | H (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.