Smart Doorbell with Camera
Build a smart doorbell with a Raspberry Pi and camera module. Includes motion detection, notifications, and video recording.
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.