ESP32-CAM Video Streaming Web Server (works with Home Assistant)

In this project we’re going to build an IP surveillance camera with the ESP32-CAM board. The ESP32 camera is going to host a video streaming web server that you can access with any device in your network.

ESP32-CAM Video Streaming Web Server works with Home Assistant Node-RED

You can integrate this video streaming web server with popular home automation platforms like Home Assistant or Node-RED. In this tutorial, we’ll show you how to integrate it with Home Assistant and Node-RED.

Watch the Video Tutorial

You can watch the video tutorial or keep reading this page for the written instructions.

Parts Required

To follow this tutorial you need the following components:

You can use the preceding links or go directly to MakerAdvisor.com/tools to find all the parts for your projects at the best price!

Introducing the ESP32-CAM

The ESP32-CAM is a very small camera module with the ESP32-S chip that costs less than $10. You can read our getting started guide for the ESP32-CAM and learn how to use the Video Streaming and Face Recognition example.

Introducing the ESP32-CAM camera board AI Thinker module

Video Streaming Server

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. Follow one of the next tutorials to install the ESP32 add-on, if you haven’t already:

2. Video Streaming Web Server Code

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

/*********
  Rui Santos
  Complete project details at https://RandomNerdTutorials.com/esp32-cam-video-streaming-web-server-camera-home-assistant/
  
  IMPORTANT!!! 
   - Select Board "AI Thinker ESP32-CAM"
   - GPIO 0 must be connected to GND to upload a sketch
   - After connecting GPIO 0 to GND, press the ESP32-CAM on-board RESET button to put your board in flashing mode
  
  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files.

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.
*********/

#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
#define CAMERA_MODEL_AI_THINKER
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WITHOUT_PSRAM

// Not tested with this model
//#define CAMERA_MODEL_WROVER_KIT

#if defined(CAMERA_MODEL_WROVER_KIT)
  #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

#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
  #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

#elif defined(CAMERA_MODEL_M5STACK_WITHOUT_PSRAM)
  #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

#elif defined(CAMERA_MODEL_AI_THINKER)
  #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
#else
  #error "Camera model not selected"
#endif

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;
  }

  while(true){
    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);
          esp_camera_fb_return(fb);
          fb = NULL;
          if(!jpeg_converted){
            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));
    }
    if(fb){
      esp_camera_fb_return(fb);
      fb = NULL;
      _jpg_buf = NULL;
    } else if(_jpg_buf){
      free(_jpg_buf);
      _jpg_buf = NULL;
    }
    if(res != ESP_OK){
      break;
    }
    //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
 
  Serial.begin(115200);
  Serial.setDebugOutput(false);
  
  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; 
  
  if(psramFound()){
    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);
    return;
  }
  // Wi-Fi connection
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  
  Serial.print("Camera Stream Ready! Go to: http://");
  Serial.print(WiFi.localIP());
  
  // Start streaming web server
  startCameraServer();
}

void loop() {
  delay(1);
}

View raw code

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.

ESP32-CAM camera board AI Thinker module CAMERA_MODEL_AI_THINKER

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

#define CAMERA_MODEL_AI_THINKER

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:

ESP32-CAM connected to an FTDI Programmer to upload program using Arduino IDE

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.

Important: GPIO 0 needs to be connected to GND so that you’re able to upload code.

ESP32-CAMFTDI Programmer
GNDGND
5VVCC (5V)
U0RTX
U0TRX
GPIO 0GND

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.

ESP32-CAM camera board upload new Arduino IDE sketch press RESET 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.

ESP32-CAM camera board IP address Arduino IDE 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.

ESP32-CAM camera board IP address web Server IP CAM demonstration

Home Assistant Integration

Having just the ESP32-CAM working via IP might be useful for most people, but you can integrate this project with Home Assistant (or with other home automation platforms). Continue reading to learn how to integrate with Home Assistant.

Prerequisites

Adding ESP32-CAM to Home Assistant

Open your Home Assistant dashboard and go to the more Settings menu.

ESP32-CAM camera board IP address web Server IP CAM home assistant integration

Open Configure UI:

ESP32-CAM camera board IP address web Server IP CAM configure UI

Add a new card to your Dashboard:

ESP32-CAM camera board home assistant add new element

Pick a card of the type Picture.

ESP32-CAM camera board home assistant Picture card

In the Image URL field, enter your ESP32-CAM IP address. Then, click the “SAVE” button and return to the main dashboard.

ESP32-CAM camera board home assistant IP Address

If you’re using the configuration file, this is what you need to add.

ESP32-CAM camera board home assistant card configuration

After that, Home Assistant can display the ESP32-CAM video streaming.

ESP32-CAM camera board home assistant demonstration

Taking It Further

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

ESP32-CAM camera board fake camera

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

Fake dummy camera with ESP32-CAM AI Thinker Module Inside IP Camera CAM web server

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

Fake dummy camera with ESP32-CAM AI Thinker Module Inside powering

Place the surveillance camera in a suitable place.

ESP32-CAM camera board surveillance IP Cam

After that, go to the camera IP address or to your Home Assistant dashboard and see in real time what’s happening. The following image shows us testing the video streaming camera. Sara is taking a screenshot while I’m filming the camera.

ESP32-CAM camera board surveillance IP Cam home assistant

It’s impressive what this little $9 ESP32 camera module can do and it’s been working reliably. Now, we can use the surveillance camera to see in real time what’s happening in my front entrance.

ESP32-CAM camera board surveillance IP Cam home assistant

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">
</div>

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

<div style="margin-bottom: 10px;">
<img src="https://192.168.1.91" width="650px">
</div>

Troubleshooting

If you’re getting any of the following errors, read our ESP32-CAM Troubleshooting Guide: Most Common Problems Fixed

  • Failed to connect to ESP32: Timed out waiting for packet header
  • Camera init failed with error 0x20001 or similar
  • Brownout detector or Guru meditation error
  • Sketch too big error – Wrong partition scheme selected
  • Board at COMX is not available – COM Port Not Selected
  • Psram error: GPIO isr service is not installed
  • Weak Wi-Fi Signal
  • No IP Address in Arduino IDE Serial Monitor
  • Can’t open web server
  • The image lags/shows lots of latency

Wrapping Up

In this tutorial, we’ve shown you how to build a simple video streaming web server with the ESP32-CAM board to build an IP camera. The web server we’ve built can be easily integrated with your home automation platform like Node-RED or Home Assistant.

We hope you’ve find this tutorial useful. If you don’t have an ESP32-CAM yet, you can grab it here.