Controlling a mobile robot via a web interface using Flask and Raspberry Pi gives you the freedom to operate your robot from any device on the same network — a phone, tablet, or laptop — without installing any special app. This tutorial builds a complete web-controlled robot from scratch: a Python Flask server running on Raspberry Pi, a real-time browser interface with live video, and GPIO-based motor control. It is an ideal project for Indian engineering students and hobbyists wanting to learn IoT, web development, and robotics simultaneously.
Table of Contents
- System Architecture Overview
- Hardware Setup and Wiring
- Building the Flask Web Server
- Motor Control with GPIO
- Live Video Streaming
- Web Interface Design
- Frequently Asked Questions
System Architecture Overview
The web-controlled robot system consists of three tiers working together seamlessly:
- Hardware layer: Raspberry Pi GPIO controlling motor drivers, which drive DC motors on a wheeled robot chassis. Optionally, a Pi Camera or USB webcam provides live video.
- Server layer: Flask web framework running on the Raspberry Pi. It serves the HTML control interface, handles HTTP requests for movement commands, and streams video via MJPEG.
- Client layer: Any web browser on the same WiFi network. The browser displays the control UI and live video. JavaScript sends control commands via AJAX or WebSockets.
WiFi connectivity is key — both the Raspberry Pi and the controlling device must be on the same network. For remote control over the internet, you can use ngrok or a VPN, but keep security in mind.
Hardware Setup and Wiring
For this project, you need:
- Raspberry Pi 3B+ or 4 (with WiFi) — ₹4,000–₹5,500
- L298N or L293D motor driver module — ₹150–₹400
- 2x DC gear motors with wheels — ₹300–₹600
- Robot chassis frame — ₹400–₹800
- 2x 18650 Li-ion cells in a holder, or 4x AA batteries — ₹200–₹600
- Pi Camera v2 or USB webcam (optional) — ₹800–₹1,500
GPIO wiring to L298N motor driver:
Raspberry Pi GPIO → L298N
---------------------------------
GPIO 17 (pin 11) → IN1 (Motor A forward)
GPIO 18 (pin 12) → IN2 (Motor A backward)
GPIO 22 (pin 15) → IN3 (Motor B forward)
GPIO 23 (pin 16) → IN4 (Motor B backward)
GPIO 24 (pin 18) → ENA (PWM speed A)
GPIO 25 (pin 22) → ENB (PWM speed B)
GND → GND
5V → +5V (logic supply only)
The motors and main battery connect separately to L298N’s 12V input and output terminals.
Building the Flask Web Server
Install Flask and RPi.GPIO on your Raspberry Pi:
sudo apt update && sudo apt install python3-pip -y
pip3 install flask RPi.GPIO
Create the Flask application:
# app.py
from flask import Flask, render_template, request, Response
import RPi.GPIO as GPIO
import time
import threading
app = Flask(__name__)
# GPIO Pin Configuration
IN1, IN2 = 17, 18 # Motor A
IN3, IN4 = 22, 23 # Motor B
ENA, ENB = 24, 25 # PWM enable pins
# Setup GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
for pin in [IN1, IN2, IN3, IN4, ENA, ENB]:
GPIO.setup(pin, GPIO.OUT)
GPIO.output(pin, GPIO.LOW)
# PWM setup for speed control
pwm_a = GPIO.PWM(ENA, 100)
pwm_b = GPIO.PWM(ENB, 100)
pwm_a.start(0)
pwm_b.start(0)
def set_speed(speed=80):
pwm_a.ChangeDutyCycle(speed)
pwm_b.ChangeDutyCycle(speed)
def stop():
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.LOW)
set_speed(0)
def forward():
GPIO.output(IN1, GPIO.HIGH)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.HIGH)
GPIO.output(IN4, GPIO.LOW)
set_speed(80)
def backward():
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.HIGH)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.HIGH)
set_speed(80)
def turn_left():
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.HIGH)
GPIO.output(IN3, GPIO.HIGH)
GPIO.output(IN4, GPIO.LOW)
set_speed(70)
def turn_right():
GPIO.output(IN1, GPIO.HIGH)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.HIGH)
set_speed(70)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/control', methods=['POST'])
def control():
command = request.json.get('command')
commands = {
'forward': forward,
'backward': backward,
'left': turn_left,
'right': turn_right,
'stop': stop
}
if command in commands:
commands[command]()
return {'status': 'ok', 'command': command}
if __name__ == '__main__':
try:
app.run(host='0.0.0.0', port=5000, debug=False)
finally:
stop()
GPIO.cleanup()
Motor Control with GPIO
The motor control functions above use GPIO outputs to set the direction of each motor via the H-bridge in the L298N. PWM (Pulse Width Modulation) on the ENA/ENB pins controls speed — 0% duty cycle = stopped, 100% = full speed.
For smoother acceleration, ramp the speed up gradually:
def forward_smooth(target_speed=80, ramp_time=0.5):
current_speed = 0
GPIO.output(IN1, GPIO.HIGH)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.HIGH)
GPIO.output(IN4, GPIO.LOW)
steps = 20
for i in range(steps + 1):
speed = int(target_speed * i / steps)
pwm_a.ChangeDutyCycle(speed)
pwm_b.ChangeDutyCycle(speed)
time.sleep(ramp_time / steps)
Live Video Streaming
Add MJPEG video streaming to the Flask server:
from picamera2 import Picamera2
import io
camera = Picamera2()
camera.configure(camera.create_video_configuration(
main={"size": (640, 480), "format": "RGB888"}
))
camera.start()
def generate_frames():
while True:
# Capture frame from Pi Camera
frame = camera.capture_array()
# Convert to JPEG
import cv2
_, buffer = cv2.imencode('.jpg', frame,
[cv2.IMWRITE_JPEG_QUALITY, 70])
frame_bytes = buffer.tobytes()
yield (b'--framern'
b'Content-Type: image/jpegrnrn' +
frame_bytes + b'rn')
@app.route('/video_feed')
def video_feed():
return Response(generate_frames(),
mimetype='multipart/x-mixed-replace; boundary=frame')
Web Interface Design
Create templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Robot Control</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: Arial; text-align: center; background: #1a1a2e; color: white; }
.control-grid { display: grid; grid-template-columns: repeat(3, 80px);
gap: 10px; justify-content: center; margin: 20px auto; }
button { width: 80px; height: 80px; font-size: 24px; border-radius: 10px;
border: none; cursor: pointer; background: #e63946; color: white; }
button:active { background: #c1121f; transform: scale(0.95); }
#video { max-width: 100%; border: 3px solid #e63946; border-radius: 8px; }
</style>
</head>
<body>
<h1>Robot Control</h1>
<img id="video" src="/video_feed">
<div class="control-grid">
<div></div>
<button ontouchstart="send('forward')" ontouchend="send('stop')"
onmousedown="send('forward')" onmouseup="send('stop')">↑</button>
<div></div>
<button ontouchstart="send('left')" ontouchend="send('stop')"
onmousedown="send('left')" onmouseup="send('stop')">←</button>
<button onclick="send('stop')">■</button>
<button ontouchstart="send('right')" ontouchend="send('stop')"
onmousedown="send('right')" onmouseup="send('stop')">→</button>
<div></div>
<button ontouchstart="send('backward')" ontouchend="send('stop')"
onmousedown="send('backward')" onmouseup="send('stop')">↓</button>
<div></div>
</div>
<script>
function send(cmd) {
fetch('/control', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({command: cmd})
});
}
// Keyboard control
document.addEventListener('keydown', (e) => {
const keys = {ArrowUp:'forward', ArrowDown:'backward',
ArrowLeft:'left', ArrowRight:'right'};
if (keys[e.key]) send(keys[e.key]);
});
document.addEventListener('keyup', () => send('stop'));
</script>
</body>
</html>
Frequently Asked Questions
How do I find the Raspberry Pi’s IP address for the web interface?
Run hostname -I on the Pi, or check your WiFi router’s admin panel. The Flask server runs on port 5000 by default — access it at http://[PI_IP]:5000 from any browser on the same network.
Can I control the robot over the internet, not just local WiFi?
Yes — use ngrok (free tier available) to create a public tunnel: ngrok http 5000. Add authentication to protect it. For permanent remote access, use WireGuard VPN or a cloud-hosted reverse proxy.
What is the latency of web-based robot control?
On a local WiFi network, latency is typically 10–50ms — responsive enough for casual robot driving. For precision control, WebSockets reduce latency further compared to plain HTTP requests. Cellular networks add 50–200ms latency.
Can this project work with ESP32 instead of Raspberry Pi?
Yes — ESP32 has built-in WiFi and can run a minimal web server using the ESPAsyncWebServer library. However, live video streaming requires a separate ESP32-CAM module. The ESP32 approach uses significantly less power and costs less than a Raspberry Pi.
How do I make the Flask server start automatically on boot?
Create a systemd service: sudo nano /etc/systemd/system/robot.service. Set ExecStart=/usr/bin/python3 /home/pi/app.py and enable it with sudo systemctl enable robot. The web server starts automatically after each reboot.
Add comment