Night light
A night light is the friendliest complete build: one sensor, a little maths, and an output you can see. A light sensor (LDR) sits in a divider, the MCP3008 digitises the divider's midpoint, and the program switches an LED on when the reading falls below a dark threshold — and back off when the room is bright again, with a gap in between so it does not flicker at dusk.
Cheap version — wire it by hand:
- Light sensor (LDR) — $0.75
- MCP3008 ADC — $4
- Two resistors: 10 kΩ for the divider, 330 Ω for the LED — 2 × $0.25
- LED — $1
Fast version — build it from kits:
- Light sensor + ADC board — $9: the LDR, its divider resistor and the MCP3008 already wired
- LED module — $2: the LED with its 330 Ω resistor
Both versions run the same program: the light reading arrives on MCP3008 channel 0, and the lamp hangs off GPIO18.
The cheap version
The LDR is the top half of the divider and the 10 kΩ resistor the bottom half, so the tap voltage — and the reading — rises with light: about 0.9 in a lit room and 0.09 at 1 lux. The LED gets its usual series resistor.
from wiring import *
pi = Pi()
ldr = LDR()
adc = MCP3008()
pi.V3_3 >> ldr.a
ldr.b >> adc.ch0
adc.ch0 >> Resistor(10000) >> pi.GND
pi.V3_3 >> adc.vdd
adc.vdd >> adc.vref
adc.agnd >> pi.GND
adc.dgnd >> pi.GND
pi.SCLK >> adc.clk
pi.MISO >> adc.dout
pi.MOSI >> adc.din
pi.CE0 >> adc.cs
pi.GPIO18 >> Resistor(330) >> LED("yellow") >> pi.GNDfrom gpiozero import LED, MCP3008
from time import sleep
light = MCP3008(channel=0) # the LDR divider: 0.0 dark … 1.0 bright
lamp = LED(18)
DARK_BELOW = 0.3 # switch on below this
BRIGHT_ABOVE = 0.5 # …and off above this; the gap stops dusk flicker
while True:
level = light.value
if level < DARK_BELOW:
lamp.on()
elif level > BRIGHT_ABOVE:
lamp.off()
print(f"light {level:.2f} lamp {'on' if lamp.value else 'off'}")
sleep(0.5)The fast version
The light sensor + ADC board is the divider and the chip in one piece, and the LED module is the LED with its resistor, so the wiring is six connections instead of fifteen. The program does not change at all — the board exposes the same channel 0.
from wiring import *
pi = Pi()
light = kits.LightADC()
lamp = kits.LEDModule()
pi.V3_3 >> light.vdd
light.gnd >> pi.GND
pi.SCLK >> light.clk
pi.MISO >> light.dout
pi.MOSI >> light.din
pi.CE0 >> light.cs
pi.GPIO18 >> lamp.sig
lamp.gnd >> pi.GNDfrom gpiozero import LED, MCP3008
from time import sleep
light = MCP3008(channel=0) # the LDR divider: 0.0 dark … 1.0 bright
lamp = LED(18)
DARK_BELOW = 0.3 # switch on below this
BRIGHT_ABOVE = 0.5 # …and off above this; the gap stops dusk flicker
while True:
level = light.value
if level < DARK_BELOW:
lamp.on()
elif level > BRIGHT_ABOVE:
lamp.off()
print(f"light {level:.2f} lamp {'on' if lamp.value else 'off'}")
sleep(0.5)How it works
MCP3008(channel=0)from gpiozero talks to the chip over the Pi's hardware SPI pins and returns the tap voltage as a number from 0 to 1. Brighter light means less LDR resistance, a higher tap and a higher reading: roughly 0.09 at 1 lux, 0.34 at 10 lux, 0.72 at 100 lux and 0.93 at 1000 lux.LED(18)is the output.lamp.valuefollows the threshold test, and the program prints both values every half second so the console shows why the lamp changed.- The two thresholds are hysteresis. With a single threshold at 0.4 the lamp would chatter whenever the reading sat on the boundary (clouds, a passing shadow); switching on below 0.3 and off above 0.5 makes every change deliberate.
- The LDR's reading follows the simulated light on the cell, not a fixed number: the LDR by default watches the illuminance of its zone, refreshing every 50 ms, so the program can genuinely darken and brighten.
Try it
Build the board in the Workspace and press Run. The LDR starts in the indoor zone, where the simulated room's lamps are on from 07:00 to 23:00 (about 300 lux) and off overnight (about 0.5 lux) — so at night the console shows light 0.09 lamp on, and in daylight light 0.91 lamp off.
To fake darkness at any hour, select the LDR and use its Datafeed section in the inspector:
- Set Source to Manual and drag the value slider to 1 lux, then 300 lux. While the simulation runs the slider moves the live value — the lamp follows within a poll or two.
- Or set the field's Zone to
outdoorto follow real daylight, or add a Wave source (period 60 s) to sweep the light automatically. The sparkline previews the next minute, hour or day before you run it.
As a contest submission, the run would be an objective that pins the world and checks behaviour: the built-in medium objective Night light sets the sensor's illuminance to 1000 lux, drops it to 2 lux at 2 s and restores it at 5 s, then requires the LED to be off, on and off again — plus a damage check. Prints are not part of the verdict, so the console lines are for you.
Variations
- Fade, don't switch: use
PWMLED(18)and map the reading to brightness, e.g.lamp.value = max(0.0, min(1.0, (0.6 - level) / 0.5)), for a lamp that dims up as dusk falls. The ready-made Night light example project in the Projects dialog does exactly this. - Dusk in a minute: set the World dialog's "Simulation starts at" to an evening time and run at 100× speed. Point the LDR's zone at
outdoorand you can watch a whole sunset move the lamp across its thresholds. - Wake-up light: invert the logic and drive a white LED or the relay + lamp kit on a schedule, using the same sensor to know when to stop.
Going deeper
- Primer: analog inputs and ADCs — why the Pi has no analog pins, and where the divider comes in.
- Parts: LDR, MCP3008, resistor, LED.
- Kits: light sensor + ADC board, LED module.
- Library: gpiozero covers
MCP3008,LEDandPWMLED. - The sibling analog recipes: LCD weather station reads a DHT22, and the NeoPixel level meter reads a knob through the same chip.