ESP32 Robotic Hand Control Using WiFi and Servo Motors By Dinesh | Tech DIY Projects | 2026

 

ESP32 Robotic Hand Control Using WiFi and Servo Motors |By Dinesh | Tech DIY Projects | 2026

Introduction

In this project, I built a wireless robotic hand control system using an ESP32, 5 servo motors, and a beautiful web-based dashboard.
The ESP32 works as a WiFi hotspot, and the hand can be controlled from a mobile phone or laptop browser.

This project is useful for:

  • Robotics projects
  • Mechatronics demonstrations
  • ESP32 web server learning
  • Servo motor control practice
  • Automation and embedded system applications

The control page includes five sliders for Thumb, Index, Middle, Ring, and Little finger, along with preset buttons like Open Hand, Fist, Thumbs Up, Point, Peace, and Half Close




Project Features

  • ESP32 hotspot-based control
  • 5 servo motor outputs
  • Mobile-friendly futuristic web UI
  • Real-time finger movement
  • Preset hand gestures
  • Simple browser control without any app

Components Required

  • ESP32 development board
  • 5 servo motors
  • External 5V power supply
  • Jumper wires
  • Robotic hand structure
  • USB cable for programming

Pin Configuration

ServoFinger NameESP32 GPIO Pin
Servo 1Thumb13
Servo 2Index12
Servo 3Middle14
Servo 4Ring27
Servo 5Little26

These GPIO values are directly taken from your sketch


Working Principle

The ESP32 creates its own WiFi hotspot named RoboHand_ESP32. After connecting to that hotspot, the user opens the ESP32 IP address in a browser. The HTML page sends servo angle values to the ESP32 using a /set request, and the ESP32 updates the correct servo motor accordingly

The hand movement is controlled using:

  • sliders for manual control
  • preset gesture buttons
  • live status updates in the web page

The HTML is designed in a futuristic red-and-white style with a virtual robotic hand illustration, giving the project a modern dashboard look


Circuit Connections

Servo to ESP32

  • Thumb servo signal → GPIO 13
  • Index servo signal → GPIO 12
  • Middle servo signal → GPIO 14
  • Ring servo signal → GPIO 27
  • Little servo signal → GPIO 26

Power Connection

  • Servo VCC → External 5V supply
  • Servo GND → ESP32 GND
  • Power supply GND must be common with ESP32 GND

Important: Do not power all servos directly from the ESP32 5V pin. Use a proper external supply for stable movement.


How to Use

  1. Power the ESP32.
  2. Connect your phone or laptop to the WiFi hotspot.
  3. Open the IP address shown in the Serial Monitor.
  4. Move the sliders to control the robotic fingers.
  5. Use the preset buttons for quick gestures.

Source Code

Below is the full code for the project.

Paste your complete ESP32 code here.

#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>

// Hotspot credentials
const char* ssid = "RoboHand_ESP32";
const char* password = "12345678";

WebServer server(80);

// Servo objects
Servo servo1, servo2, servo3, servo4, servo5;

// GPIO pins
int s1 = 13;
int s2 = 12;
int s3 = 14;
int s4 = 27;
int s5 = 26;

// ================= HTML PAGE =================
String webpage = R"====(
<!DOCTYPE html>
<html>
<head>
  <title>NeuroGrip Hand Control</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    :root{
      --bg:#070b14;
      --bg2:#0d1424;
      --card:#0f1728cc;
      --card2:#111b31;
      --line:rgba(255,255,255,.08);
      --text:#f4f7ff;
      --muted:#9aa7c7;
      --primary:#ff3b5c;
      --primary2:#ff6b81;
      --accent:#ff9aa8;
      --good:#55f2b6;
      --shadow:0 24px 80px rgba(0,0,0,.45);
    }

    *{box-sizing:border-box;margin:0;padding:0}

    body{
      font-family:Inter,Segoe UI,Arial,sans-serif;
      min-height:100vh;
      color:var(--text);
      background:
        radial-gradient(circle at 15% 15%, rgba(255,59,92,.18), transparent 24%),
        radial-gradient(circle at 85% 20%, rgba(255,107,129,.14), transparent 20%),
        radial-gradient(circle at 50% 100%, rgba(255,255,255,.05), transparent 35%),
        linear-gradient(160deg, var(--bg) 0%, #090f1b 45%, #05070d 100%);
      display:flex;
      justify-content:center;
      padding:24px 14px;
    }

    .shell{
      width:100%;
      max-width:1180px;
      display:grid;
      gap:18px;
      grid-template-columns:1.05fr .95fr;
      align-items:stretch;
    }

    .hero,
    .panel{
      background:linear-gradient(180deg, rgba(17,27,49,.88), rgba(8,12,22,.88));
      border:1px solid var(--line);
      border-radius:28px;
      box-shadow:var(--shadow);
      backdrop-filter:blur(18px);
      overflow:hidden;
      position:relative;
    }

    .hero::before,
    .panel::before{
      content:"";
      position:absolute;
      inset:0;
      background:linear-gradient(135deg, rgba(255,59,92,.12), transparent 30%, rgba(255,255,255,.03));
      pointer-events:none;
    }

    .hero{
      padding:26px;
      display:flex;
      flex-direction:column;
      gap:18px;
      min-height:680px;
    }

    .panel{
      padding:22px;
      min-height:680px;
    }

    .topline{
      display:flex;
      justify-content:space-between;
      align-items:center;
      gap:12px;
      flex-wrap:wrap;
    }

    .brand{
      display:flex;
      flex-direction:column;
      gap:6px;
    }

    .brand h1{
      font-size:30px;
      line-height:1.05;
      letter-spacing:.2px;
    }

    .brand p{
      color:var(--muted);
      font-size:14px;
    }

    .chiprow{
      display:flex;
      gap:10px;
      flex-wrap:wrap;
    }

    .chip{
      padding:10px 14px;
      border-radius:999px;
      background:rgba(255,255,255,.04);
      border:1px solid var(--line);
      color:#dce5ff;
      font-size:12px;
      letter-spacing:.3px;
    }

    .statusdot{
      display:inline-flex;
      align-items:center;
      gap:8px;
    }

    .dot{
      width:10px;
      height:10px;
      border-radius:50%;
      background:var(--good);
      box-shadow:0 0 0 6px rgba(85,242,182,.12), 0 0 22px rgba(85,242,182,.55);
    }

    .scene{
      flex:1;
      display:grid;
      grid-template-columns:1fr;
      gap:16px;
    }

    .handcard{
      position:relative;
      border-radius:26px;
      background:
        radial-gradient(circle at 30% 20%, rgba(255,255,255,.10), transparent 18%),
        linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
      border:1px solid var(--line);
      min-height:390px;
      overflow:hidden;
      display:flex;
      align-items:center;
      justify-content:center;
    }

    .gridbg{
      position:absolute;
      inset:0;
      background-image:
        linear-gradient(rgba(255,255,255,.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,.04) 1px, transparent 1px);
      background-size:34px 34px;
      mask-image: radial-gradient(circle at center, black 28%, transparent 78%);
      opacity:.42;
    }

    .glow{
      position:absolute;
      width:340px;
      height:340px;
      border-radius:50%;
      background:radial-gradient(circle, rgba(255,59,92,.22), transparent 68%);
      filter:blur(12px);
    }

    .glow.one{top:-60px;left:-40px}
    .glow.two{bottom:-100px;right:-40px;background:radial-gradient(circle, rgba(255,255,255,.09), transparent 68%)}

    svg{
      width:min(92%, 600px);
      height:auto;
      position:relative;
      z-index:2;
      overflow:visible;
      filter:drop-shadow(0 18px 40px rgba(0,0,0,.45));
    }

    .finger,
    .palm{
      fill:url(#handGrad);
      stroke:rgba(255,255,255,.42);
      stroke-width:2;
    }

    .joint{
      fill:#ff7d92;
      stroke:#fff;
      stroke-width:2;
      filter:drop-shadow(0 0 8px rgba(255,59,92,.6));
    }

    .ui-card{
      border:1px solid var(--line);
      border-radius:22px;
      background:rgba(255,255,255,.04);
      padding:16px;
    }

    .metrics{
      display:grid;
      grid-template-columns:repeat(3,1fr);
      gap:12px;
    }

    .metric .k{
      color:var(--muted);
      font-size:12px;
      margin-bottom:6px;
    }

    .metric .v{
      font-size:20px;
      font-weight:800;
      letter-spacing:.2px;
    }

    .section-title{
      display:flex;
      justify-content:space-between;
      align-items:center;
      gap:10px;
      margin-bottom:14px;
    }

    .section-title h2{
      font-size:18px;
    }

    .section-title span{
      color:var(--muted);
      font-size:12px;
    }

    .controls{
      display:grid;
      gap:12px;
    }

    .control{
      padding:14px;
      border-radius:18px;
      border:1px solid var(--line);
      background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.03));
    }

    .head{
      display:flex;
      justify-content:space-between;
      align-items:center;
      margin-bottom:10px;
      gap:12px;
    }

    .label{
      font-size:14px;
      font-weight:700;
    }

    .value{
      font-size:13px;
      font-weight:800;
      color:var(--primary2);
      min-width:54px;
      text-align:right;
    }

    input[type=range]{
      width:100%;
      -webkit-appearance:none;
      appearance:none;
      height:10px;
      border-radius:999px;
      background:linear-gradient(to right, rgba(255,59,92,.35), rgba(255,255,255,.14));
      outline:none;
      cursor:pointer;
    }

    input[type=range]::-webkit-slider-thumb{
      -webkit-appearance:none;
      appearance:none;
      width:24px;
      height:24px;
      border-radius:50%;
      border:2px solid #fff;
      background:linear-gradient(180deg, #ff8d9f, #ff3b5c);
      box-shadow:0 0 0 8px rgba(255,59,92,.12);
    }

    .presets{
      display:grid;
      grid-template-columns:repeat(2,1fr);
      gap:10px;
      margin-top:14px;
    }

    .presets button{
      border:none;
      border-radius:16px;
      padding:13px 14px;
      font-size:13px;
      font-weight:800;
      color:#fff;
      background:linear-gradient(135deg, var(--primary), #ff5f79);
      box-shadow:0 12px 24px rgba(255,59,92,.18);
      cursor:pointer;
      transition:.18s ease;
    }

    .presets button:hover{transform:translateY(-1px)}
    .presets button:active{transform:scale(.98)}

    .status{
      margin-top:14px;
      padding:14px 16px;
      border-radius:16px;
      border:1px solid var(--line);
      background:rgba(255,255,255,.04);
      color:#d9e5ff;
      text-align:center;
      font-size:13px;
      min-height:48px;
      display:flex;
      align-items:center;
      justify-content:center;
    }

    .footer{
      margin-top:12px;
      color:var(--muted);
      font-size:12px;
      text-align:center;
    }

    @media (max-width: 980px){
      .shell{grid-template-columns:1fr}
      .hero,.panel{min-height:auto}
      .metrics{grid-template-columns:1fr}
      .presets{grid-template-columns:1fr 1fr}
    }

    @media (max-width: 560px){
      .hero,.panel{padding:18px}
      .brand h1{font-size:24px}
      .presets{grid-template-columns:1fr}
    }
  </style>
</head>
<body>
  <div class="shell">
    <section class="hero">
      <div class="topline">
        <div class="brand">
          <h1>NeuroGrip</h1>
          <p>Futuristic robotic hand dashboard for ESP32 servo control.</p>
        </div>
        <div class="chiprow">
          <div class="chip statusdot"><span class="dot"></span> LIVE</div>
          <div class="chip">ESP32 Hotspot</div>
          <div class="chip">5-DOF Hand</div>
        </div>
      </div>

      <div class="scene">
        <div class="handcard">
          <div class="gridbg"></div>
          <div class="glow one"></div>
          <div class="glow two"></div>

          <svg viewBox="0 0 820 540" aria-label="Robotic hand illustration">
            <defs>
              <linearGradient id="handGrad" x1="0%" y1="0%" x2="100%" y2="100%">
                <stop offset="0%" stop-color="#ffe4ea"/>
                <stop offset="45%" stop-color="#ff7a92"/>
                <stop offset="100%" stop-color="#d90429"/>
              </linearGradient>
            </defs>

            <ellipse cx="410" cy="430" rx="170" ry="55" fill="rgba(255,255,255,.08)"/>

            <g transform="translate(165,45)">
              <rect class="palm" x="180" y="190" rx="48" ry="48" width="210" height="190"/>

              <rect class="finger" x="190" y="55" rx="24" ry="24" width="48" height="155" transform="rotate(-8 190 55)"/>
              <circle class="joint" cx="214" cy="82" r="9"/>
              <circle class="joint" cx="210" cy="138" r="8"/>

              <rect class="finger" x="252" y="28" rx="24" ry="24" width="52" height="182"/>
              <circle class="joint" cx="278" cy="60" r="9"/>
              <circle class="joint" cx="278" cy="124" r="8"/>

              <rect class="finger" x="322" y="52" rx="24" ry="24" width="50" height="158"/>
              <circle class="joint" cx="347" cy="82" r="9"/>
              <circle class="joint" cx="347" cy="138" r="8"/>

              <rect class="finger" x="386" y="92" rx="22" ry="22" width="42" height="120" transform="rotate(6 386 92)"/>
              <circle class="joint" cx="407" cy="116" r="8"/>
              <circle class="joint" cx="410" cy="158" r="7"/>

              <path class="finger" d="M170 270 C130 282, 102 310, 98 346 C96 362, 108 374, 126 376 C150 378, 171 360, 186 336 C201 311, 206 287, 205 255 Z"/>
              <circle class="joint" cx="152" cy="306" r="9"/>
              <circle class="joint" cx="128" cy="338" r="8"/>
            </g>
          </svg>
        </div>

        <div class="ui-card">
          <div class="metrics">
            <div class="metric">
              <div class="k">Mode</div>
              <div class="v">Manual</div>
            </div>
            <div class="metric">
              <div class="k">Latency</div>
              <div class="v">Low</div>
            </div>
            <div class="metric">
              <div class="k">Power</div>
              <div class="v">Stable</div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <section class="panel">
      <div class="section-title">
        <h2>Servo Control</h2>
        <span>Tap presets or drag sliders</span>
      </div>

      <div class="controls">
        <div class="control">
          <div class="head"><span class="label">Thumb</span><span class="value" id="v1">90°</span></div>
          <input type="range" min="0" max="180" value="90" id="s1" oninput="send(1,this.value)">
        </div>
        <div class="control">
          <div class="head"><span class="label">Index</span><span class="value" id="v2">90°</span></div>
          <input type="range" min="0" max="180" value="90" id="s2" oninput="send(2,this.value)">
        </div>
        <div class="control">
          <div class="head"><span class="label">Middle</span><span class="value" id="v3">90°</span></div>
          <input type="range" min="0" max="180" value="90" id="s3" oninput="send(3,this.value)">
        </div>
        <div class="control">
          <div class="head"><span class="label">Ring</span><span class="value" id="v4">90°</span></div>
          <input type="range" min="0" max="180" value="90" id="s4" oninput="send(4,this.value)">
        </div>
        <div class="control">
          <div class="head"><span class="label">Little</span><span class="value" id="v5">90°</span></div>
          <input type="range" min="0" max="180" value="90" id="s5" oninput="send(5,this.value)">
        </div>
      </div>

      <div class="presets">
        <button onclick="preset([90,90,90,90,90])">Open Hand</button>
        <button onclick="preset([0,0,0,0,0])">Fist</button>
        <button onclick="preset([180,0,0,0,0])">Thumbs Up</button>
        <button onclick="preset([0,180,0,0,0])">Point</button>
        <button onclick="preset([180,180,0,0,0])">Peace</button>
        <button onclick="preset([150,150,150,150,150])">Half Close</button>
      </div>

      <div class="status" id="status">System ready — move a slider to control the hand.</div>
      <div class="footer">Designed for a futuristic robotic hand control experience.</div>
    </section>
  </div>

  <script>
    function send(servo, value) {
      document.getElementById('v' + servo).textContent = value + '°';
      const st = document.getElementById('status');
      st.textContent = 'Sending Servo ' + servo + ' → ' + value + '°';

      const xhr = new XMLHttpRequest();
      xhr.open('GET', '/set?servo=' + servo + '&value=' + value, true);
      xhr.onload = function() {
        st.textContent = '✓ Servo ' + servo + ' set to ' + value + '°';
      };
      xhr.onerror = function() {
        st.textContent = '⚠ Connection error';
      };
      xhr.send();
    }

    function preset(vals) {
      vals.forEach(function(v, i) {
        document.getElementById('s' + (i + 1)).value = v;
        document.getElementById('v' + (i + 1)).textContent = v + '°';
        send(i + 1, v);
      });
    }
  </script>
</body>
</html>
)====";

// ===========================================

void handleRoot() {
  server.send(200, "text/html", webpage);
}

void handleServo() {
  int servo = server.arg("servo").toInt();
  int value = constrain(server.arg("value").toInt(), 0, 180);

  if (servo == 1) servo1.write(value);
  if (servo == 2) servo2.write(value);
  if (servo == 3) servo3.write(value);
  if (servo == 4) servo4.write(value);
  if (servo == 5) servo5.write(value);

  Serial.print("Servo ");
  Serial.print(servo);
  Serial.print(" -> ");
  Serial.println(value);

  server.send(200, "text/plain", "OK");
}

void setup() {
  Serial.begin(115200);

  servo1.attach(s1);
  servo2.attach(s2);
  servo3.attach(s3);
  servo4.attach(s4);
  servo5.attach(s5);

  // Initial position
  servo1.write(90);
  servo2.write(90);
  servo3.write(90);
  servo4.write(90);
  servo5.write(90);

  // Start Hotspot
  WiFi.softAP(ssid, password);

  Serial.println("\nHotspot Started!");
  Serial.print("SSID: ");
  Serial.println(ssid);
  Serial.print("IP Address: ");
  Serial.println(WiFi.softAPIP());

  server.on("/", handleRoot);
  server.on("/set", handleServo);

  server.begin();
  Serial.println("Web server started.");
}

void loop() {
  server.handleClient();

}

3D Printable STL Files : https://inmoov.fr/inmoov-stl-parts-viewer/?bodyparts=Right-Hand

Output / Result

After uploading the code:

  • the ESP32 starts a hotspot
  • the browser opens the control dashboard
  • the sliders move the robotic fingers
  • the preset buttons change hand gestures instantly

The interface is clean, responsive, and more professional than a basic control page.


Applications

  • Robotic arm and hand projects
  • Gesture-based control systems
  • Classroom robotics demonstration
  • Smart automation prototype
  • IoT web control projects

Advantages

  • No external app needed
  • Easy mobile access
  • Smooth browser-based control
  • Professional futuristic interface
  • Suitable for demo and project presentation

Conclusion

This ESP32 robotic hand control project is a simple but powerful example of combining embedded systems, servo motion control, and web-based UI design. With the futuristic dashboard and live finger visualization, the project looks more attractive and professional for presentation or blog publishing.

                              

 

Comments

Popular posts from this blog

🎯 Build Your Own RFID Attendance System Using Arduino + ESP32 + Google Sheets

How to make a Rfid id tag to turn on the Motor

Build Your Own Timer Plug – Automatic Mobile Charger Cut-Off Using ESP32 (Hotspot Mode)