FPV Internet Controlled Toy Excavator




Introduction: FPV Internet Controlled Toy Excavator

The goal was to transform an old and broken RC toy excavator into a First Person View controlled over the internet one.
Trying to reuse as much as possible, and involving the kids in the process.

Controlled from a web browser from anywhere in the world with internet connection.

The main components I used:

  • rc toy excavator
  • raspberry Pi 2 Model B
  • USB WIFI adapter
  • webcam
  • sd card (16Gb was used here)
  • Two external batteries (one of them an external usb battery)
  • Dozens of jump wires
  • Some tools ( Soldering iron, saw, scissors, screw drivers...)

The software involved :

  • Raspbian as the OS of the Raspberry Pi
  • Webiopi as the Raspberry Pi IOT Framework to control the Pi's GPIO's
  • Some scripts and configuration files
  • mjpg_streamer

The first two steps are about the instalation and configuration of the RPi software If you want to see something more specific about this instructable, skip to step 4.

To fully understand this instructable You should have some basic knowledge about the usage of the shell, linux, some raspberry basics (what GPIO are...) and some basic notions about electronics too.
If you don't, you should spend some time learning about that. It's not necessary to have a degree, just some basics :)

Step 1: Setting Up the Raspberry Pi (1)

The software installation into the sd card has been done using a computer running Debian.

I think the process is almost the same for most of the other Gnu/Linux based distros.

I set up a headless instalation in the Raspberry Pi2 but maybe it could be easier connecting a monitor and a keyboard.

These are the steps:

1-Download raspbian image (Jessie) from:


There are many places that explain very well how to install and configure your Raspberry Pi, for example:


2- Connect the sd card to the PC ( running Debian), unzip the os for the RPi.

Check the path where the sd is mounted.

$ unzip 2016-02-26-raspbian-jessie.zip 
$ df -h
/dev/sdc1         15G    32K   15G   1% /media/USER/79C2-6827

3- Log in as root. Unmount the sd card. Check again to see if it is really unmounted.


#umount /dev/sdc1
#df -h

4-Write the unzipped .img into ALL the partitition (in this case /dev/sdc) NOT only in sdc1.

#dd bs=4M if=2016-02-26-raspbian-jessie.img of=/dev/sdc

After a while... quite a long time. (The output is in Spanish here, just information that the process was completed)

960+1 registros leídos
960+1 registros escritos
4029677568 bytes (4,0 GB) copiados, 3883,82 s, 1,0 MB/s

Step 2: Setting Up the Raspberry Pi (2)

Unplug and plug again your sd card. It will be recognized. You should see that you have two partitions, something like that:

/dev/sdc1 mounted on /boot

/dev/sdc2 mounted on /media/$USER/443559ba-b80f-4fb6-99d9-ddbcd6138fbd

You can check it out using

#df -h 

Now we can enter in our main partiton

#cd /media/$USER/443559ba-b80f-4fb6-99d9-ddbcd61

And we must modify some configuration files. I used nano as editor. And I decided to perform a headless setup. The only RPI connections will be the power source and the ethernet cable.That's why it is needed to configure some files beforehand.

#nano /etc/network/interfaces

We should change this paragraph

allow-hotplug wlan0

iface wlan0 inet manual
    wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

For this one

auto wlan0
allow-hotplug wlan0 auto wlan0
allow-hotplug wlan0
iface wlan0 inet dhcp
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf 
iface default inet dhcp

Remember saving using CRTL+O. Before exiting (CRTL+X)

Now we open resolv.conf

# nano /etc/resolv.conf

By default we'll have google namserver(, we can add our own..

# Generated by NetworkManager

Now we configure wpa_supplicant.conf file

# nano /etc/wpa_supplicant/wpa_supplicant.conf

We must add:

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev


Do not forget to save it.

We unmount the sd card ( the two partitons) and we plug it into the RPi.

We also attach the usb wifi adapter. We power up the Rpi and we can check what is our local address, looking in our router.

Sometimes It is difficult to make the usb wifi works at the first try, so in this case it is better to connect the ethernet cable to the Rpi, log into the Rpi from your pc using ssh (my local address

If you don't know how to set up you ssh client/server, check this place


We'll log into the RPI in graphical mode.

More info about graphical loggin here:


ssh -X pi@

The default password is "raspberry", you can change it after the initial setup.

and once logged into the Rpi and after some initial setup configuration (if any) try this command:

$ sudo ifdown wlan0 && sudo ifup wlan0

If you still have problems trying to connect to your Rpi using the WIFI, follow the instructions of this website, with very specific information about this step.

WiFi Issues ? Start here !


Step 3: Setting Up the Webcam

A FPV vehicle obviously needs some kind of video capture device, and streaming it.

I tried several webcams. The best are the ones which have hardware mjpeg encoding, less cpu usage and less memory requirements.

Another mjpeg's advantage is that it requires no plugins or browser extensions, it can be played natively in most web browsers.

And we'll use any web browser from pc. laptops, tablets, smartphones, TV, using chrome, firefox, safari, opera, iceweasel... as the only software requirement for the end user.

Now you should be able to connect to you Rpi with the wifi usb dongle,using ssh from your pc.

All the instructions are performed from a remote pc conected to the RPi

Connect the camera to the Rpi, and check if it is recognized.

$ dmesg

See if your camera is in the output.

To check our camera capabilities we can install uvcdyncrt

pi@raspberrypi ~ $ sudo apt-get install uvcdynctrl<br>pi@raspberrypi ~ $ uvcdynctrl -f

We're looking for

Pixel format: MJPG (MJPEG; MIME type: image/jpeg)
Frame size: 640x480<br>Frame rates: 30, 25, 20, 15, 10, 5
Frame size: 160x120  
Frame rates: 30, 25, 20, 15, 10, 5
Frame size: 1280x960<br>Frame rates: 30, 25, 20, 15, 10, 5

We will also install a bunch of packages related with v4l

$ sudo install v4l2ucp v4l2loopback-utils v4l2loopback-source gem-plugin-v4l2 libjpeg8-dev libv4l-dev

And we finally must install mjpeg streamer. But it is not yet on the raspbian repositories

$sudo apt-get install subversion libjpeg8-dev imagemagick libav-tools cmake git clone <a href="https://github.com/jacksonliam/mjpg-streamer.git" rel="nofollow">   https://github.com/jacksonliam/mjpg-streamer.git<...>
$cd mjpg-streamer/mjpg-streamer-experimental

To run mjpeg streamer

$./mjpg_streamer -i "./input_uvc.so" -o "./output_http.so"

To see the photos or video, we simply must open a web broser, and:

To take pictures

Or video

http://[ your IP address]:8080/?action=stream

You can watch it on your tv web browser too.

Later In the seventh step of this instructable we will return to configure some more things related with the video streaming and we'll see how to integrate it into our system.

Step 4: Setting Up the Electronics

The original electronic setup was composed of a PCB and 6 simple dc motors. No servos or limit switches in the boom or arm, so do not force them too much.

To power the RPi we'll use an external usb battery pack of 10Ah.

To power the motors we'll use a 12v lead battery 7Ah, with a dc-dc step down module to 9,6v.

Each wheel has its own dc motor. The steering system is quite simple. Like a caterpillar vehicle.

  • To go forward, all wheels spin forward.
  • To go backwards, all wheels spin backwards.
  • To go right . the two left wheels spin forward, and the two right wheels spin backwards.
  • To go left . the two right wheels spin forward, and the two left wheels spin backwards.

I tried to savage as much as possible so I sawed the original circuit, removing the rc parts.

I obtained three pieces of the pcb, one composed of 4 relays that control the 4 motors.

The other two parts are typical transistors H bridges, that control the boom and the arm-shovel motors.

The PCB was literally sawed so many conductive tracks were severed and had to be rebuilt later soldering cables.

This requires some knowledge in the electronics involved, some skill and be willing to spend some time.

Any mistake, a wrong connection, a shortcircuit... could destroy your RPi once connected.

In case you choose to do this, you must test it thoroughly before conecting the hacked pieces of circuitry to the RPi.

You'll need to do extensive testing with a multimeter, and simulating the RPi signals with a 3v battery pack.

Maybe the most simple, safest and straightforward way to control the motors is using 2 sets of 4 relay modules.

They're cheap and safe to use as most of the them are opto-isolated.

*I didn't review (yet) the circuit sketches in the pic, so review them, if you plan to build anything with them.

Step 5: Webiopi and Control Software (1)

We need to set up our Pi to use Python to communicate with the GPIO pins.

Install python packages

sudo apt-get update<br>sudo apt-get install python-dev
sudo apt-get install python-rpi.gpio

Installing the WebIOPi framework


Webiopi is the ready-made framework that allow us to remotely handle our GPIO's, Serial, I2C, SPI, 1-Wire...

Works almost out of the box, safe, without any special knowledge, for the client there's no need to use any additional software, just a web browser.

It is not available yet in the raspbian repositories. But but installing it is quite easy.

$ cd ~

Getting the file:

$ wget <a href="http://sourceforge.net/projects/webiopi/files/WebIOPi-0.7.0.tar.gz" rel="nofollow">  http://sourceforge.net/projects/webiopi/files/Web...> 

When finished, extract it:

$ tar -xvzf WebIOPi-0.7.0.tar.gz 

Enter the directory

$ cd WebIOPi-0.7.0/

Run the setup script as root:

$ sudo ./setup.sh 

It could take a long time. Answer YES to all dependencies.

Once finished test it

$ sudo webiopi -d -c /etc/webiopi/config

If you find this error

AttributeError: 'module' object has no attribute 'GPIO'

You can solve i following the instructions here:


In the webiopi-0.7.X directory is the /python directory:
1.CD into /python/native/ Edit the file: cpuinfo.c change "BCM2708" to "BCM2709"
2.While still in python/native/ Edit the file gpio.c change #define BCM2708_PERI_BASE 0x20000000 to #define BCM2708_PERI_BASE 0x3f000000
3.Head back out to your root webiopi-0.7.X directory and run setup.sh again.

You can easily start it

$ sudo /etc/init.d/webiopi start

Now open a browser, check what it is your local IP.

Default user is webiopi

Default password is raspberry

To stop it

$ sudo /etc/init.d/webiopi stop

If we wish to start webiopi when starting the Rpi

$ sudo update-rc.d webiopi defaults

Recommended sites:


I specially recommend this site. In Spanish, but very clear and with a lot of comprehensible information.


Step 6: Webiopi and Control Software (2)

Finally to use our toy we need to create or configure some files.

Create a folder

$mkdir /home/pi/cambot

Get into that folder

$cd /home/pi/cambot

FILE 1. stream.sh

Copy this script. We will name it stream.sh. It is useful to run our video streaming in the background. we can change the device, resolution and frame rate.

HTTP_PORT=8001# check for existing webcam device
if [ ! -e "/dev/video0" ]; then
  echo "stream.sh: Error - NO /dev/video0 device" 2>&1 | logger
  exit 2


$STREAMER -i "$PLUGINPATH/input_uvc.so -y -n -d $DEVICE -r $RESOLUTION -f $FRAMERATE" -o "$PLUGINPATH/output_http.so -n -p $HTTP_PORT" -b

FILE 2. cambot.py

Create a python file called cambot.py

import webiopi

# Libreria GPIO
GPIO = webiopi.GPIO

# -------------------------------------------------- #
# Definición constantes                              #
# -------------------------------------------------- #

# GPIOs motores DERECHA
L1=22  #  Derecha-adelante
L2=23  # Derecha-atras

R1=13 # izquierda-adelante
R2=19 #izquierda-atras

# GPIOs motor pluma
B1=25 # arriba
B2=26 # abajo

# GPIOs motor brazo
P1=24 # arriba
P2=16 # abajo

# -------------------------------------------------- #
# Funciones motores derecho                          #
# -------------------------------------------------- #
def right_stop():
    GPIO.output(L1, GPIO.LOW)
    GPIO.output(L2, GPIO.LOW)
def right_forward():
    GPIO.output(L1, GPIO.HIGH)
    GPIO.output(L2, GPIO.LOW)
def right_backward():
    GPIO.output(L1, GPIO.LOW)
    GPIO.output(L2, GPIO.HIGH)

# -------------------------------------------------- #
# Funciones motor izquierdo                          #
# -------------------------------------------------- #

def left_stop():
    GPIO.output(R1, GPIO.LOW)
    GPIO.output(R2, GPIO.LOW)

def left_forward():
    GPIO.output(R1, GPIO.HIGH)
    GPIO.output(R2, GPIO.LOW)

def left_backward():
    GPIO.output(R1, GPIO.LOW)
    GPIO.output(R2, GPIO.HIGH)
# -------------------------------------------------- #
# Funciones motor pluma                              #
# -------------------------------------------------- #

def pluma_stop():
    GPIO.output(B1, GPIO.LOW)
    GPIO.output(B2, GPIO.LOW)

def pluma_sube():
    GPIO.output(B1, GPIO.HIGH)
    GPIO.output(B2, GPIO.LOW)
def pluma_baja():
    GPIO.output(B1, GPIO.LOW)
    GPIO.output(B2, GPIO.HIGH)¡

# -------------------------------------------------- #
# Funciones motor brazo                              #
# -------------------------------------------------- #

def brazo_stop():
    GPIO.output(P1, GPIO.LOW)
    GPIO.output(P2, GPIO.LOW)

def brazo_sube():
    GPIO.output(P1, GPIO.LOW)
    GPIO.output(P2, GPIO.HIGH)
def brazo_baja():
    GPIO.output(P1, GPIO.HIGH)
    GPIO.output(P2, GPIO.LOW)   

# -------------------------------------------------- #
# Definición macros                               #
# -------------------------------------------------- #

def go_forward(): 
def go_backward():
def turn_left():
def turn_right():
def stop():
def plumaarriba():
def plumaabajo():
def brazoarriba():
def brazoabajo():
def brazoplumastop():

# -------------------------------------------------- #
# Inicialización                                     #
# -------------------------------------------------- #

def setup():
# Instalacion GPIOs
    GPIO.setFunction(L1, GPIO.OUT)
    GPIO.setFunction(L2, GPIO.OUT)

    GPIO.setFunction(R1, GPIO.OUT)
    GPIO.setFunction(R2, GPIO.OUT)
    GPIO.setFunction(B1, GPIO.OUT)
    GPIO.setFunction(B2, GPIO.OUT)
    GPIO.setFunction(P1, GPIO.OUT)
    GPIO.setFunction(P2, GPIO.OUT)

def destroy():
# Resetea las funciones GPIO
    GPIO.setFunction(L1, GPIO.IN)
    GPIO.setFunction(L2, GPIO.IN)

    GPIO.setFunction(R1, GPIO.IN)
    GPIO.setFunction(R2, GPIO.IN)

    GPIO.setFunction(B1, GPIO.IN)
    GPIO.setFunction(B2, GPIO.IN)

    GPIO.setFunction(P1, GPIO.IN)
    GPIO.setFunction(P2, GPIO.IN)

In this script we'll define which gpio pins will be used. And the macros that will be used by another file...

The configuration is simple. We initialize the gpio pins using OUT, and we change the state for the different movements using HIGH or LOW. I used some words in Spanish. brazo= arm, pluma=boom

Step 7: Webiopi and Control Software (3)

We want this script (cambot.py) to be called when initializing the webiopi server.

We must specify it in the configuration file located at /etc/webiopi/config in the SCRIPTS section.

FILE3. /etc/webiopi/config

# Load custom scripts syntax : # name = sourcefile # each sourcefile may have setup, loop and destroy functions and macros #myscript = /home/pi/webiopi/examples/scripts/macros/script.py cambot = /home/pi/cambot/cambot.py

In the same file, we'll change the passwd-file, doc-root and index.file locations.

# HTTP Server configuration enabled = true port = 8000 # File containing sha256(base64("user:password")) # Use webiopi-passwd command to generate it passwd-file = /etc/webiopi/passwd # Use doc-root to change default HTML and resource files location #doc-root = /home/pi/webiopi/examples/scripts/macros doc-root = /home/pi/cambot # Use welcome-file to change the default "Welcome" file welcome-file = index.html

FILE4. Index.html

After that we need to generate the psswd file, so run the webiopi-passwd command.

$ sudo webiopi-passwd
WebIOPi passwd file generator Enter Login: webiopi Enter Password: Confirm password: Hash: f576340c89121g9gf5673c4b1a7d6g04bv67c7894a456ce370fe1bf2v5c25798 Saved to /etc/webiopi/passwd

Finally we create the file index.html
In the location specified before. The controls are created using jQuery and the javascript functions to call the macros in the python script The video streaming It is also embeded using the [IMG] tag. Copy the file (index.html), remove the parentheses.

Step 8: Final Steps

There's another optional script, as a failsafe in case the wifi conection becomes too weak.

If you're planning to use your vehicle near the wifi source, there's no problem, but if you're using it in the outside and the vehicle looses the signal, it will continue doing was it was doing at that moment, with no possibility to stop it.

This pyhton script tests the signal and if the Quality level becomes lower than 50 ( value taken from /proc/net/wireless) stops the webiopi server. You must see what's the apropiate value in your case. So that you can restart webiopi manuallly and put your vehicle into safety or leave it where it is. I called it deten.py It uses less that 1% CPU.

Beware, I'm not a programmer but I had to create this script myself as I couldn't find anything to do the job, so ...

Anyway as far as I tested it runs ok, and does the job. We'll run it in the background as we'll see later.

FILE5. deten.py

#!/usr/bin/python<br># Script seguridad en caso de perdida señal wifi. As a failsafe in case of signal loss #
#Ajustar el valor según necesidad(defecto a 50).Adjust the value to your needs(default 50)#  
#No me responsabilizo de su uso.Disclaimer.No guarantee at all #
# C. BAHILLO                                                   #
from subprocess import PIPE
import subprocess
import time
pro = subprocess.Popen("cat /proc/net/wireless", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
data = pro.communicate()[0].split()
valor = int(float(data[0:][30]))

while True: 

 if valor < 50:
     subprocess.Popen("sudo /etc/init.d/webiopi stop", shell=True

There are many ways to initialize all of this ( the video streaming, the webiopi server, failsafe script..).

It can be automated so that when booting our Rpi, the webiopi server starts too.

$ sudo update-rc.d webiopi defaults

If you change your mind...

$ sudo update-rc.d webiopi remove

But I like to use it launching a tiny script I called inicia.sh So I log into the RPi using shh and launch it.

FILE6. inicia.sh

#!/bin/bash<br>/bin/bash streamer.sh 
sudo /etc/init.d/webiopi start
python3 deten.py &

We place ALL these files into the Cambot directory

We go there

$ cd /home/pi/Cambot


 $ sh inicia.sh

Step 9: LET'S PLAY

Step 10: Next Challenge: Controlling the Vehicle in Murguía,Spain From Kentucky, USA

In a few days a friend will try to control the toy excavator from 6500km away.

Now charging, prepairing for the event. It is quite easy to recharge, with a car battery charger(old edited later).

The results were better than expected!!!

The user was in Kenctucky, using a web browser in a PC.

The vehicle was in Alava, Spain, 6500km away, connected through a wifi router.

The average lag for the ouput response was about 3 seconds.

Sometimes the vehicle got freezed and didn't respond at the first or second input from the user.

Sometimes it continued running for several seconds, despite the user was trying to stop it ( here the script deten.py is a must if the vehicle is alone).

But most of the time it worked fine!!!!!!!!!!!!

The video is horrible, only a toy excavator moving, just for fun, just a "souvenir" for my friend Ritchie :D
The first man in the US to control a toy excavator in Spain LOL.

Ritchie, thanks for being my testing operator!

Robotics Contest 2016

Participated in the
Robotics Contest 2016

Make it Move Contest 2016

Participated in the
Make it Move Contest 2016

Be the First to Share


    • Make it Glow Contest

      Make it Glow Contest
    • First Time Author Contest

      First Time Author Contest
    • PCB Challenge

      PCB Challenge

    13 Discussions


    4 years ago

    Very cool project. Thanks for sharing. I tried to look at the index . html code, but received "Request forbidden by administrative rules" error both in Chrome and Firefox. Any chance you can send the file to al5322 "at" outlook "dot" com?


    Reply 4 years ago

    Copy the content of the post above and save it as index.html


    Reply 4 years ago

    I copied it here.


    4 years ago

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" />
    <script type="text/javascript" src="/webiopi.js"></script>
    <script type="text/javascript">
    function init() {
    var button;

    button = webiopi().createButton("bt_up", "/\\", go_forward, stop);

    button = webiopi().createButton("bt_left", "<", turn_left, stop);

    button = webiopi().createButton("bt_stop", "X", stop);

    button = webiopi().createButton("bt_right", ">", turn_right, stop);

    button = webiopi().createButton("bt_down", "\\/", go_backward, stop);

    button = webiopi().createButton("pl_ar", "PA", plumaarriba,brazopalastop);

    button = webiopi().createButton("pl_ab", "PB", plumaabajo,brazopalastop);

    button = webiopi().createButton("br_ar", "BA", brazoarriba,brazopalastop);

    button = webiopi().createButton("br_ab", "BB", brazoabajo,brazopalastop);

    button = webiopi().createButton("bp_stop", "S", brazoplumastop);

    function go_forward() {

    function go_backward() {

    function turn_right() {

    function turn_left() {

    function stop() {

    function plumaarriba() {

    function plumaabajo() {

    function brazoarriba() {

    function brazoabajo() {

    function brazoplumastop() {


    <style type="text/css">
    button {
    margin: 5px 5px 5px 5px;
    width: 50px;
    height: 50px;
    font-size: 24pt;
    font-weight: bold;
    color: black;
    <div id="content" align="center">
    <img width="640" height="480" src="http://YOUR.IP.HERE:8001/?action=stream"><br/>
    <div id="up"></div>
    <div id="middle"></div>
    <div id="down"></div>
    <div id="left"></div>
    <div id="right"></div>
    <div id="top-down"></div>


    4 years ago

    hey can't you use arduino


    Reply 4 years ago

    hey bahi can you give your email id


    Reply 4 years ago

    Probably, using arduino yun, not sure about using arduino uno even with a wifi shield. At least not with the same features.


    4 years ago

    Thanks for inviting me to participate in this project. It was a lot of fun and very interesting.


    Reply 4 years ago

    Thanks to you my friend.


    4 years ago

    Very nice project! I will try it! thanks.


    Reply 4 years ago

    Thanks.You're welcome!!

    I had never tried something like this before.

    It was easier than I expected. Carry on!


    4 years ago

    hey man this is awesome......

    i like it . i only vote for you


    Reply 4 years ago

    Oh, thank you so much!

    I'm very happy you liked it!