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.
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.
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)
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.
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.
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.
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.
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.
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.
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.
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.
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.
Mount the motor driver
Fix the motor driver board to the frame close to the motors to keep wiring short.
Mount the ESP32-CAM
Attach the ESP32-CAM facing outward so its lens has a clear view through the transparent shell.
Add the gas sensor
Mount the MQ-2 sensor on the frame somewhere with open airflow, away from the motors' heat.
Add the LEDs
Glue a small ring of LEDs around the camera lens to light up dark spaces during a scan.
Wire the motor driver
Connect the driver's control pins to the ESP32-CAM's GPIO pins as shown in the circuit diagram.
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.
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.
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.
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.
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(); }
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.
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.

Comments
Post a Comment