DIY Rolling Ball Rescue Robot

DIY Rolling Ball Rescue Robot | ESP32-CAM Robotics Project for Kids
🔵 Kid-friendly robotics build

The Rolling Ball Rescue Robot

A clear plastic ball rolls itself across the floor while a small camera inside stays perfectly upright, streaming live video to your phone. It can also sniff the air for gas and light up dark corners — like a mini search-and-rescue scout you build yourself.

Age11 and up
Build time4–5 hours
DifficultyIntermediate
Cost$30–40
192.168.1.42
● LIVE
▲ Forward
◀ Left
■ Stop
▶ Right
▼ Back
Gas level: 412 💡 Light: off
Why build this

A camera that always lands on its feet

This rolling ball rescue robot is inspired by real search-and-rescue robots that can be tossed into rubble or tight spaces to look around before people go in. Ours uses an ESP32-CAM to stream live video to a web page, an internal frame that stays upright no matter which way the outer shell rolls, a gas sensor to sniff the air, and small LEDs to light up dark corners. It's a fantastic project for learning about center of gravity, motor control, wireless video, and simple web servers — all inside one satisfying rolling ball.

Shopping list

What you'll need

Every part below is common in beginner electronics kits or easy to order online.

Electronics

  • 1x ESP32-CAM module (AI-Thinker)
  • 1x FTDI USB-to-serial adapter (3.3V, for programming)
  • 1x TB6612FNG or L298N dual motor driver
  • 2x small TT/N20 gear motors with wheels
  • 1x MQ-2 gas sensor module
  • 4–6 small LEDs + resistors + 1 NPN transistor
  • 2x 18650 batteries + holder, or a small LiPo
  • 1x 5V buck converter (UBEC)
  • Slide switch, jumper wires, small protoboard

Sphere & frame

  • 1x clear plastic ball, 15–20cm (large ornament ball or hamster ball)
  • Lightweight internal frame (3D printed, or foam board + skewers)
  • Foam padding or felt strips (cushions the frame)
  • Small zip ties

Tools

  • Hot glue gun
  • Craft knife
  • Small screwdriver
  • Wire stripper
  • Soldering iron (optional but helpful)
The concept

How it stays upright while it rolls

The clear shell and the internal frame are almost completely separate. They only touch through two small rubber wheels. Gravity does the rest.

Battery (counterweight) ESP32-CAM Clear outer shell (rolls freely)
1

Heavy base, light top

The battery pack sits at the very bottom of the frame, so the frame's center of gravity is low — just like a weighted wobble toy that always tips back upright.

2

Only two contact points

The frame only touches the shell through its two rubber-tired wheels, so the shell can spin freely around the frame without dragging it along.

3

Wheels push, ball rolls

When the wheels spin, they push against the inside of the shell. That push rolls the whole ball forward while the frame stays calmly in place.

Kid-friendly tip: Try this with a small bowl and a toy figure with a weight taped to its feet — spin the bowl and watch the figure stay upright inside. That's exactly the same trick this robot uses, just electronic.
Wiring

Circuit diagram

The ESP32-CAM is the brain of the robot. It controls the motor driver, reads the gas sensor, and switches the LEDs, all while streaming video over Wi-Fi.

ESP32-CAM GPIO 12,13,14,15,2,16,33 Motor driver (TB6612) MQ-2 gas sensor → GPIO33 LED ring → GPIO16 5V buck converter from battery pack
Motor driver control pins → ESP32-CAM GPIO 12, 13, 14, 15, standby on GPIO 2 Gas sensor analog output → GPIO 33 (ADC) LED circuit (via transistor) → GPIO 16 5V power and common ground to every part
Important: The ESP32-CAM's Wi-Fi radio needs a steady, clean 5V supply. Power it and the motors from the same battery pack through a dedicated 5V buck converter, not directly from a single small battery, or the video stream may drop out when the motors start.
Assembly

Step-by-step build instructions

Test every part on the bench before sealing the shell — it's much easier to fix wiring while you can still reach it.

1

Prepare the internal frame

Build or 3D-print a small lightweight frame sized to sit inside the shell with room to spare on every side.

2

Mount the drive wheels

Fix the two gear motors to the frame at an angle so their wheels press firmly against the inside of the shell.

3

Add the counterweight

Mount the battery pack at the very bottom of the frame — this keeps the frame's center of gravity low so it self-rights.

4

Mount the motor driver

Fix the motor driver board to the frame close to the motors to keep wiring short.

5

Mount the ESP32-CAM

Attach the ESP32-CAM facing outward so its lens has a clear view through the transparent shell.

6

Add the gas sensor

Mount the MQ-2 sensor on the frame somewhere with open airflow, away from the motors' heat.

7

Add the LEDs

Glue a small ring of LEDs around the camera lens to light up dark spaces during a scan.

8

Wire the motor driver

Connect the driver's control pins to the ESP32-CAM's GPIO pins as shown in the circuit diagram.

9

Wire the sensor and LEDs

Connect the gas sensor's analog output and the LED transistor to their GPIO pins, and connect all grounds together.

10

Test on the bench

Power everything up outside the shell first. Check that the motors spin, the LEDs light, and the gas reading changes when you wave a marker pen nearby.

11

Flash the code

Connect the FTDI adapter, hold GPIO0 to ground while powering on to enter programming mode, then upload the sketch below from the Arduino IDE.

12

Close the shell

Carefully lower the frame into one half of the shell, tuck in any loose wire, then join the two halves together without pinching anything.

ESP32 sketch

The code

This sketch streams live video, serves a simple control webpage with direction buttons, reads the gas sensor, and toggles the LEDs — all from the ESP32-CAM itself.

// Rolling Ball Rescue Robot - ESP32-CAM
// Serves a web page with live video, drive controls, a light switch, and a gas reading

#include "esp_camera.h"
#include <WiFi.h>
#include <WebServer.h>

// AI-Thinker ESP32-CAM pin map
#define PWDN_GPIO_NUM 32
#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

const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";

// Motor driver pins
#define AIN1 12
#define AIN2 13
#define BIN1 15
#define BIN2 14
#define STBY 2

// Gas sensor + LED ring
#define GAS_PIN 33
#define LED_PIN 16
const int GAS_THRESHOLD = 1800;

WebServer server(80);
bool lightOn = false;

void stopMotors(){
  digitalWrite(AIN1, LOW); digitalWrite(AIN2, LOW);
  digitalWrite(BIN1, LOW); digitalWrite(BIN2, LOW);
  digitalWrite(STBY, LOW);
}
void driveForward(){ digitalWrite(STBY,HIGH); digitalWrite(AIN1,HIGH); digitalWrite(AIN2,LOW); digitalWrite(BIN1,HIGH); digitalWrite(BIN2,LOW); }
void driveBack(){ digitalWrite(STBY,HIGH); digitalWrite(AIN1,LOW); digitalWrite(AIN2,HIGH); digitalWrite(BIN1,LOW); digitalWrite(BIN2,HIGH); }
void turnLeft(){ digitalWrite(STBY,HIGH); digitalWrite(AIN1,LOW); digitalWrite(AIN2,HIGH); digitalWrite(BIN1,HIGH); digitalWrite(BIN2,LOW); }
void turnRight(){ digitalWrite(STBY,HIGH); digitalWrite(AIN1,HIGH); digitalWrite(AIN2,LOW); digitalWrite(BIN1,LOW); digitalWrite(BIN2,HIGH); }

void handleRoot(){
  String html = "<html><body style='font-family:sans-serif;text-align:center'>"
    "<h2>Rolling Ball Rescue Robot</h2>"
    "<img src='/stream' width='320'><br><br>"
    "<button onclick=\"fetch('/fwd')\">Forward</button> "
    "<button onclick=\"fetch('/back')\">Back</button> "
    "<button onclick=\"fetch('/left')\">Left</button> "
    "<button onclick=\"fetch('/right')\">Right</button> "
    "<button onclick=\"fetch('/stop')\">Stop</button><br><br>"
    "<button onclick=\"fetch('/light')\">Toggle light</button>"
    "<p id='gas'>Gas level: --</p>"
    "<script>setInterval(()=>{fetch('/gas').then(r=>r.text()).then(t=>document.getElementById('gas').innerText='Gas level: '+t)},1000);</script>"
    "</body></html>";
  server.send(200, "text/html", html);
}

void handleStream(){
  WiFiClient client = server.client();
  server.sendContent("HTTP/1.1 200 OK\r\nContent-Type: multipart/x-mixed-replace; boundary=frame\r\n\r\n");
  while (client.connected()){
    camera_fb_t * fb = esp_camera_fb_get();
    if (!fb) continue;
    server.sendContent("--frame\r\nContent-Type: image/jpeg\r\nContent-Length: " + String(fb->len) + "\r\n\r\n");
    client.write(fb->buf, fb->len);
    server.sendContent("\r\n");
    esp_camera_fb_return(fb);
    if (!client.connected()) break;
  }
}

void handleLight(){
  lightOn = !lightOn;
  digitalWrite(LED_PIN, lightOn ? HIGH : LOW);
  server.send(200, "text/plain", lightOn ? "on" : "off");
}

void handleGas(){
  int level = analogRead(GAS_PIN);
  if (level > GAS_THRESHOLD) digitalWrite(LED_PIN, HIGH); // flash a warning
  server.send(200, "text/plain", String(level));
}

void setup(){
  Serial.begin(115200);
  pinMode(AIN1, OUTPUT); pinMode(AIN2, OUTPUT);
  pinMode(BIN1, OUTPUT); pinMode(BIN2, OUTPUT);
  pinMode(STBY, OUTPUT); pinMode(LED_PIN, OUTPUT);
  pinMode(GAS_PIN, INPUT);
  stopMotors();

  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 = -1;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_QVGA;
  config.jpeg_quality = 12;
  config.fb_count = 1;
  esp_camera_init(&config);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/stream", handleStream);
  server.on("/fwd",  [](){ driveForward(); server.send(200,"text/plain","ok"); });
  server.on("/back", [](){ driveBack();    server.send(200,"text/plain","ok"); });
  server.on("/left", [](){ turnLeft();     server.send(200,"text/plain","ok"); });
  server.on("/right",[](){ turnRight();    server.send(200,"text/plain","ok"); });
  server.on("/stop", [](){ stopMotors();   server.send(200,"text/plain","ok"); });
  server.on("/light", handleLight);
  server.on("/gas", handleGas);
  server.begin();
}

void loop(){
  server.handleClient();
}
Try this: Change GAS_THRESHOLD to make the alert more or less sensitive, or change FRAMESIZE_QVGA to FRAMESIZE_VGA for a sharper (but slower) video stream.

⚡ Safety first

  • Ask an adult to help with soldering, the hot glue gun, and any drilling.
  • Handle batteries carefully — never puncture, overcharge, or short the terminals.
  • Keep loose wires and fingers away from the spinning wheels while testing.
  • The MQ-2 sensor is a fun educational tool, not a certified safety device — never rely on it in a real emergency.
  • Keep the robot's video stream on your home Wi-Fi network only, not exposed to the internet.
Good to know

Frequently asked questions

Can I use a hamster ball instead of buying a special shell?

Yes. A large clear hamster exercise ball is one of the easiest shells to start with, since it already opens into two halves.

Do I need to know how to solder?

It helps for reliable connections, but you can start with a small breadboard and jumper wires while you're testing, then solder the final version once everything works.

Why does the frame stay upright instead of spinning with the shell?

Because the frame only touches the shell through its two wheels, and its battery weight is mounted low, gravity keeps it hanging the same way up no matter how the shell rolls around it.

Can I drive it from my phone?

Yes. Once it's connected to your Wi-Fi, open the ESP32-CAM's IP address in your phone's browser to see the live video and use the on-screen buttons.

Is the gas sensor accurate enough for a real emergency?

No — treat it as a learning tool. It's great for noticing changes in air quality during a science project, but it isn't a substitute for a certified gas detector.

Built with an ESP32-CAM, a lot of gravity, and a good pair of safety glasses. Happy making!

Comments

Product Cards
Buddy Bot eBook
⭐ New 2026 Release
Build Your
Own Robot!
3D design, wiring &
Arduino coding.
Young inventors love it!
🖨️
3D Print
All parts
Wire it
Circuit guide
💻
Code it
Arduino IDE
🤖
Watch it
Walk & react
📋 Your Details
Enter your name
Valid 10-digit no.
Enter a valid email
Special Website Offer
₹499 300
🌍 International: $5 USD
One-time · Instant digital delivery
🔒 Secured by Razorpay · Your data is safe
📄 Download Free Sample Copy
🔒 Secured by Razorpay · Your data is safe
🍓
Raspberry Pi Pico Mastery
21 Projects
⚡ Launch Price — 80% OFF
Learn Pico
Build 21 Projects!
MicroPython · Wokwi
IoT · Certificate
Perfect for beginners!
🖥️
Wokwi
No hardware
🐍
MicroPy
From zero
🔨
21 Projects
IoT + sensors
📄
Certificate
Verified cert
📋 Your Details
Enter your name
Valid 10-digit no.
Enter a valid email
Special Launch Offer
₹999 200 80% OFF
🌍 International: $5 USD
One-time · Lifetime access · No subscription
🔒 Secured by Razorpay · UPI · Cards · NetBanking
🎉

You're in!

Payment successful! Your Buddy Bot eBook is ready. Time to build!

📖 Access Your eBook Now
🎉

Enrolled!

Payment successful! Lifetime access to all 21 Pico Projects is yours!

🍓 Go to My Course