BLOG / TUTORIALS / ESP32-CAM Project: Wireless Security Cam…
Blog Yazısı

ESP32-CAM Project: Wireless Security Camera with Motion Detection

Viktor Build ~10 min read

Build your own wireless security camera with an ESP32-CAM and PIR sensor for under $20. Step-by-step guide with code, wiring, and email alerts.

Everyone wants a security camera, but not everyone wants to pay a subscription or trust a cloud service with their home feed. An ESP32-CAM project solves that. You build your own wireless security camera that streams video over Wi-Fi, detects motion with a PIR sensor, and sends you an email alert when something moves — all for under $20 in parts.

This guide walks you through the entire build: wiring the PIR sensor to the ESP32-CAM, configuring the camera for streaming, and writing the code that ties motion detection to email notifications. By the end, you'll have a functional, low-power security camera you can place anywhere in your home.

Why Build an ESP32-CAM Security Camera?

The ESP32-CAM is a tiny development board that packs a 2MP camera, an ESP32 microcontroller (with Wi-Fi and Bluetooth), and a microSD card slot — all for about $10. Compared to a Raspberry Pi with a camera module (which costs 3–4x more), the ESP32-CAM is perfect for compact, always-on projects.

Paired with a PIR motion sensor, this project becomes genuinely useful. Instead of recording hours of empty hallway footage, the camera only captures events when something happens. And with email alerts, you see what triggered the detection immediately, without needing to connect to the camera's stream first.

Parts List for This ESP32-CAM Project

Component Quantity Notes
ESP32-CAM module (AI-Thinker) 1 Most common variant, OV2640 camera
FTDI programmer (3.3V) 1 For programming the ESP32-CAM
PIR motion sensor (HC-SR501) 1 Adjustable sensitivity and delay
Jumper wires (female-to-female) 4 For PIR connection
MicroSD card (8–32GB) 1 Optional, for local recording
5V power supply (1A) 1 USB or wall adapter
Breadboard (optional) 1 For prototyping

Total cost: Approximately $18–22.

Wiring the PIR Sensor to the ESP32-CAM

The ESP32-CAM doesn't have a USB port like regular Arduino boards. You program it through a serial connection (typically an FTDI adapter), and all GPIO pins are accessible on the board's pin headers.

Here's the wiring:

FTDI to ESP32-CAM (for programming)

FTDI Adapter ESP32-CAM
3.3V VCC (3.3V input)
GND GND
TXD U0R (receive)
RXD U0T (transmit)
DTR GPIO0 (via 10µF cap for auto-reset)

Important: Connect GPIO0 to GND before powering on to enter programming mode. Or use the auto-reset circuit with a capacitor between DTR and GPIO0.

PIR Sensor to ESP32-CAM

PIR HC-SR501 ESP32-CAM
VCC 3.3V (or 5V if module has a regulator)
GND GND
OUT GPIO 13

The HC-SR501 PIR sensor runs on 5V, but most ESP32-CAM boards have a 5V input that supplies the onboard regulator. You can connect PIR VCC to the ESP32-CAM's 5V pin, then its output to any GPIO (I used GPIO 13). The output is 3.3V when triggered, so it's safe for the ESP32.

Setting Up the Arduino IDE for ESP32-CAM

If you haven't programmed an ESP32 before, you need to install board support. This takes two minutes:

  1. Open Arduino IDE → File → Preferences
  2. In the "Additional Boards Manager URLs" field, paste:
    https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
    
  3. Tools → Board → Boards Manager → search for "ESP32" → install "ESP32 by Espressif Systems"

Then select your board: Tools → Board → ESP32 Arduino → AI Thinker ESP32-CAM.

Code: Streaming Camera with PIR Motion Detection

The code below does three things:

  1. Starts an MJPEG video stream accessible over Wi-Fi
  2. Monitors the PIR sensor pin for motion
  3. Sends an email with a captured image when motion is detected

Required Libraries

Install these through the Arduino Library Manager:

  • ESP32-MJPEG (by Kevin Hester) — for the camera stream
  • ESP_Mail_Client (by Mobizt) — for sending emails via SMTP

The Full Sketch

#include "esp_camera.h"
#include <WiFi.h>
#include <ESP_Mail_Client.h>

// ===== CONFIGURE THESE =====
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

// Email settings (use Gmail App Password or SMTP credentials)
#define SMTP_HOST "smtp.gmail.com"
#define SMTP_PORT 465
#define AUTHOR_EMAIL "your_email@gmail.com"
#define AUTHOR_PASSWORD "your_app_password"
#define RECIPIENT_EMAIL "recipient@example.com"
// ============================

// PIR sensor pin
const int pirPin = 13;
bool motionDetected = false;
unsigned long lastMotionTime = 0;
const unsigned long cooldown = 10000; // 10 seconds between alerts

// Camera pins for AI-Thinker ESP32-CAM
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

SMTPSession smtp;
Session_Config config;
SMTP_Message message;

void setup() {
  Serial.begin(115200);
  pinMode(pirPin, INPUT);
  
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected");
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());

  // Initialize camera
  camera_config_t camera_config;
  camera_config.ledc_channel = LEDC_CHANNEL_0;
  camera_config.ledc_timer = LEDC_TIMER_0;
  camera_config.pin_d0 = Y2_GPIO_NUM;
  camera_config.pin_d1 = Y3_GPIO_NUM;
  camera_config.pin_d2 = Y4_GPIO_NUM;
  camera_config.pin_d3 = Y5_GPIO_NUM;
  camera_config.pin_d4 = Y6_GPIO_NUM;
  camera_config.pin_d5 = Y7_GPIO_NUM;
  camera_config.pin_d6 = Y8_GPIO_NUM;
  camera_config.pin_d7 = Y9_GPIO_NUM;
  camera_config.pin_xclk = XCLK_GPIO_NUM;
  camera_config.pin_pclk = PCLK_GPIO_NUM;
  camera_config.pin_vsync = VSYNC_GPIO_NUM;
  camera_config.pin_href = HREF_GPIO_NUM;
  camera_config.pin_sscb_sda = SIOD_GPIO_NUM;
  camera_config.pin_sscb_scl = SIOC_GPIO_NUM;
  camera_config.pin_pwdn = PWDN_GPIO_NUM;
  camera_config.pin_reset = RESET_GPIO_NUM;
  camera_config.xclk_freq_hz = 20000000;
  camera_config.pixel_format = PIXFORMAT_JPEG;
  camera_config.frame_size = FRAMESIZE_VGA;  // 640x480 — good balance
  camera_config.jpeg_quality = 12;
  camera_config.fb_count = 1;

  esp_err_t err = esp_camera_init(&camera_config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }

  // Start MJPEG stream server
  startCameraServer();

  // Configure email
  mail.smtp.debug(1);
  config.server.host_name = SMTP_HOST;
  config.server.port = SMTP_PORT;
  config.login.email = AUTHOR_EMAIL;
  config.login.password = AUTHOR_PASSWORD;
  config.login.user_domain = "";

  message.sender.name = "ESP32-CAM Security";
  message.sender.email = AUTHOR_EMAIL;
  message.subject = "Motion Detected!";
  message.addRecipient("You", RECIPIENT_EMAIL);
  message.text.content = "Motion was detected by your ESP32-CAM security camera.\nCheck the stream at: http://" + WiFi.localIP().toString();
  message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;

  Serial.println("System ready — monitoring for motion");
}

void loop() {
  int pirState = digitalRead(pirPin);
  
  if (pirState == HIGH && !motionDetected) {
    motionDetected = true;
    lastMotionTime = millis();
    Serial.println("Motion detected!");
    
    // Capture and send email
    camera_fb_t * fb = esp_camera_fb_get();
    if (fb) {
      message.addAttachment("snapshot.jpg", fb->buf, fb->len, "image/jpeg");
      if (!MailClient.sendMail(&smtp, &config, &message)) {
        Serial.println("Error sending email: " + smtp.errorReason());
      }
      message.clearAttachment();
      esp_camera_fb_return(fb);
    }
  }
  
  if (motionDetected && (millis() - lastMotionTime > cooldown)) {
    motionDetected = false;
  }
  
  delay(100);
}

// Minimal MJPEG server — you need the full implementation
// from the ESP32-MJPEG library. The library provides 
// startCameraServer() and handles streaming.

Note: The startCameraServer() function comes from the ESP32-MJPEG library. Install it and include the example's server code, or use the built-in example that ships with the library.

Testing the Motion Detection

  1. Upload the code with GPIO0 connected to GND
  2. Disconnect GPIO0 after upload, press the reset button
  3. Open the Serial Monitor at 115200 baud
  4. You'll see the IP address printed — open that in a browser to see the stream
  5. Wave your hand in front of the PIR sensor

If the email doesn't arrive, check three things:

  • App passwords: With Gmail, you need a 16-character app password, not your regular password. Generate one in Google Account → Security → App passwords.
  • Less secure apps: Some email providers block SMTP from unknown IPs. You may need to enable "Allow less secure apps" or create a dedicated SMTP credential.
  • Firewall: The ESP32-CAM needs internet access to reach the SMTP server. If your Wi-Fi has a portal, it might block outbound connections.

Adjusting PIR Sensitivity and Timing

The HC-SR501 PIR sensor has two small potentiometers:

  • Sensitivity (Sx): Adjusts detection range. Turn clockwise to increase (up to ~7 meters), counterclockwise to decrease (~3 meters).
  • Time delay (Tx): Controls how long the output stays HIGH after detecting motion. For this project, set it to the minimum (fully counterclockwise) — about 3 seconds. The cooldown in the code handles the rest.

The jumper on the sensor selects mode:

  • H (repeatable): Output stays HIGH as long as motion continues. Use this — the code's cooldown prevents email spam.
  • L (non-repeatable): Output goes HIGH once, then stays LOW until motion stops. Not ideal for this project.

Improving the ESP32-CAM Project

This basic build works well, but you can push it further:

Add Local Recording to SD Card

The ESP32-CAM has a microSD slot. Modify the code to save images to the card when motion is detected. Use SD_MMC.begin() and SD_MMC.open() to write JPEG files with timestamps. This gives you a backup if email fails.

Use Deep Sleep for Battery Power

The ESP32 draws about 180mA when streaming. For battery operation, put it into deep sleep between detections. Wire the PIR output to the ESP32's RTC pin (GPIO 3 or 16), and use esp_sleep_enable_ext0_wakeup() to wake on motion. This drops current to under 10µA in sleep mode.

Check out our guide on How Capacitors Work for tips on smoothing power supply noise — useful when switching between sleep and active modes.

Stream to a Web Dashboard

Instead of raw MJPEG, send the stream to a dashboard like Home Assistant or Node-RED. The ESP32 can publish MQTT messages when motion occurs, and you can view the camera feed through an integrated UI. Our OLED Display Arduino guide shows similar SPI communication patterns that apply to camera configuration.

Multiple Cameras, One Interface

Build several ESP32-CAM units, each with a unique static IP. Point them at different angles in your home. You can view all streams in a simple HTML page hosted on a Raspberry Pi or an ESP32 server.

Common Problems and Fixes

Problem Likely Cause Fix
No Wi-Fi connection Wrong SSID or password Double-check credentials. ESP32 is case-sensitive.
Camera stream shows gray/static Loose FFC ribbon cable Re-seat the ribbon cable on the camera module.
Email not sending Wrong SMTP settings or blocked port Test with a Gmail App Password first. Port 465 (SSL) is most reliable.
PIR constantly triggering Sensor facing a heat source Move away from vents, windows, or sunlight. Reduce sensitivity.
Board doesn't boot after upload GPIO0 not disconnected Remove the programming jumper and press reset.

Why This Project Shines for Makers

An ESP32-CAM project like this ties together everything that makes embedded systems fun: networking, sensors, cameras, and communication protocols. You're not just following a tutorial — you're building a device that genuinely improves your home security, without monthly fees or privacy concerns.

The skills transfer directly to other projects. Working with the camera interface prepares you for Potentiometer with Arduino style sensor reading. The email integration uses the same SMTP patterns you'd use for a Remote Control Fan with ESP8266. Every component in this build teaches something you'll reuse.


The code and full project files are available on GitHub in the viktor/esp32-cam-security repo. For questions or mods, join the community on Discord — makers there have added features like Telegram alerts, time-lapse recording, and even pan-tilt mounts.

Discord'da Topluluğa Katıl

Sorular sor, yapımlarını paylaş ve diğer makerlarla takıl.

Discord'a Katıl — ücretsiz

Bu dersi beğendin mi?

Projelere erken erişim ve daha fazlası için Patreon'da kanalı destekle.

Patreon'da Destekle →