PTZ or Pan-tilt-zoom cameras are off the shelf and reasonably low cost. This project is a home made pan-tilt camera that shoots nerf rockets.
I got the rocket launcher in a bargain bin, but they can be found online starting around $15.
For this project there were 3 main steps:
- getting the Python code talking to the rocket launcher
- loading some USB webcam software
- loading a Python web framework (bottle).
The hardware could be a PC (preferably running Linux) or a Raspberry Pi. On this project I used a Pi clone (an Orange Pi Lite). The faster your hardware the better the streaming video performance that you’ll get.
Getting the Rocket Launcher Working
There are a number of Python libraries that need to loaded:
pip install setuptools pip install usb
To control the rocket launcher Python can connect to the USB device using the vendor id (0x2123) and product id (0x1010). Rocket launcher commands are issued as USB transfer codes.
A command line test program (rocket1.py) would be:
import usb
import sys
import time
device = usb.core.find(idVendor=0x2123, idProduct=0x1010)
# On Linux we need to detach usb HID first
try:
device.detach_kernel_driver(0)
# except Exception, e:
except Exception:
pass # already unregistered
device.set_configuration()
endpoint = device[0][(0,0)][0]
down = 1 # down
up = 2 # up
left = 4 # rotate left
right = 8 # rotate right
fire = 16 # fire
stop = 32 # stop
#device.ctrl_transfer(0x21, 0x09, 0x0200, 0, [signal])
while True:
print('r = right, l = left, u = up, d = down, f = fire ')
key = raw_input ('enter key:')
if (key == 'l'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, left, 0x00,0x00,0x00,0x00,0x00,0x00])
if (key == 'u'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, up, 0x00,0x00,0x00,0x00,0x00,0x00])
if (key == 'r'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, right, 0x00,0x00,0x00,0x00,0x00,0x00])
if (key == 'd'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, down, 0x00,0x00,0x00,0x00,0x00,0x00])
if (key == 'f'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, fire, 0x00,0x00,0x00,0x00,0x00,0x00])
time.sleep(4)
time.sleep(0.1)
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, stop, 0x00,0x00,0x00,0x00,0x00,0x00])
By default the USB port requires superuser rights, so run the program using sudo:
$ sudo python rocket1.py
r = right, l = left, u = up, d = down, f = fire
enter key:r
r = right, l = left, u = up, d = down, f = fire
enter key:
Getting the Web Cam working
A standard USB Web Cam can be connected to a Linux PC or Raspberry Pi using the motion package. Motion is super easy to setup and it’s got lots of added features if you want to look at enhancing things later. To install motion:
sudo apt-get install motion
Once you have motion installed you’ll need to tweek some of it’s parameters by:
sudo nano /etc/motion/motion.conf
The /etc/motion/motion.conf file contains a lot of parameters, some of the more important ones are:
# Image width (pixels). Valid range: Camera dependent, default: 352 width 800 # Image height (pixels). Valid range: Camera dependent, default: 288 height 600 # Maximum number of frames to be captured per second. framerate 1 # Maximum framerate for stream streams (default: 1) stream_maxrate 1 # Restrict stream connections to localhost only (default: on) stream_localhost off
The speed of your hardware and network will determine how many frames per second you can use.
To run the video server enter:
sudo motion &
The motion package has a built in web server that is accessed by: http://your_ip:8081
Getting the Bottle Web Server Running
There are a lot of web programming options that are available. In my case I wanted to run the project as a simple standalone app on an Orange Pi (a Raspberry Pi clone). To install bottle:
sudo apt-get install python-bottle
The Pan-Tilt-Shoot Web page (ptscam.html) was designed with 5 buttons and the Web Cam video below. When a button is clicked a javascript function called sendcmd passes a command as a header item in a AJAX request. The motion web server camweb video is included using the html img tag, with the source being the web link.
<!DOCTYPE html> <html> <head> <title>Pan-Tilt-Shoot Webcam</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="data:,"> <style> html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center;} h1{color: #0F3376; padding: 2vh;}p{font-size: 1.5rem;} .button{display: inline-block; background-color: #4286f4; border: none; border-radius: 4px; color: white; text-decoration: none; font-size: 30px; width:100%} .button2{background-color: green ;width:31%} .button3{background-color: red; width:33%} </style> </head> <script> function sendcmd(thecmd) { // send the action as a header item var xhttp = new XMLHttpRequest(); xhttp.open("GET","/action" , true); xhttp.setRequestHeader("myaction", thecmd); xhttp.send() } </script> <body> <h2>Pan-Tilt-Shoot Webcam</h2> <button onclick="sendcmd('up')" class="button">UP</button> <button onclick="sendcmd('left')" class="button button2">LEFT</button> <button onclick="sendcmd('fire')" class="button button3">FIRE</button> <button onclick="sendcmd('right')" class="button button2" >RIGHT</button> <button onclick="sendcmd('down')" class="button button">DOWN</button> <p><img src='http://192.168.0.117:8081/'></p> </body> </html>
The Python web server application sends the web page at startup, and then it processes AJAX requests and passed the requested action (as a header item) to the rocket launcher USB device.
# Python Bottle
#
import os, socket
from bottle import route, run, static_file, request
import usb
import sys
import time
# Send an action to the rocket launcher
def do_action(theaction):
print("Action : " + theaction)
down = 1 # down
up = 2 # up
left = 4 # rotate left
right = 8 # rotate right
fire = 16 # fire
stop = 32 # stop
if (theaction == 'left'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, left, 0x00,0x00,0x00,0x00,0x00,0x00])
if (theaction == 'up'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, up, 0x00,0x00,0x00,0x00,0x00,0x00])
if (theaction == 'right'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, right, 0x00,0x00,0x00,0x00,0x00,0x00])
if (theaction == 'down'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, down, 0x00,0x00,0x00,0x00,0x00,0x00])
if (theaction == 'fire'):
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, fire, 0x00,0x00,0x00,0x00,0x00,0x00])
time.sleep(4)
time.sleep(0.1)
device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, stop, 0x00,0x00,0x00,0x00,0x00,0x00])
# Send the starting page
@route('/')
def server_static():
return static_file("ptscam.html", root='')
# Process an AJAX GET request, pass the action to the rocket launcher code
@route('/action')
def get_action():
print("Requested action: " + request.headers.get('myaction'))
do_action(request.headers.get('myaction'))
# On Linux we need to detach usb HID first
device = usb.core.find(idVendor=0x2123, idProduct=0x1010)
try:
device.detach_kernel_driver(0)
except Exception:
pass # already unregistered
# Start the bottle web server
run(host="192.168.0.117" , port=8000, debug=False)
Because of the USB connection the Python application need to be run under sudo. When the program is running some diagnostics will show connections and actions.
$ sudo python ptscam.py
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://192.168.0.117:8000/
Hit Ctrl-C to quit.
192.168.0.114 - - [23/Nov/2019 19:26:24] "GET / HTTP/1.1" 200 1251
192.168.0.114 - - [23/Nov/2019 19:26:24] "GET /:8081 HTTP/1.1" 404 736
192.168.0.114 - - [23/Nov/2019 19:26:53] "GET / HTTP/1.1" 200 1271
Requested action: up
Action : up
The web page is accessed by : http://your_ip:8000
Final Comments
The next step will be to mount the rocket launcher with the USB web cam on a little rover.