Dojo Derby Mark 3 – Basic Version (DRAFT)


The Dojo Derby Mark 3 vehicle is the first standardized vehicle design for Dojo Derby. All the parts can be purchased from Amazon and can be assembled relatively easily. The code for this version is the basic required to run a Dojo Derby Vehicle. Improvements and upgrades can be found in follow on projects.

Understanding Power and Battery Utilization

For standard Dojo Derby Vehicles we use USB battery packs and power the motors from the 5V pins on the Raspberry Pi board. The reason for this is that standard Lithium Ion Batteries can be dangerous. If you short a Lithium Ion Battery it can violently explode, and if you touch the positive and negative terminals with your skin you can hurt yourself severely.

USB Battery packs contain anti-short circuitry so that the battery pack will simply turn itself off if there is a short. The 5V pin max’s at 2 amps output and should be safe and not cause injury.

Electricity can always be dangerous, so do not assume a USB Battery is “safe”.

Understanding the Physical Build

Raspberry Pi 4 B with 4GB of RAM is the standard compute system that we use. This gives more than enough computational power for our projects and has full wifi and bluetooth stacks. Using the version with 4GB of RAM gives extra wiggle room for inefficient code. 1 GB versions will work, but you may have to be concerned about optimizing code so that there are not lockups or glitches.

  • Raspberry Pi 4 B with 4GB RAM –
  • MicroSD (Suggest 64GB)
  • USB C Power Supply
  • Micro HDMI to HDMI Cable

This vehicle is based on the FeeTech FT-MC-002-SMC Chassis. This is a small, lightweight, and versatile aluminum chassis. This product was selected because it is easy to purchase and the layout is very functional for our needs. When we experimented with other vehicle bodies we found the weight and size of the bodies made them difficult to maneuver with the small motors.

  • FeeTech Body –

For motors we use generic the DC Motor in Servo Body. These are small 4-6V motors that fit the FeeTech Chassis. We use 4 for our Derby Vehicles. We have found 3 wheel vehicles are needlessly difficult to control.

We then use standard 60mm wheels for the wheels. These wheels give the vehicle enough traction, but since they are thin do not cause undo friction when making turns.

  • DC Motor in Servo Body –
  • 60mm Wheels –

For the H Bridge Motor Driver we use an L9110S Drive Module. This module supports 2.5-12V and is significantly smaller than the standard H Bridges that are generally used

  • L9110S H Bridge –

The OLED Screen is a .96″ i2c 128×64 module.

  • 128×64 OLED –

For the camera we use an OV5647 1080p ArduCam Camera. For our uses this works as well as the official Pi camera and only costs around $10.

To attach the camera you will need a longer ribbon cable than what comes with the camera. I suggest buying a kit with a few lengths to find what suits your vehicle.

  • OV5647 ArduCam –
  • Ribbon Cable Kit –×2pcs-30cm×2pcs-50cm×2pcs/dp/B089LM5D1T/

Beyond the major components you’ll need wires, a small breadboard and such to build the vehicle.

  • Small Breadboard –
  • Female – Female Wires –
  • Male – Female Wires –
  • Nylon Spacers and Screws –
  • Brass Spacers and Screws –
  • USB A to C Cable –
  • Super Glue

Understanding the Code

To understand the code it’s best to first designate the subsystems for this project.

The first component is the web based user interface. This PHP script shows the video stream, and allows users to click on HTML links to move the vehicle and change the speed. The links are GET links that direct to PHP scripts on the vehicle. The PHP scripts take the $_GET value and save them to text files.

The Video Steam is provided by a Python script that auto starts when the vehicle boots up.

The wheels are controlled by a Python script that starts when the vehicle boots. The script continuously loops and based off of the commands it finds in the text files it triggers the wheels to move using the GPIO pins.

The OLED Display also has a script that starts when the vehicle boots. The script loops to see what the current SSD, IP Address and commands are.

Understanding the OS Setup

For this vehicle we will need to use an older version of the Raspberry Pi OS. The latest version got rid of camera functionality that we need. In the raspberry Pi Imager software choose “Other OS” and install the Legacy version. When you boot you should see a background of the temples in Myanmar.

When setting up the OS make sure to set the Operating System to auto login.

Update and Upgrade the OS so all of the repositories are updated.

We will then install Apache 2 and PHP.

Then for testing purposes I would recommend setting the permission to /var/www/html to 777 and set permissions to all files in the folder to 777. This is poor security, but for test and build purposes will save you aggravation.

Copy the python script to the /var/www/html directory. Then set the script to auto run by editing the /etc/rc.local file.

Copy the script to /var/www/html and set the script to auto run by editing /etc/rc.local

Install the Adafruit OLED library in PIP3. Copy to the /var/www/html directory and set the script to auto run in the /etc/rc.local file.


I find using my normal computer with VScode is the easiest way to write and edit scripts. By installing SAMBA on the Pi and then setting the share directory as /var/www/html this allows me to directly edit and save files to the Pi from my regular computer. Again this is bad for security, but makes testing easier.

Code and Explanation

The code can be found at GitHub at:

This is the core python script that runs the vehicle. This script reads commands from text files and then tuns on the GPIO pins based off of the values.

Line 1 – Import GPIO library as GPIO

Line 3 – Set board mode to Board – This means the numbers for the GPIO pins will correspond to the numbers inside the circles on a standard Pi diagram

Lines 5-8 – Set GPIO pins to control wheels to out

Lines 10 – 13 – Set GPIO pins for PWM (Pulse Width Modulation) at 100 Hertz. (Note: This might not be the best setting.)

Lines 15-17 – Sets the value to stop in command.txt immediately. If not done the last command that was issued before the vehicle was powered off will automatically be used. (A good way to have your vehicle drive off a desk)

Line 19 – Starts the loop. This loop will run continuously so that the vehicle will move. You will add this script to the /etc/rc.local file so that the script starts when the vehicle boots, and then runs while it is on.

Lines 21-25 – Opens the speed.txt file and takes the value and sets the speedCommand variable value to it. Also prints out the value to the terminal so that you can troubleshoot to make sure the file is being read. At this point make sure that the file permission for speed.txt is set to 777 so that this script has permission to read it.

Lines 27-35 – Take the value from the speed.txt file and depending on the value set the speed variable. “slow” is at 40% max speed, “medium” is 50%, and “fast” is 100%. You can adjust these values based on your vehicle performance and what you prefer. (Note: This is a bit basic and clunky. Would be good to update this to a way of increasing or decreasing by 10’s)

Lines 37-41 – Opens the command.txt file and uses it to set the value of command variable. Also prints out value to terminal for troubleshooting purposes. At this point set the permissions to command.txt to 777 so that the script can read the file.

Lines 43 – 85 – Series of if/elif/else statements based on the value of command variable. For forwardLeft and forwardRight we use speed*.1 to make one side continue moving, but not as fast as the other. This means the vehicle will turn more slowly as it continues to move forward and mimics more of how a car steers, vs. full left or right which has the vehicle pivot like a tank. for forwardLeft and forwardRight you may want to tweek the math for the performance you prefer.


  • Make sure you’re using the right pin numbers. On the pin diagram use the numbers inside the circle.
  • Verify that the permissions for speed.txt and command.txt are set at 777.
  • You can open speed.txt and command.txt files directly with a text editor or VScode and change/ save the settings. Start this script at the command line with python3 and then verify the output is what is in the text files.
  • Make sure that the speed.php and command.php scripts are writing the right commands to the text files. With the if/elif/else statements if the value is anything other than what is looked for the script won’t trigger a command. (Sometimes you might add extra whitespaces when you write to a file)

import RPi.GPIO as GPIO GPIO.setmode(GPIO.BOARD) GPIO.setup(35, GPIO.OUT) GPIO.setup(36, GPIO.OUT) GPIO.setup(37, GPIO.OUT) GPIO.setup(38, GPIO.OUT) lf = GPIO.PWM(35,100) lb = GPIO.PWM(36,100) rf = GPIO.PWM(37,100) rb = GPIO.PWM(38,100) file = open("/var/www/html/command.txt","w") file.write("stop") file.close() while True: file = open("/var/www/html/speed.txt","r") print("from Speed: ") speedCommand = print(speedCommand) file.close() if speedCommand == "slow": speed = 40 elif speedCommand == "medium": speed = 50 elif speedCommand == "fast": speed = 100 else: speed = 0 print("Speed Error") file = open("/var/www/html/command.txt","r") print("from Command: ") command = print(command) file.close() if command == "forward": lf.start(speed) lb.start(0) rf.start(speed) rb.start(0) elif command == "forwardLeft": rf.start(speed) rb.start(0) lf.start(speed*.1) lb.start(0) elif command == "forwardRight": rf.start(speed*.1) rb.start(0) lf.start(speed) lb.start(0) elif command == "backward": rf.start(0) rb.start(speed) lf.start(0) lb.start(speed) elif command == "left": lf.start(0) lb.start(speed) rf.start(speed) rb.start(0) elif command == "right": lf.start(speed) lb.start(0) rf.start(0) rb.start(speed) elif command == "stop": lf.start(0) lb.start(0) rf.start(0) rb.start(0) else: print ("Error")
Code language: PHP (php)

This is the script that takes the camera output and turns it into a web page that we will embed onto the index.php file. This script is relatively cut and paste from .

Make sure to enable the Camera in the Raspberry Pi settings and then reboot the Pi.

Of interest:

Line 11 – Sets how large the video will be displayed on the page.

Line 76 – Sets resolution to 320×240 with a frame rate of 24 fps. This is very low resolution, but has very low lag for viewing on the web browser. At higher resolutions the response rate takes a dramatic hit. Tweak as you see fit, but just remember that the lower the quality of video the higher the speed of the feed.

Line 80 – Sets port to 8000. So to view this feed you would go to in the web browser (or the IP address of the vehicle at port 8000)


  • Make sure your PC isn’t blocking port 8000.
  • Some web browsers handle this script better than others. I prefer using Chrome for driving the vehicle.
  • Resolution and framerate will effect how quickly the video is delivered to your browser.
import io import picamera import logging import socketserver from threading import Condition from http import server PAGE="""\ <html> <body> <img src="stream.mjpg" width="640" height="480" /> </body> </html> """ class StreamingOutput(object): def __init__(self): self.frame = None self.buffer = io.BytesIO() self.condition = Condition() def write(self, buf): if buf.startswith(b'\xff\xd8'): # New frame, copy the existing buffer's content and notify all # clients it's available self.buffer.truncate() with self.condition: self.frame = self.buffer.getvalue() self.condition.notify_all() return self.buffer.write(buf) class StreamingHandler(server.BaseHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(301) self.send_header('Location', '/index.html') self.end_headers() elif self.path == '/index.html': content = PAGE.encode('utf-8') self.send_response(200) self.send_header('Content-Type', 'text/html') self.send_header('Content-Length', len(content)) self.end_headers() self.wfile.write(content) elif self.path == '/stream.mjpg': self.send_response(200) self.send_header('Age', 0) self.send_header('Cache-Control', 'no-cache, private') self.send_header('Pragma', 'no-cache') self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME') self.end_headers() try: while True: with output.condition: output.condition.wait() frame = output.frame self.wfile.write(b'--FRAME\r\n') self.send_header('Content-Type', 'image/jpeg') self.send_header('Content-Length', len(frame)) self.end_headers() self.wfile.write(frame) self.wfile.write(b'\r\n') except Exception as e: logging.warning( 'Removed streaming client %s: %s', self.client_address, str(e)) else: self.send_error(404) self.end_headers() class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): allow_reuse_address = True daemon_threads = True with picamera.PiCamera(resolution='320x240', framerate=24) as camera: output = StreamingOutput() camera.start_recording(output, format='mjpeg') try: address = ('', 8000) server = StreamingServer(address, StreamingHandler) server.serve_forever() finally: camera.stop_recording()
Code language: HTML, XML (xml)


This script will be embedded on index.php. The first portion is the PHP script that writes as value to the command.txt file. The second half is the HTML form that has hyperlinks with GET values to pass to the PHP script.

Line 1 – Open PHP script

Line 3 – Take the GET value and assign it to the variable $command

Line 4 – Overwrite command.txt with the value of $command

Line 5 – Print the value of $command to the HTML screen for troubleshooting purposes

Line 8 – Create a DIV in HTML to center the control panel and set a size

Line 9 – Open a TABLE. We use an HTML table so that the control panel is formatted like a control pad.

Lines 10 – 13 – Create Hyperlinks with TABLE cells with values for command (command=left or command=right) that are sent to the command.php script (the top of this script)

Lines 14-15 – Close the TABLE and DIV

<?php $command=$_GET['command']; file_put_contents("command.txt", $command); print "Command: ".$command."<br>"; ?> <div style="width:170; height:100; margin-left:auto; margin-right:auto;"> <table> <tr><td><a href="controls.php?command=forwardLeft">F Left</a></td><td><a href="controls.php?command=forward">Forward</a></td><td><a href="controls.php?command=forwardRight">F Right</a></td></tr> <tr><td><a href="controls.php?command=left">Left</a></td><td style="text-align:center;"><a href="controls.php?command=stop">Stop</a></td><td><a href="controls.php?command=right">Right</a></td></tr> <tr><td></td><td><a href="controls.php?command=backward">Backward</a> </td><td></td></tr> </table> </div>
Code language: HTML, XML (xml)


This script will be embedded on index.php. The first portion of the script saves the speed value to speed.txt. The second portion is simply hyperlinks with GET values.

Line 1 – Opens PHP script

Line 3 – Takes the GET value and assigns it to the $speed variable

Line 4 – Overwrites speed.txt with the value of $speed

Line 5 – Prints out the value of $speed on the HTML page for troubleshooting purposes.

Line 6 – Closes the PHP script

Lines 8-10 – Hyperlinks with GET values for slow, medium and fast. These values are sent to speed.php which is the top of this script.

<?php $speed=$_GET['speed']; file_put_contents("speed.txt", $speed); print "Speed: ".$speed."<br>"; ?> <a href="speed.php?speed=slow">Slow</a> <a href="speed.php?speed=medium">Medium</a> <a href="speed.php?speed=fast">Fast</a>
Code language: HTML, XML (xml)


The index.php combines, controls.php and speed.php into a single page with IFRAMES so that you can drive the vehicle.

Line 1 – Gives the page a title

Line 3 – Opens PHP script

Line 4 – Gets the IP Address of the vehicle. This is needed to embed the video stream.

Lines 6 – 16 – We dynamically write the HTML for the iframes so that we can add the IP Address of the vehicle to the video iframe.

Lines 6 – 8 – Creates DIV and IFRAME for video.

Line 7 – Creates IFRAME and sets the value to the IP Address of the vehicle at port 8000.

Line 9 – Break Line

Lines 10 – 13 – Creates a DIV an IFRAME for controls.php

Line 13 – Break Line

Lines 14 – 16 – Creates DIV and IFRAME for speed.php

Line 18 – Close PHP script.

<h1>WiFi Car App</h1> <?php $ip = $_SERVER['SERVER_ADDR']; echo "<div style='width:640; height:480; margin-left:auto; margin-right:auto;'>"; echo "<iframe src='http://".$ip.":8000' height=480 width=640></iframe>"; echo "</div>"; echo "<br>"; echo "<div style='width:250; height:100; margin-left:auto; margin-right:auto;'>"; echo "<iframe src='controls.php' height=100 width=250></iframe>"; echo "</div>"; echo "<br>"; echo "<div style='width:175; height:60; margin-left:auto; margin-right:auto;'>"; echo "<iframe src='speed.php' height=60 width=175></iframe>"; echo "</div>"; ?>
Code language: HTML, XML (xml)

Load Scripts on Boot

For the vehicle to function the python scripts have to be running. It is easiest if you have the scripts autostart when the Pi boots up. We have added the scripts to the /etc/rc.local file for this. You can edit this file with sudo nano /etc/rc.local

Line 16 – 17 – use sudo to run scripts as root and python3 to run in the python3 enviornment. At the end add a & to signify the scripts will continuously run


  • Typos will be your biggest issue, make sure you typed the path to the scripts correctly.
  • When doing troubleshooting and testing you may want to remove the scripts from the startup routine. If you modify a script and then run it the terminal, or Thony, you may end up running your modified script at the same time the original script is running and you could have odd problems.

# This script is executed at the end of each multiuser runlevel. # Make sure that the script will "exit 0" on success or any other # value on error. # # In order to enable or disable this script just change the execution # bits. # # By default this script does nothing. # Print the IP address _IP=$(hostname -I) || true if [ "$_IP" ]; then printf "My IP address is %s\n" "$_IP" fi sudo python3 /var/www/html/ & sudo python3 /var/www/html/ & exit 0
Code language: PHP (php)