So, you want a GUI for your robot. Sure, you could make a nice GUI in Python with Tkinter, but there are some really good reasons to try it as a website instead:
There are some drawbacks to implementing your GUI as a website though:
I used the Tornado web server for my projects. It is better than just using CGI, since the environment is always loaded while the server is running instead of loading it every time a request is made. CGI would be problematic then, since serial.begin would be run every time a request was made, which simply wouldn't work since the Arduino would also restart all the time.
On Ubuntu, the installation is as easy as:
sudo apt-get install python-tornado python-serialSo one of the challenges with this is that reading serial input in python is usually blocking, so the webserver becomes unresponsive to browsers' requests while waiting for serial input.
#! /usr/bin/python2.7
import os
import json
import tornado.ioloop
import tornado.web
from serial import *
tornadoPort = 8888
cwd = os.getcwd() # used by static file server
# Make a Serial object
serialPort = '/dev/ttyACM0'
serialBaud = 9600
ser = Serial( serialPort, serialBaud, timeout=0, writeTimeout=0 )
# gets serial input in a non-blocking way
serialPending = ''
def checkSerial():
try:
s = ser.read( ser.inWaiting() )
except:
print("Error reading from %s " % serialPort )
return
if len(s):
serialPending += s
paseSerial()
#called whenever there is new input to check
serialHistory = ''
mostRecentLine = ''
def parseSerial():
split = serialPending.split("\r\n")
if len( split ) > 1:
for line in split[0:-1]:
print( line )
#do some stuff with the line, if necessary
#example:
mostRecentLine = line
# in this example, status will show the most recent line
serialHistory += line
pending = split[-1]
# send the index file
class IndexHandler(tornado.web.RequestHandler):
def get(self, url = '/'):
self.render('index.html')
def post(self, url ='/'):
self.render('index.html')
# handle commands sent from the web browser
class CommandHandler(tornado.web.RequestHandler):
#both GET and POST requests have the same responses
def get(self, url = '/'):
print "get"
self.handleRequest()
def post(self, url = '/'):
print 'post'
self.handleRequest()
# handle both GET and POST requests with the same function
def handleRequest( self ):
# is op to decide what kind of command is being sent
op = self.get_argument('op',None)
#received a "checkup" operation command from the browser:
if op == "checkup":
#make a dictionary
status = {"server": True, "mostRecentSerial": mostRecentLine }
#turn it to JSON and send it to the browser
self.write( json.dumps(status) )
#operation was not one of the ones that we know how to handle
else:
print op
print self.request
raise tornado.web.HTTPError(404, "Missing argument 'op' or not recognized")
# adds event handlers for commands and file requests
application = tornado.web.Application([
#all commands are sent to http://*:port/com
#each command is differentiated by the "op" (operation) JSON parameter
(r"/(com.*)", CommandHandler ),
(r"/", IndexHandler),
(r"/(index\.html)", tornado.web.StaticFileHandler,{"path": cwd}),
(r"/(.*\.png)", tornado.web.StaticFileHandler,{"path": cwd }),
(r"/(.*\.jpg)", tornado.web.StaticFileHandler,{"path": cwd }),
(r"/(.*\.js)", tornado.web.StaticFileHandler,{"path": cwd }),
(r"/(.*\.css)", tornado.web.StaticFileHandler,{"path": cwd }),
])
if __name__ == "__main__":
#tell tornado to run checkSerial every 10ms
serial_loop = tornado.ioloop.PeriodicCallback(checkSerial, 10)
serial_loop.start()
#start tornado
application.listen(tornadoPort)
print("Starting server on port number %i..." % tornadoPort )
print("Open at http://127.0.0.1:%i/index.html" % tornadoPort )
tornado.ioloop.IOLoop.instance().start()To make it easy to send requests to the server, it's really a good idea to use jquery, and the easiest way to use jquery is to use the Google API for it. To add jquery to the webpage, just add this script:
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> So to start, we'll just have a simple webpage that has a div. When the div is clicked, jquery will ask the status of the tornado webserver.
<!DOCTYPE html>
<html>
<head>
<title>Python Tornado Test Page</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script>
function serverResponded( data ) {
/*
log the event data, so you can see what's going on.
Shows up in the console on your browser. (Chrome: Tools > Developer Tools > Console)
*/
console.log( data );
// check the server status, and report it on the screen
if ( data.server === true ) {
$('#status .value').html("OK");
}
else {
$('#status .value').html("NOT OK");
}
// add the last serial to the div on the screen
$('#serial .value').html( data.mostRecentSerial );
}
$(document).ready( function() {
/* handle the click event on the clickme */
$('#clickme').click( function() {
params = { op: "checkup" };
$.getJSON( 'http://localhost:8888/com' , params, serverResponded );
});
});
</script>
</head>
<body>
<div id="clickme" style="cursor: pointer;">CLICK ME</div>
<div id="status">
Server Status: <span class="value">?</span>
</div>
<div id="serial">
Last Serial Input: <span class="value"></span>
</div>
</body>