#!/usr/bin/env python
# coding: Latin-1
# Creates a web-page interface for MonsterBorg
# Import library functions we need
import ThunderBorg
import time
import sys
import threading
import SocketServer
import picamera
import picamera.array
import cv2
import datetime
# Settings for the web-page
webPort = 80 # Port number for the web-page, 80 is what web-pages normally use
imageWidth = 240 # Width of the captured image in pixels
imageHeight = 192 # Height of the captured image in pixels
frameRate = 30 # Number of images to capture per second
displayRate = 10 # Number of images to request per second
photoDirectory = '/home/pi' # Directory to save photos to
flippedCamera = True # Swap between True and False if the camera image is rotated by 180
jpegQuality = 80 # JPEG quality level, smaller is faster, higher looks better (0 to 100)
# Global values
global TB
global lastFrame
global lockFrame
global camera
global processor
global running
global watchdog
running = True
TB = ThunderBorg.ThunderBorg()
#TB.i2cAddress = 0x15 # Uncomment and change the value if you have changed the board address
TB.Init()
if not TB.foundChip:
boards = ThunderBorg.ScanForThunderBorg()
if len(boards) == 0:
print 'No ThunderBorg found, check you are attached :)'
else:
print 'No ThunderBorg at address %02X, but we did find boards:' % (TB.i2cAddress)
for board in boards:
print ' %02X (%d)' % (board, board)
print 'If you need to change the I²C address change the setup line so it is correct, e.g.'
print 'TB.i2cAddress = 0x%02X' % (boards[0])
sys.exit()
TB.SetCommsFailsafe(False)
TB.SetLedShowBattery(False)
TB.SetLeds(0,0,1)
# Power settings
voltageIn = 1.2 * 10 # Total battery voltage to the ThunderBorg
voltageOut = 12.0 * 0.95 # Maximum motor voltage, we limit it to 95% to allow the RPi to get uninterrupted power
# Setup the power limits
if voltageOut > voltageIn:
maxPower = 1.0
else:
maxPower = voltageOut / float(voltageIn)
# Read the battery limit settings
TB.battMin, TB.battMax = TB.GetBatteryMonitoringLimits()
# Timeout thread
class Watchdog(threading.Thread):
def __init__(self):
super(Watchdog, self).__init__()
self.event = threading.Event()
self.terminated = False
self.start()
self.timestamp = time.time()
def run(self):
timedOut = True
# This method runs in a separate thread
while not self.terminated:
# Wait for a network event to be flagged for up to one second
if timedOut:
if self.event.wait(1):
# Connection
print 'Reconnected...'
TB.SetLedShowBattery(True)
timedOut = False
self.event.clear()
else:
if self.event.wait(1):
self.event.clear()
else:
# Timed out
print 'Timed out...'
TB.SetLedShowBattery(False)
TB.SetLeds(0,0,1)
timedOut = True
TB.MotorsOff()
# Image stream processing thread
class StreamProcessor(threading.Thread):
def __init__(self):
super(StreamProcessor, self).__init__()
self.stream = picamera.array.PiRGBArray(camera)
self.event = threading.Event()
self.terminated = False
self.start()
self.begin = 0
def run(self):
global lastFrame
global lockFrame
# This method runs in a separate thread
while not self.terminated:
# Wait for an image to be written to the stream
if self.event.wait(1):
try:
# Read the image and save globally
self.stream.seek(0)
if flippedCamera:
flippedArray = cv2.flip(self.stream.array, -1) # Flips X and Y
retval, thisFrame = cv2.imencode('.jpg', flippedArray, [cv2.IMWRITE_JPEG_QUALITY, jpegQuality])
del flippedArray
else:
retval, thisFrame = cv2.imencode('.jpg', self.stream.array, [cv2.IMWRITE_JPEG_QUALITY, jpegQuality])
lockFrame.acquire()
lastFrame = thisFrame
lockFrame.release()
finally:
# Reset the stream and event
self.stream.seek(0)
self.stream.truncate()
self.event.clear()
# Image capture thread
class ImageCapture(threading.Thread):
def __init__(self):
super(ImageCapture, self).__init__()
self.start()
def run(self):
global camera
global processor
print 'Start the stream using the video port'
camera.capture_sequence(self.TriggerStream(), format='bgr', use_video_port=True)
print 'Terminating camera processing...'
processor.terminated = True
processor.join()
print 'Processing terminated.'
# Stream delegation loop
def TriggerStream(self):
global running
while running:
if processor.event.is_set():
time.sleep(0.01)
else:
yield processor.stream
processor.event.set()
# Class used to implement the web server
class WebServer(SocketServer.BaseRequestHandler):
def handle(self):
global TB
global lastFrame
global watchdog
# Get the HTTP request data
reqData = self.request.recv(1024).strip()
reqData = reqData.split('\n')
# Get the URL requested
getPath = ''
for line in reqData:
if line.startswith('GET'):
parts = line.split(' ')
getPath = parts[1]
break
watchdog.event.set()
if getPath.startswith('/cam.jpg'):
# Camera snapshot
lockFrame.acquire()
sendFrame = lastFrame
lockFrame.release()
if sendFrame is not None:
self.send(sendFrame.tostring())
elif getPath.startswith('/off'):
# Turn the drives off
httpText = '
'
httpText += 'Speeds: 0 %, 0 %'
httpText += '
'
self.send(httpText)
TB.MotorsOff()
elif getPath.startswith('/set/'):
# Motor power setting: /set/driveLeft/driveRight
parts = getPath.split('/')
# Get the power levels
if len(parts) >= 4:
try:
driveLeft = float(parts[2])
driveRight = float(parts[3])
except:
# Bad values
driveRight = 0.0
driveLeft = 0.0
else:
# Bad request
driveRight = 0.0
driveLeft = 0.0
# Ensure settings are within limits
if driveRight < -1:
driveRight = -1
elif driveRight > 1:
driveRight = 1
if driveLeft < -1:
driveLeft = -1
elif driveLeft > 1:
driveLeft = 1
# Report the current settings
percentLeft = driveLeft * 100.0;
percentRight = driveRight * 100.0;
httpText = '
\n'
httpText += '\n'
httpText += '\n'
self.send(httpText)
else:
# Unexpected page
self.send('Path : "%s"' % (getPath))
def send(self, content):
self.request.sendall('HTTP/1.0 200 OK\n\n%s' % (content))
# Create the image buffer frame
lastFrame = None
lockFrame = threading.Lock()
# Startup sequence
print 'Setup camera'
camera = picamera.PiCamera()
camera.resolution = (imageWidth, imageHeight)
camera.framerate = frameRate
print 'Setup the stream processing thread'
processor = StreamProcessor()
print 'Wait ...'
time.sleep(2)
captureThread = ImageCapture()
print 'Setup the watchdog'
watchdog = Watchdog()
# Run the web server until we are told to close
try:
httpServer = None
httpServer = SocketServer.TCPServer(("0.0.0.0", webPort), WebServer)
except:
# Failed to open the port, report common issues
print
print 'Failed to open port %d' % (webPort)
print 'Make sure you are running the script with sudo permissions'
print 'Other problems include running another script with the same port'
print 'If the script was just working recently try waiting a minute first'
print
# Flag the script to exit
running = False
try:
print 'Press CTRL+C to terminate the web-server'
while running:
httpServer.handle_request()
except KeyboardInterrupt:
# CTRL+C exit
print '\nUser shutdown'
finally:
# Turn the motors off under all scenarios
TB.MotorsOff()
print 'Motors off'
# Tell each thread to stop, and wait for them to end
if httpServer != None:
httpServer.server_close()
running = False
captureThread.join()
processor.terminated = True
watchdog.terminated = True
processor.join()
watchdog.join()
del camera
TB.SetLedShowBattery(False)
TB.SetLeds(0,0,0)
TB.MotorsOff()
print 'Web-server terminated.'