Monday, December 20, 2010

An aside: Twitter and RSS

The Department of Engineering twitter news feed has been dead since the autumn, when Twitter ended simple authentication in favour of OAuth. I've just resurrected it (the sort of job I get round to doing in the week before Christmas), and here's how (for the OAuth part I found this blog post very helpful - there are various broken and/or poorly-documented attempts to link Python, OAuth and Twitter around the web).
  1. Sign in to Twitter, and go to http://dev.twitter.com/ - then "Your apps" (top of the page) and register a new application.
  2. Assuming you've registered correctly, the next page will include a consumer key (a long alphanumeric string) and a consumer secret (an even longer alphanumeric string). Put these in a text file called consumer_keys.txt.
  3. Go to "My Access Token" (right hand side of the page) to get your access token. Two long strings again, put these in a text file called access_token.txt.
  4. Go to http://bit.ly, sign in, and then http://bit.ly/a/your_api_key
    - put your bit.ly username and this API key in a text file called bitly.txt
  5. Install tweepy and feedparser. (Some useful info on feedparser here).
  6. Code is below (note that I have shortened the RSS URL to prevent it running off the screen on this blog entry - in the actual program it needs to be the full RSS URL). I now just need to add a cron job to my PC, and it will check the website's news RSS feed and update Twitter periodically. You could, of course, simply put the various API and OAuth keys directly into the code, instead of having them in their own files.

#!/usr/bin/python

import tweepy
import feedparser
import urllib
import urllib2

rss_url = "http://www2.le.ac.uk/.../RSS"

oauth_file = open("access_token.txt", "r")
oauth_token = oauth_file.readline().rstrip()
oauth_token_secret = oauth_file.readline().rstrip()

consumer_file = open("consumer_keys.txt", "r")
consumer_key = consumer_file.readline().rstrip()
consumer_secret = consumer_file.readline().rstrip()

bitly_file = open("bitly.txt", "r")
bitly_username = bitly_file.readline().rstrip()
bitly_apikey = bitly_file.readline().rstrip()

bitly_base = "http://api.bit.ly/v3/shorten?"
bitly_data = {
"login" : bitly_username,
"apiKey" : bitly_apikey,
"format" : "txt",
"longUrl" : ""
}

already_done = []
done_file = open("done.txt", "r")
for line in done_file:
already_done.append(line.rstrip())
done_file.close()

auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(oauth_token, oauth_token_secret)

api = tweepy.API(auth)
feed = feedparser.parse(rss_url)

for item in feed["items"]:
url = item["link"]
title = item["title"]
if url not in already_done:
bitly_data["longUrl"] = url
to_shorten = bitly_base + urllib.urlencode(bitly_data)
result = urllib2.urlopen(to_shorten).read()
api.update_status(title + " : " + result)
already_done.append(url)

done_file = open("done.txt", "w")
for url in already_done:
done_file.write(url + "\n")
done_file.close()


Thursday, December 16, 2010

Callback function

Just a quick bit of code to show how the callback function works - 1+2 to connect, then it starts streaming accelerometer and (if available) motionplus data. Press the home button to end.

I'm using two global variables - loop to keep the main loop running, and callback_active to make sure that when you do press the home button, the loop doesn't terminate while the callback function is doing something (I was getting all sorts of exceptions and segmentation faults before including this).

#!/usr/bin/python

import cwiid

loop = True
callback_active = False

def main():
print "Press 1+2"
wiimote = cwiid.Wiimote()
print "OK"

wiimote.mesg_callback = callback
wiimote.enable(cwiid.FLAG_MESG_IFC | cwiid.FLAG_MOTIONPLUS)
wiimote.rpt_mode = cwiid.RPT_ACC | cwiid.RPT_BTN | cwiid.RPT_MOTIONPLUS

global loop
global callback_active
while loop or callback_active:
# Messages will be sent to callback function
pass

wiimote.disable(cwiid.FLAG_MESG_IFC | cwiid.FLAG_MOTIONPLUS)

#----------------------------------------------------------------------
def callback (mesg_list, time):
global callback_active
callback_active = True
for (message, data) in mesg_list:
if message == cwiid.MESG_ACC:
print data
elif message == cwiid.MESG_MOTIONPLUS:
print data
elif message == cwiid.MESG_BTN:
if data & cwiid.BTN_HOME:
global loop
loop = False
else:
pass
callback_active = False

#----------------------------------------------------------------------
main()

Wednesday, December 15, 2010

Translation

Final video of the day - why translation can't be picked up by the motionplus.

Horizontal orientation


I can thoroughly recommend Will Kymlicka's Contemporary Political Philosophy, if you're into that sort of thing. What I mean while I'm waggling my hand around is that an airship shouldn't pitch or roll too much (yaw being rotation in the horizontal plane).

More code

This will assume the wiimote has a motionplus attached - if it doesn't, no cwiid.MESG_MOTIONPLUS events will be recorded, and it will just plot the three graphs for the accelerometers rather than the five for accelerometers and motionplus.


#!/usr/bin/python

import cwiid
from time import time, asctime, sleep
from numpy import *
from pylab import *

def plotter(plot_title, timevector, data, position, n_graphs):
subplot(n_graphs, 1, position)
plot(timevector, data[0], "r",
timevector, data[1], "g",
timevector, data[2], "b")
xlabel("time (s)")
ylabel(plot_title)

print "Press 1+2 on the Wiimote now"
wiimote = cwiid.Wiimote()

# Rumble to indicate a connection
wiimote.rumble = 1
print "Connection established - release buttons"
sleep(0.2)
wiimote.rumble = 0
sleep(1.0)

wiimote.enable(cwiid.FLAG_MESG_IFC | cwiid.FLAG_MOTIONPLUS)
wiimote.rpt_mode = cwiid.RPT_BTN | cwiid.RPT_ACC | cwiid.RPT_MOTIONPLUS

print "Press plus to start recording, minus to end recording"
loop = True
record = False
accel_data = []
angle_data = []

while (loop):
sleep(0.01)
messages = wiimote.get_mesg()
for mesg in messages:
# Motion plus:
if mesg[0] == cwiid.MESG_MOTIONPLUS:
if record:
angle_data.append({"Time" : time(), \
"Rate" : mesg[1]['angle_rate']})
# Accelerometer:
elif mesg[0] == cwiid.MESG_ACC:
if record:
accel_data.append({"Time" : time(), "Acc" : mesg[1]})
# Button:
elif mesg[0] == cwiid.MESG_BTN:
if mesg[1] & cwiid.BTN_PLUS and not record:
print "Recording - press minus button to stop"
record = True
start_time = time()
if mesg[1] & cwiid.BTN_MINUS and record:
if len(accel_data) == 0:
print "No data recorded"
else:
print "End recording"
print "{0} data points in {1} seconds".format(
len(accel_data), time() - accel_data[0]["Time"])
record = False
loop = False
else:
pass

wiimote.disable(cwiid.FLAG_MESG_IFC | cwiid.FLAG_MOTIONPLUS)
if len(accel_data) == 0:
sys.exit()


timevector = []
a = [[],[],[]]
v = [[],[],[]]
p = [[],[],[]]
last_time = 0
velocity = [0,0,0]
position = [0,0,0]

for n, x in enumerate(accel_data):
if (n == 0):
origin = x
else:
elapsed = x["Time"] - origin["Time"]
delta_t = x["Time"] - last_time
timevector.append(elapsed)
for i in range(3):
acceleration = x["Acc"][i] - origin["Acc"][i]
velocity[i] = velocity[i] + delta_t * acceleration
position[i] = position[i] + delta_t * velocity[i]
a[i].append(acceleration)
v[i].append(velocity[i])
p[i].append(position[i])
last_time = x["Time"]

n_graphs = 3

if len(angle_data) == len(accel_data):
n_graphs = 5
ar = [[],[],[]] # Angle rates
aa = [[],[],[]] # Angles
angle = [0,0,0]
for n, x in enumerate(angle_data):
if (n == 0):
origin = x
else:
delta_t = x["Time"] - last_time
for i in range(3):
rate = x["Rate"][i] - origin["Rate"][i]
angle[i] = angle[i] + delta_t * rate
ar[i].append(rate)
aa[i].append(angle[i])
last_time = x["Time"]


plotter("Acceleration", timevector, a, 1, n_graphs)
plotter("Velocity", timevector, v, 2, n_graphs)
plotter("Position", timevector, p, 3, n_graphs)
if n_graphs == 5:
plotter("Angle Rate", timevector, ar, 4, n_graphs)
plotter("Angle", timevector, aa, 5, n_graphs)

show()

Angle

Secondly, I now have a genuine wiimote with the motionplus attachment. This, at the moment, only gives the rotation speed (or rate of change of angle, if you want to put it that way) of the three axes - none of the reverse-engineering projects seem to have been able to get any other information out of it.

Top three graphs are the acceleration, velocity, and position again. The bottom two are the rate of angle change and (by integration again) the angle. What I did with the wiimote was to hold it in a steady orientation, and then twist it (as accurately as I could) 90 degrees around each axis and back again fairly quickly - you can see that in the three humps in the bottom graph. This integrated data seems to be more accurate in some respects than the raw orientation data you get from the accelerometers.


It may look as if I fumble and drop the wiimote at the start of this video. This was entirely intentional. Honest.

Position

Still haven't become a LabVIEW expert, though I did start the computer-based training and there's a fairly good explanation of state transition diagrams which should be useful for the students when they come to design code.

What I have done is to modify the code from here, so I can plot graphs of acceleration, velocity and position. As I thought, nowhere near accurate enough for navigation - although talking to Simon before a meeting this morning, it seems they aren't going to be expected to fly the airship around, just take off, hover and land.

Here's a graph showing what happens when I lift the wiimote off the desk and put it down again, fairly quickly.





The top graph shows acceleration, the middle one velocity, and the bottom one position. I think the best you can say about this is that it's clear that the blue line is the vertical acceleration, and the wiimote started moving about 1.75 seconds after I hit the record button. Calculation errors eventually lead to a bogus velocity in all three directions, even when the device is returned to the rest position - all three velocity lines and the position line should return to zero after about 2.5 seconds.

Wednesday, December 8, 2010

More LabVIEW

Another one of the example VIs, showing orientation and button presses. Unfortunately I didn't have time this afternoon to become a LabVIEW expert from scratch, maybe tomorrow.

LabVIEW and Windows

Instructions are here: http://decibel.ni.com/content/docs/DOC-1353

This is running inside a Windows XP virtual machine, thanks to VirtualBox.

Time to start learning about LabVIEW...

Multiple IR sources

Aiming the wiimote out of the window on a moderately sunny day, to show how it picks up multiple IR highlights. (I say "intensity and size" towards the end - I mean "intensity and position", of course).

Tuesday, December 7, 2010

More video

Roughly the same program as in the previous post, though I have fiddled with it slightly.




#!/usr/bin/python

import cwiid
from time import sleep

print "Press 1+2 on the Wiimote now"
wiimote = cwiid.Wiimote()

# Rumble to indicate a connection
wiimote.rumble = 1
print "Connection established - release buttons"
sleep(0.2)
wiimote.rumble = 0
sleep(2.0)
print "Up / Down / Left / Right = toggle LEDs."
print "A/B = long/short vibrate."
print "+/- = start/stop display of accelerometer data."
print "1/2 = start/stop display of infra-red data."
print "Home = end program."

wiimote.enable(cwiid.FLAG_MESG_IFC)
wiimote.rpt_mode = cwiid.RPT_ACC | cwiid.RPT_BTN | cwiid.RPT_IR | cwiid.RPT_STATUS

loop = True
show_acc = False
show_infrared = False
last_acc = (0, 0, 0)
delta_acc = [0, 0, 0]
led_status = 0
last_ir = ""
rumble_counter = 0

while (loop):
sleep(0.01)
messages = wiimote.get_mesg()
if rumble_counter:
rumble_counter = rumble_counter - 1
if rumble_counter == 0:
wiimote.rumble = 0
for mesg in messages:
# Accelerometer:
if mesg[0] == cwiid.MESG_ACC:
if show_acc:
acc = mesg[1]
if acc != last_acc:
for i in range(3):
delta_acc[i] = acc[i] - last_acc[i]
print "Acc: {0[0]:5} {0[1]:5} {0[2]:5} {1}".format(
delta_acc, acc)
last_acc = acc
# Button:
elif mesg[0] == cwiid.MESG_BTN:
if mesg[1] & cwiid.BTN_HOME:
print "Ending Program"
loop = False
if mesg[1] & cwiid.BTN_PLUS and not show_acc:
show_acc = True
if mesg[1] & cwiid.BTN_MINUS and show_acc:
print "Ending accelerometer display"
show_acc = False
if mesg[1] & cwiid.BTN_B:
rumble_counter = 10
wiimote.rumble = 1
if mesg[1] & cwiid.BTN_A:
rumble_counter = 30
wiimote.rumble = 1
if mesg[1] & cwiid.BTN_UP:
led_status = led_status ^ cwiid.LED1_ON
wiimote.led = led_status
if mesg[1] & cwiid.BTN_DOWN:
led_status = led_status ^ cwiid.LED2_ON
wiimote.led = led_status
if mesg[1] & cwiid.BTN_LEFT:
led_status = led_status ^ cwiid.LED3_ON
wiimote.led = led_status
if mesg[1] & cwiid.BTN_RIGHT:
led_status = led_status ^ cwiid.LED4_ON
wiimote.led = led_status
if mesg[1] & cwiid.BTN_1 and not show_infrared:
show_infrared = True
last_ir = ""
if mesg[1] & cwiid.BTN_2 and show_infrared:
print "Ending IR display"
show_infrared = False
# Infra-red
elif mesg[0] == cwiid.MESG_IR:
if show_infrared:
sources = mesg[1]
output = "IR: "
for spot in sources:
if spot:
output = output + \
"S {0} P {1:12}".format(spot["size"], spot["pos"])
found = True
if output != last_ir:
print output
last_ir = output
else:
print mesg

Infra-red

Now picking up the IR data - the hardware seems to pick the four brightest spots (above a brightness threshold, so it may identify less than four) and report their position.  Buttons B and A turn the reporting of IR data on and off, respectively.  I'm also showing the difference in the accelerometer readings, rather than the absolute readings.  This is interesting - the values are integers, and the difference between being at rest and being shaken around quite violently is about 40-50.  Not fantastic sensitivity, if you're hoping to use it for positioning - I think it may be better to rig up some sort of infra-red "landing beacon" and navigate that way.

#!/usr/bin/python

import cwiid
from time import sleep

print "Press 1+2 on the Wiimote now"
wiimote = cwiid.Wiimote()

# Rumble to indicate a connection
wiimote.rumble = 1
print "Connection established"
sleep(0.2)
wiimote.rumble = 0

wiimote.enable(cwiid.FLAG_MESG_IFC)
wiimote.rpt_mode = cwiid.RPT_ACC | cwiid.RPT_BTN | cwiid.RPT_IR

loop = True
show_acc = False
show_infrared = False
last_acc = (0, 0, 0)
delta_acc = [0, 0, 0]
led_status = 0

while (loop):
    sleep(0.01)
    messages = wiimote.get_mesg()
    for mesg in messages:
        # Accelerometer:
        if mesg[0] == cwiid.MESG_ACC:
            if show_acc:
                acc = mesg[1]
                if acc != last_acc:
                    for i in range(3):
                        delta_acc[i] = acc[i] - last_acc[i]
                    print "Acc: {0[0]:5} {0[1]:5} {0[2]:5}".format(delta_acc)
                    last_acc = acc
        # Button:
        elif mesg[0] == cwiid.MESG_BTN:
            if mesg[1] & cwiid.BTN_HOME:
                print "Ending Program"
                loop = False
            if mesg[1] & cwiid.BTN_PLUS:
                show_acc = True
            if mesg[1] & cwiid.BTN_MINUS:
                print "Ending accelerometer display"
                show_acc = False
            if mesg[1] & cwiid.BTN_1:
                wiimote.rumble = 1
            if mesg[1] & cwiid.BTN_2:
                wiimote.rumble = 0
            if mesg[1] & cwiid.BTN_UP:
                led_status = led_status ^ 0x01 
                wiimote.led = led_status
            if mesg[1] & cwiid.BTN_DOWN:
                led_status = led_status ^ 0x02 
                wiimote.led = led_status
            if mesg[1] & cwiid.BTN_LEFT:
                led_status = led_status ^ 0x04 
                wiimote.led = led_status
            if mesg[1] & cwiid.BTN_RIGHT:
                led_status = led_status ^ 0x08 
                wiimote.led = led_status
            if mesg[1] & cwiid.BTN_A:
                print "Ending IR display"
                show_infrared = False
            if mesg[1] & cwiid.BTN_B:
                show_infrared = True
        elif mesg[0] == cwiid.MESG_IR:
            if show_infrared:
                sources = mesg[1]
                found = False
                output = "IR:  "
                for spot in sources:
                    if spot:
                        output = output + \
                            "S {0} P {1:12}".format(spot["size"], spot["pos"])
                        found = True
                if found:
                    print output
                else:
                    print "No IR data"
        else:
            print mesg

Monday, December 6, 2010

Accelerometer data

Thanks to this web site and this forum posting, I've got accelerometer data from the wiimote. I've also found out how to check button presses.

The program below buzzes (rumbles) the wiimote when the connection is made. After that:

  • The A and B buttons print "A" and "B" to the screen.
  • The + button starts the display of accelerometer data, and the - button stops this display.
  • The 1 button starts the buzzer, and the 2 button stops it.
  • The 4-way button at the top changes the state of the four LEDs at the bottom.
  • The home button ends the program.



#!/usr/bin/python

import cwiid
from time import sleep

print "Press 1+2 on the Wiimote now"

w = cwiid.Wiimote()
# Rumble to indicate a connection
w.rumble = 1
print "Connection established"
sleep(0.2)
w.rumble = 0

w.enable(cwiid.FLAG_MESG_IFC)
w.rpt_mode = cwiid.RPT_ACC | cwiid.RPT_BTN

loop = True
show_accelerometers = False
last_accelerometers = (0, 0, 0)
led_status = 0

while (loop):
sleep(0.01)
messages = w.get_mesg()
for mesg in messages:
# Accelerometer:
if mesg[0] == cwiid.MESG_ACC:
if show_accelerometers:
accelerometers = mesg[1]
if accelerometers != last_accelerometers:
print accelerometers
last_accelerometers = accelerometers
# Button:
elif mesg[0] == cwiid.MESG_BTN:
if mesg[1] & cwiid.BTN_HOME:
loop = False
if mesg[1] & cwiid.BTN_PLUS:
show_accelerometers = True
if mesg[1] & cwiid.BTN_MINUS:
show_accelerometers = False
if mesg[1] & cwiid.BTN_1:
w.rumble = 1
if mesg[1] & cwiid.BTN_2:
w.rumble = 0
if mesg[1] & cwiid.BTN_UP:
led_status = led_status ^ 0x01
w.led = led_status
if mesg[1] & cwiid.BTN_DOWN:
led_status = led_status ^ 0x02
w.led = led_status
if mesg[1] & cwiid.BTN_LEFT:
led_status = led_status ^ 0x04
w.led = led_status
if mesg[1] & cwiid.BTN_RIGHT:
led_status = led_status ^ 0x08
w.led = led_status
if mesg[1] & cwiid.BTN_A:
print "A"
if mesg[1] & cwiid.BTN_B:
print "B"
else:
print mesg

Range

Just tried the flashing LED program on my netbook, and it works from the other end of the computing and design lab - which is enough range for controlling an airship.  I have got the wiimote to buzz at my command, but not yet got round to reading accelerometer data.  However, being able to control the buzzer and the LEDs means that we can get two-way communication and hopefully use the wiimote to control an airship as well as checking its orientation and (hopefully) position.

Friday, December 3, 2010

The video:

The Python code:
#!/usr/bin/python

import cwiid

from time import sleep

print "Press 1+2 on the Wiimote now"
w = cwiid.Wiimote()
print "Connection established"

while (True):
for x in range(0,16):
sleep(1.0)
w.led = x
print "LED status is %02d" % w.state["led"]

Connecting the Wiimote

Equipment:
  1. A Trust 17124 Bluetooth adapter. Works out of the box in Linux, needs a driver for Windows.
  2. A Shiro "Wü Remote" (sic). Cheap wiimote clone.
Windows:
  1. Install the driver, plug in the Bluetooth adapter, run the Bluetooth software, put the wiimote into discovery mode (hold down buttons 1 and 2). The wiimote is found, and the software then asks for a PIN.
  2. Swear, poke around on the web, find this YouTube video which explains how to skip the PIN.
  3. Press ALT-S when the program asks for the PIN.
  4. Confirm that the wiimote is now recognised as a device - the status shows some bytes being transferred when I press buttons.
  5. Go back to my office because the over-efficient heating in the PC lab is making me uncomfortable.
Linux:
  1. Discover this blog post which links to the CWiid library.
  2. sudo apt-get install wminput wmgui lswm
  3. lswm and wmgui work as advertised. With wmgui I can see button presses and accelerometer data (which is the main thing I'm interested in).
  4. Investigate CWiid. It has a Python interface!
  5. Write a simple Python program to change the LED status.
  6. That's enough for a Friday afternoon.
Next week:
  1. Get the wiimote working with LabVIEW?