Project Overview & Camera Module Selection

Building a reliable raspberry pi surveillance camera requires moving past legacy tutorials. With the release of Raspberry Pi OS Bookworm and the Pi 5, the legacy picamera library is deprecated. Modern builds must use the picamera2 Python API and the libcamera stack. This guide targets the Raspberry Pi 5 (8GB variant) paired with a hardware PIR (Passive Infrared) sensor for zero-latency wake-up, bypassing the CPU overhead of continuous software-based frame differencing.

Before ordering parts, you must select the right camera module. The physical form factor and sensor capabilities dictate your low-light performance and field of view (FoV). Below is a spec-sheet comparison of the current official modules to help you choose the right optic for your deployment environment.

Table 1: Official Raspberry Pi Camera Module Comparison (2026)
Module Variant Sensor & Resolution Field of View (FoV) Low-Light / HDR Approx. Price Best Deployment
Camera Module 3 (Standard) Sony IMX708, 12MP (4608x2592) 75° (H) x 58° (V) Excellent (Native HDR + PDAF) $30 General indoor/outdoor, porch monitoring
Camera Module 3 (Wide) Sony IMX708, 12MP (4608x2592) 120° (H) x 90° (V) Excellent (Native HDR + PDAF) $35 Small rooms, blind-corner coverage
HQ Camera (12MP) Sony IMX477, 12.3MP Depends on C/CS Lens Good (Larger 1.55µm pixels) $50 + Lens Long-range zoom, license plate capture
GS Camera (Global Shutter) Sony IMX296, 1.58MP Depends on C/CS Lens Poor (Monochrome/Color variants) $50 + Lens High-speed industrial, no motion blur

Recommendation: For 90% of DIY surveillance builds, the Camera Module 3 (Standard) offers the best balance of resolution, native Phase Detection Auto Focus (PDAF), and hardware-level HDR for dealing with porch backlighting.

Parts List & Pin Mapping

A common failure point in Pi 5 camera builds is the ribbon cable. The Pi 5 uses smaller 22-pin CSI/DSI connectors, whereas the Camera Module 3 ships with a 15-pin to 15-pin cable. You must acquire a 15-pin to 22-pin adapter cable.

Required Hardware

  • Compute: Raspberry Pi 5 (8GB RAM) - Handles libcamera ISP pipelines without thermal throttling.
  • Optic: Raspberry Pi Camera Module 3 (IMX708).
  • Cable: 15-pin to 22-pin CSI ribbon cable (Pi Zero / Pi 5 specific).
  • Trigger: AM312 Mini PIR Motion Sensor (Strictly 3.3V logic output, safe for Pi 5 GPIO).
  • Power: Official 27W USB-C PD Power Supply (5V/5A). Do not use standard phone chargers; the Pi 5 will throttle peripherals if it cannot negotiate 5A via PD.
  • Storage: 32GB+ microSD card (A2 application performance class rated for high IOPS).

GPIO Pin Mapping (Pi 5 to AM312 PIR)

We use the AM312 instead of the common HC-SR501 because the HC-SR501 outputs 5V on its data pin when powered, which will fry the Pi 5's 3.3V GPIO bank. The AM312 operates natively at 3.3V.

Table 2: Pi 5 GPIO to AM312 PIR Wiring
AM312 Pin Pi 5 Physical Pin Pi 5 GPIO / Function Wire Color (Standard)
VCC Pin 1 3.3V Power Red
GND Pin 9 Ground Black
OUT Pin 11 GPIO 17 (Input/Pull-down) Yellow/Orange

Assembly & Software Setup

Follow this sequence to ensure the hardware and software stack are correctly initialized before writing code.

  1. Seat the CSI Cable: Lift the black plastic retaining collar on the Pi 5's CAM1 connector. Insert the 22-pin end of the ribbon cable. Critical: The exposed metal contacts must face towards the USB-C power port. Push the collar down to lock.
  2. Connect the PIR: Wire the AM312 to Pin 1, Pin 9, and Pin 11 as mapped above. Use Dupont connectors or solder directly for permanent deployments.
  3. Flash the OS: Use Raspberry Pi Imager to flash Raspberry Pi OS (64-bit, Bookworm). Do not use Bullseye; it lacks the native Pi 5 kernel support and modern libcamera stack.
  4. Update and Install Dependencies: Boot the Pi, open a terminal, and run the following commands to ensure the camera firmware and Python bindings are current:
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y python3-picamera2 python3-libcamera python3-rpi-lgpio
  5. Verify Hardware Detection: Run libcamera-hello --list-cameras. You should see 0 : imx708 [4608x2592 10-bit] in the output. If you do not, halt and check your ribbon cable orientation.

Complete Python Surveillance Code

This script targets the Raspberry Pi 5 (8GB) running Bookworm. It uses picamera2 for hardware-accelerated image capture and gpiozero (backed by rpi-lgpio) for the PIR interrupt. It includes robust error handling for camera initialization and GPIO factory fallbacks.

import os
import time
import logging
from datetime import datetime
from picamera2 import Picamera2
from gpiozero import MotionSensor
from gpiozero.exc import GpioZeroError

# --- PIN & PATH DEFINITIONS ---
PIR_PIN = 17  # Physical Pin 11 / BCM GPIO 17
SAVE_DIR = "/home/pi/surveillance_captures"
LOG_FILE = "/home/pi/surveillance.log"

# Setup logging
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s"
)

# Ensure save directory exists
os.makedirs(SAVE_DIR, exist_ok=True)

def init_camera():
    """Initialize Picamera2 with a 1080p still configuration."""
    try:
        camera = Picamera2()
        # Configure for 1080p stills to balance file size and detail
        config = camera.create_still_configuration(
            main={"size": (1920, 1080), "format": "RGB888"}
        )
        camera.configure(config)
        camera.start()
        logging.info("Camera initialized and started successfully.")
        return camera
    except RuntimeError as e:
        logging.critical(f"Camera initialization failed: {e}")
        raise

def capture_event(camera):
    """Capture and save an image upon motion trigger."""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = os.path.join(SAVE_DIR, f"motion_{timestamp}.jpg")
    
    # Capture metadata and image
    metadata = camera.capture_file(filename)
    logging.info(f"Motion detected. Saved: {filename} | Exposure: {metadata.get('ExposureTime', 'N/A')}us")

def main():
    camera = None
    pir = None
    
    try:
        camera = init_camera()
        # Initialize PIR sensor with a 2-second queue to debounce false triggers
        pir = MotionSensor(PIR_PIN, queue_len=2, threshold=0.5)
        logging.info(f"PIR Sensor active on GPIO {PIR_PIN}. Waiting for motion...")
        
        while True:
            # Block until motion is detected (hardware interrupt driven)
            pir.wait_for_motion(timeout=None)
            capture_event(camera)
            # Cooldown to prevent SD card flooding from continuous motion
            time.sleep(3) 
            
    except GpioZeroError as e:
        logging.error(f"GPIO Error: {e}. Ensure rpi-lgpio is installed for Pi 5.")
    except KeyboardInterrupt:
        logging.info("Shutdown requested by user.")
    except Exception as e:
        logging.error(f"Unexpected error: {e}")
    finally:
        if camera:
            camera.stop()
            logging.info("Camera stopped.")

if __name__ == "__main__":
    main()

Debugging: Camera Errors & GPIO Failures

When building a raspberry pi surveillance camera, hardware and driver mismatches are the primary culprits for failure. If your script crashes on startup, check these first three things:

  1. Ribbon Cable Orientation & Seating: 80% of "camera not found" errors are caused by the CSI cable being inserted upside down or not fully seated before locking the collar.
  2. Power Supply Brownouts: The Pi 5 will silently disable the camera I2C bus if the power supply cannot deliver 5A. Check dmesg | grep -i voltage for undervoltage warnings.
  3. Missing lgpio Backend: Pi 5 requires rpi-lgpio to translate gpiozero commands to the new RP1 silicon. If you missed the apt install step, GPIO will fail.

Exact Error Strings & Ranked Causes

Error 1: RuntimeError: Failed to open camera or [0:12:34.567] ERROR Camera camera_manager.cpp:284 : Camera manager failed to start

  • Cause A (Most Likely): The 22-pin to 15-pin ribbon cable is loose, or the contacts are facing the Ethernet port instead of the USB-C port.
  • Cause B: I2C bus conflict. Another HAT or peripheral is holding the I2C lines (GPIO 2/3) low, preventing the camera EEPROM from being read.
  • Cause C: You are running a 32-bit OS or an older Bullseye kernel that lacks the Pi 5 RP1 media driver.

Error 2: gpiozero.exc.GpioZeroError: Unable to load any default pin factory

  • Cause A (Most Likely): The python3-rpi-lgpio package is not installed. The Pi 5's RP1 chip is not natively supported by the legacy RPi.GPIO factory.
  • Cause B: You are running the script inside a virtual environment (venv) that does not have system site-packages enabled, isolating it from the apt-installed GPIO libraries.

Extending vs. Simplifying the Build

Depending on your deployment environment, you may need to alter the complexity of this surveillance node.

How to Simplify (The Timelapse Route)

If you are monitoring a construction site or a garden where continuous recording is preferred over instant motion alerts, strip out the PIR sensor and gpiozero entirely. Replace the while True loop with a simple time.sleep(300) (5 minutes) interval. You can then run the script via a systemd service or a basic cron job, eliminating the need for GPIO wiring and reducing power draw by allowing the Pi to idle between captures.

How to Extend (Software AI & NVR Integration)

Hardware PIR sensors are excellent for waking the camera, but they cannot tell the difference between a human, a stray cat, and a swaying tree branch. To extend this build into a smart security node:

  • Add OpenCV Zone Masking: Import cv2 and use the picamera2 preview stream to draw a polygon mask over the street, ignoring motion pixels that fall outside your property line.
  • Integrate Frigate NVR: Instead of saving local JPEGs, configure the Pi 5 to output an RTSP stream using mediamtx. Point a centralized Frigate NVR instance at the stream. Frigate will use a Coral TPU or the Pi 5's CPU to run YOLO object detection, sending you Telegram or Home Assistant notifications only when a "Person" or "Car" is classified.
  • Implement Watchdog Timers: Use the hardware watchdog built into the Pi 5's RP1 chip to automatically reboot the system if the Python script hangs or the network drops, ensuring 24/7 reliability without manual intervention.

For deeper technical specifications on the libcamera pipeline and Pi 5 peripheral limits, consult the official Picamera2 Python Manual and the Raspberry Pi Hardware Documentation.