ESP32-CAM Pan and Tilt Video Streaming Web Server (2 Axis)

In this project, we’ll attach the ESP32-CAM to a pan and tilt stand with two SG90 servo motors. With a pan and tilt camera stand, you can move the camera up, down, to the left, and the right— this is great for surveillance. The ESP32-CAM hosts a web server that shows video streaming and buttons to control the servo motors to move the camera.

Boards compatibility: for this project, you need an ESP32 camera development board with
access to two GPIOs to control two servo motors. You can use: ESP32-CAM AI-Thinker, T-Journal or TTGO T-Camera Plus.

Parts Required

For this project, we’ll use the following parts:

Pan and Tilt Stand and Motors

For this project, we’ll use a pan and tilt stand that already comes with two SG90 servo motors. The stand is shown in the following figure.

Pan and Tilt with SG90 Servo Motors ESP32-CAM

We got our stand from Banggood, but you can get yours from any other store.

Alternatively, you can get two SG90 servo motors and 3D print your own stand.

Servo motors have three wires with different colors:

WireColor
PowerRed
GNDBlack or brown
SignalYellow, orange or white

How to Control a Servo?

You can position the servo’s shaft at various angles from 0 to 180º. Servos are controlled using a pulse width modulation (PWM) signal. This means that the PWM signal sent to the motor determines the shaft’s position.

ESP32-CAM servo shaft angles from 0 to 180º

To control the servo motor, you can use the PWM capabilities of the ESP32 by sending a signal with the appropriate pulse width. Or you can use a library to make the code simpler. We’ll be using the ESP32Servo library.

Installing the ESP32Servo Library

To control servo motors, we’ll use the ESP32Servo library. Make sure you install that library before proceeding. In your Arduino IDE, go to Sketch > Include Library > Manage Libraries. Search for ESP32Servo and install the library version 0.4.2. At the time of writing this project, more recent versions crash the camera video streaming.

ESP32Servo library

Code

Copy the following code to your Arduino IDE.

/*********
  Rui Santos
  Complete instructions at https://RandomNerdTutorials.com/esp32-cam-projects-ebook/
  
  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"
#include <ESP32Servo.h>

// Replace with your network credentials
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

#define PART_BOUNDARY "123456789000000000000987654321"

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

#elif defined(CAMERA_MODEL_M5STACK_PSRAM_B)
  #define PWDN_GPIO_NUM     -1
  #define RESET_GPIO_NUM    15
  #define XCLK_GPIO_NUM     27
  #define SIOD_GPIO_NUM     22
  #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    25
  #define HREF_GPIO_NUM     26
  #define PCLK_GPIO_NUM     21

#else
  #error "Camera model not selected"
#endif

#define SERVO_1      14
#define SERVO_2      15

#define SERVO_STEP   5

Servo servoN1;
Servo servoN2;
Servo servo1;
Servo servo2;

int servo1Pos = 0;
int servo2Pos = 0;

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 camera_httpd = NULL;
httpd_handle_t stream_httpd = NULL;

static const char PROGMEM INDEX_HTML[] = R"rawliteral(
<html>
  <head>
    <title>ESP32-CAM Robot</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
      body { font-family: Arial; text-align: center; margin:0px auto; padding-top: 30px;}
      table { margin-left: auto; margin-right: auto; }
      td { padding: 8 px; }
      .button {
        background-color: #2f4468;
        border: none;
        color: white;
        padding: 10px 20px;
        text-align: center;
        text-decoration: none;
        display: inline-block;
        font-size: 18px;
        margin: 6px 3px;
        cursor: pointer;
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
      }
      img {  width: auto ;
        max-width: 100% ;
        height: auto ; 
      }
    </style>
  </head>
  <body>
    <h1>ESP32-CAM Pan and Tilt</h1>
    <img src="" id="photo" >
    <table>
      <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('up');" ontouchstart="toggleCheckbox('up');">Up</button></td></tr>
      <tr><td align="center"><button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');">Left</button></td><td align="center"></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');">Right</button></td></tr>
      <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('down');" ontouchstart="toggleCheckbox('down');">Down</button></td></tr>                   
    </table>
   <script>
   function toggleCheckbox(x) {
     var xhr = new XMLHttpRequest();
     xhr.open("GET", "/action?go=" + x, true);
     xhr.send();
   }
   window.onload = document.getElementById("photo").src = window.location.href.slice(0, -1) + ":81/stream";
  </script>
  </body>
</html>
)rawliteral";

static esp_err_t index_handler(httpd_req_t *req){
  httpd_resp_set_type(req, "text/html");
  return httpd_resp_send(req, (const char *)INDEX_HTML, strlen(INDEX_HTML));
}

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

static esp_err_t cmd_handler(httpd_req_t *req){
  char*  buf;
  size_t buf_len;
  char variable[32] = {0,};
  
  buf_len = httpd_req_get_url_query_len(req) + 1;
  if (buf_len > 1) {
    buf = (char*)malloc(buf_len);
    if(!buf){
      httpd_resp_send_500(req);
      return ESP_FAIL;
    }
    if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
      if (httpd_query_key_value(buf, "go", variable, sizeof(variable)) == ESP_OK) {
      } else {
        free(buf);
        httpd_resp_send_404(req);
        return ESP_FAIL;
      }
    } else {
      free(buf);
      httpd_resp_send_404(req);
      return ESP_FAIL;
    }
    free(buf);
  } else {
    httpd_resp_send_404(req);
    return ESP_FAIL;
  }

  sensor_t * s = esp_camera_sensor_get();
  //flip the camera vertically
  //s->set_vflip(s, 1);          // 0 = disable , 1 = enable
  // mirror effect
  //s->set_hmirror(s, 1);          // 0 = disable , 1 = enable

  int res = 0;
  
  if(!strcmp(variable, "up")) {
    if(servo1Pos <= 170) {
      servo1Pos += 10;
      servo1.write(servo1Pos);
    }
    Serial.println(servo1Pos);
    Serial.println("Up");
  }
  else if(!strcmp(variable, "left")) {
    if(servo2Pos <= 170) {
      servo2Pos += 10;
      servo2.write(servo2Pos);
    }
    Serial.println(servo2Pos);
    Serial.println("Left");
  }
  else if(!strcmp(variable, "right")) {
    if(servo2Pos >= 10) {
      servo2Pos -= 10;
      servo2.write(servo2Pos);
    }
    Serial.println(servo2Pos);
    Serial.println("Right");
  }
  else if(!strcmp(variable, "down")) {
    if(servo1Pos >= 10) {
      servo1Pos -= 10;
      servo1.write(servo1Pos);
    }
    Serial.println(servo1Pos);
    Serial.println("Down");
  }
  else {
    res = -1;
  }

  if(res){
    return httpd_resp_send_500(req);
  }

  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  return httpd_resp_send(req, NULL, 0);
}

void startCameraServer(){
  httpd_config_t config = HTTPD_DEFAULT_CONFIG();
  config.server_port = 80;
  httpd_uri_t index_uri = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = index_handler,
    .user_ctx  = NULL
  };

  httpd_uri_t cmd_uri = {
    .uri       = "/action",
    .method    = HTTP_GET,
    .handler   = cmd_handler,
    .user_ctx  = NULL
  };
  httpd_uri_t stream_uri = {
    .uri       = "/stream",
    .method    = HTTP_GET,
    .handler   = stream_handler,
    .user_ctx  = NULL
  };
  if (httpd_start(&camera_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(camera_httpd, &index_uri);
    httpd_register_uri_handler(camera_httpd, &cmd_uri);
  }
  config.server_port += 1;
  config.ctrl_port += 1;
  if (httpd_start(&stream_httpd, &config) == ESP_OK) {
    httpd_register_uri_handler(stream_httpd, &stream_uri);
  }
}

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
  servo1.setPeriodHertz(50);    // standard 50 hz servo
  servo2.setPeriodHertz(50);    // standard 50 hz servo
  servoN1.attach(2, 1000, 2000);
  servoN2.attach(13, 1000, 2000);
  
  servo1.attach(SERVO_1, 1000, 2000);
  servo2.attach(SERVO_2, 1000, 2000);
  
  servo1.write(servo1Pos);
  servo2.write(servo2Pos);
  
  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_VGA;
    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.println(WiFi.localIP());
  
  // Start streaming web server
  startCameraServer();
}

void loop() {
  
}

View raw code

Network Credentials

Insert your network credentials and the code should work straight away.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";

How the Code Works

Let’s take a look at the relevant parts to control the servo motors.

Define the pins the servo motors are connected to. In this case, they are connected to the ESP32-CAM GPIOs 14 and 15.

#define SERVO_1 14
#define SERVO_2 15

Create Servo objects to control each motor:

Servo servoN1;
Servo servoN2;
Servo servo1;
Servo servo2;

You may be wondering why we are creating four Servo objects when we only have two servos. What happens is that the servo library we’re using automatically assigns a PWM channel to each servo motor (servoN1 → PWM channel 0; servoN2 → PWM channel 1; servo1 → PWM channel 2; servo2 → PWM channel 3).

The first channels are being used by the camera, so if we change those PWM channels’ properties, we’ll get errors with the camera. So, we’ll control servo1 and servo2 that use PWM channels 2 and 3 that are not being used by the camera.

Define the servos initial position.

int servo1Pos = 0;
int servo2Pos = 0;

Web Page

The INDEX_HTML variable contains the HTML text to build the web page. The following lines display the buttons.

<table>
  <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('up');" ontouchstart="toggleCheckbox('up');">Up</button></td></tr>
  <tr><td align="center"><button class="button" onmousedown="toggleCheckbox('left');" ontouchstart="toggleCheckbox('left');">Left</button></td><td align="center"></td><td align="center"><button class="button" onmousedown="toggleCheckbox('right');" ontouchstart="toggleCheckbox('right');">Right</button></td></tr>
  <tr><td colspan="3" align="center"><button class="button" onmousedown="toggleCheckbox('down');" ontouchstart="toggleCheckbox('down');">Down</button></td></tr>                   
</table>

When you click the buttons, the toggleCheckbox() JavaScript function is called. It makes a request on a different URL depending on the button clicked.

function toggleCheckbox(x) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", "/action?go=" + x, true);
  xhr.send();
}


Here are the requests made depending on the button that is being pressed:

Up:

/action?go=up

Down:

/action?go=down

Left:

/action?go=left

Right:

/action?go=right

Handle Requests

Then, we need to handle what happens when we get those requests. That’s what’s done in the following lines.

if(!strcmp(variable, "up")) {
  if(servo1Pos <= 170) {
    servo1Pos += 10;
    servo1.write(servo1Pos);
  }
  Serial.println(servo1Pos);
  Serial.println("Up");
}
else if(!strcmp(variable, "left")) {
  if(servo2Pos <= 170) {
    servo2Pos += 10;
    servo2.write(servo2Pos);
  }
  Serial.println(servo2Pos);
  Serial.println("Left");
}
else if(!strcmp(variable, "right")) {
  if(servo2Pos >= 10) {
    servo2Pos -= 10;
    servo2.write(servo2Pos);
  }
  Serial.println(servo2Pos);
  Serial.println("Right");
}
else if(!strcmp(variable, "down")) {
  if(servo1Pos >= 10) {
    servo1Pos -= 10;
    servo1.write(servo1Pos);
  }
  Serial.println(servo1Pos);
  Serial.println("Down");
}

To move a motor, call the write() function on the servo1 or servo2 objects and pass the angle (0 to 180) as an argument. For example:

servo1.write(servo1Pos);

setup()

In the setup(), set the servo motor properties: define the signal frequency.

servo1.setPeriodHertz(50); // standard 50 hz servo
servo2.setPeriodHertz(50); // standard 50 hz servo

Use the attach() method to set the servo GPIO and minimum and maximum pulse width in microseconds.

servo1.attach(SERVO_1, 1000, 2000);
servo2.attach(SERVO_2, 1000, 2000);

Set the motors to its initial position when the ESP32 first boots.

servo1.write(servo1Pos);
servo2.write(servo2Pos);

That’s pretty much how the code works when it comes to control the servo motors.

Testing the Code

After inserting your network credentials, you can upload the code to your board. You can use an FTDI programmer or an ESP32-CAM MB programmer. Read one of the following articles:

After uploading, open the Serial Monitor to get the board IP address.

ESP32-CAM Getting IP Address Serial Monitor

Note: if you’re using an FTDI programmer, don’t forget to disconnect GPIO 0 from GND before opening the Serial Monitor.

Open a browser and type the board IP address to get access to the web server. Click on the buttons and check on the Serial Monitor if everything seems to be working as expected.

ESP32-CAM Pan and Tilt Web Server Serial Monitor

If everything is working as expected you can wire the servo motors to the ESP32-CAM and continue with the project.

Circuit

After assembling the pan and tilt stand, connect the servo motors to the ESP32-CAM as shown in the following schematic diagram. We’re connecting the servo motor data pins to GPIO 15 and GPIO 14.

ESP32-CAM Pan and Tilt Servo Motors

You can use a mini breadboard to assemble the circuit or build a mini stripboard with header pins to connect power, the ESP32-CAM, and the motors, as shown below.

ESP32-CAM Pan and Tilt Prototype Board

The following figure shows how the pan and tilt stands looks like after assembling.

ESP32-CAM Pan and Tilt Assembled

Demonstration

Apply power to your board. Open a browser and type the ESP32-CAM IP address. A web page with real-time video streaming should load. Click the buttons to move the camera up, down, left, or right.

ESP32-CAM Pan and Tilt Web Server Video Streaming Smartphone Arduino IDE

You can move the camera remotely using the buttons on the web page. This allows you to
monitor a different area accordingly to the camera position. This is a great solution for surveillance applications.

Wrapping Up

In this tutorial, you’ve learned how to build a pan and tilt web server with video streaming to control the ESP32-CAM.

Controlling servo motors with the ESP32-CAM is the same as controlling them using a “regular” ESP32. You can read the following tutorial to learn more about servo motors with the ESP32:

If you want to control your robot outside the range of your local network, you might consider setting the ESP32-CAM as an access point. This way, the ESP32-CAM doesn’t need to connect to your router. It creates its own wi-fi network, and nearby wi-fi devices like your smartphone can connect to it.

For more projects and tutorials with the ESP32-CAM:



Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »
Learn how to build a home automation system and we’ll cover the following main subjects: Node-RED, Node-RED Dashboard, Raspberry Pi, ESP32, ESP8266, MQTT, and InfluxDB database DOWNLOAD »

Enjoyed this project? Stay updated by subscribing our newsletter!

148 thoughts on “ESP32-CAM Pan and Tilt Video Streaming Web Server (2 Axis)”

      • No – if you can to port forwarding, then anything which is accessible on your local network, can be made accessible worldwide via the internet.

        Reply
      • I agree with Sara per these instructions! Thank you Rui and Sara for all your time and great work!!!

        With some “EXTRA” work, it can be accessed from anywhere on the Web securely. Currently, I’m able to view/access my HomeAssistant, all my MotionEye Cameras and all the host on my internal home network. My solution uses a VPN, WireGuard specifically. With the VPN connected, I can access from my phone via it’s browser or a ssh client, my remote LAN IP’s like 192.168.1.85, my HomeAssistant server. And still browse from my phone to access other “Public Web Sites”

        It’s not the simplest task, but, very useful once completed. I use the following components:
        – VPN: Wireguard
        It creates an encrypted tunnel from my phone, computer or chromebook to allow/forward “Interesting traffic”, ie, IP addresses that are on my Home LAN to be accessed remotely.
        – Duckdns
        To determine my router’s current WAN External Public IP’s DHCP address assigned my my Internet Service Provider, ISP.
        – Port forwarding
        On my router for inbound traffic from the internet into my local network/LAN to establish the Wireguard encrypted tunnel which the “traffic” from my phone, etc passes through securely.

        Reply
    • It is possible followingg these steps:
      1) subscribe a DDNS service (needed if you have an ISP that assignes a dynmaic IP to your router: best thing if you check before which DDNS services your router will accept);
      2) on your router configure the DDNS service ;
      3) on your ruoter assign a static IP address to the MAC address of your microcontroller/cam ;
      4) on your router assign a TCP/HTTTP port forwording rule to the MAC/IP address of your microcontroller (WAN and LAN ports = 80);
      After that you can access your cam locally via its assigned IP address (e.g. on your browser go to the address 192.168.1.XX) or via internet using the address that was given to you by the DDNS provider (e.g. arduinocam.mypc.com).
      Many thanks to Rui and Sara for their fantastic site!!

      Reply
      • Bonjour,
        Merci pour votre mail.

        J’ai bien un lien dynamique et ca fonctionne bien.
        Le port 80 est le port par défaut et donc avec mon lien DNS je ne peut accéder qu’à un seul montage.

        Mon problème c’est que j’ai besoin d’accéder à plusieurs caméras Pan et Tilt et c’est pour cela que je voudrai pouvoir affecter un no de port différent.
        ex: arduinocam.mypc.com:49160

        Cordialement
        Bernard

        Reply
        • Bernard,
          try these steps:
          1) on your router side configure different ports in the portwarding rules: e.g. CAM1: WAN Port = 80 & LAN Port =80
          CAM2: WAN Port = 82 & LAN Port =82
          2) in the Arduino code change this line: config.server_port = 80; to config.server_port = 82; for CAM2

          Now you should be able to access CAM1 on arduinocam.mypc.com:80 (or arduinocam.mypc.com, as port 80 is the dafault one) and CAM2 on arduinocam.mypc.com:82
          Let me know …

          Reply
          • Bonjour,

            désolé pour ce retard…
            Je reprend ce tuto.
            Pouvez vous me dire à quoi correspond CAM1 et CAM2 dans votre
            explication?
            cordialement

          • En fait j’ai testé avec port 82 pour CAM2 en local et je me connecte bien,
            j’ai bien les boutons mais pas de flux vidéo?

            Merci

          • Open port 80 on your WAN router is not a secure method to connect on. All traffic passes in “clear text”, ie, any passwords you enter. You might think it only video, well a man-in-the-middle attact could hijack your video and re-post it to the web allowing anyone to see where you live, what you look like, or oh, any valuables you have.

            Please use a VPN for your families safety.

  1. Vielen Dank für dieses Projekt. Ich bin vermutlich einer von vielen die danach gefragt haben und freue mich sehr das ihr es realisiert habt. Durch dieses Projekt werden die ESP32-CAMs besonders nützlich und ermöglichen mir ganz neue Einblicke in die Tier Welt.
    Nochmals Vielen Dank,
    Horst Ketter

    Translated:
    “Thank you for this project. I am probably one of many who asked about it and I am very happy that you have realized it. This project makes the ESP32-CAMs particularly useful and gives me completely new insights into the animal world.
    Thanks again,
    Horst Ketter”

    Reply
  2. It Works!!!

    Just finished this project and all works fine.
    I had trouble with another ESP32 Cam with pan and tilt hanging on me.
    I see you solved that problem by creating four servo objects, nice work!

    Thanks for sharing this with us!

    Reply
  3. Great, I have that Pan and Tilt gimball, including the servo’s exactly for this purpose, but had not gotten around to program it. Thanks for making it easy for me

    Reply
  4. Hi . thank you for this project. is there any way that transfer voice of enviroment with esp32-cam to webserver or webpage?
    thanks for attention
    regards

    Reply
  5. Thank you for this project. It works very well.
    Is there a safe way to be able to access it from out side the local network without opening any ports on the router.

    Reply
  6. Your circuit diagram shows 3.3v connected to the servos but servos specs say operating voltage is 4.8v to 6.5v. Is 3.3v in your circuit diagram correct?
    Thanks

    Reply
    • Hi Dave.
      Yes, you are right.
      I was a mistake in designing the diagram.
      You should connect to the 5V pin.
      If you look closely at the stripboard, you’ll notice that it will connect 5V to the motors.
      I’ll fix the schematic now.
      Thanks for noticing.
      Regards,
      Sara

      Reply
  7. Do you have a picture or schematic of the underside of the stripboard? The wiring and circuit diagram are not making sense to me.
    Thanks

    Reply
    • Hi Dave.
      Unfortunately, we don’t have a scheme for the stripboard.
      You just need to take into account the following connections (then, you can build a stripboard to make wiring easier)

      Servo 1:
      GND –> GND
      VCC –> 5V
      Data –> GPIO 15

      Servo 2:
      GND –> GND
      VCC –> 5V
      Data –> GPIO 14

      Because the ESP32 only has one 5V pin, you need to wire both VCC pins to the same ESP32 pin.
      I hope this is clear.
      Regards,
      Sara

      Reply
  8. Hello your program is great.
    Thank you, this gives very good possibilities.
    I haven’t done this project yet, but I see the photo on the smartphone is tilted 90 degrees.
    Is it possible to straighten the streaming image for a good view please?

    Reply
  9. Hi Sara,
    I got the tilt platform to work, the only thing I do have it is that the picture is 90 degree.
    Is there any thing to rotate the picture. I know with the normal view in the browser there is a click field to turn the view.
    Regards
    Willem

    Reply
    • Hello from France,
      I saw a tutorial concerning the possibility of making 90 ° rotations, I put the link in a post above.
      I think this possibility is very interesting, but I don’t know how to integrate it into Sara’s excellent tutorial.
      Maybe this will interest Sara and she can add a modification for this 90 ° rotation function, that would be very interesting? …

      Reply
      • Hello Gérard,
        I read your previous e-mail about rotating the image.
        After the command on the original scrip:
        window.onload = document.getElementById(“photo”).src = Window.location.href.slice(0, -1) + “:81/stream”;
        Add this 2 lines:
        var deg = -90;
        window.onload = document.getElementById(“photo”).style.transform = ‘rotate(‘ + deg + ‘deg)’;
        Changing the “var deg” you can rotate the image to 180º
        Hope it helps.

        Reply
        • Hello Arlvaro Henriques,
          Great (by changing -90 to 90), it’s great.
          I just have to go down the image to be below the Title

          ESP32-CAM Pan and Tilt

          Then move down the buttons to be below the image
          I’ll do some testing so as not to make a mistake.
          thank you so much

          Reply
        • Hello Arlvaro Henriques,
          Ok to go down the image under the title, and go down the buttons under the image.
          I added :
          h2> & nbsp </ h2 under h1> ESP32-CAM Pan and Tilt </ h1 to lower the position of the image
          Then:
          h2> & nbsp </ h2 under img src = “” id = “photo” to lower the position of the buttons
          it works very well, thanks again, it’s great !!!

          Reply
  10. Hi Sara ,

    Thank you for your job !

    I try to add a button to turn on the lamp but I can not! Can you help me, thank you?

    Reply
  11. Hi Sara,

    Found this a very interesting tutorial.

    However I ordered pan & tilt mechanism from your link, but it needs a 5V signal to operate. Had to use 2 level shifters & 2 power supplies (1 for each servo), to achieve the desired effect) . Using same power supply for both servo’s resulted in interfarance between the 2.

    Reply
  12. Bonjour,

    Excellent tuto!
    Pour moi j’ai bien l’image sur mon PC mais rien sur mon telephone portable, je ne vois que les boutons.
    Pouvez vous m’aider?

    Merci

    Reply
    • Hi
      You can only watch the video streaming in one place at a time.
      Please close your computer web browser window before opening it on the smartphone.
      Regards,
      Sara

      Reply
      • Merci pour votre réponse rapide!

        OK effectivement c’était bien mon pb…. sorry.
        Par contre si je change le port 80 par un autre port dans le PGM
        je n’ai plus l’image, j’ai contrôlé dans la boxe et le port concerné est bien activé.

        Voici ou j’ai modifié:
        void startCameraServer(){
        httpd_config_t config = HTTPD_DEFAULT_CONFIG();
        config.server_port = 49161; //port validé dans la boxe

        et à quoi servent ces 2 lignes:
        config.server_port += 1;
        config.ctrl_port += 1;

        Grand merci pour votre aide et vos tutos géniaux.

        Reply
  13. To avoid problems with the servos, you need to put a 10 uf , and a 0.1 uf condenser between + and – and the source is 5V /1Amp

    Reply
  14. Hello,
    Great your program, thank you.
    I’m in the middle of testing, but there must be some problem with my setup, but I don’t know what …
    My low servo (to go from left to right or vice versa) does not stop, it seems that it is still looking for a position because it is continually panicking.
    Do you have an opinion on my problem please?
    (By the way, I posted a suggestion to rotate the image 90 °, if that helps …)
    Please let me know about my panicking servo problem.

    Reply
      • I thought I had solved my pan-tilt problem which trembles as soon as I plug it in, but no, it starts again.
        it might be necessary to put capacitors, but which ones and where, I am zero in electronics …
        Anyone have an idea, and if so can I refer me to a tutorial, please? I searched but could not find anything regarding this problem …

        Reply
        • I continue my research regarding servomotor shaking, so the esp32-cam and pan-tilt servomotors all connected to 5v, the pan-tilt shakes and is uncontrollable from the power on 5v.
          With a 3.3v / 5v power supply module (https://fr.aliexpress.com/item/1722852745.html?detailNewVersion=&categoryId=400103), the esp32-cam connected in 5v, and the pan-tilt servomotors connected in 3.3v, it works, the servomotors no longer shake.
          That’s good, but how do you make a mini board like you suggest, putting 5v for the esp32-com and 3.3v for the servos?
          Can you help me, I’m lost and I’m really bad at electronics, please?

          Reply
          • Hello from France,

            For other people who have the same pan-tilt shaking problem, I follow up: my problem is solved, I definitely hope.
            I had changed several usb cables for the 5v power supply, unfortunately with the same problem.
            And this morning, finding yet another usb cable, I tried it on the off chance, and it works perfectly!
            The esp32-cam AND the Pan-Tilt, all connected to the 5v.
            So this problem was simply the power supply, it will have made me waste a lot of time, but it works very well now with this cable found at the bottom of a drawer …

            This project and tutorial is great, thank you Sara, and well done …

  15. Good day and thanks for posting this article. I was hoping to please ask for some assistance with the esp32-cam.

    I have been attempting to enable face detection on the ESP32-Cam however it has failed on multiple boards. The boards flash ok and the webserver example runs but when I turn on face detection on the webserver web interface, the esp32-cam reboots or shows a memory heap error in Arduino serial monitor (baud rate 115200). I have tried six units and all have failed. I tried powering the esp32-cam directly to a power supply (5V), it still failed. I have flashed using various methods, ESP32-CAM-MB USB Programming Adapter, UNO and a FT232RL FTDI USB to TTL Serial Adapter; they all flash the board but face detection still crashes it.

    Thanks for any ideas or suggestions.

    Reply
    • Hi.
      Please check the version of the ESP32 Boards Installation. It should be 1.0.4. (recent versions have some sort of bug that crashes face recognition).
      In your Arduino IDE, go to Tools > Board > Boards Manager and search for ESP32. Make sure you have version 1.0.4.
      Regards,
      Sara

      Reply
      • Thank you for that tip.

        So, I tried that, changed board to 1.0.4, still no luck. A bunch of gibberish in serial monitor then it says connected to webserver but crashes when face detection is selected using the example webserver code. I tried downgrading Arduino IDE to 1.8.12 as well, but still no luck on the face detection. I have 5 of these left, I did have 6 but one met an untimely demise with a hammer (it felt sooooo good), the rest are destined to this fate soon.

        Reply
        • What’s the board that you’re using?
          Are you using an external antenna?
          Make sure it can catch a good wi-fi signal from your router.
          Regards,
          Sara

          Reply
          • Hello again and thank you for your assistance.

            I am using this board (esp32S chip) and have tried others: https://www.amazon.ca/gp/product/B08XYLSH15/ref=ppx_yo_dt_b_asin_title_o03_s00?ie=UTF8&psc=1

            No external antenna. Wi-fi Router is right beside me.

            The part that is strange, is that directly after a successful flash with Arduino IDE and before any testing, I open serial monitor where it shows a bunch of garbage characters, as if the baud rate is wrong (it’s 115200), right after the garbage it says connecting…, it connects and says successfully connected and gives me the IP of the webserver. I browse to the IP and the Webserver works until I select Face Detection.

            In Arduino IDE, I am choosing the AI THINKER board, should I be choosing Wrover?

            Thanks,
            Norton

          • Hi.
            Try selecting the Wrover module with the following settings and see if it works.
            In Tools > Board select ESP32 Wrover Module.
            In Tools > Partition Scheme select Huge App 3MB No OTA.
            Regards,
            Sara

          • Thanks so much for all the help.

            So here is what worked for me:

            Board: “ESP32 Wrover Module”
            Upload Speed: “115200”
            Flash Frequency: “40MHz”
            Flash Mode: “QIO”
            Partition Scheme: “Huge APP (3MB No OTA/1MB SPIFFS)”
            Cored Debug Level: “None”
            Port: the CH340 port
            Programmer: “AVR ISP”

            Arduino IDE version 1.8.12
            Board: esp32 version 1.0.4

            I did have to push the reset button on the esp32-Cam and timing sometimes was hit and miss however when it worked, it worked successfully.
            The part I’m not getting is that in Arduino IDE the board selected is ESP32 Wrover Module whereas the board in the code is AI_THINKER.

            Thanks again, you guys rock!

          • Hi.
            I’m also not sure why that happens.
            But, I’m glad it is working now.
            Regards,
            Sara

  16. Hello Rui & Sara
    It works!
    One question: I was using 2 Wifi analyzer apps (Netspot on Windows / WIFI Analyzer on Android) in order to measure the signal strength of the ESP32.
    On both apps the ESP32 does not show up.
    Strange because the camera was working.
    What is going on here?
    Regards
    Chris

    Reply
  17. Hi, this project is working in lovalhost, but when I tried to use port forward, using ngrok, the video is not loading (sreaming), but the servos works fine. Could you explain me why it is happening?

    Reply
  18. Hello from France,
    Always to learn, and going further to put your excellent tutorial to my taste, I would like to mark, on the web page, the current position of the servomotors at each change of position, therefore the value of servo1Pos and servo2Pos.
    Do you think this is possible, and if so, can you help me do it, please?

    Reply
    • Hello,
      To have the current position, or the requested position of the servomotor, on the WEB page, I try to take inspiration from your excellent tutorial:
      https://randomnerdtutorials.com/esp32-servo-motor-web-server-arduino-ide/
      But I am very afraid of doing anything stupid to do so.
      Could you help me please, to have the same possibility in the web page of the tutorial with the pan-tilt, I would be really grateful to you, I am not very good at programming, and I really have need to know.

      Reply
      • Hello,
        Nobody has an idea to write the current position, OR the requested position, of the servomotors (servo1Pos and servo2Pos) on the WEB page, at each rotation of the servomotors?
        I really try, but I can’t, help

        Reply
  19. When I used the verify/compile command of Arduino 1.8.13 on this project I got an error on line 299. The variable “s” was not defined. I corrected the error by un-commenting the “flip the camera vertically” setting. Did I miss that in the instructions or is it unique to my installation? Once that error was cleaned up my servos started acting correctly–before that I had some jerky motion and random movements and I would lose connection to the video stream.

    One other thing I noticed is that my up and down servo is opposite–when I click on the down button it goes up and vice versa. I’m using Tower Pro SG92R actuators. I swapped the up and down variables and it seems to do what it should.

    Thanks and as always, a well done, great tutorial.

    Reply
  20. Good Day,

    Would you happen to have any tutorials using the esp32-cam with object tracking using pan/tilt servos?

    Thanks!
    N.

    Reply
  21. Hello,

    Thanks for the reply, yes, there is some helpful information in that tutorial.
    Would you consider doing such a project?

    Thanks,
    N.

    Reply
  22. Hello,
    Really good and helpful project.
    I am just facing problem in adding new variable to display on webpage such as Temperature etc.
    Can you please help me to display such variable on Webpage in the same project

    Thank You,

    Reply
    • Hi.
      You just need to add the needed HTML to display the temperature in the PROGMEM INDEX_HTML and concatenate with a variable that saves the temperature reading.
      Regards,
      Sara

      Reply
      • Hello,
        Thank you for the reply,
        Yes i can make the changes in the Program INDEX_HTML but not able to concatenate.
        Means how to link the temperature data from the main loop to the INDEX_HTML
        Please if possible share some example to understand.
        Thank you,
        Saurabh

        Reply
          • Hi, Thanks again
            As in this code below function is added for the buttons in HTML
            function toggleCheckbox(x) {
            var xhr = new XMLHttpRequest();
            xhr.open(“GET”, “/action?go=” + x, true);
            xhr.send();
            }
            and then below code is used to detect buttons from HTML
            if (httpd_query_key_value(buf, “go”, variable, sizeof(variable)) == ESP_OK)

            So how to use such thing for temperature data to display in the HTML page. Means what to add in the HTML and what in the loop
            I might sound stupid but i am really confused and not able to figure out the solution so please let me how can i do this
            Thank you,
            Saurabh

  23. Bonjour,

    Apres de nombreux tests et essais et controls de la conf des routages sur la boxe je constate que ça ne fonctionne pas sur un port différent de 80 ?!

    Si on ouvre une connexion sur PC -> impossible sur smartphone, et vice versa.
    Apparemment il ne peut y avoir qu’une seule connexion à la fois possible.

    Pourquoi? et Comment faire?
    Quelqu’un a t-il la solution?

    encore bravo pour vos travaux!!!!
    Merci

    Reply
  24. Hello Sara,
    With your simplified program, it’s really great.

    But I would also like to use the Pan-Tilt with the version of the program which is with its menu (original program for example).
    There are 4 tabs:
    The .ino sctech
    app_httpd.cpp
    camera_index.h
    camera_pins.h
    I added the direction buttons in the camera_index.h html file, but for the rest, ie to operate the servomotors, I admit that I am lost, I do not know where to put the different commands. ..
    I tried a lot of hypotheses, but failed …

    Could you please help me, to tell me where to put the different controls in the original program, to get the Pan-Tilt to work, I’m quite a novice …

    Reply
  25. Hi Sara,
    Thanks for the excellent project!
    One of my servos seems to be going backwards… Clicking ‘Up’ makes the tilt go ‘Down’ and clicking ‘Down’ makes it go ‘Up’. ‘Left’ & ‘Right’ appear to be working properly as viewed through the camera.
    What could make this happen?
    I have triple checked my wiring and it looks correct to me…
    Kind regards,
    Mark

    Reply
  26. Hello Sara,

    If I upload the software without CamWebServerTilt It works coorectly. But with the Pan and tilt i have one error: ‘httpd_req_t’ was not declared in this scope.
    Could you help me, please?

    Beleljam

    Reply
  27. I would like to add a voltage meter to this project. And I would like the measurements to be shown on the web server. Do you have any tutorial on how I can attach this meter to this code?

    this is my voltage meter;
    ptrobotics.com/sensor-de-corrente/5099-funduino-voltage-sensor-for-arduino.html?gclid=CjwKCAjwlcaRBhBYEiwAK341jSQk_DlqQ4X-XGamn14BqkWpHKG34_utJKG8pTVRcsei1DSVfOMGWRoCna8QAvD_BwE

    Reply
  28. Hi Sara!
    That’s great project. By the way I have try this and it’s happened that the servo motor wouldn’d work (but while I’m press the up, right, left, bottom it shows just find in arduino monitor) I’ve also check the Vin from batteries and it definitely 5 V.
    Please help. Thanks !

    Reply
    • Hi.
      Check that your servo is working properly with a servo example sketch.
      Also, make sure you’re powering the servo correctly and that it is wired properly on the right pins.
      You can also try to check with an oscilloscope (if you have one) if the GPIOs are outputting the corresponding PWM signals.
      Regards,
      Sara

      Reply
  29. i’ve issue my adapter was blinking when plugging to esp32 cam. So i try to press the reset button in my AI Thinker it’s blinking too.

    My adapter 5v 2A just normally no problem in other. Then i test the servos is was good and work normally.
    i confused in this, where’s the problem..

    Reply
    • Hello Sara,
      Thanks for this project
      Nice tutorial and easy to understand

      I’ve one issue
      The esp32 cam doesn’t connected with adapter.
      Even though when uploading the code esp32 cam is fine and i can access the webserver normally. The problem came after I connected to some wire, servos pin, then to the adapter.

      My adapter was blinking when plugging to esp32 cam that’s mean it doesn’t connected. From there i confused what happen actually. I try to press the RESET button in my AI Thinker it’s blinking too.

      My adapter 5v 2A just normally no problem in other. Then i test the servos is was good and work normally. The wiring like pins and other everything is correct.

      Please help this problem
      Thanks!

      Reply
  30. Many thanks for another great project. Is there a way of using AsyncElegantOTA with this video streaming. When I try I get the compile error redefinition of HTTP_GET.

    Reply
  31. Hi! Thank you very much for this wonderful tutorial!

    I also would like to ask about pin used for the servo.
    Can i use other pin beside 14 and 15 for the servo? For example pin 2 and 13, and should i change Servo1N and Servo2N pin attach?

    Thanks!

    Reply
  32. Hi, I am a new learner to ESP32 cam. I use your code and my pre-writing code in my project to let pan tilt automatically track the human face. However, when I open the website, the stream JPG does not show up. It was quite weird because all the code seem to be the same except for other fuction in my pre-writing code for face recognition and drawing detecting box for human face. Could I send email to you with my code? I have difficulty to figure out which place is the problem for error. Thank you very much

    Reply
  33. Hello Sara, great project. I recreated it and it works fine so far.
    I have only one problem:
    The left<>right servo can only be moved within a range of approx. 90 degrees. But I want 180 degrees.
    How can I implement this?
    Greetings Ulli

    Reply
  34. Problem solved.

    servo1.attach(SERVO_1, 650, 2000); // up – down
    servo2.attach(SERVO_2, 600, 2400); // left – right

    Reply
  35. Hello,

    Love the external antenna on this ESP32 CAM. Do you have any photos or instructions on how to make that happen?

    Reply
  36. Hi Sara, Great project! I did a similiar project with your code, the camera is working fine, but the 2 servo is not responding! when i press the up down left right button on the web page it hanged! could you pls advise, thanks.

    Reply
  37. Hi Sara,
    I had tested both servos with AI thinker board and its working fine, supply the 2 servos with separate 5V and update ESP32Servo.h with version 0.7. But it still hang when button UP was pressed, tough video seems to be working fine. Could you pls advise thanks.

    Reply
    • Had the same issue here, even if the servo weren’t wired.
      Thanks for the hint!
      I was using the ESP32Servo.h with version 0.12.1
      I downgraded to version 0.4.0 and the code worked when button UP was pressed.
      Something was changed between version 0.4.2 and 0.5.0 that interfere with the camera.

      Reply
  38. I have uploaded this code to ESP32-CAM. When I start the webserver and click the UP button the stream stops and everything freezes. Other buttons are OK.
    Note: I do not have servos attached, just testing code with serial monitor.

    Reply
  39. I made a couple builds of this project along with 3d printed enclosures. It has been a while but the files are available at
    https://github.com/trentbrown13/ESP32_Cam_Pan_Tilt

    Thank you to the Santos’s for such great inspiration and also thank you to ‘the man with the Swedish accent’ as well as to the person who posted a link on how to rotate the images but is not longer available.

    Reply
  40. Installed Trent Brown’s of esp32-CAM Pan & Tilt Sweep and having problems. Been working on it for a couple of days.
    The web page comes up but image disappears immediately with Serial Monitor displaying ‘Camera capture failed’. I can see responses from button pushes but servos do not respond.
    Any help is appreciated.

    Reply
    • I think you can try changing the PWM channel for camera on the following line:
      config.ledc_channel = LEDC_CHANNEL_0;

      Try it out and see if it works.
      Regards,
      Sara

      Reply
  41. I have been refining this code for some time. I have the pan and tilt sevos working with sliders. It is much smoother than buttons.
    Anyway, I would like to move the html code outside of the sketch, to clean up the code, and to learn how to do that. I have tried to imitate such code from other projects but just can’t get it to work.
    Any help is greatly appreciated.

    Reply
  42. Hi Sara,

    I copied your original code. The program works except for one problem. If I press the up button, the camera stream stops and the message “Camera capture failed” appears in the serial monitor. The problem also occurred with other users and your recommendation was to reset the ESP32-Servo.h to version 0.4.2. But I use the latest version 0.12.1.

    I found a solution with this version too. After the servo objects ServoN1 and ServoN2, I have added another servo object, ServoN3. Servo1 and Servo2 now use channels 4 and 5 and the camera is no longer disturbed.

    Reply
  43. OK, this pan & tilt project is becoming a career. I have made many improvements (in my opinion) to the code. It’s running perfectly.
    So, I have now added a function to send a capture frame to a folder on Google drive. This works fine if run by itself. I have added a capture button to the the web page and when pressed it runs the Capture_Handler. I have added the SendToGDrive function on the end of the Capture_Handler.
    Problem is this stops the Stream_Handler and causes an exception and ESP32 reboot.
    I want this system as a security camera so it will be mounted outside. Saving images to SD card makes things complicated when I need to get the image. Also saving to SPIFFS is not a good plan.
    I am wondering if I can run the Stream_Handler as vTask so it would be run on a separate thread (maybe) and would not be interrupted.
    I have tried various methods such as delays to no avail.
    Any suggestions are appreciated.

    Reply
    • Hi.
      I don’t think you can run both tasks simultaneously.
      When you need a picture, you need to stop the streaming.
      You can return to the streaming right after taking the picture.
      I think this is the best strategy so that the board doesn’t crash.
      Regards,
      Sara

      Reply
  44. Re: ESP32-CAM watchdog timeout when sending a photo to Google Drive while video streaming.
    I managed to get the Pan & Tilt app to send a photo to Google Drive. After creating a Google Drive app script, I created RTOS task, sendToGDrive, and pinned it to core 0 in the ESP32-CAM. It works like a charm. I put a Capture button on the web page and when pressed a single frame is captured and sent to Google Drive.
    So now I can mount the ESP32-CAM fixture outside and can manage everything, including OTA updates, from my computer.

    Reply
  45. I figured out another solution to the problem of ‘camera capture failed ‘ error when activating one of the servo motors. Most people are solving it with the work around of using an older version of the esp32Servo library. One comment above adds a 3rd dummy, servoN3. What I found was that sometime between version 0.4 (which most people report works) and 0.13 (current as of April 6, 2023) the issue of PWM interfering with the camera was changed. In this example 2 dummy servo instances are created servoN1 and servoN2. In the setup(void) function they are attached to pin 2 and 13, The explanation is that PWM channels interfere with the camera, so they have to be set up as dummies.

    What I suspected was that it was the dummies that were actually interfering with the camera, since it is the camera that locked up when the servos were activated.

    I commented out the dummy servo entries:
    //Servo servoN1;
    //Servo servoN2;
    Servo servo1;
    Servo servo2;

    //servoN1.attach(2, 1000, 2000);
    //servoN2.attach(13, 1000, 2000);
    servo1.attach(SERVO_1, 1000, 2000);
    servo2.attach(SERVO_2, 1000, 2000);

    Now the ESP32-CAM behaves correctly with ESP32Servo.h version 0.13. So it seems like PWM channel 3 is what conflicts with the camera now, since 1 and 2 work in my case and 4 and 5 work in the previous comment. Added bonus, is that you can use GPIO 2 and 13 for other things.

    Reply
    • Hi.
      Thanks for sharing that information.
      I’ll need to try this example again with the latest version of the library and sww which PWM channels I can use.
      Regards,
      Sara

      Reply
    • Picture perfect result thanks to your comment. Thankyou, thankyou, thankyou.

      Rui & Sara … your tutorials and post tutorial assistance is great work. Thank you

      Reply
    • I confirm David results. I am using a Freenove ES32-WRover board with esp32servo 0.13.0.

      Activating servos locks video stream, no matter the pins you use. Commenting out ServoN1/ServoN2 lines works a treat.

      Currently using pines 32 and 33 for servos (for a minirobot, not for pan tilt mechanism)

      Reply
  46. Hi! thanks a lot for this!

    I still have a problem on something i am working on.
    I am trying to create an python script on my pc which tries to videocapture from the stream hosted by the esp32CAM on the webserver. because i want to use opencv on the videostream. When i use your code for the esp32Cam and run this python code below on my pc with the correct ip adress it crashes. But when i use the example CameraWebServer code in arduino for the esp32Cam and run this python code below on my pc it works just fine.

    Do you know what the problem is?
    when i go to http://:81/stream i am able to see the stream.

    import cv2
    stream_url = “http://:81/stream”
    cap = cv2.VideoCapture(stream_url)

    while True:
    ret, frame = cap.read()

    if not ret:
    break

    cv2.imshow("Video", frame)

    if cv2.waitKey(1) & 0xFF == ord('q'):
    break

    cap.release()
    cv2.destroyAllWindows()

    Reply
  47. Good Day,

    Thanks for sharing this, I have gotten it to work however I was wondering if it would be possible to also make the device an access point at the same time?

    Thanks,
    Norton

    Reply
    • Ok, I think I figured it out…

      // Wi-Fi connection
      WiFi.softAP(ssid, password);
      IPAddress IP = WiFi.softAPIP();
      Serial.print(“AP IP address: “);
      Serial.println(IP);
      startCameraServer();

      // 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.println(WiFi.localIP());
      //
      // // Start streaming web server
      // startCameraServer();
      }

      Reply
  48. Hi
    Great project an well explained. I’m going to implement it for monitoring my 3D printer, and also add small relay from GPIO4 LED to activate existing enclosure light. It’s a LED light strip so relay be overkill, but it uses 12V plug pack and is already installed so inserting relay inline is straightforward

    Thanks again

    Reply
  49. rst:0x8 (TG1WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)

    i am facing this error when uploading the code to esp32 cam and pressing the reset button please help!

    Reply
  50. Hi Sara, I am currently working on an antenna tracker project with 2 servos. In the process, I became aware of the great project with PanTilt.
    Since I would like to use an ESP32 WROOM 32, I have a question:
    Is it possible to shorten the code so that I can use it for my project without a camera? What do I have to remove from the code or comment out in order to flash it to ESP32?
    Sorry I am still a beginner with ESP32.

    greetings Georg

    Translated with http://www.DeepL.com/Translator (free version)

    Reply
  51. Hello from Scotland.
    I have been working on this project for a couple of days and have enjoyed doing it. I have however got an issue with the image. I currently have the ESP32 Cam connected to the ESP32 MB, connecting using USB. I have no servos connected as yet.
    On first load the image from the camera appears but after about 30 seconds the image disappears. I’m using Chrome browser on Windows 10. Inspecting the web page as soon as the image disappears the following error appears “Failed to load resource: net::ERR_INCOMPLETE_CHUNKED_ENCODING” followed by a link to “stream:1”.
    I have done a line by line comparison of my code against the code in the article and also checked the streaming element of the robot car and can’t find any difference.

    I’m sure it’s something obvious but I’ve checked all I can think of, What am I missing?
    Thanks.

    Reply
    • Hello, unfortunately I never encountered that issue before.
      Make sure you are using a good power source that supplies enough current to make the board work reliably.
      You also need to keep in mind that the ESP32-CAM can’t have more than 1 client accessing the web server at the same time.

      Regards,
      Rui

      Reply
      • Hello Rui

        Thank you for the reply.

        I did try using a 5 volt 2 amp power supply but that made no difference.

        I then wondered about the strength of the wi-fi signal and as soon as I moved the camera to a different location it all worked perfectly. I am currently using the internal aerial, I’ve ordered an external one but it hasn’t arrived as yet, but will fit the new one as soon as it arrives.

        Hopefully this might help if anyone else has this problem in the future. Wi-fi strength is worth checking.

        Thank you for your help.
        Bill

        Reply
  52. I finished the esp cam pan tilt tutorial, it worked without any problem. Thank you for creating this easy is to follow tutorial.
    I ran into an odd problem with the video stream, and hope you can help me find a solution.

    I am able to stream video via the LAN and control the servos okay. When i port forward the stream, I don’t get the video stream, i only get the text. I am able to control the servos from the forwarded text but never get any video.

    I tried a different browser, and changing the image size but this did not help,

    Thank you.

    Larry

    Reply
  53. Good morning Sara and Rui, thank you for your projects and your availability. I created this project but I have a problem, when I go to manage the camera, the image is seen in the browser, but when I press a button for the direction of the servos the program goes haywire and the image disappears. I have to press reset to restart it. The servos don’t move. You can help me . Thank you very much.

    Reply
  54. Good evening Sara E Rui, I would like to add a Flash on/off button, can you kindly give me some instructions to do so. Thanks in advance

    Reply

Leave a Reply to Dzikri Cancel reply

Download Our Free eBooks and Resources

Get instant access to our FREE eBooks, Resources, and Exclusive Electronics Projects by entering your email address below.