We’ll use the ESP32-CAM board to build an IP surveillance camera in this project. The ESP32 camera will run a video streaming web server that can be accessed from any device on your network.


To follow this tutorial you need the following components:

  • ESP32-CAM with OV2640 – The ESP32 CAM WiFi Module Bluetooth with OV2640 Camera Module 2MP For Face Recognization has a very competitive small-size camera module that can operate independently as a minimum system with a footprint of only 40 x 27 mm; a deep sleep current of up to 6mA and is widely used in various IoT applications.
  • FTDI programmer
  • Female-to-female jumper wires
  • Fake/dummy dome security camera-This decoy security camera is made to look like the real thing making it the ultimate deterrent against crime. A light is on when the camera is turned on.
  • Optional – Home Assistant on Raspberry Pi:
    • Raspberry Pi Board  – Raspberry Pi Board is a low cost, credit-card sized computer that plugs into a computer monitor or TV, and uses a standard keyboard and mouse. It is a capable little device that enables people of all ages to explore computing and to learn how to program in languages like Scratch and Python.
    • MicroSD Card – 32GB Class10-With a storage capacity of up to 32 GB, this microSDHC and microSDXC card lets you store Full HD Videos as well as music, photos and other files. … They are shockproof, temperature-proof, waterproof and x-ray proof, so that you don’t have to worry about the durability of your memory card.
    • Raspberry Pi Power Supply (5V 2.5A)-The CanaKit 2.5A Raspberry Pi power supply differs from typical standard 5V USB power supplies in the market in that it can deliver a full 2.5A and still output a voltage well within the USB minimum voltage specifications. … This adapter has been specially designed and tested for the new Raspberry Pi 3.


The ESP32-CAM is a camera module with the ESP32-S chip. It features an OV2640 camera, several GPIOs to connect peripherals, and microSD card slot.


Follow the next steps to build a video streaming web server with the ESP32-CAM that you can access on your local network. 

1. Install the ESP32 add-on

In this example, we use Arduino IDE to program the ESP32-CAM board. So, you need to have Arduino IDE installed as well as the ESP32 add-on.

2. Video Streaming Web Server Code

           After that, copy the code below to your Arduino IDE.

#include "esp_camera.h"
#include <WiFi.h>
#include "esp_timer.h"
#include "img_converters.h"
#include "Arduino.h"
#include "fb_gfx.h"
#include "soc/soc.h" //disable brownout problems
#include "soc/rtc_cntl_reg.h"  //disable brownout problems
#include "esp_http_server.h"
//Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
#define PART_BOUNDARY "123456789000000000000987654321"
// This project was tested with the AI Thinker Model, M5STACK PSRAM Model and M5STACK WITHOUT PSRAM
// Not tested with this model
  #define PWDN_GPIO_NUM    -1
  #define RESET_GPIO_NUM   -1
  #define XCLK_GPIO_NUM    21
  #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      19
  #define Y4_GPIO_NUM      18
  #define Y3_GPIO_NUM       5
  #define Y2_GPIO_NUM       4
  #define VSYNC_GPIO_NUM   25
  #define HREF_GPIO_NUM    23
  #define PCLK_GPIO_NUM    22
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     25
  #define SIOC_GPIO_NUM     23
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       32
  #define VSYNC_GPIO_NUM    22
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     25
  #define SIOC_GPIO_NUM     23
  #define Y9_GPIO_NUM       19
  #define Y8_GPIO_NUM       36
  #define Y7_GPIO_NUM       18
  #define Y6_GPIO_NUM       39
  #define Y5_GPIO_NUM        5
  #define Y4_GPIO_NUM       34
  #define Y3_GPIO_NUM       35
  #define Y2_GPIO_NUM       17
  #define VSYNC_GPIO_NUM    22
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21
  #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
  #error "Camera model not selected"
static const char* _STREAM_CONTENT_TYPE = "multipart/x-mixed-replace;boundary=" PART_BOUNDARY;
static const char* _STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
static const char* _STREAM_PART = "Content-Type: image/jpeg\r\nContent-Length: %u\r\n\r\n";
httpd_handle_t stream_httpd = NULL;
static esp_err_t stream_handler(httpd_req_t *req){
  camera_fb_t * fb = NULL;
  esp_err_t res = ESP_OK;
  size_t _jpg_buf_len = 0;
  uint8_t * _jpg_buf = NULL;
  char * part_buf[64];
  res = httpd_resp_set_type(req, _STREAM_CONTENT_TYPE);
  if(res != ESP_OK){
    return res;
    fb = esp_camera_fb_get();
    if (!fb) {
      Serial.println("Camera capture failed");
      res = ESP_FAIL;
    } else {
      if(fb->width > 400){
        if(fb->format != PIXFORMAT_JPEG){
          bool jpeg_converted = frame2jpg(fb, 80, &_jpg_buf, &_jpg_buf_len);
          fb = NULL;
            Serial.println("JPEG compression failed");
            res = ESP_FAIL;
        } else {
          _jpg_buf_len = fb->len;
          _jpg_buf = fb->buf;
    if(res == ESP_OK){
      size_t hlen = snprintf((char *)part_buf, 64, _STREAM_PART, _jpg_buf_len);
      res = httpd_resp_send_chunk(req, (const char *)part_buf, hlen);
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, (const char *)_jpg_buf, _jpg_buf_len);
    if(res == ESP_OK){
      res = httpd_resp_send_chunk(req, _STREAM_BOUNDARY, strlen(_STREAM_BOUNDARY));
      fb = NULL;
      _jpg_buf = NULL;
    } else if(_jpg_buf){
      _jpg_buf = NULL;
    if(res != ESP_OK){
    //Serial.printf("MJPG: %uB\n",(uint32_t)(_jpg_buf_len));
  return res;
void startCameraServer(){
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  //Serial.printf("Starting web server on port: '%d'\n", config.server_port);
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &index_uri);
void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG; 
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  // Camera init
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
  // Wi-Fi connection
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
  Serial.println("WiFi connected");
  Serial.print("Camera Stream Ready! Go to: http://");
  // Start streaming web server
void loop() {


Before uploading the code, you need to insert your network credentials in the following variables:

const char* ssid = “REPLACE_WITH_YOUR_SSID”;

const char* password = “REPLACE_WITH_YOUR_PASSWORD”;

Then, make sure you select the right camera module. In this case, we’re using the AI-THINKER Model.

If you’re using the same camera module, you don’t need to change anything on the code.


Now, you can upload the code to your ESP32-CAM board.

3. Uploading the Code

Connect the ESP32-CAM board to your computer using an FTDI programmer. Follow the next schematic diagram:

Many FTDI programmers have a jumper that allows you to select 3.3V or 5V. Make sure the jumper is in the right place to select 5V.

To upload the code, follow the next steps:

1) Go to Tools > Board and select AI-Thinker ESP32-CAM.

2) Go to Tools > Port and select the COM port the ESP32 is connected to.

3) Then, click the upload button to upload the code.

4) When you start to see these dots on the debugging window as shown below, press the ESP32-CAM on-board RST button.

After a few seconds, the code should be successfully uploaded to your board.

Getting the IP address

After uploading the code, disconnect GPIO 0 from GND. Open the Serial Monitor at a baud rate of 115200. Press the ESP32-CAM on-board Reset button.

The ESP32 IP address should be printed in the Serial Monitor.

Accessing the Video Streaming Server

Now, you can access your camera streaming server on your local network. Open a browser and type the ESP32-CAM IP address. A page with the current video streaming should load.

Home Assistant Integration

For most people, having simply the ESP32-CAM functioning over IP is sufficient, but you may also integrate this project with Home Assistant (or with other home automation platforms). Continue reading to find out how to use Home Assistant.

Adding ESP32-CAM to Home Assistant

  1. Open your Home Assistant dashboard and go to the more Settings menu.
  2. Open Configure UI:
  3. Add a new card to your Dashboard:
  4. Pick a card of the type Picture.
  5. In the Image URL field, enter your ESP32-CAM IP address. Then, click the “SAVE” button and return to the main dashboard.
  6. After that, Home Assistant can display the ESP32-CAM video streaming.

Taking It Further

To take this project further, you can use one fake dummy camera and place the ESP32-CAM inside.

The ESP32-CAM board fits perfectly into the dummy camera enclosure.

You can power it using a 5V power adapter through the ESP32-CAM GND and 5V pins.

Place the surveillance camera in a suitable place.

After that, go to the camera IP address or to your Home Assistant dashboard and see in real-time what’s happening. 

Tip: Node-RED Integration

The video streaming web server also integrates with Node-RED and Node-RED Dashboard. You just need to create a Template node and add the following:

<div style=”margin-bottom: 10px;”>

<img src=”https://YOUR-ESP32-CAM-IP-ADDRESS” width=”650px”>


In the src attribute, you need to type your ESP32-CAM IP address:

<div style=”margin-bottom: 10px;”>

<img src=”″ width=”650px”>



In this tutorial, we have understood how to make an IP camera using the ESP32-CAM board and a simple video streaming web server. Our web server is simple to integrate with your home automation platforms, such as Node-RED or Home Assistant.