Back to Blog

Building a DIY Desk Security Camera with ESP32-CAM: Complete Guide

TechFlunky Labs
10 min read
29 views

Ever wanted a security camera that doesn't phone home to some cloud service in China? One that you actually own and control? For about $10 and an afternoon of tinkering, you can build exactly that.

The ESP32-CAM is one of the most capable microcontrollers for the price. It packs a dual-core processor, WiFi, 4MB of flash, and an OV2640 camera sensor into a package smaller than a credit card. Today we're turning it into a desk security camera with features that rival commercial products.

What We're Building

By the end of this guide, you'll have a camera that can:

  • Stream live video to any browser (including iPhone Safari)
  • Detect motion and send instant push notifications to your phone
  • Record time-lapse photos to an SD card
  • Save motion events automatically when movement is detected
  • Control a flash LED for low-light situations
  • Expose a JSON API for integration with other systems

All running on a $10 board with no monthly fees or cloud dependencies.

Hardware Requirements

Component Purpose Approx. Cost
AI-Thinker ESP32-CAM Main board with camera $8-12
ESP32-CAM-MB USB Dock Programming interface $3-5
MicroSD Card (optional) Photo storage $5-10
USB-A to Micro-USB Cable Power and programming $2

Total investment: Under $20 for a complete system.

You can find these on Amazon, AliExpress, or your favorite electronics supplier. The "AI-Thinker" variant is the most common and well-documented.

The ESP32-CAM Board

Before we dive into code, let's understand what we're working with:

┌─────────────────────────────────────────────────────────────┐
│                      ESP32-CAM Board                         │
├─────────────────┬───────────────────┬───────────────────────┤
│   OV2640        │    ESP32          │      Flash LED        │
│   Camera        │    Processor      │      (GPIO 4)         │
│   Sensor        │    + 4MB PSRAM    │                       │
└────────┬────────┴─────────┬─────────┴───────────────────────┘
         │                  │
    Camera Ribbon       MicroSD Slot
      Cable              (on back)

Key specs:

  • Processor: Dual-core Xtensa LX6 @ 240MHz
  • Memory: 520KB SRAM + 4MB PSRAM (for high-res images)
  • Camera: OV2640, up to 1600x1200 resolution
  • WiFi: 802.11 b/g/n (2.4GHz only)
  • Storage: MicroSD slot (up to 4GB officially, 16GB works fine)
  • GPIO: Limited pins available (camera uses most of them)

Software Architecture

Our firmware has four main components working together:

1. Web Server (Port 80)

A simple HTTP server that handles:

  • Dashboard at / - Live stream with controls
  • MJPEG Stream at /stream - Continuous video feed
  • Snapshots at /capture - Single JPEG frames
  • API endpoints for toggling features

2. Motion Detection

Here's where it gets clever. Traditional motion detection requires decoding JPEG frames to raw pixels, which would crush the ESP32's limited resources. Instead, we use a byte-sampling algorithm:

cpp
// Sample every 100th byte of the JPEG data
for (size_t i = 0; i < frameLength; i += 100) {
    int diff = abs(currentFrame[i] - previousFrame[i]);
    if (diff > MOTION_THRESHOLD) {
        changedBytes++;
    }
}

This approach works because JPEG compression is spatially consistent. If a region of the image changes significantly, the corresponding bytes in the JPEG stream will also change. We're essentially detecting compression differences rather than pixel differences.

It's not pixel-perfect, but it's remarkably effective for detecting people walking by or objects being moved. And it runs in microseconds instead of seconds.

3. Push Notifications

When motion is detected, we send an HTTP POST to ntfy.sh - a free, open-source push notification service. No accounts needed, no API keys, just a unique topic name.

cpp
HTTPClient http;
http.begin("https://ntfy.sh/your-unique-topic");
http.addHeader("Title", "Motion Detected!");
http.addHeader("Priority", "high");
http.addHeader("Tags", "warning,camera");
http.POST("Movement detected at your desk!");

Install the ntfy app on your phone, subscribe to your topic, and you'll get instant notifications with a link to view the stream.

4. SD Card Recording

The ESP32-CAM has a MicroSD slot that we use for two recording modes:

  • Motion Capture: Automatically saves a JPEG when motion is detected
  • Time-Lapse: Captures frames at configurable intervals (default: 30 seconds)

Photos are organized into session folders with timestamps:

/sdcard/
├── session_20250122_090000/
│   ├── motion_20250122_091532.jpg
│   ├── motion_20250122_093217.jpg
│   ├── timelapse_20250122_090030.jpg
│   ├── timelapse_20250122_090100.jpg
│   └── ...

Setting Up the Development Environment

We're using PlatformIO instead of the Arduino IDE. It's faster, has better dependency management, and integrates with VS Code.

1. Install PlatformIO

bash
pip install platformio

# Or install the VS Code extension
# Search "PlatformIO IDE" in extensions

2. Clone the Project

bash
git clone https://github.com/cozyartz/esp32-cam.git
cd esp32-cam

3. Configure Your Settings

Edit include/config.h:

cpp
// WiFi credentials
#define WIFI_SSID "YourNetworkName"
#define WIFI_PASSWORD "YourPassword"

// Push notifications - pick a unique random string
#define NTFY_TOPIC "mydeskcamera-a7x9k2"
#define NTFY_SERVER "https://ntfy.sh"

// Motion detection tuning
#define MOTION_THRESHOLD 15        // Sensitivity (0-255)
#define MOTION_PIXEL_COUNT 5000    // How many changed bytes trigger alert
#define MOTION_COOLDOWN_MS 30000   // 30 seconds between alerts

// Time-lapse interval
#define TIMELAPSE_INTERVAL_MS 30000  // 30 seconds

// Timezone for photo timestamps (EST = -18000)
#define TIMEZONE_OFFSET_SEC -18000

4. Build and Upload

Connect your ESP32-CAM to the USB dock, then:

bash
# Build the firmware
pio run

# Upload to the board
pio run -t upload

# Monitor serial output
pio device monitor

You should see output like:

=== Desk Security Camera ===

Initializing camera...
PSRAM found, using high quality settings
Camera initialized!
Connecting to WiFi: YourNetwork
...
WiFi connected!
IP Address: 192.168.1.100
SD Card Type: SDHC
SD Card Size: 14920MB
Created session folder: /session_20250122_143052
SD card ready for recording!

========================================
Stream URL: http://192.168.1.100
Snapshot:   http://192.168.1.100/capture
========================================

Open that IP address in your browser, and you're live!

The Web Dashboard

The embedded dashboard provides everything you need in a clean, mobile-friendly interface:

Controls:

  • Motion: ON/OFF - Toggle motion detection
  • Flash: ON/OFF - Control the LED flash
  • Timelapse: ON/OFF - Enable automatic time-lapse capture
  • Save Motion: ON/OFF - Save photos when motion detected
  • Take Snapshot - Download current frame

Status indicators:

  • Motion detection state (armed/disabled)
  • SD card availability
  • Total photos saved this session

The dashboard polls /status every 5 seconds to keep everything in sync.

Creating Time-Lapse Videos

After your SD card has collected a few hundred (or thousand) photos, you can turn them into a video using FFmpeg:

bash
# Copy photos from SD card to your computer
# Then run FFmpeg to create the video

# 30 fps time-lapse from all timelapse photos
ffmpeg -framerate 30 -pattern_type glob -i 'timelapse_*.jpg' \
       -c:v libx264 -pix_fmt yuv420p timelapse.mp4

# Slower 10 fps for more detail
ffmpeg -framerate 10 -pattern_type glob -i 'timelapse_*.jpg' \
       -c:v libx264 -pix_fmt yuv420p timelapse_slow.mp4

This is perfect for monitoring plant growth, tracking desk activity over a workday, or watching weather changes.

Integrating with Apple Home (HomeKit)

If you're in the Apple ecosystem, you're probably wondering if this can work with the Home app. The short answer: partially.

The ESP32 doesn't have the processing power to encode H.264 video at the quality HomeKit requires (it needs 15-20 fps, the ESP32 tops out at 6-7 fps with native HomeKit). However, you have options:

Option 1: Homebridge (Full Camera Support)

Run Homebridge on a Raspberry Pi with the homebridge-camera-ffmpeg plugin. Point it at your ESP32-CAM's MJPEG stream, and the Pi handles the transcoding to HomeKit format.

Option 2: HomeSpan Motion Sensor (Lightweight)

Use the HomeSpan library to expose just the motion sensor to HomeKit. Your camera becomes a native HomeKit motion sensor without needing any bridge hardware. Notifications go straight to your iPhone.

We're working on a homekit branch with this integration.

API Reference

For those building integrations or automations:

Endpoint Method Description
/ GET Web dashboard
/stream GET MJPEG video stream
/capture GET Single JPEG snapshot
/motion?enable=0|1 GET Toggle motion detection
/flash?enable=0|1 GET Toggle flash LED
/timelapse?enable=0|1 GET Toggle time-lapse mode
/motioncapture?enable=0|1 GET Toggle motion photo saving
/photos GET List saved photos (JSON)
/photo?name=filename.jpg GET Download specific photo
/status GET Full system status (JSON)

Example status response:

json
{
  "motion": true,
  "flash": false,
  "timelapse": true,
  "motionCapture": true,
  "sdCard": true,
  "photoCount": 147,
  "ip": "192.168.1.100",
  "uptime": 86400,
  "rssi": -62
}

Troubleshooting

Camera won't initialize

  • Check the ribbon cable - It's fragile and often comes loose during shipping
  • Verify PSRAM - The AI-Thinker variant should have it; knockoffs sometimes don't
  • Try lower resolution - Change FRAME_SIZE to FRAMESIZE_SVGA or FRAMESIZE_VGA

WiFi connection fails

  • 2.4GHz only - ESP32 doesn't support 5GHz networks
  • SSID/password - Double-check config.h, no typos
  • Move closer - Start testing near your router

Stream is choppy

  • One viewer at a time - The web server is single-threaded
  • Reduce resolution - VGA (640x480) is the sweet spot
  • Check WiFi signal - /status shows RSSI, aim for better than -70dBm

SD card not detected

  • Format as FAT32 - exFAT won't work
  • Capacity limit - Cards over 32GB may have issues
  • Clean the slot - Dust is the enemy

No push notifications

  • Check your topic - Must match exactly in config.h and ntfy app
  • Internet access - ESP32 needs to reach ntfy.sh
  • Cooldown period - Default 30 seconds between alerts

What's Next?

This camera is just the beginning. Here are some directions you could take it:

  • Face detection using TensorFlow Lite on the ESP32
  • MQTT integration for Home Assistant
  • Deep sleep mode for battery-powered operation
  • Pan/tilt mount with servo motors
  • Multi-camera system with a central dashboard

The complete source code is on GitHub. Pull requests welcome!

The Bottom Line

For less than the cost of a fancy coffee, you can build a security camera that:

  • Streams to any device with a browser
  • Notifies you instantly when something moves
  • Records photos locally with no cloud dependency
  • Runs forever on USB power

No subscriptions. No accounts. No cloud. Just a tiny board doing exactly what you tell it to.

That's the beauty of DIY IoT done right.


This project is part of our hardware experiments series. Have questions or built something cool with the ESP32-CAM? Drop us a line at hello@techflunkylabs.com.


Build Your Financial Freedom While Building Cool Projects

Speaking of building things yourself - if you're the type who takes control of your tech instead of paying monthly subscriptions, you might appreciate taking the same approach with your finances.

While we're all about hardware hacks here, the same DIY mindset applies to building wealth. Check out this comprehensive forex trading course that teaches you to trade currencies with the same systematic, technical approach we use for engineering projects.

Sponsored content. TechFlunky Labs may receive compensation for purchases made through affiliate links.

Share:
AI-assisted content

Related Posts