Introduction: Counter-Strike Robot

About: These instructables are mostly about electronics. I hope you find them helpful!
DISCLAIMER: Don't do anything silly with this instructables because this is purely for ejudimucation and science.


It was November 2013. On Thanksgiving day, my cousin and my brother's friend came over and we were enjoying the break by playing Left 4 Dead and CS:GO.

My mom asked me if I wanted this metal rack from the disposable baking tine.

For some reason after hours of CS:GO and a metal rack I got an idea: What if we could play CS:GO in real life? With robots, of course. What if you could control a physical body using WASD, ctrl, spacebar, and all of these game controls?

For the next few months my brother and I worked on this robot. Since it was our first time building one of these, we were both trying different things to see what worked and what didn't.

The goal was to build a remote controlled robot (by remote I mean half way across the globe) that had a live camera feed and could be controlled using the typical keyboard game controls. So we decided to build something that used internet to control a tank-like machine that could fire pellets from an airsoft gun.

PS check out the original blog for hardware: nugget

Step 1: Stuff You Need to Gather

Now, first of all, I would like to defend myself.

This robot is a first: we never built anything like it before, and we have pretty much no experience doing this sort of thing, so a lot of it was guess and check, prototyping, and Thomas Edison style building.

SO, that being said, the material list below might not be totally complete; if you have any questions we appreciate them!

  • A motorized vehicle with differential (preferably) that has great torque.
  • MUCH plexiglass
  • 4 pairs of nuts and bolts
  • Zip ties
  • some tape
  • maybe some cardboard
  • some string
  • Airsoft pistol (preferably electric)
  • SG-5010 servo motor
  • SG-90 servo motor
  • 4 5v relays
  • 4 TIP31 NPN transistors
  • 6 1k resistors
  • 2 2n2222 NPN transistors
  • some PCB material
  • Rainbow bacon
  • Headers and wires
  • External 5vdc battery (Smartphone charger)
  • Raspberry Pi (in this we use Rev2)
  • An Arduino (optional)

Step 2: Dissassemble the Chassis

This is actually a pretty big step. Once you accomplish this step you are probably 20% done!

First, take off the cover, remove all screws, etc, until you see a circuit board.

In my case, the circuit board was broken, so I removed it, keeping note of possible wires to motors
If your vehicle works, you might consider using the already installed H-bridge. It will require a lot of research and reverse engineering but it's less costly!

After noting the wires to the motors (there should be two motors, so four wires), you should strip them and convert the wires to something more breadboard friendly if they are not.

Then, using a 9v battery (or another battery with the appropriate power for your motors), test to see if the motors run.
In my case, the motors were locked by some locking mechanism, which I had to remove.

If your motors are broken, then don't bother using the chassis unless you have a replacement.

You can also use the battery to determine the polarity of the motor, to know which way the motor turns clockwise or counterclockwise. mark this down with wire color or some method that you will remember.

Step 3: Trigger Pulling Mechanism

This part is subjective to the airsoft gun you have.

If you have a spring airsoft gun, you will need to find a way to cock it each time the gun fires. Also, you will probably need a stronger servo motor to pull the trigger.
Using an electric airsoft gun was optimal for us, because it was automatic and required little force to pull the trigger. The one we bought was $15, so that was cool.

Securing the safety

I used zip ties to secure down the safety trigger system on the gun. From now on the gun fires if there is a battery inside, so try not to load it with pellets or you might forget and "shoot your eye out."

Tying the servo

Us the single ended servo motor head on the SG90 and bring the servo to 120 degrees. When the servo is at 120 degrees, it should be pulling the trigger all the way back.

Tape overkill

In order to make sure stuff didn't shift around, I taped the gun with a whole lot of masking tape (so it doesn't leave residue).

After doing that, I taped on a piece of flat cardboard for the sake of the servo motor being on a stable surface.

Step 4: Gun Trigger Servo Testing

Using an Arduino, your probably want to test out your mechanism before moving on.

Attached are two short movies showing how the servo motors should work.

Also attached is the Arduino sketch which I used in those movies.

The servo motor pinout should be self explanatory:

RED - Vcc (minimum 5v)

BROWN - GND

YELLOW - PWM signal (minimum 3.3 v)

Testing your servo motor would also be good in determining if you put on the servo head correctly.

// Sweep
// by BARRAGAN <http://barraganstudio.com>
// This example code is in the public domain.


#include <Servo.h>

Servo myservo; // create servo object to control a servo
// a maximum of eight servo objects can be created

int pos = 0; // variable to store the servo position

void setup()
{
myservo.attach(9); // attaches the servo on pin 9 to the servo object
}


void loop()
{
for(pos = 0; pos < 120; pos += 1) // goes from 0 degrees to 180 degrees
{ // in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(5); // waits 15ms for the servo to reach the position
}
for(pos = 120; pos>=1; pos-=1) // goes from 180 degrees to 0 degrees
{
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(5); // waits 15ms for the servo to reach the position
}
}

Step 5: The Handle Plexiglass

We want the gun to be able to move up and down the the robot, right?

To accomplish this, I had to do some extreme googling to determine the servo motor with the right torque for the gun. It happens that the SG-5010 fit our budget, our power restraints, and torque needs.

The SG-5010 will serve as the servo motor to rotate the gun about it's butt. In order to hold the gun without destructing it, I assembled a plexiglass holder.

The scrap plexiglass was about 4.5 cm in width and 15cm in length.
Engineering principles about precise measurements were sort of disregarded in this section.


How to bend plexiglass without expensive machinery

Not many of us own plexiglass benders at home. Besides, they take a long time to heat up a single bend.

There are two ways of bending:

  1. Using a soldering iron on a piece of plexiglass that spans less than the entire length of the iron; basically, you hold a soldering iron right below your bend line. The bend line must fit on the soldering iron, or it won't bend all the way. It takes some time to heat up, and steady hands not to come in contact with the plexiglass.
    I generally prefer not to use this method after I discovered method two.
  2. If you own a cigar lighter, or any sort of torch, this is the method for you. It takes less than 30 seconds.
    All you have to do is light up the torch and move it up and down along the plexiglass about 10 passes. Then flip over the plexiglass and do the same. You should notice the plexiglass becoming more malleable.
    When the plexiglass starts having small bubbles, it's time to bend. You generally want to bend before that happens.

In both these methods, after heating up the plexiglass, use a ruler as a flat edge to bend.

In the first bend, I sort of estimated what it should have been. It was just a random bend that was sort of reasonable.

In the next bend, I fit the plexiglass around the gun and sort of marked off where I needed to bend. I did this for the next bend also.

I did not have a fourth bend because I ran out of plexiglass, so I just glued on a random piece of scrap using Krazy Glue. Krazy Glue works like magic on plexiglass: strongest bond ever.

Step 6: The Servo Attached to the Handle

After getting a snug fitting handle, drill a hole where you want to connect the servo motor. This creates a depression so the servo fits on nice and flat.

Again, I used Krazy Glue to secure the plastic together. Honestly I think it does a better job than screws.

Step 7: Gun<>Chassis Stand

In this step, we will also be using plexiglass.

Standing bracket

In order to secure the servo motor to the chassis, I had to measure the minimum height the gun had to be (to give it the most flexible range of rotation)

It turns out that the gun had to be elevated about 6cm about the gun.

So, I made a bracket that was 2cm on each base and 6cm high. That means I used a 10cm * 10cm peice of plexiglass (it was 10 cm wide). This bracket will support the servo motor.

You will have to use the torch method or a plexiglass bending machine to bend plexiglass this long. 10cm was longer than my soldering iron, so it might be the same for you.

After bending it, I drilled two holes at the top for another piece of plexiglass (mentioned later)

Securing bracket

This thing was sort of unnecessary, but I made it anyways.

This piece of plexiglass was 2cm by 14 cm originally. The flat bottom parts are 3cm, the height is 2cm, and it is 4cm at the top.

The purpose of this part is to hold down the servo motor, so if your servo motor is a different size, you will need different dimensions.

Drilling

In order to guarantee flawless drilling, I taped the two pieces together before drilling, to make sure holes match.

Of course, the purpose of drilling is so that we can secure the nuts and bolts, so make sure the hole is wide enough and not too wide.

Step 8: Gun<>Chassis Calibration

Testing

You might want to calibrate your servo motor before securing the head onto it. When the servo motor is at 0 degrees, we want it to point UP, when it is at 180, we want the gun pointing DOWN, so that means to point straight the servo motor must be at 90 degrees.

Attached is another Arduino sketch to calibrate the servo to 90 degrees. You want to secure the gun horizontally at 90 degrees.

// to make the gun turning servo to 90 degrees. 0 is full up, 180 is full down
//90 is point forward
#include <Servo.h>

Servo myservo;

int pos = 0;

void setup()
{
myservo.attach(9);
myservo.write(180);
delay(1000);
myservo.write(0);
delay(1000);
myservo.write(90);
}


void loop(){}

After securing the servo motor on at 90 degrees, try testing the servo motor with the gun on. It looks pretty cool.

Pat yourself on the back.

Step 9: Gun<>Chassis Securing

Now, we would like to connect the bottom of the standing bracket to the chassis.

  1. First, I had to remove some plastic that was preventing a flat bond.
  2. After that, I marked down where I wanted holes on the chassis.
  3. Using those holes as a reference, I marked down where to drill on the plexiglass.
  4. I drilled
  5. Using nuts and bolts, they were secured tightly.

This step is pretty self explanatory. Feel free to use Krazy Glue if you like and tell us how it ended! I wouldn't be surprised if it held up the gun just as well as nuts and bolts, if not better.

Step 10: Motor Control Board

After I finished the hardware, my brother worked on the software part of this robot.

In order to make life easy for him, I made a control board for easy interface with Raspberry Pi's 3.3v logic.

This took me a bit of hair tearing and head scratching, but I came up with a solution.

Attached are files that should make life easy:

  • PCB toner transfer
  • PCB photosensitive
  • .rrb file for Robot Room Copper Connection sofware

H-bridge

If you look at the first image, you will see my design for an H-bridge. I decided to use relays as an H-bridge because they had no current loss across the switch.

Basically, when the relay is turned ON, the current flows backwards. This is the same concept used in the PCB agitator I made.

Conversion

In order to convert from 3.3v to at least 5v, I had to use a darlington pair of transistors to amplify the signal.

WARNING: I did not update the PCB files for a darlington pair, so you will have to improvise that in or find a transistor with a low saturation current

These circuits were duplicated on one PCB to accommodate two motors. All of these circuits fed back to one line of headers for easy connection from the Raspberry Pi to the control board, using rainbow bacon.

We used battery power source from the original car battery. After completing the PCB I connected the motors to the outputs and tested the 3.3 v conversion using the Arduino 3.3v port.

Make sure when connecting the motors to consider polarity. If you don't get it right the first time just switch

Control Board explained

Rp and Lp determine whether power is fed to the motors or not. When Rp is on and Rd is off, the right motor turns forward.

When Rd is on, it activates the relays and when Rp is on, it turns backwards.

The same applies to Ld and Lp.

The servo motor circuits are totally independant of the H-bridge circuit. They are just there for easy connection.

Step 11: **Transistion**

From this point on my brother shall be writing the instructables.

Attached are some obsolete files from the guessing and checking I have done to get the hardware down. If you are really interested in how I came about to the final design, check it out, along with my blog.

Thanks!

Step 12: Raspberry Pi + Hardware and Configuration

On the Pi side of things, there are three parts:

  • Python server running the Raspberry Pi that controls movement
  • `motion` webcam server that runs the webcam
  • Ruby-based GUI interface (running on the Shoes.rb project)

A bit of introduction for each of these parts...

Raspberry Pi server

On the server itself, we need to do a bit of configuration. Specifically, we'll be dealing with some PWM. The easiest thing to do is to include the RPIO library. We can download it and install by the following:

$ sudo apt-get install python-setuptools
$ sudo easy_install -U RPIO

In this project, we also chose to use a wifi connection as well. You can use the ethernet port, but for us we wanted the full untethered experience. We went with the EDIMAX wifi dongle, available at your local eBay. You can use the Raspbian Wheezy's built-in WLAN configuration tool to connect to the SSID of your choice.

Motion Webcam

We elected to use the `motion` webcam package as our eyes for this project. There are a plethora of tutorials out there showing you how to - we don't want to belabor the topic, so we'll link you to the one that we used: http://pingbin.com/2012/12/raspberry-pi-web-cam-server-motion/ . With minimal configuration, we were able to get our remote webcam server running remotely on a webcam within 5 minutes.

Shoes.rb

For our GUI, we chose to use the Shoes.rb project. We loved Ruby's ease of use, and the fact that it was running in a GUI made it a cool new tool we wanted to use. In addition, Shoes.rb has the advantage of being able to package executable files for Windows, OSX, and Linux, which makes this more distributable (we like running it raw though).

Download Shoes.rb from their webpage: http://rdoc.info/github/shoes/shoes .

Step 13: Raspberry Pi Server Code - Set Up

In this section, we'll go over the code for the server. The code is attached, but let's walk through each part of the code so that you can modify it to fit your needs.

# Basic Pi libraries
import time
from time import sleep
import termios, fcntl, sys, os


# For PWM
import RPi.GPIO as GPIO
from RPIO import PWM

# For socket connections
import socket

This first part is for importing the libraries we need.

Next, we'll set up our GPIO pins (note that these are based on Pi Revision-2 layouts. Check which revision you have here, and which layout you have here). Make sure that you're plugging them into the right pins, or changing your pin configurations correctly below:

# Set up pins
# NOTE - THESE ARE BASED ON PI REVISION 2 GPIO PINOUTS

PIN_RIGHT_POWER=7 # if this is on, then send power to the right tread
PIN_RIGHT_DIRECTION=11 # if this is on, then relay for the right tread will be on (sending the tread into reverse)

PIN_LEFT_POWER=13 # if this is on, then send power to the left tread
PIN_LEFT_DIRECTION=15 # if this is on, then relay for the left tread will be on (sending the tread into reverse)


# Initialize these GPIO pins as GPIO.OUT
GPIO.setmode(GPIO.BOARD)
GPIO.setup(7, GPIO.OUT)
GPIO.setup(11, GPIO.OUT)
GPIO.setup(13, GPIO.OUT)
GPIO.setup(15, GPIO.OUT)


After that, we'll initialize our servo motors (note the comment on the GPIO BCM layout):

# Initialize the servo library
servo = PWM.Servo()

# NOTE - the RPIO.PWM pin outs refer to the BCM GPIO layout numbers
# See: <a href="http://www.hobbytronics.co.uk/raspberry-pi-gpio-pinout"> <a href="http://www.hobbytronics.co.uk/raspberry-pi-gpio-p...</a"> http://www.hobbytronics.co.uk/raspberry-pi-gpio-p...</a>>
# Example: Pin 18 is actually BCM GPIO 24 on the Revision 2 Pi (5th from bottom on the right)

# Initialize the servo position for gun
pos=1500

This next section, we're setting up some helper methods so that we don't need to manually call pins each time. This helps us keep our code DRY and reusable. We'll only post a truncated version here, since it's fairly self explanatory in the code comments:

# Define functions for pins

# Turn on a pin
def powerOn(pin):
GPIO.output(pin, GPIO.HIGH)
return

# Turn off a pin
def powerOff(pin):
GPIO.output(pin, GPIO.LOW)
# time.sleep(1)
return

# Fire the gun. NOTE - RPIO PWM uses the
def fireGun():
# See note about about BCM GPIO pin
servo.set_servo(23, 1800)
sleep(0.5)
servo.set_servo(23, 1000)
return

# Set the turret to a position using PWM
def turretX(pos):
servo.set_servo(24, pos)
return

# Set the right tread into forawrd mode
def rightForward():
powerOff(PIN_RIGHT_DIRECTION)
# time.sleep(0.5)
sleep(0.5)
return

.
.
.
.


# Turn the vehicle left
def spinLeft(duration=1):
powerOn(PIN_LEFT_DIRECTION)
sleep(0.2)
powerOn(PIN_LEFT_POWER)
sleep(duration)
fullStop()
return

Finally, the last bit of set up before the real fun: sockets. In this project, we will use a basic TCP socket to send commands to our Pi. You don't need to understand too much about how this works, but basically, this means that anyone on your networkcan just send a simple message to the host/port of this Pi, as long as they have the IP and port number.

We ran this project on our own home Wifi. To get the local IP address, run this command on your Pi:

ifconfig /all

And the ip address attached to WLAN should be your wifi. Ours was 10.0.0.101 . Replace the `host` variable below with your ip address:

# SOCKET SET UP 
# We use TCP to communicate with the Pi
s = socket.socket() # Create a socket object
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Keep the socket open to re-use
host = '10.0.0.101' # The local IP address of your Pi. Run `ifconfig /all` to see your WiFi address
port = 12345 # Reserve a port for your service.
s.bind(('', port)) # Bind to the port and accept connections from anywhere</p><p>s.listen(5) # Now wait for client connection, with a backlog queue size of 5</p><p># Establish connection
connection, address = s.accept()
print "Got a connection from", address
message = "Thanks for connecting to " + str(host) + " at port " + str(port)
connection.send(message)

This concludes the setup part of the code. The next step will walk you through the actual functional part of the program.

Step 14: Raspberry Pi Server Code - Functional Code

In the previous section, we went over the part of the code that sets up the Raspberry Pi server. In this step, we'll go over the second half of the server code.

First, we need a way to parse messages that come in through the TCP socket. We develop our own protocol for interpreting messages: Valid commands must begin with a letter that signifies the type of command, a parameters that specifies the value (if applicable), and ends with a |. As you can see in the comments, the '|' helps us avoid situations where commands become bunched together and we crash the server.

# Use this to parse the data.
# Our incoming data protocol comes first with a single capital letter signifying the type of command
# It then comes with the value, whose ending is marked by a '|'
# The '|' is necessary to prevent bunching together of network packets

# For example: `Y1320|` tells the Pi that this is a 'Y' command (turret), set it to a value of 1320, and the '|' indicates end of this command
# We need the '|' to demarcate the end of a command to prevent bunching together of data
# For example, we might accidentally have commands like `Y1230X343` without the pipe, and the pi wouldn't know what to do with the command

def parse_data(raw_data):
pipe_pos = raw_data.find('|')
command_key = raw_data[0]
command_value = raw_data[1:pipe_pos]
return [command_key, command_value]

Finally, we go over the actual action code. Since we defined a lot of code as helper methods in previous parts of the file, this section actually looks relatively clean. This is essentially an if/else statement that switches through the commands, and if it latches onto a correct command, executes the correct movement code. And then at the end of the file, once the client exits, we'll close the connection.

while True:

# Read in data
raw_data = connection.recv(1024).rstrip() # rstrip removes trailing spaces

print "Received raw data of " + str(raw_data)

data = parse_data(raw_data)

# Y commands the turret angle
if data[0] == "Y":
print "Received Y direction for: " + str(data[1])
turretX(int(data[1]))

# X commands the spin of the vehicle (left or right)
elif data[0] == "X":
if (float(data[1]) < 0):
# negative, spin left
print "Received LEFT X direction for: " + str(data[1])
spinLeft(abs(float(data[1])))

elif (float(data[1]) > 0):
# positive, spin right
print "Received RIGHT X direction for: " + str(data[1])
spinRight(abs(float(data[1])))

else:
# Full stop
print "received 0 X - full stop!"
# fullStop()

# F commands firing
elif data[0] == "F":
print "Received Firing command"
fireGun()

# W commands forward
elif data[0] == "W":
print "FORWARD"
moveForward()

# S commands reverse
elif data[0] == "S":
print "REVERSE"
moveReverse()

# P commands full-stop
elif data[0] == "P":
print "FULLSTOP"
fullStop()

else:
print "No mapped command: (Key/Value) = " + str(data[0]) + "/" + str(data[1])



print 'Closed'
connection.close()



And that's it for the server code! In the next section, we'll go over the client code.

Step 15: Client GUI

As stated before, we'll be using Shoes.rb, a GUI wrapper for Ruby code. The Shoes project is pretty neat - definitely check out their site. I could've opted to use Java, but decided it'd be neater trying out a different framework.

The client code is attached. As you can see, it's a relatively normal .rb ruby file.

Much of this code is self explanatory, so I'll only go over the parts that could be confusing.

# MOTION CAPTURE BEGIN
@x, @y = nil, nil

motion do |_x, _y|
if @x and @y and (@x != _x or @y != _y) and _x < 500 and _y < 500 and _x > 0 and _y > 0 and @x < 500 and @y < 500
append do
line @x, @y, _x, _y
end
@xpos.replace "X is now #{@x.inspect}"
@ypos.replace "Y is now #{@y.inspect}"

end


@x, @y = _x, _y


end

Here, we use Shoe's `motion` block to capture mouse movement. We save the new x,y of the mouse as _x and _y. That first if statement, we check to make sure x and y both exist, and then specify that we only care about x/y values that are changed if the initial/product are both within 0 and 500 pixels (a 500x500 pixel square).

If that satisfies our condition, we'll update @x and @y with the new positions.

Next, we have our block that is responsible for actually sending messages to our server. The comments explain quite a bit.:

def x_scaler(raw_x)
(raw_x.to_f / 250.to_f).round(1) * 2
end

def y_scaler(raw_y)

# smallest value should be 800 (all the way up): -700
# largest value should be 2200 (all the way down): +800
# zero should be 1500 (center)

# so:
# 0 -> 800
# 250 -> 1500
# 500 -> 2200

return (((raw_y * 2.8) + 800) / 10).to_i * 10

end


# Initial value
@send_dy = 1500

# Animate(2) runs it twice a second. Animate(8) (the fastest) would run this code 8 times a second.
animate(2) do

# For y variable
if @y and @y > 0 and @y < 500 and @y != @prev_y and @x > 0 and @x < 500
@send_dy = y_scaler(@y)
@boty.replace "Sending Y: #{@send_dy} (raw: #{@y})"
@socket.send("Y#{@send_dy}|", 0)

else

end


# For x variable
if @x and @prev_x and @x > 0 and @x < 500 and @x != @prev_x

@send_dx = x_scaler(@x - @prev_x) #x_scaler(@x)
@botx.replace "Sending X: #{@send_dx} (raw: #{@x})"
@socket.send("X#{@send_dx}|", 0)

else
@botx.replace "Nothing changed."
@send_dx = 0

end

# Save these values for next time
@prev_y = @y
@prev_x = @x


end

We define x_scaler and y_scaler as the formatters for the raw X and Y input, to give it a value that is more friendly to the set up we have on the Pi server.

The animate(2) block means that we will run this command twice every second. Shoes.rb allows things to run as quickly as 8 times a second, but for our purposes, twice a second is enough. This essentially controls our sampling rate of @x and @y (how often we are polling those variables)

Within that block, we handle @x and @y different. In our case, @y signifies the turret, which is controlled by a PWM servo motor pulse. This means that we only need to give it one value for it to go to, and so we can feed in @y directly to y_scaler

In contrast, since @x signifies yaw angle of the pi, which is controlled by our motors running at a variable time length, this will actually need to be a number that signifies the change in value since the last polling. A bit more complicated, but not rocket-science math.

At the end of this block, we save @x and @y for future comparison for next polling cycle.

The rest of the client GUI code is relatively self explanatory, so I won't belabor the point trying to go over it here.

Step 16: Conclusion

That basically concludes our robot.

If you have any questions about any steps, feel free to ask them! We will answer them as best as possible.

Please remember that my brother and I started off with no idea how we would go about building this robot.

We made mistakes; we didn't get things right the first time, so do not be discouraged if you fail the first time.

We will probably continue working on this, so stay tuned.

Thanks,

TSJ and YSJ

Makerlympics Contest

Participated in the
Makerlympics Contest

Game.Life 4 Contest

Participated in the
Game.Life 4 Contest