Pan and Tilt Control for an ESP32-CAM

Pan and Tilt ESP32

Mounting an ESP32-CAM into a pan and tilt platform with a mobile phone touch interface to control and view the video stream.

This servo part of this project was much harder to make work than with other ESP32 modules. The ESP32-CAM has limited resources available and at every turn it presented a new problem!

With a standard ESP32, controlling servo motors is easy using the ESP32Servo library. For example this pan and tilt sketch controls the 3D printed pan and tilt platform below. You can even power the servos from the 5v pin of the micro-controller.

Lolin ESP32 Pan and Tilt

Attempting to use this library with the ESP32-CAM however led to instant hangs. From my experiments it appears there is a clash on the channels. I found some different code that allows the channels to be chosen and after a few tests I found I could use channels 2 and 4 with the ESP32-CAM.

Mounting the ESP32-CAM in a pan and tilt platform was also a bit tricky. In the end I 3D printed some parts and glued and bolted it together. It doesn’t look great and could do with being designed from the ground up but this is just a proof of concept for later projects so it doesn’t matter

ESP32-CAM Pan and Tilt Platform

I also tried various web server libraries and the ESPAsyncServer library seemed to work the best. I also moved to WebSockets for the data transfer because it seemed to be more efficient and not queue up requests from the mobile phone.

ESP32-CAM Pan and Tilt Wiring Diagram

ESP32-CAM Pan Tilt Code

If you need help with the ArduinoWebSockets or ESPAsyncWebServer libraries please see this tutorial:

Demonstration Video

I got variable results from the ESP32-CAM. Sometimes it was as fluid as the Lolin in the video below and other times it was quite jerky. I don’t know if this is an issue with (my) Wi-Fi or if the data transfer from the video overwhelms the web socket servo control data.


Lolin32 Pan and Tilt Platform:
ESP32-CAM Pan Tilt with Servos:
Touch Interface:

42 Replies to “Pan and Tilt Control for an ESP32-CAM”

  1. rob says:

    Do you think it would be possible to overlay the pan tilt control on top of the camera image, a bit like this

    1. WordBot says:

      I looked at the Github quickly and it looks like he’s using HTML Canvas to draw the interface on top of the video. This could be one approach. It might also work using CSS Z-Index to layer the control with a transparent background on top of the video.

  2. Enn Ellandi says:

    I made a little investigation abd foun out that this part on ESP32 Cam code is preventing Servo library to work:

    // camera init
    esp_err_t err = esp_camera_init(&config);
    if (err != ESP_OK) {
    Serial.printf(“Kaamera init viga 0x%x”, err);

    sensor_t * s = esp_camera_sensor_get();
    s->set_framesize(s, FRAMESIZE_SVGA);

    1. WordBot says:

      It should be OK. Which camera are you using?

  3. Petko Petkov says:

    Hello my friend!!!
    I am quite a beginner and I would very much like to carry out your project. Can you send me screenshots of Arduino ID to learn out which sketch you use, which board, which file (.in) because this one is here (.cpp) and only I can’t add it.
    I apologize for my bad English !!!
    Thank you very much !!!

    1. WordBot says:

      Hi, everything is in the tutorial. You can use any ESP32 board. The code in the tutorial is an .ino file so you just paste it into a new Sketch in the Arduino IDE.

  4. Petko Petkov says:

    It worked….. 🙂 Supeeeerrrrrr !!!
    Thank you so much!!!

  5. CaptainJ says:

    Hi, and thank you for this useful tutorial. It works fine to me.
    In my case, I want to pan and tilt an ESP32-CAM as a surveillance camera through internet.
    Using NO-IP services I can use Dynamic DNS (I did it in another application).
    For security reasons, I want to change ports 80 and 82 you are using in your program, which I will port forward in my router. So far I haven’t had any success. It seems to work only with 80 and 82 ports. How can I do it?
    Please help me. Thank you.

    1. WordBot says:

      Hi, Did you you change the ports in the HTML? See this tutorial for help with this –

  6. Token says:

    Hello Fellow Maker,

    Great tutorial, I am doing something very similar and wanted get your opinion/advice on the following.

    Currently I have an application that is sending polar coordinates (magnitude, theta) and wanted to know what would you suggest to map the panValue & tiltValue so that it can handle the conversion.

    I’m thinking that magnitude could be used as the “speed” or “power” of the servo and convert the theta into some useable mapping angle value.

    Magnitude = 0 to 1
    Theta = 0 to 359.99

    I’m having a bit of trouble with the logic and trying to use your example as a base when handling the WebSockets on the ESP32 Cam

    panValue = map(panValue, -90, 90, 0, 180); // 0-180
    tiltValue = map(tiltValue, -90, 90, 180, 0); // 0-180 reversed

    any advice is greatly appreciated 🙂

    1. WordBot says:

      Hi, I’m not really sure but one thing to note is map is only for integers so you need to multiple the numbers to get whole numbers first. For example magnitude should be 0 to 100 to map it. With map you start with the values you get from your sensor or whatever and ‘map’ them to a range of values you need for the function (servo in this case). If you need help with more maths stuff this site is great:

  7. CaptainJ says:

    Hi again, and thank you for your reply.
    Studding your tutorial you mention above, and using CyberChef, I converted hex to html. The only point I can change port is in line const WS_URL = “ws://” + + “:82”;
    I changed 82 with 8084.
    I converted html to hex again and correct the index_html_gz length to 1744 in camera code.
    Also in camera code, I changed line 120, the WSserver.listen(82); to WSserver.listen(8084);
    and the line 38, the AsyncWebServer webserver(80); to AsyncWebServer webserver(8082);
    After uploading code, removing GND from IO0, pressing RESET button, in browser look for 192.168.x.x:8082.
    The only I can see in browser, is the touch interface in the upper portion of screen, with the yellow spot in the middle, but I cannot move it to any direction. No image at all, no servo movement.
    Probably I miss something. Have you any advice to give me?
    Thank you.

    1. WordBot says:

      Hi. Sounds like you are doing well following the tutorial. Can you press F12 in the browser can take a look in the Console tab at the moment. It sounds like the Javascript isn’t loading.

  8. CaptainJ says:

    Hi again.
    I followed your advice (Console tab using F12). A Syntax Error appeared:

    “SyntaxError: An invalid or illegal string was specified”

    What is that :71 ? There is neither in code nor html.
    Also I changed the 8082 and 8084 ports with 2 digit number (p.s. 90 and 92) but the same error appeared.
    That error is not appearing when using 80 and 82 ports.
    Have you any suggestion?
    Thank again for your time.

    1. WordBot says:

      Hi, Can you post the HTML and sketch into two pastes here: and I’ll take a look.

  9. CaptainJ says:

    OK I made registration to Pastebin and I made two pastes.
    I am not familiar with Pastebin.
    Now how do you will see them?

    1. WordBot says:

      Ah.. I need the link to the pastes. When you save them, just copy the URL into a comment here.

  10. CaptainJ says:

    Wednesday 25th of March 2020 04:19:49 PM CDT (My registration)
    Postes, a few minutes later…

    1. WordBot says:

      Hi, When you copy and paste into Pastebin and save, you can just copy the URL of the page and paste it here.

    1. WordBot says:

      I think I’ve sussed it. Try:
      const WS_URL = "ws://" + window.location.hostname + ":8084";

      host includes the port number. I didn’t know this before.

  11. Captainj56 says:

    IT IS WORKING !!!!!!
    I appreciate your help.
    Thanks again !!!

  12. Injee says:

    Hi will be great if you can post the working program with the port set for 8084.
    I am trying the same and not able to get it to work.

    1. WordBot says:

      IF you check this comment (with the two examples) and my correction below it should work:

  13. Hossein Fatehi says:

    hi . very nice project . it works very good.
    Please help me for working this project on other port not “80” .
    thanks alot for your project

    1. WordBot says:

      Hi. You should be able to change the port here:
      AsyncWebServer webserver(80);
      and then just go to

  14. Jim says:

    I havent connected up the servo’s, but it worked first time for me.
    I have dissected the code and removed the servo control and html interface so I can use the cam websocket stream by itself.
    I was using my esp32 with it cam with a tcp interface, websockets os much quicker.

  15. Nunyabizzness says:

    The code does not work. You have two references to index_html_gz, but have never defined or included it anywhere.

    As given, the code returns

    exit status 1
    ‘index_html_gz’ was not declared in this scope

    Is there a working version of the code that you could post? Perhaps this was an earlier version or something?

    1. WordBot says:

      Something weird has happened with the paste on Pastebin. If you look at the bottom of the page the raw paste has the definition. I’ll move this to Github later.

  16. Atlas says:

    Hi, awesome project. I have tried out the code from the raw paste, and have attached everything according to the schematic provided, the camera works fine but I am unable to make the servo work (have tested that servo is working with normal Arduino).
    I have connected the servo to an external 5V power source, so the power issue should have been settled, may I ask which partition scheme did you choose for your ESP32 cam board, or if possible all the options that you picked.

    1. WordBot says:

      Hi partition scheme shouldn’t affect this project. If you choose one too small it just won’t flash. Maybe try some Serial.print() commands before this code to make sure the panValue and tiltValue are correct.

      ledcAnalogWrite(2, panValue); // channel, value
      ledcAnalogWrite(4, tiltValue);

  17. Mark says:

    This pan/tilt/camera works beautifully. Indoors. When I take it outside in the bright sunlight the feed freezes and will only normalize again in the shade or indoors. Anyone else experience this? Using the same esp32 cam the Arduino example sketch has no issues.

    1. WordBot says:

      Hi, Are you sure it’s not just losing connection to the Wi-Fi? The websocket connection seems to be more sensitive to connection problems than the normal http.

      Thanks for the coffee BTW!

      1. Mark says:

        I am running it in AP mode, with the camera and tablet both in hand. The Wi-Fi connection appears to remain stable. I’ll do some more experimenting today. Thanks.

  18. Louis Jobin says:

    Which card are you using, I got an error Compilation error for the card AI Thinker ESP32-CAM.

    1. WordBot says:

      Probably a problem with the code display from Pastebin. I should move this to Github. In the meantime the raw view should be OK:

  19. Ion Stefanache says:

    very good work!!!
    please post the original content of intex.html ( not gz)

    1. WordBot says:

      Hi, View Source in the browser and you will see all the HTML etc.

      1. Ion Stefanache says:

        thanks a lot … I resolved with

        and I seen(decode) HTML code with that receipt:


        thei I modified the HTML
        (I add code for rotation 90 degree:

        document.getElementById(“stream”).style.transform = ‘rotate(‘ + deg + ‘deg)’;
        Tutorial: }

        and finally I re-encode with this receipt:
        Gzip(‘Dynamic Huffman Coding’,”,”,false)
        To_Hex(‘0x with comma’,16)

        In this moment all work in rule for me….

  20. Emerson Amaral says:

    Hi! Thanks for the great tutorial!

    I have implemented this model, and I need a couple of ajustments:
    1 – I would like to set both servos to start at position 90 degrees (center). When I touch the controls the tilt servo goes immediately to the up position.
    2 – I have placed the ESP32 in a horizontal position, so I need to rotate the image of the camera in 90 degrees.

    How can I implement these changes in the code?

    1. Emerson Amaral says:

      Just an update about item #2, I have followed the instructions from the comment above, so I’ve converted the hex code to HTML and included a row with the suggested syntax, then I’ve transformed into hex again and uploaded the code. Although it managed to create the web server correctly, the page shows nothing… after converting again, do I need to perform other changes in the code?

      1. WordBot says:

        Hi, Can you see anything in the browser when you ‘view source’?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

scroll to top