Skip to the content.

HTML Controlled Mini Tank Rover

For my Bluestamp Engineering project, I decided to make a Mini Tank Rover. The base project was very easy: assemble the main frame using unclear instructions, attach the motors to the back wheels, and connect the motors to the Arduino R3 Board using H-bridges so that one could control the movement of the rover when the Arduino was plugged into the computer. Once I was done with this part, I could modify the rover as much as I wanted. Thus, I decided to add a Raspberry Pi with a Picamera and a phone battery so that I could control the rover from far away, view its surroundings, and let it run without needing to have it plugged into an outlet. I even made the Picamera footage stream to an HTML website where I could also control the direction the rover was moving.

Engineer School Area of Interest Grade
Luna S. Hunter College High School Mechanical Engineering Incoming Sophomore

Book logo

Final Milestone

For my third milestone, I got the buttons to work so that I could control the rover’s movements and stream the picamera footage from the same HTML website. In the code for the HTML website in Thonny, I added an onclick sendCommand function to each button so that when I pressed a button, the VNC viewer would tell the Arduino to make the rover move in a specific direction. However, I ran into trouble because I couldn’t get the rover to stop. Rather than create a stop button, I decided to make the default setting for the rover to stop. I wanted the buttons going in different directions would overwrite the stop command and make the rover move, but the moment I stopped pressing on the buttons, for the rover to stop moving. It didn’t work out as intended because I didn’t know what to write for stop to become the default setting. Eventually, I added a onmouseleave sendCommand(‘Stop’) after the onclick sendCommand for each button so that the moment the mouse stopped hovering over the button, the rover would stop. Now, I had working buttons on my HTML website and I could make my rover move and stop whenever I wanted. My phone battery had also arrived so I was able to get the rover working without having to keep it plugged into an outlet.

In the future, I might add a laser pointed to the rover for my cat to chase around. Overall, this was a really fun project and I learned a lot about HTML and Raspberry Pis since I had never worked with either in the past. I’m so happy that I finally completeted my rover and I can’t wait to terrorize my cat with it!

Second Milestone

For my second milestone, I incorporated a Raspberry Pi into my project. In order to be able to give the Arduino Board directions wirelessly from my computer, I needed a Raspberry Pi to act as a bridge between my computer and Arduino Board. The Raspberry Pi, which connected to my computer via the internet, would easily be able to receive directions from my computer wirelessly. So, I set up my Raspberry Pi and plugged it into the Arduino Board, making sure they could communicate between themselves. I coded for the Raspberry Pi using Thonny in VNC Viewer and connected it to the Arduino IDE. Pretty soon, I was able to send an instruction on which direction to move into my Raspberry Pi which would pass that on to the Arduino IDE and my mini tank rover would move in the desired direction. However, the Raspberry Pi still had to be plugged into an outlet for power and this energy would end up powering my entire rover. My phone battery hasn’t arrived yet, but once it does, I hope to be able to power the Raspberry Pi and rover with it.

I also set up a Picamera2 so that I would be able to view my rover’s surroundings in order to control the rover’s movements from far away. I sourced code from the internet to be able to stream the camera footage on an HTML website. I even added buttons for forward, left, right, and backward on the website, but they do not work yet. For my third milestone, I would like to get the buttons working so that I can control the rover from a single server instead of going back and forth between the VNC Viewer and an HTML website.

First Milestone

For my first milestone, I began by building my chassis by screwing 2 motors onto the back of the frame and attaching the back wheels to those motors. I then attached the front wheels to the frame by passing a screw through the hole and securing them to the side of my main frame using nuts. I then attached the treads to both wheels. Though the original tread was 50 pieces long, I removed six pieces from each tread to make two treads with 44 pieces. Once I could control the movement of the car, I would be able to move the car forward and backwards by using the back wheels to move the front wheels.

After building the car, I attached the H-bridge to the motors using the wires that came attached to the motors and screwed them into the H-bridge. I then used dupont wires to attach the H-bridge to my Arduino R3 Board so that I could control the movement of the motors and power them. For this, I coded what is shown below in Arduino IDE. AIA and AIB stood for Input A and B for Motor A (the motor on the right wheel) and BIA and BIB stood for Input A and B for Motor B (the motor on the left wheel). I made AIA and BIA move to make the car go backwards and AIB and BIB to make the car move forwards. To go right, I had the right wheel go backwards and the left wheel go forward and vice versa to move the car to the left.

With this, I was able to make my car go forwards, backwards, left, and right when the Arduino was plugged into my computer and using my computer as a power source. Though I could not spontaneously change the direction of the rover, it was able to follow a set of directions in the Arduino IDE and loop through the commands of moving forward for 3 seconds, backwards for 3 seconds, right for 3 seconds, and left for 3 seconds.

However, this was when I ran into my first problem. The front wheels would unscrew themselves as they ran and were not parallel to the main frame, preventing the rover from going straight. I solved this problem by adding 2 more nuts to the screw so that the nuts would unscrew more slowly. Though the nuts would still unscrew themselves with time, the car ran much more smoothly. In order to fully fix the problem, I would have needed a new set of wheels and another frame. Another issue I encountered was that even after I had uploaded the code from the Arduino IDE to the Arduino R3 Board and used a 9V battery for a power source, the rover would not be able to move in more than one direction and would stall after just a few seconds. My instructor and I attributed this problem to the rover being too heavy so we decided to remove the treads. This did make the rover move for a much longer period of time. However, only 3V of the 5V from the battery were making it to the wheels and the car would eventually stall after 1 minute without being able to go in multiple directions. Believing a repressor was limiting the capabilities of the Arduino, I replaced the motors. However, this changed nothing so we ordered a phone battery, which would be much more powerful than a 9V battery, to use for my second milestone to see whether that would make a difference.

Schematics

Here’s where you’ll put images of your schematics. Tinkercad and Fritzing are both great resoruces to create professional schematic diagrams, though BSE recommends Tinkercad becuase it can be done easily and for free in the browser.

Code

Code for the Arduino IDE

//motor A input A for backward
const int AIA = 3;
//motor A input B for forward
const int AIB = 4;
//motor B input A for backward
const int BIA = 5;
//motor B input B for forward
cont inst BIB = 6;
char c = "";



void setup() {
  // put your setup code here, to run once:

  //set up variables representing the motors for output
  pinMode(AIB, OUTPUT);
  pinMode(AIA, OUTPUT);
  pinMode(BIB, OUTPUT);
  pinMode(BIA, OUTPUT);

  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:

  //set up the if statement for the switch case statement
  if (Serial.available()){
    c = (char)Serial.read();
    Serial.println(c);
  }

  //switch case statement to perform a single function under a single condition when multiple exist
  switch(c){
    //if letter "i" is inputed, the tank performs the forward action
    case 'i':
      forward();
      break;

    //if letter "k" is inputed, the tank performs the backward action
    case 'k':
      backward();
      break;

    //if letter "l" is inputed, the tank performs the right action
    case 'l':
      right();
      break;

    //if letter "j" is inputed, the tank performs the left action
    case 'j':
      left();
      break;

    //if letter "s" is inputed, the tank performs the stop action
    case 's':
      stop();
      break;
  }
}

//function to make tank go forward
void forward() {
  digitalWrite(AIB, HIGH);
  digitalWrite(AIA, LOW);
  digitalWrite(BIB, LOW);
  digitalWrite(BIA, HIGH);
}

//function to make tank go backward
void backward() {
  digitalWrite(AIB, LOW);
  digitalWrite(AIA, HIGH);
  digitalWrite(BIB, HIGH);
  digitalWrite(BIA, LOW);
}

//function to make tank go right
void right() {
  digitalWrite(AIB, HIGH);
  digitalWrite(AIA, LOW);
  digitalWrite(BIB, HIGH);
  digitalWrite(BIA, LOW);
}

//function to make tank go left
void left() {
  digitalWrite(AIB, LOW);
  digitalWrite(AIA, HIGH);
  digitalWrite(BIB, LOW);
  digitalWrite(BIA, HIGH);
}

//function to make tank stop
void stop() {
  digitalWrite(AIB, LOW);
  digitalWrite(AIA, LOW);
  digitalWrite(BIB, LOW);
  digitalWrite(BIA, LOW);
}

Code for the HTML Website and Camera Streaming

import io
import logging
import socketserver
import serial
from http import server
from threading import Condition

from picamera2 import Picamera2
from picamera2.encoders import JpegEncoder
from picamera2.outputs import FileOutput

PAGE = """\
<html>
<head>
<title>picamera2 stream</title>
<style>
    .controller {
        display: grid;
        grid-template-areas: 
             ". forward ."
            "left . right"
            ". backward .";
        gap: 5px;
        justify-items: center;
        align-items: center;
        height: 100px;
        width: 100px;
        margin: 0 auto;
        }
    .forward { grid-area: forward; }
    .left { grid-area: left; }
    .right { grid-area: right; }
    .backward { grid-area: backward; }
    </style>
</head>
<body>
<h1>Picamera2 stream</h1>
<img src="stream.mjpg" width="640" height="480" />
<div class="controller">
        <button class="forward" onclick="sendCommand('Forward')" onmouseleave="sendCommand('Stop')">Forward</button>
        <button class="left" onclick="sendCommand('Left')" onmouseleave="sendCommand('Stop')">Left</button>
        <button class="right" onclick="sendCommand('Right')" onmouseleave="sendCommand('Stop')">Right</button>
        <button class="backward" onclick="sendCommand('Backward')" onmouseleave="sendCommand('Stop')">Backward</button>
</div>
<script>
function sendCommand(command) {
    fetch('/command?direction=' + command);
}
</script>
</body>
</html>
"""

class StreamingOutput(io.BufferedIOBase):
    def __init__(self):
        self.frame = None
        self.condition = Condition()

    def write(self, buf):
        with self.condition:
            self.frame = buf
            self.condition.notify_all()


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))
        elif self.path.startswith('/command'):
            query = self.path.split('?')[1]
            params = dict(qc.split('=') for qc in query.split('&'))
            direction = params.get('direction', '')
            
            if direction == "Forward":
                ser.write(b"i\n")
                print(direction)
            elif direction == "Left":
                ser.write(b"j\n")
                print(direction)
            elif direction == "Backward":
                ser.write(b"k\n")
                print(direction)
            elif direction == "Right":
                ser.write(b"l\n")
                print(direction)
            elif direction == "Stop":
                ser.write(b"s\n")
                print(direction)
                
                
            # Send command to Arduino
            
            # if direction == "Forward":
            #     ser.write(b"i\n")
            # elif direction == "Left":
            #     ser.write(b"j\n")
            # elif direction == "Backward":
            #     ser.write(b"k\n")
            # elif direction == "Right":
            #     ser.write(b"l\n")
                #else:
                #    self.send_response(400)
                #    self.end_headers()
        # else:
        #     self.send_error(404)
        #     self.end_headers()


class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True


# Setup serial connection to Arduino
ser = serial.Serial('/dev/ttyACM0', 9600)  # Adjust the port name accordingly


picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration(main={"size": (640, 480)}))
output = StreamingOutput()
picam2.start_recording(JpegEncoder(), FileOutput(output))

try:
    address = ('', 7123)
    server = StreamingServer(address, StreamingHandler)
    server.serve_forever()
finally:
    picam2.stop_recording()
    ser.close()

Bill of Materials

Part Note Price Link
Tank Chassis Kit Main Build $20.99 Link
UNO R3 Board Told the Motors Which Direction to Spin In $16.99 Link
Electronics Kit Didn’t end up using this $15.99 Link
Motor 8-pack Turned the Back Wheels $10.99 Link
H-Bridge 5-pack Connected Motors to Arduino R3 Board $7.79 Link
Digital Multimeter Checked if the Arduino R3 Board Received Power $12.99 Link
9V Batttery Clip Snap DC Plug 5-pack I didn’t end up using them $5.99 Link
9V Battery 10-pack I didn’t end up using them either $8.88 Link
Raspberry Pi Kit Receives Input from the Computer $119.99 Link
Picamera2 Camera $8.99 Link
Phone Battery Powers the Rover $17.99 Link

Other Resources/Examples