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.

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:

  • ESP32-CAM with OV2640 – read Best ESP32-CAM Dev Boards
  • FTDI programmer
  • Female-to-female jumper wires
  • Fake/dummy dome security camera
  • 5V power supply for ESP32-CAM
  • Optional – Home Assistant on Raspberry Pi:
    • Raspberry Pi Board  – read Best Raspberry Pi Starter Kits
    • MicroSD Card – 32GB Class10
    • Raspberry Pi Power Supply (5V 2.5A)

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.

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.

/*********
  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
  
*********/

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

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.

#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:

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.

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

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.

Open Configure UI:

Add a new card to your Dashboard:

Pick a card of the type Picture.

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

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

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. The following image shows us testing the video streaming camera. Sara is taking a screenshot while I’m filming the camera.

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.

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.Report this ad

If you like this project, you may also like other projects with the ESP32-CAM:

Published by Gnd_To_Vcc

Here to spread my knowledge . Knowledge should always be spread not stored.

7 thoughts on “ESP32-CAM Video Streaming Web Server (works with Home Assistant)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Create your website with WordPress.com
Get started
%d bloggers like this: