ESP32 - Control Servo Motor via Web

This tutorial instructs you on how to use the ESP32 to control a servo motor via the web from a browser on your smartphone or PC. We'll use something called WebSocket to control the servo motor smoothly and dynamically through a graphical web user interface.

The video below is a demonstration:

Now, why use WebSocket? Here's the idea:

Let's start!

Hardware Used In This Tutorial

1×ESP-WROOM-32 Dev Module
1×USB Cable Type-C
1×Servo Motor
1×Jumper Wires
1×(Optional) DC Power Jack
1×(Recommended) ESP32 Screw Terminal Adapter

Or you can buy the following sensor kits:

1×DIYables Sensor Kit (30 sensors/displays)
1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: some of these links are affiliate links. We may earn a commission on your purchase at no extra cost to you. We appreciate it.

Introduction to Servo Motor and WebSocket

We have specific tutorials about servo motor and WebSocket. Each tutorial contains detailed information and step-by-step instructions about hardware pinout, working principle, wiring connection to ESP32, ESP32 code... Learn more about them at the following links:

How It Works

The ESP32 code creates both a web server and a WebSocket Server. Here's how it works:

  • When you enter the ESP32's IP address in a web browser, it requests the webpage (User Interface) from the ESP32.
  • The ESP32's web server responds by sending the webpage's content (HTML, CSS, JavaScript).
  • Your web browser then displays the webpage.
  • The JavaScript code within the webpage establishes a WebSocket connection to the WebSocket server on the ESP32.
  • Once this WebSocket connection is established, if you turn the handle on the webpage, the JavaScript code quietly sends the angle value to the ESP32 through this WebSocket connection in the background.
  • The WebSocket server on the ESP32, upon receiving the angle value, controls the servo motor accordingly.

In a nutshell, the WebSocket connection enables the smooth, real-time control of the servo motor angle.

Wiring Diagram between Servo Motor and ESP32

ESP32 Servo Motor Wiring Diagram

This image is created using Fritzing. Click to enlarge image

If you're unfamiliar with how to supply power to the ESP32 and other components, you can find guidance in the following tutorial: How to Power ESP32.

For the sake of simplicity, the above wiring diagram is used for the testing or learning purposes, and for small-torque servo motor. In practice, we highly recommend using the external power supply for the servo motor. The below wiring diagram shows how to connect servo motor to an external power source.

ESP32 servo motor external power supply wiring diagram

This image is created using Fritzing. Click to enlarge image

The below show a real wiring between servo motor and ESP32.

ESP32 servo motor power supply wiring diagram

ESP32 Code

The webpage's content (HTML, CSS, JavaScript) are stored separately on an index.h file. So, we will have two code files on Arduino IDE:

  • An .ino file that is ESP32 code, which creates a web sever and WebSocket Server, and control servo motor
  • An .h file, which contains the webpage's content.

Quick Instructions

  • If this is the first time you use ESP32, see how to setup environment for ESP32 on Arduino IDE.
  • Do the wiring as above image.
  • Connect the ESP32 board to your PC via a micro USB cable
  • Open Arduino IDE on your PC.
  • Select the right ESP32 board (e.g. ESP32 Dev Module) and COM port.
  • Open the Library Manager by clicking on the Library Manager icon on the left navigation bar of Arduino IDE.
  • Search “ESPAsyncWebServer”, then find the ESPAsyncWebServer created by lacamera.
  • Click Install button to install ESPAsyncWebServer library.
ESP32 ESPAsyncWebServer library
  • You will be asked to install the dependency. Click Install All button.
ESP32 ESPAsyncWebServer dependencies library
  • Search “WebSockets”, then find the WebSockets created by Markus Sattler.
  • Click Install button to install WebSockets library.
ESP32 WebSockets library
  • Type ServoESP32 on the search box, then look for the servo library by Jaroslav Paral. Please be aware that both version 1.1.1 and 1.1.0 are affected by bugs. Kindly choose a different version.
  • Click Install button to install servo motor library for ESP32.
ESP32 servo motor library
  • On Arduino IDE, create new sketch, Give it a name, for example,
  • Copy the below code and open with Arduino IDE
/* * This ESP32 code is created by * * This ESP32 code is released in the public domain * * For more detail (instruction and wiring diagram), visit */ #include <Servo.h> #include <WiFi.h> #include <ESPAsyncWebServer.h> #include <WebSocketsServer.h> #include "index.h" #define SERVO_PIN 26 // ESP32 pin GPIO26 connected to servo motor Servo servo; const char* ssid = "YOUR_WIFI_SSID"; // CHANGE IT const char* password = "YOUR_WIFI_PASSWORD"; // CHANGE IT AsyncWebServer server(80); WebSocketsServer webSocket = WebSocketsServer(81); // WebSocket server on port 81 void webSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) { switch (type) { case WStype_DISCONNECTED: Serial.printf("[%u] Disconnected!\n", num); break; case WStype_CONNECTED: { IPAddress ip = webSocket.remoteIP(num); Serial.printf("[%u] Connected from %d.%d.%d.%d\n", num, ip[0], ip[1], ip[2], ip[3]); } break; case WStype_TEXT: //Serial.printf("[%u] Received text: %s\n", num, payload); String angle = String((char*)payload); int angle_value = angle.toInt(); Serial.println(angle_value); servo.write(angle_value); break; } } void setup() { Serial.begin(9600); servo.attach(SERVO_PIN); // attaches the servo on ESP32 pin // Connect to Wi-Fi WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("Connected to WiFi"); // Initialize WebSocket server webSocket.begin(); webSocket.onEvent(webSocketEvent); // Serve a basic HTML page with JavaScript to create the WebSocket connection server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { Serial.println("Web Server: received a web page request"); String html = HTML_CONTENT; // Use the HTML content from the index.h file request->send(200, "text/html", html); }); server.begin(); Serial.print("ESP32 Web Server's IP address: "); Serial.println(WiFi.localIP()); } void loop() { webSocket.loop(); }
  • Modify the WiFi information (SSID and password) in the code to match your own network credentials.
  • Create the index.h file On Arduino IDE by:
    • Either click on the button just below the serial monitor icon and choose New Tab, or use Ctrl+Shift+N keys.
    Arduino IDE 2 adds file
    • Give the file's name index.h and click OK button
    Arduino IDE 2 adds file index.h
    • Copy the below code and paste it to the index.h.
    /* * This ESP32 code is created by * * This ESP32 code is released in the public domain * * For more detail (instruction and wiring diagram), visit */ const char *HTML_CONTENT = R"=====( <!DOCTYPE html> <html> <head> <title>ESP32 Controls Servo Motor via Web</title> <meta name="viewport" content="width=device-width, initial-scale=0.7"> <style> body { text-align: center; } canvas { background-color: #ffffff; } </style> <script> var canvas_width = 401, canvas_height = 466; var pivot_x = 200, pivot_y = 200; var bracket_radius = 160, bracket_angle = 0; var bracket_img = new Image(); var click_state = 0; var last_angle = 0; var mouse_xyra = {x:0, y:0, r:0.0, a:0.0}; var ws; bracket_img.src = ""; function init() { var servo = document.getElementById("servo"); servo.width = canvas_width; servo.height = canvas_height; = "url('')"; = "center"; = "contain"; servo.addEventListener("touchstart", mouse_down); servo.addEventListener("touchend", mouse_up); servo.addEventListener("touchmove", mouse_move); servo.addEventListener("mousedown", mouse_down); servo.addEventListener("mouseup", mouse_up); servo.addEventListener("mousemove", mouse_move); var ctx = servo.getContext("2d"); ctx.translate(pivot_x, pivot_y); rotate_bracket(0); ws = new WebSocket("ws://" + + ":81"); document.getElementById("ws_state").innerHTML = "CONNECTING"; ws.onopen = function(){ document.getElementById("ws_state").innerHTML = "CONNECTED" }; ws.onclose = function(){ document.getElementById("ws_state").innerHTML = "CLOSED"}; ws.onerror = function(){ alert("websocket error " + this.url) }; ws.onmessage = ws_onmessage; } function ws_onmessage(e_msg) { e_msg = e_msg || window.event; // MessageEvent alert("msg : " +; } function rotate_bracket(angle) { var servo = document.getElementById("servo"); var ctx = servo.getContext("2d"); ctx.clearRect(-pivot_x, -pivot_y, canvas_width, canvas_height); ctx.rotate(angle / 180 * Math.PI); ctx.drawImage(bracket_img, -pivot_x, -pivot_y); ctx.rotate(-angle / 180 * Math.PI); } function check_range_xyra(event, mouse_xyra) { var x, y, r, a, rc_x, rc_y, radian; var min_r, max_r, width; if(event.touches) { var touches = event.touches; x = (touches[0].pageX - touches[0].target.offsetLeft) - pivot_x; y = pivot_y - (touches[0].pageY - touches[0].target.offsetTop); min_r = 60; max_r = pivot_x; width = 40; } else { x = event.offsetX - pivot_x; y = pivot_y - event.offsetY; min_r = 60; max_r = bracket_radius; width = 20; } /* cartesian to polar coordinate conversion */ r = Math.sqrt(x * x + y * y); a = Math.atan2(y, x); mouse_xyra.x = x; mouse_xyra.y = y; mouse_xyra.r = r; mouse_xyra.a = a; radian = bracket_angle / 180 * Math.PI; /* rotate coordinate */ rc_x = x * Math.cos(radian) - y * Math.sin(radian); rc_y = x * Math.sin(radian) + y * Math.cos(radian); if((r < min_r) || (r > max_r)) return false; if((rc_y < -width) || (rc_y > width)) return false; return true; } function mouse_down() { if(event.touches && (event.touches.length > 1)) click_state = event.touches.length; if(click_state > 1) return; if(check_range_xyra(event, mouse_xyra)) { click_state = 1; last_angle = mouse_xyra.a / Math.PI * 180.0; } } function mouse_up() { click_state = 0; } function mouse_move() { var angle; if(event.touches && (event.touches.length > 1)) click_state = event.touches.length; if(click_state > 1) return; if(!click_state) return; if(!check_range_xyra(event, mouse_xyra)) { click_state = 0; return; } angle = mouse_xyra.a / Math.PI * 180.0; if((Math.abs(angle) > 90) && (angle * last_angle < 0)) { if(last_angle > 0) last_angle = -180; else last_angle = 180; } bracket_angle += (last_angle - angle); last_angle = angle; if(bracket_angle > 90) bracket_angle = 90; if(bracket_angle < -90) bracket_angle = -90; rotate_bracket(bracket_angle); if(ws.readyState == 1) ws.send(Math.floor(90 - bracket_angle) + "\r\n"); debug = document.getElementById("debug"); debug.innerHTML = Math.floor(90 - bracket_angle); event.preventDefault(); } window.onload = init; </script> </head> <body> <h2> ESP32 - Servo Motor via Web<br> <canvas id="servo"></canvas> <p> WebSocket : <span id="ws_state" style="color:blue">null</span><br> Angle : <span id="debug" style="color:blue">90</span> </p> </h2> <div class="sponsor">Sponsored by <a href="">DIYables</a></div> </body> </html> )=====";
    • Now you have the code in two files: and index.h
    • Click Upload button on Arduino IDE to upload code to ESP32
    • Open the Serial Monitor
    • Check out the result on Serial Monitor.
    Connecting to WiFi... Connected to WiFi ESP32 Web Server's IP address IP address:
    Autoscroll Show timestamp
    Clear output
    9600 baud  
    • Take note of the IP address displayed, and enter this address into the address bar of a web browser on your smartphone or PC.
    • You will see the webpage it as below:
    ESP32 controls servo motor via web browser
    • The JavaScript code of the webpage automatically creates the WebSocket connection to ESP32.
    • Now you can control the servo motor's angle via the web interface.

    To save the memory of ESP32, the images of servo motor are NOT stored on ESP32. Instead, they are stored on the internet, so, your phone or PC need to have internet connection to load images for the web control page.

    ※ NOTE THAT:

    • If you modify the HTML content in the index.h and does not touch anything in file, when you compile and upload code to ESP32, Arduino IDE will not update the HTML content.
    • To make Arduino IDE update the HTML content in this case, make a change in the file (e.g. adding empty line, add a comment....)

    Line-by-line Code Explanation

    The above ESP32 code contains line-by-line explanation. Please read the comments in the code!