To build a reliable, low-power Raspberry Pi DVR, you need to pair a modern board with the picamera2 Python library and a hardware motion trigger. Using a Raspberry Pi 5 (8GB) and the Camera Module 3 (IMX708 sensor), you can leverage the Pi's dedicated hardware H.265 encoder to record 1080p60 video at roughly 15GB per 24 hours of continuous footage. By adding an HC-SR501 PIR motion sensor, the system stays idle until movement is detected, slashing storage requirements and extending SD card lifespan.

This guide walks through the exact hardware, wiring, and Python code required to build this DVR, along with the specific debugging steps for the most common libcamera errors.

Parts List and Hardware Specifications

Difficulty Rating: Intermediate (Requires basic Python and GPIO wiring)
Estimated Build Time: 45 minutes
Target Board Variant: Raspberry Pi 5 (8GB) running Raspberry Pi OS (64-bit, Bookworm or later). Note: The code also runs on the Pi 4 Model B (4GB+), but the Pi 5's RP1 I/O chip handles hardware encoding and CSI lane switching significantly faster.
Component Exact Model / Variant Estimated Cost (USD) Why this specific part?
Compute Board Raspberry Pi 5 (8GB RAM) $80.00 8GB prevents buffer overflows during 4K/H.265 encoding spikes; RP1 chip handles dual CSI natively.
Camera Module Camera Module 3 (IMX708) $25.00 Features phase-detection autofocus and HDR. The IMX708 sensor natively supports the modern libcamera pipeline.
Motion Sensor HC-SR501 PIR Sensor $3.00 Outputs a clean 3.3V HIGH signal on the OUT pin when infrared heat signatures cross its Fresnel lens.
Ribbon Cable 15-pin to 22-pin CSI FFC (16-pin compatible) $5.00 Pi 5 uses a smaller 22-pin CSI connector; you must use the specific Pi 5 camera cable, not the legacy Pi 4 cable.
Storage Samsung EVO Plus 256GB MicroSD (A2 rated) $22.00 A2 rating ensures high random I/O operations per second (IOPS), critical for writing video chunks without dropping frames.

Storage and Bitrate Planning

Before writing code, you must calculate your storage burn rate. The Raspberry Pi 5 supports hardware-accelerated H.265 (HEVC) encoding, which yields files roughly 40% smaller than H.264 at the same visual quality. Below is the data-dense breakdown of what to expect based on your picamera2 configuration.

Resolution & Framerate Codec Target Bitrate Storage per 1 Hour Storage per 24h (Continuous)
1080p @ 30fps H.264 (AVC) 8 Mbps ~3.6 GB ~86.4 GB
1080p @ 60fps H.265 (HEVC) 6 Mbps ~2.7 GB ~64.8 GB
4K (2304x1296) @ 30fps H.265 (HEVC) 15 Mbps ~6.7 GB ~160.8 GB
720p @ 30fps H.264 (AVC) 4 Mbps ~1.8 GB ~43.2 GB
Callout Tip: If you are using a PIR motion sensor, your actual daily storage will be a fraction of these 24-hour continuous figures. A typical driveway capturing 40 motion events of 30 seconds each at 1080p60 H.265 will only consume about 1.3 GB per day.

Wiring the PIR Trigger and Camera

The HC-SR501 PIR sensor has three pins: VCC, OUT, and GND. While the HC-SR501 is typically powered by 5V, its OUT pin outputs 3.3V when triggered, making it perfectly safe to connect directly to the Raspberry Pi's 3.3V-tolerant GPIO pins without a logic level shifter.

Pin Mapping Table

HC-SR501 Pin Raspberry Pi 5 Pin (Physical) GPIO / Function Wire Color (Typical)
VCC Pin 2 or 4 5V Power Red
OUT Pin 11 GPIO 17 Yellow / Orange
GND Pin 9 Ground Black

Tuning the PIR Sensor

Before sealing your enclosure, adjust the two orange potentiometers on the HC-SR501 board:

  1. Sensitivity (Left Pot): Turn clockwise to increase detection range (up to 7 meters). For a doorway, set it to 50% to avoid triggering on pets in adjacent rooms.
  2. Time Delay (Right Pot): Turn fully counter-clockwise for the minimum delay (~0.3 seconds). We want the sensor to drop LOW quickly so the Python script can cleanly close the video file. We will handle the 'keep-alive' logic in software.

The Python DVR Code (picamera2 + gpiozero)

This script targets the Raspberry Pi 5 (8GB) running a 64-bit OS. It uses the modern picamera2 library (which wraps libcamera) and gpiozero for the PIR input. Do not use the legacy picamera library; it is deprecated and lacks hardware encoding support on the Pi 5.

Install the required dependencies first:

sudo apt update
sudo apt install -y python3-picamera2 python3-gpiozero

Create a file named dvr_capture.py:

import time
import os
from datetime import datetime
from picamera2 import Picamera2
from picamera2.encoders import H265Encoder
from picamera2.outputs import FileOutput
from gpiozero import MotionSensor

# --- PIN & PATH DEFINITIONS ---
PIR_PIN = 17  # Physical Pin 11, BCM GPIO 17
VIDEO_DIR = '/home/pi/dvr_footage/'
BITRATE = 6000000  # 6 Mbps for H.265 1080p60

# Ensure storage directory exists
os.makedirs(VIDEO_DIR, exist_ok=True)

# Initialize PIR Sensor
pir = MotionSensor(PIR_PIN)

# Initialize Camera
picam2 = Picamera2()
# Configure for 1080p video pipeline
video_config = picam2.create_video_configuration(
    main={'size': (1920, 1080), 'format': 'RGB888'},
    controls={'FrameRate': 60}
)
picam2.configure(video_config)

# Setup Hardware Encoder (H.265 supported natively on Pi 5)
encoder = H265Encoder(bitrate=BITRATE)

def start_recording():
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    filepath = os.path.join(VIDEO_DIR, f'motion_{timestamp}.mp4')
    output = FileOutput(filepath)
    
    try:
        picam2.start_recording(encoder, output)
        print(f'[+] Motion Detected: Recording started -> {filepath}')
    except Exception as e:
        print(f'[-] Error starting recording: {e}')

def stop_recording():
    try:
        picam2.stop_recording()
        print('[-] Motion Cleared: Recording stopped and file saved.')
    except Exception as e:
        print(f'[-] Error stopping recording: {e}')

if __name__ == '__main__':
    print('Initializing Raspberry Pi DVR...')
    picam2.start()
    
    # Bind PIR events to recording functions
    pir.when_motion = start_recording
    pir.when_no_motion = stop_recording
    
    try:
        print('DVR Active. Waiting for motion...')
        while True:
            time.sleep(1)  # Keep main thread alive
    except KeyboardInterrupt:
        print('\nShutting down DVR gracefully...')
    finally:
        # Crucial: Release the camera node to prevent lockups
        if picam2.is_recording:
            picam2.stop_recording()
        picam2.stop()
        print('Camera resources released.')

Debugging: 'no cameras available' and Buffer Errors

When working with libcamera and picamera2, hardware initialization errors are common. Below are the exact error strings you will encounter and how to fix them.

Error 1: ERROR: *** no cameras available ***

This is a low-level libcamera error indicating the I2C/CSI bus cannot handshake with the IMX708 sensor.

The first three things to check when it fails:

  1. Ribbon Cable Seating and Orientation: The blue stiffener tab on the FFC ribbon must face the outside of the Pi 5 board (away from the center). Ensure the cable is pushed all the way down before clamping the latch. A partially seated cable will pass the 5V power check but fail the high-speed MIPI CSI data lanes.
  2. Legacy Camera Stack Interference: Run sudo raspi-config, go to Interface Options, and ensure Legacy Camera is DISABLED. The modern libcamera stack requires the legacy MMAL stack to be turned off.
  3. Power Supply Brownout: The Pi 5 requires a 27W USB-C PD power supply (5V/5A). If you are using a standard 5V/3A phone charger, the Pi will throttle the CSI bus power during camera initialization, causing the sensor to fail enumeration.

Error 2: RuntimeError: Failed to acquire camera: Device or resource busy

This occurs at the Python level when Picamera2() attempts to open /dev/video0 but another process holds the lock.

  • Cause: A previous instance of your script crashed without hitting the finally block, or a background service like motioneye or motion is running.
  • Fix: Find and kill the holding process by running fuser -k /dev/video0 in the terminal, then restart your script.

Error 3: mmal: mmal_vc_port_enable: failed to enable port vc.ril.camera

If you see this exact string, you are accidentally importing the legacy picamera library instead of picamera2. The MMAL (Multi-Media Abstraction Layer) stack is completely removed from Pi 5 firmware. Verify your import statement reads from picamera2 import Picamera2.

Extending or Simplifying the Build

Depending on your deployment environment, you may want to alter the complexity of this DVR setup.

How to Simplify the Build

If running wires to a PIR sensor is impractical (e.g., mounting the camera high on an exterior wall), you can drop the HC-SR501 entirely and rely on software-based pixel change detection. While picamera2 does not have built-in motion detection like the old picamera library, you can pass the video frames to OpenCV. Alternatively, for a zero-code approach, flash MotionEyeOS onto your SD card. It provides a web GUI to configure software motion detection, masking, and scheduling without writing a single line of Python.

How to Extend the Build

For a production-grade home security node, extend the Python script with these two additions:

  1. MQTT Push Notifications: Import the paho-mqtt library. Inside the start_recording() function, publish a payload to your Home Assistant MQTT broker containing the filepath and timestamp. This allows Home Assistant to instantly display the video thumbnail on your dashboard.
  2. Network Attached Storage (NAS): Writing continuous video to a MicroSD card will degrade the flash memory within months due to write-cycle exhaustion. Mount a Synology or TrueNAS SMB share to /home/pi/dvr_footage/ via /etc/fstab. This offloads the write cycles to your NAS drives and provides RAID redundancy for your security footage.

For deeper reading on the hardware encoding pipelines, refer to the official Raspberry Pi Picamera2 Manual and the Raspberry Pi Camera Software Documentation.