The Raspberry Pi Zero 2 W is the definitive board variant for a DIY smart door viewer. Its 512MB RAM footprint, quad-core processing, and low idle power draw allow it to run a modern libcamera stack while fitting inside a 3D-printed enclosure mounted directly to your entry door. When paired with the official Raspberry Pi Camera Module 3 Wide (120° FOV), you get a true peephole perspective without the extreme barrel distortion of cheap third-party fisheye lenses.
This guide walks through building a wireless raspberry pi peephole camera that wakes on motion, locks the IMX708 autofocus to a 0.5-meter distance (preventing low-light hunting), captures a snapshot, and pushes it to a local MQTT broker. All code targets the modern picamera2 Python library, which is mandatory for the IMX708 sensor.
Hardware Spec Sheet & Power Budget
A door-mounted camera cannot rely on a wall-wart power supply without drilling through your door frame. We use the PiSugar 3 for Pi Zero, which provides a regulated 5V output and integrates a battery management system (BMS) with I2C fuel gauging. Below is the exact bill of materials and power budget for this build.
| Component | Exact Model / Variant | Key Spec | Active Power Draw | Est. Cost (2026) |
|---|---|---|---|---|
| Compute Board | Raspberry Pi Zero 2 W (v1.0) | Quad-core A53, 512MB RAM | ~180mA (idle WiFi) | $15.00 |
| Camera Sensor | Pi Camera Module 3 Wide (IMX708) | 12MP, 120° FOV, Autofocus | ~220mA (capture) | $35.00 |
| Battery HAT | PiSugar 3 for Pi Zero (1200mAh) | 5V/2A output, I2C BMS | N/A (Source) | $28.00 |
| Motion Sensor | AM312 Mini PIR (Digital Out) | 3.3V logic, 3m range, 120° | ~0.01mA | $2.50 |
| CSI Ribbon | 150mm Zero-specific CSI Cable | 22-pin to 22-pin (1mm pitch) | N/A | $4.00 |
picamera2 will immediately trigger an Out-Of-Memory (OOM) kernel panic. The code below restricts the buffer count and resolution to safely fit within the Zero's memory envelope.
Wiring the CSI Ribbon and PIR Wake Sensor
Physical assembly requires bypassing the interior lens of your existing peephole. Unscrew the interior viewer from the door barrel, leaving the exterior lens intact. The Pi Camera Module 3 Wide will sit approximately 15mm behind the exterior glass. The AM312 PIR sensor mounts to the side of the door frame or inside the 3D-printed housing to detect approaching body heat.
Pin Mapping Table
The PiSugar 3 HAT covers the primary GPIO header, but it passes through the pins. Use the following BCM pin mapping for the PIR sensor. Do not use 5V pins for the AM312; while it accepts 5V, the 3.3V output logic is safer for the Zero's GPIO.
| Module | Module Pin | Pi Zero 2 W Pin (BCM) | Physical Pin # | Wire Color Standard |
|---|---|---|---|---|
| AM312 PIR | VCC | 3.3V Power | Pin 1 | Red |
| AM312 PIR | OUT | GPIO 17 | Pin 11 | Yellow |
| AM312 PIR | GND | Ground | Pin 9 | Black |
| Camera CSI | Ribbon Cable | CSI Port (Edge) | N/A | Blue stiffener away from PCB |
Assembly Steps
- Seat the CSI Cable: Pull the black plastic collar on the Pi Zero 2 W CSI port outward by 1mm. Insert the ribbon cable with the blue stiffener facing away from the PCB (metal contacts facing the chips). Push the collar back in to lock.
- Stack the PiSugar: Align the PiSugar 3 HAT with the GPIO header and press down evenly. Secure with the included M2.5 brass standoffs.
- Solder PIR Jumper: Solder three 24 AWG silicone wires to the AM312 PIR. Route them to the exposed passthrough pins on the PiSugar HAT according to the table above.
- Mount the Camera: Use double-sided VHB tape to secure the Camera Module 3 PCB to the inside of your door mount. Ensure the lens is centered with the exterior peephole glass.
Python Capture Code with Autofocus Locking
Legacy picamera is deprecated and does not support the IMX708 sensor. We use picamera2, which interfaces directly with libcamera. A critical feature of this script is the autofocus lock. Peephole cameras operate in low-light entryways where the IMX708's phase-detection autofocus will "hunt" (pulse in and out) endlessly, resulting in blurry captures. We manually lock the lens position to 2.0 diopters (0.5 meters), which is the exact focal distance of a person standing outside the door.
sudo apt update && sudo apt install python3-picamera2 python3-libcamera python3-paho-mqtt python3-gpiozero before executing this script. Ensure Legacy Camera is disabled in sudo raspi-config.
import time
import sys
import os
import paho.mqtt.client as mqtt
from gpiozero import MotionSensor
from picamera2 import Picamera2
from libcamera import controls
# --- Pin & Network Definitions ---
PIR_GPIO = 17 # BCM 17 (Physical Pin 11)
MQTT_BROKER = "192.168.1.50"
MQTT_PORT = 1883
MQTT_TOPIC = "home/entryway/peephole/snapshot"
IMAGE_PATH = "/tmp/peephole_capture.jpg"
def on_connect(client, userdata, flags, rc, properties=None):
if rc == 0:
print("[MQTT] Connected to broker successfully.")
else:
print(f"[MQTT] Connection failed with code {rc}")
def main():
# Initialize PIR Sensor
pir = MotionSensor(PIR_GPIO)
print(f"[INIT] PIR Sensor active on GPIO {PIR_GPIO}...")
# Initialize MQTT
mqtt_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
mqtt_client.on_connect = on_connect
try:
mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
mqtt_client.loop_start()
except Exception as e:
print(f"[MQTT ERROR] Broker unreachable: {e}. Continuing in local-only mode.")
# Initialize Camera
try:
picam2 = Picamera2()
# CRITICAL: Zero 2 W Memory Management
# Limit buffer_count to 2 and downscale to 1536x864 to prevent OOM
config = picam2.create_still_configuration(
main={"size": (1536, 864)},
buffer_count=2
)
picam2.configure(config)
# Lock Autofocus to 0.5 meters (2.0 diopters) to prevent low-light hunting
picam2.set_controls({
"AfMode": controls.AfModeEnum.Manual,
"LensPosition": 2.0
})
picam2.start()
time.sleep(2.0) # Allow sensor to adjust to ambient entryway lighting
print("[CAM] IMX708 initialized. Focus locked at 0.5m.")
except RuntimeError as e:
print(f"[CAM FATAL] {e}")
sys.exit(1)
# Main Loop
try:
while True:
print("[WAIT] Sleeping until motion detected...")
pir.wait_for_motion()
print("[TRIG] Motion detected! Capturing...")
# Capture and Save
picam2.capture_file(IMAGE_PATH)
# Publish to MQTT
if os.path.exists(IMAGE_PATH):
with open(IMAGE_PATH, "rb") as f:
img_bytes = f.read()
try:
mqtt_client.publish(MQTT_TOPIC, img_bytes, qos=1)
print(f"[MQTT] Published {len(img_bytes)} bytes to {MQTT_TOPIC}")
except Exception as e:
print(f"[MQTT ERROR] Publish failed: {e}")
# Cooldown to prevent spamming the broker
pir.wait_for_no_motion()
time.sleep(5.0)
except KeyboardInterrupt:
print("\n[EXIT] Shutting down gracefully...")
finally:
picam2.stop()
mqtt_client.loop_stop()
mqtt_client.disconnect()
if __name__ == "__main__":
main()
Debugging: Memory Errors and Boot Failures
The Pi Zero 2 W is unforgiving with memory allocation. If your script crashes immediately upon calling picam2.start(), you are likely hitting the V4L2 buffer limit. This is the most common failure point for this specific hardware combination.
Exact Error String: Buffer Allocation Failure
[0:05:12.123456] ERROR V4L2 v4l2_videodevice.cpp:1502 : /dev/video0[13:cap]: Unable to request 4 buffers: Cannot allocate memory
RuntimeError: Failed to allocate capture sequence
Ranked Causes & Fixes
- Default Buffer Count Too High (90% of cases):
picamera2defaults to requesting 4 to 6 memory buffers. On a 512MB board running an OS and WiFi stack, this exceeds contiguous memory limits. Fix: Explicitly setbuffer_count=2increate_still_configuration()as shown in the code above. - Resolution Exceeds RAM: Requesting the native 12MP (4608x2592) still capture while streaming a preview. Fix: Drop the main stream size to 1536x864 or 1920x1080. The peephole viewing angle makes 1080p more than sufficient for facial identification.
- GPU Memory Split: The default
gpu_memin/boot/firmware/config.txtmight be too low for the DRM/KMS stack. Fix: Addgpu_mem=128to your config.txt and reboot.
The First Three Things to Check When It Fails
If the camera returns *** no cameras available *** or the system fails to boot entirely, check these three physical and configuration states before rewriting code:
- CSI Ribbon Orientation: The Pi Zero CSI port is notoriously tricky. If the blue stiffener is facing the PCB instead of away from it, the data lanes are shorted to ground. The Pi will boot, but
libcamerawill see no I2C response from the IMX708. - Legacy Camera Stack Interference: If you previously enabled the legacy camera stack for an older project,
picamera2will fail silently or throw device busy errors. Runsudo raspi-config→ Interface Options → Legacy Camera → Disable. - PiSugar Voltage Brownout: The IMX708 draws a sudden 220mA spike when the autofocus voice coil motor engages. If the PiSugar 3 battery is below 20% charge, this spike causes a voltage drop below 4.63V, triggering the Pi's brownout detector and resetting the camera bus. Check battery levels via the PiSugar I2C CLI tool before testing.
Extending or Simplifying the Build
Depending on your home automation ecosystem, you may want to scale this project up or down.
Simplify: Local SD Card Logging (No Network)
If you do not run an MQTT broker or want to eliminate WiFi power draw to extend battery life from 3 days to 3 weeks, remove the paho-mqtt logic. Replace the network publish block with a timestamped local save:
import datetime
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
picam2.capture_file(f"/mnt/usb_drives/peephole_log/{ts}.jpg")
Note: You must disable WiFi (sudo rfkill block wifi) to realize the actual power savings on the Zero 2 W.
Extend: Frigate NVR Integration
For continuous recording rather than snap-shots, swap the PiSugar battery for a flat 5V USB ribbon cable routed through the door weatherstripping to a wall outlet. Replace the Python script with Frigate NVR's go2rtc integration. You will need to add the following to your /boot/firmware/config.txt to enable the hardware H.264 encoder on the Zero 2 W:
dtoverlay=imx708
dtoverlay=vc4-kms-v3d
Frigate will ingest the CSI feed directly via the rpicam-vid wrapper, providing object detection (person/package) via a Coral TPU connected to a secondary host machine, turning your peephole into a full smart-doorbell replacement.






