To control a 120V AC lamp with a Raspberry Pi, you cannot connect the mains voltage directly to the board's 3.3V GPIO pins. You must use a 5V optocoupler relay module to provide galvanic isolation between the low-voltage DC logic and the high-voltage AC load. The Raspberry Pi 4 Model B GPIO pin 18 (BCM 18, Physical Pin 12) drives the relay's input, while the relay's Common (COM) and Normally Open (NO) terminals switch the lamp's hot (line) wire. This guide covers the exact hardware, safe mains wiring procedures, and robust Python code to get your raspberry pi lamp control project running without bricking your board or tripping a breaker.

Project Spec Sheet & Hardware Requirements

Difficulty Rating: Intermediate (Requires mains AC wiring experience)
Estimated Time: 90 minutes
Estimated Cost: $85 - $100 (assuming you already own basic hand tools and a multimeter)

The most common mistake in embedded mains projects is underspecifying the relay or using flimsy jumper wires for the AC side. The table below details the exact components required for a safe, code-compliant (NEC-style guidance for hobbyist bench setups) build.

Component Exact Variant / Model Est. Cost Technical Notes
Microcontroller Raspberry Pi 4 Model B (4GB RAM) $55.00 Target board for this guide. BCM2711 SoC.
Relay Module 5V 1-Channel Optocoupler (Songle SRD-05VDC-SL-C) $6.50 Rated 10A @ 120VAC. Optocoupler isolates logic.
AC Load Standard 120V AC Desk Lamp (LED or Incandescent) $20.00 Keep under 60W to avoid inrush current contact welding.
Mains Wiring 18 AWG THHN Stranded Copper (Black/White/Green) $15.00 Minimum 14 AWG required by NEC for 15A branch circuits; 18 AWG acceptable for internal appliance/lamp wiring.
Logic Wiring 22 AWG Silicone Female-to-Female Jumpers $5.00 Silicone insulation resists melting if routed near warm components.
Connectors Wago 221 Lever Nuts (3-Port) $4.00 Superior to twist-on wire nuts for mixing solid and stranded wire.

Pin Mapping & AC Mains Wiring Safety

⚠️ HIGH VOLTAGE WARNING: This project involves 120V AC mains electricity, which can be lethal. Before making any connections, ensure the lamp is unplugged and the circuit breaker is OFF. Verify the wires are dead using a Non-Contact Voltage (NCV) tester or a multimeter set to AC voltage. Local electrical codes may require a licensed electrician for permanent in-wall wiring; this guide is for bench-top, plug-in hobbyist setups only.

The Raspberry Pi GPIO operates at 3.3V logic, but the Songle relay coil requires 5V to pull the mechanical contact closed. Furthermore, the relay's 'IN' pin usually features an optocoupler LED that drops about 1.2V, meaning the 3.3V GPIO signal is perfectly sufficient to trigger the 5V-powered relay module safely.

Raspberry Pi 4 Pin BCM GPIO Number Relay Module Pin Function / Notes
Pin 2 N/A (5V Power) VCC Powers the relay coil and optocoupler.
Pin 6 N/A (Ground) GND Common ground reference for DC logic.
Pin 12 GPIO 18 IN Control signal (Active LOW on most modules).

Step-by-Step Wiring Procedure

  1. DC Logic Side: Connect Pi Pin 2 to Relay VCC, Pi Pin 6 to Relay GND, and Pi Pin 12 to Relay IN. Do not power the Pi yet.
  2. AC Mains Side (Lamp Cord): Cut the hot (black/smooth) wire of the lamp's power cord. Strip 1/2 inch of insulation from both cut ends using wire strippers.
  3. Relay Terminals: Loosen the screw terminals on the relay module's blue terminal block. Insert one stripped end of the lamp's hot wire into the COM (Common) terminal and tighten. Insert the other stripped end into the NO (Normally Open) terminal.
  4. Neutral and Ground: The lamp's neutral (white/ribbed) wire must remain continuous. If you cut it to route through an enclosure, reconnect it using a Wago 221 lever nut. The ground wire (green/bare) must be bonded to the lamp's metal chassis if it is Class I insulated.
  5. Physical Separation: Ensure the 120V AC THHN wires are physically separated from the 22 AWG DC jumper wires by at least 2 inches to prevent capacitive coupling and reduce the risk of a short circuit if a wire pulls loose.

Python Control Script with Error Handling

The following script targets the Raspberry Pi 4 Model B running Raspberry Pi OS (Bookworm or later). It uses the RPi.GPIO library. Most 5V relay modules are 'Active LOW', meaning the relay engages when the IN pin is pulled to ground (0V) and disengages when it is HIGH (3.3V). The code below accounts for this logic inversion and includes robust error handling to ensure the GPIO pins are cleaned up if the script crashes.

import RPi.GPIO as GPIO
import time
import sys

# --- Pin Definitions ---
# BCM GPIO 18 corresponds to Physical Pin 12 on the Pi 40-pin header
RELAY_PIN = 18 

# Most 5V relay modules are Active LOW
# GPIO.LOW turns the relay ON (closes the NO contact)
# GPIO.HIGH turns the relay OFF (opens the NO contact)
RELAY_ON = GPIO.LOW
RELAY_OFF = GPIO.HIGH

def setup_gpio():
    """Initialize GPIO settings with safety defaults."""
    GPIO.setmode(GPIO.BCM)
    GPIO.setwarnings(False)
    # Set pin as output and default to HIGH (Relay OFF) for safe startup
    GPIO.setup(RELAY_PIN, GPIO.OUT, initial=RELAY_OFF)

def toggle_lamp(duration_on=5, duration_off=5, cycles=3):
    """Cycle the lamp on and off with error handling."""
    try:
        setup_gpio()
        print(f'Starting lamp control sequence for {cycles} cycles...')
        
        for i in range(1, cycles + 1):
            print(f'Cycle {i}/{cycles}: Turning Lamp ON')
            GPIO.output(RELAY_PIN, RELAY_ON)
            time.sleep(duration_on)
            
            print(f'Cycle {i}/{cycles}: Turning Lamp OFF')
            GPIO.output(RELAY_PIN, RELAY_OFF)
            if i < cycles:
                time.sleep(duration_off)
                
    except RuntimeError as e:
        # Catch specific SoC peripheral errors common on Pi 4/5
        print(f'RuntimeError caught: {e}')
        sys.exit(1)
    except KeyboardInterrupt:
        print('\nSequence interrupted by user.')
    except Exception as e:
        print(f'An unexpected error occurred: {e}')
    finally:
        # CRITICAL: Always cleanup to release hardware resources
        GPIO.cleanup()
        print('GPIO cleanup complete. Relay is safely de-energized.')

if __name__ == '__main__':
    # Run 3 cycles: 5 seconds ON, 3 seconds OFF
    toggle_lamp(duration_on=5, duration_off=3, cycles=3)

Debugging: "RuntimeError: Cannot determine SOC peripheral base address"

When working with Raspberry Pi GPIO code, few errors are as frustrating as running your script and immediately seeing this traceback:

RuntimeError: Cannot determine SOC peripheral base address

This error occurs when the RPi.GPIO library attempts to map the memory addresses of the Broadcom System-on-Chip (SoC) peripherals but fails to read the device tree or lacks the permissions to access /dev/mem or /dev/gpiomem. According to the official Raspberry Pi hardware documentation, the memory mapping changed significantly between the BCM2837 (Pi 3) and the BCM2711 (Pi 4) / BCM2712 (Pi 5).

The First Three Things to Check When It Fails

  1. Insufficient Permissions: The most common cause is running the script as a standard user without access to the GPIO memory group. Fix: Run your script with sudo python3 lamp_control.py, or add your user to the gpio group via sudo usermod -aG gpio $USER and reboot.
  2. Outdated RPi.GPIO Library: If you are using a Pi 4 or Pi 5, older versions of RPi.GPIO (pre-0.7.0) do not recognize the BCM2711/2712 SoC base addresses. Fix: Update the library using pip3 install --upgrade RPi.GPIO. For Pi 5 specifically, consider migrating to the gpiozero library with the lgpio backend, as RPi.GPIO is legacy.
  3. Missing Device Tree Mappings (Custom OS): If you are running a stripped-down Docker container, a custom Yocto build, or Ubuntu Server without the Raspberry Pi kernel overlays, the /proc/device-tree/soc/ranges file may be missing. Fix: Ensure you are using the official Raspberry Pi OS, or manually mount the /dev/gpiomem device into your container.

Extending and Simplifying the Build

Once you have the basic raspberry pi lamp control working, you will likely want to either reduce the hardware complexity or add smart-home integration. Here is how to approach both paths based on NFPA electrical safety guidelines and modern IoT protocols.

How to Simplify: The Software-Only Route

If your primary goal is simply to turn a lamp on and off via Python without dealing with 120V AC wiring, optocouplers, and relay modules, ditch the hardware relay entirely. Purchase a Wi-Fi smart plug like the TP-Link Tapo P110 or Kasa EP25 (approx. $12-$15). You can control these plugs directly from your Raspberry Pi using the open-source python-kasa library or via the Matter protocol. This completely eliminates the electrocution hazard, requires zero wire stripping, and takes 5 minutes to set up.

How to Extend: Daylight Harvesting & MQTT

If you want to push the hardware build further, consider these two upgrades:

  • Add an LDR for Daylight Harvesting: Wire a Light Dependent Resistor (LDR) in a voltage divider circuit to the Pi's GPIO. Since the Pi lacks a built-in Analog-to-Digital Converter (ADC), route the LDR signal through an MCP3008 SPI ADC chip. Write a Python loop that reads the ambient light level and only triggers the relay if the room is darker than a set threshold (e.g., < 300 lux).
  • Integrate MQTT for Home Assistant: Replace the time.sleep() loop with an MQTT client using the paho-mqtt Python library. Subscribe to a topic like home/office/lamp/set. This allows your Raspberry Pi to act as a bridge, letting you control the physical lamp from Home Assistant dashboards, Zigbee switches, or voice assistants via your local network.

By understanding the isolation requirements of AC mains and the specific memory-mapping quirks of the Raspberry Pi's SoC, you can build embedded lighting controls that are both robust and safe for long-term bench operation.