Smart Doorbell with Camera

Build a smart doorbell with a Raspberry Pi and camera module. Includes motion detection, notifications, and video recording.

Andreas · April 12, 2026 · 9 min read

Introduction

A Raspberry Pi smart doorbell gives you live video, motion detection, and instant notifications without cloud dependencies. When someone presses your doorbell button, the Pi captures a photo and sends you a notification. You can also stream live video from a web interface.

Hardware Bill of Materials

Item Cost Purpose
Raspberry Pi 4 (2GB min) $25 Main processor
Camera Module 3 (12MP) $25-30 Video capture
USB Buzzer or speaker $3-5 Alert sound
Momentary push button $1 Doorbell trigger
4-pin connector + wires $2 Wiring harness
USB-C power supply $10 Power
Total ~$70 Complete system

Prerequisites

  • Raspberry Pi 4/5 with 2GB+ RAM
  • Pi Camera Module 3 (or any CSI camera)
  • Push button switch
  • USB buzzer or small speaker
  • Python 3.9+
  • pip install picamera2 opencv-python-headless requests
  • SSD recommended for storage (camera footage writes frequently)

Wiring Diagram

Connect the push button to GPIO 27, buzzer/speaker to GPIO 17:

Push Button:
  Pin 1 (switch) ──[GPIO 27 (pin 36)]
  Pin 2 (GND) ────[GND (pin 34 or 39)]

USB Buzzer:
  +5V ──[USB power]
  GND ──[USB power]

The Pi Camera Module 3 connects via the CSI ribbon on the Raspberry Pi board (ribbon slot, not GPIO).

Step 1 — Install Camera Support and Dependencies

Enable the camera in raspi-config:

sudo raspi-config
# Navigate to: Interfacing Options → Camera → Enable
# Reboot after

Install libraries:

sudo apt update
sudo apt install -y python3-picamera2 libopencv-dev python3-opencv
pip3 install requests

Test the camera:

libcamera-hello --duration 5000

You should see a preview for 5 seconds.

Step 2 — Motion Detection with OpenCV

Create doorbell.py:

import cv2
import numpy as np
import threading
import requests
from datetime import datetime
from picamera2 import Picamera2
from libcamera import controls

picam = Picamera2()
picam.configure(picam.create_video_configuration(main={"size": (1280, 720)}))
picam.start()

# Motion detection
prev_frame = None

def detect_motion(frame):
    global prev_frame
    if prev_frame is None:
        prev_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        return False
    
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    diff = cv2.absdiff(prev_frame, gray)
    _, thresh = cv2.threshold(diff, 25, 255, cv2.THRESH_BINARY)
    contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    
    motion = len(contours) > 5
    prev_frame = gray
    return motion

def send_notification(message):
    # Send via ntfy.sh (free notification service)
    try:
        requests.post(
            "https://ntfy.sh/doorbell_alerts",
            data=message,
            timeout=5
        )
        print(f"Notification sent: {message}")
    except Exception as e:
        print(f"Notification failed: {e}")

def save_snapshot(name="motion"):
    frame = picam.capture_array()
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"/tmp/doorbell_{name}_{timestamp}.jpg"
    cv2.imwrite(filename, cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
    return filename

try:
    frame_count = 0
    motion_cooldown = 0
    
    while True:
        frame = picam.capture_array()
        frame_count += 1
        
        if frame_count % 5 == 0:  # Check every 5 frames
            if detect_motion(frame) and motion_cooldown == 0:
                snapshot = save_snapshot("motion")
                send_notification(f"Motion detected at doorbell! Image: {snapshot}")
                motion_cooldown = 60  # 60-frame cooldown
        
        if motion_cooldown > 0:
            motion_cooldown -= 1

except KeyboardInterrupt:
    print("Shutdown")
finally:
    picam.stop()

Run it:

python3 doorbell.py

When motion is detected in frame, it saves a snapshot and sends a notification.

Step 3 — Button Detection and Alert

Extend doorbell.py to handle button presses:

import RPi.GPIO as GPIO

BUTTON_PIN = 27
BUZZER_PIN = 17

GPIO.setmode(GPIO.BCM)
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(BUZZER_PIN, GPIO.OUT)

def button_press_callback(channel):
    print("Button pressed!")
    # Sound buzzer
    GPIO.output(BUZZER_PIN, GPIO.HIGH)
    
    # Capture and send
    snapshot = save_snapshot("button")
    send_notification(f"DOORBELL PRESSED! Image: {snapshot}")
    threading.Thread(target=lambda: (
        __import__('time').sleep(2),
        GPIO.output(BUZZER_PIN, GPIO.LOW)
    )).start()

GPIO.add_event_detect(BUTTON_PIN, GPIO.FALLING, callback=button_press_callback, bouncetime=200)

Add this before the main loop. Bouncetime=200ms prevents false triggers from button noise.

Step 4 — Web Viewer with Flask

Create web_viewer.py:

from flask import Flask, Response
from picamera2 import Picamera2
import cv2

app = Flask(__name__)
picam = Picamera2()
picam.configure(picam.create_video_configuration(main={"size": (640, 480)}))
picam.start()

def generate():
    while True:
        frame = picam.capture_array()
        _, buffer = cv2.imencode('.jpg', cv2.cvtColor(frame, cv2.COLOR_RGB2BGR))
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + buffer.tobytes() + b'\r\n')

@app.route('/stream')
def stream():
    return Response(generate(),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

@app.route('/')
def index():
    return '''
    <html>
      <body style="text-align:center; background:#1a1a1a; color:#fff; font-family:Arial;">
        <h1>Doorbell Camera</h1>
        <img src="/stream" style="width:640px; height:480px; border:2px solid #0066cc;">
        <p>Live from your Raspberry Pi doorbell</p>
      </body>
    </html>
    '''

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080, debug=False, threaded=True)

Install Flask:

pip3 install flask

Run it:

python3 web_viewer.py

Access at http://<pi-ip>:8080 to view live stream.

Step 5 — Systemd Service for Autostart

Create /etc/systemd/system/doorbell.service:

[Unit]
Description=Smart Doorbell with Camera
After=network.target

[Service]
Type=simple
User=pi
WorkingDirectory=/home/pi/doorbell
ExecStart=/usr/bin/python3 /home/pi/doorbell/doorbell.py
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable doorbell
sudo systemctl start doorbell

Troubleshooting

Camera not detected — Run libcamera-hello to verify. If it fails, check ribbon connection: fully insert and reseat the CSI cable in the camera slot.

Motion detection too sensitive — Increase the contour threshold from 5 to 10, or adjust the cv2.threshold() value from 25 to 35-50 for more robust motion.

Notifications not arriving — Check internet connectivity. ntfy.sh is a free service; if it's down, self-host ntfy or use Telegram/Pushbullet instead: requests.post("https://api.pushbullet.com/v2/pushes", json={"type":"note","title":"Doorbell","body":message}, headers={"Access-Token":"<key>"})

Button press not detected — Test with gpio readall | grep -A 2 -B 2 27. Verify GPIO 27 is available and the button is wired to GND correctly. Add a test script to verify: GPIO.input(BUTTON_PIN) should return 0 when pressed.

Summary

Your smart doorbell now captures motion, responds to button presses, streams live video, and sends instant notifications. Expand it by adding email alerts, 24/7 recording to USB SSD, or integrating with Home Assistant for whole-home automation.

Related Tools

Comments