Showing posts with label python. Show all posts
Showing posts with label python. Show all posts

Tuesday, July 30, 2013

Twitter, again


Using the Arduino ethernet shield on the Mega 2560 Arduino, this displays tweets.  But not directly - Twitter's new API requires SSL, and the Arduino isn't up to doing that.  So I set up a CGI program on my desktop computer which checks a twitter feed, and returns a plain text version of the username and message.  Code for the CGI program:

#!/usr/bin/python

import twitter, sys, time, pickle

class Timeline:
    def __init__ (self):
        self.seen = set()
        self.cache = []

    def add_to_cache (self, statuses):
        for d in [s.AsDict() for s in statuses]:
            if d["id"] not in self.seen:
                self.seen.add(d["id"])
                self.cache.append(d)

    def __iter__ (self):
        return self

    def next (self):
        if len(self.cache) == 0:
            raise StopIteration
        else:
            return self.cache.pop()
 
oauth_file = open("access_token.txt", "r")
akey = oauth_file.readline().rstrip()
asec = oauth_file.readline().rstrip()

consumer_file = open("consumer_keys.txt", "r")
ckey = consumer_file.readline().rstrip()
csec = consumer_file.readline().rstrip()

try:
    api = twitter.Api(consumer_key = ckey, consumer_secret = csec,
        access_token_key = akey, access_token_secret = asec)
except Exception as e:
    print e
else:
    try:
        timeline = pickle.load(open("timeline.dat"))
    except:
        timeline = Timeline()

    timeline.add_to_cache(api.GetUserTimeline("LE17RH"))

    print "Content-type: text/plain\n"
    try:
        x = timeline.next()
        print x["user"]["screen_name"]
        print x["text"]
    except StopIteration:
        pass

    pickle.dump(timeline, open("timeline.dat", "w"))

The Timeline class is an iterator because in an earlier incarnation I was loading a lot of tweets and wanting to iterate over them, it isn't necessary for this program which just returns a plain text rendition of one username and message.

Code for the Arduino:

#include <SPI.h>
#include <Ethernet.h>
#include <LiquidCrystal.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x7F, 0xB6 };
const IPAddress ip(143,210,109,74);
const IPAddress dnsserver(143,210,12,154);
const IPAddress server(143,210,108,92);
const char get_header[] = "GET /cgi-bin/twittino.cgi";
const char server_header[] = "Host: 143.210.108.92 80";

EthernetClient client;
LiquidCrystal lcd(8, 9, 17, 16, 15, 14);

const int SDSELECT = 4;
const int PIN_G    = 21; // Pins on Mega board!
const int PIN_R    = 20;
const int PIN_B    = 19;

void setup() {
  pinMode(PIN_R, OUTPUT);
  pinMode(PIN_G, OUTPUT);
  pinMode(PIN_B, OUTPUT);
  pinMode(SDSELECT, OUTPUT);
  digitalWrite(PIN_R, HIGH);
  digitalWrite(PIN_G, LOW);
  digitalWrite(PIN_B, HIGH);
  digitalWrite(SDSELECT, LOW);
  Serial.begin(9600);  
  lcd.begin(16, 2);
  Ethernet.begin(mac, ip, dnsserver);
  delay(1000);
  digitalWrite(SDSELECT, HIGH);
  Serial.println(Ethernet.localIP());
  digitalWrite(PIN_G, HIGH);
}

void loop()
{
  static int counter = 1;
  char username[30];
  char tweet[141];
  
  Serial.println(counter++);
  
  if (client.connect(server, 80)) 
  {
    // Serial.println("Connected");
    // Make a HTTP request:
    client.println(get_header);
    client.println(server_header);
    client.println("Connection: close");
    client.println();
    int ulen = 0;
    int tlen = 0;
    boolean username_found = false;
    while (client.connected())
    {  
      if (client.available()) 
      {
        char c = client.read();
        if (!username_found)
        {
          if (c == '\n')
          {
            username_found = true;
            username[ulen] = '\0';
          }
          else
          {
            if (c != '\n') username[ulen++] = c;
          }
        }
        else
        {
          if (c == '\n') 
          {
            tweet[tlen++] = ' ';
          }
          else
          {
            tweet[tlen++] = c;
          }
          switch(c)
          {
            case 'R' : digitalWrite(PIN_R, LOW); break;
            case 'G' : digitalWrite(PIN_G, LOW); break;
            case 'B' : digitalWrite(PIN_B, LOW); break;
            case 'r' : digitalWrite(PIN_R, HIGH); break;
            case 'g' : digitalWrite(PIN_G, HIGH); break;
            case 'b' : digitalWrite(PIN_B, HIGH); break;
          }
        }
      }
    }
    client.stop();
    // Serial.println("Disconnected");
    tweet[tlen] = '\0';
    if (ulen != 0 && tlen != 0)
    {
      Serial.print("Username : ");
      Serial.println(username);
      Serial.print("Tweet    :\n");
      Serial.println(tweet);
      lcd.setCursor(0, 0);
      for (int i=0; i < ulen; i++)
      {
        lcd.print(username[i]);
      }
      for (int i=0; i <= tlen; i++)
      {
        lcd.setCursor(0, 1);
        for (int j=0; j < 16; j++)
        {
          if (j + i < tlen) lcd.print(tweet[j+i]);
        }
        lcd.print(' ');
        delay(500);
      }
    }
    else
    {
      Serial.println("Nothing.");
    }
  } 
  else 
  {
    Serial.println("Connection failed.");
  }
  delay(10000); // Check every 10 seconds
  lcd.clear();
}

Things to note: the SD card, if present, needs to be deactivated (by setting SD_SELECT, pin 4, to LOW) before setting up the ethernet connection.  In addition to the LCD panel, I have one of my RGB LED units connected, and if there is a capital R, G or B in the tweet, the appropriate colour is displayed (the lowercase equivalents turn the colour off).  The username is displayed on the top row of the LCD panel, and the tweet scrolls along the bottom row.

 

Monday, July 1, 2013

Open Day, Part II

The main things I learned from the open day:
  1. It is possible to mull over these things for a few weeks when you are too busy to do anything serious, and then put in a few hours' work late on a Friday afternoon and come up with something working.
  2. iPads are fun, and there are some great apps to do serious engineering, but you can't hack those apps, and you can't do much to hack the iPad.  You get to do what Apple, and Apple-approved developers, allow you to do.  Which is a great deal, but it's not the sort of activity that engineers find really satisfying (and despite the fact that I'm an ex-geochemist with a master's degree in philosophy, I still consider myself a kind of engineer).
  3. On Friday afternoon, I needed the python-twitter library to check the twitter feed from Python.  Downloading and installing it via apt-get from the Ubuntu repository took a few seconds. When I realised I needed a newer version to cope with changes in Twitter's API, it took a couple of minutes to find the newer version, download it and install it.  If I had been using the locked-down campus version of Windows, it would not have been that trivial. Perhaps not even possible. Linux makes it much, much easier to do this sort of thing.
  4. I enjoyed myself more late on Friday afternoon than I had done for weeks doing more "important" things.  It really fired my enthusiasm for using these things in next year's programmable electronics module (I have sixty kits sitting in a cupboard), and encouraging students to play around with them and see what they can do.
  5. I thought I had fried the GPIO port on my Raspberry Pi.  A morning sitting in a room with disappointingly few visitors meant I had time to do some troubleshooting with a multimeter, and it turned out to be a broken connection on the ground rail of one of my breadboards, which was a relief.  I love my multimeter.
  6. To some visitors, sending a tweet to turn on an LED was like magic.  To some, it was deeply unimpressive.  Only colleagues were able to grasp how much was really going on between pressing the send button on the iPad, and the red and green lights coming on.

Open Day, Part I

I was asked to demonstrate the use of an iPhone in Engineering at Saturday's alumni open day.  I demonstrated AutoDesk ForceEffect Motion (which is pretty good fun) on the iPad rather than iPhone, but also demonstrated using the iPad to send tweets to the department's Twitter account (@le_engineering), which was more fun for me to do.  I had a Python program running on my Linux netbook, which checked Twitter for mentions of @le_engineering, and then searched the rest of the text for the characters R, G, r, g, b and 1 or 0.  These characters were sent via USB to an Arduino, which interpreted them as commands to light (uppercase) or turn off (lowercase) a red and green LED, sound a buzzer, or move a servo to 90 or 0 degrees.

Thursday, July 26, 2012

Looking ahead

As the previous post suggests, I've been thinking about Twitter bots today, and in particular how to post lengthy text in 140-character chunks. A diversion was to think about how to "read ahead" - I wanted to be able to say "if the next chunk of text will take me over the 140-character limit, don't read it in". But I can't do that without reading it in (I could of course read the whole file into an array, then use pop() and append() to treat it as a stack, but I want to avoid having to read in the whole file if possible). A solution is below:
#!/usr/bin/python

from tempfile import mkstemp
import shutil
import os

ifpath = "a.txt"

fh, ofpath = mkstemp()
ofile = open(ofpath, "w")
ifile = open(ifpath)

lines = []
length = 0

while True:
    previous = ifile.tell() 
    line = ifile.readline()
    if length + len(line) < 140:
        lines.append(line)
        length += len(line)
    else:
        ifile.seek(previous)
        print lines
        break

for line in ifile:
    ofile.write(line)

ifile.close()
ofile.close()
os.remove(ifpath)
shutil.move(ofpath, ifpath)
This opens a.txt for reading (as ifile), and creates a temporary file (ofile). There's then an infinite loop - each time round, I store the current file location as previous, read in a new line, and check to see if that would take it over the limit. If not, I add the new line to the stored array. If it will go over the limit, I use ifile.seek() to go back to the file location before I read in the new line, and then print out the lines stored so far. All that remains is to write the unread lines (including the one I read in and decided not to use) to a temporary file, remove the original a.txt, and replace it with the temporary one.

Wednesday, July 25, 2012

Twitter Updating

Revised version of the code I originally wrote about here. The main difference is that I've included a counter so it doesn't post more than two tweets every time it is run (and since it only runs once an hour, this shouldn't make it too antisocial even when it hasn't run for a while and there are a lot of items in the RSS feed).
#!/usr/bin/python

import tweepy
import feedparser
import urllib
import urllib2

url = "http://www2.le.ac.uk/departments/engineering/news-and-events/blog/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(url)

count = 0
for item in feed["items"]:
    url   = item["link"]
    title = item["title"]
    if url not in already_done and count < 2:
        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)
        count = count + 1

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

Friday, December 16, 2011

A stack on the Arduino



 I have put together a collection of Python scripts which allow me to build proper C++ programs for the Arduino Uno and Mega 2560 from the command line, and upload them all in one go. At the moment, these are only working in Linux but I think I know enough now that in principle I could get this going on Windows, possibly within Dev-C++ - a project for after Christmas, I think.

Tuesday, November 8, 2011

Netbook

The Department recently bought me a netbook - the HP Mini 110-3704sa (and a two gigabyte RAM upgrade). It came with Windows 7 Starter, which was removed within half an hour - it is now dual-booting Ubuntu Linux 11.10 and Windows 7 Professional.  I've had an older HP Mini (running only Linux) for a couple of years and found myself increasingly bringing it in to work because it was so useful.  I've been using the Windows installation more than I thought, mainly because of Office 2010 (not ideal, due to the "looking through a letterbox" screen dimensions, but usable - pop-up forms present a more serious problem, with the "Submit" button sometimes being off the bottom of the screen with no way to reach it other than tabbing through the fields and guessing when to stop).

One of the first things I do with any new PC is set up a web server on it - even on a netbook.  I have (or had) an ugly combination of PHP and Perl which allowed me to set up lists of links, which can be updated through a web form.  Having the dual-boot machine made me wonder about setting up the servers on the two operating systems to look at the same CGI and HTML directories.  The first thing I did was to translate the Perl CGI script into Python, which is the programming language I'm most comfortable using these days.

I installed Apache 2.2.21 on Windows, then PHP 5.3.8 (many web pages say use the VC6-compiled version, I used the VC9 build, thread-safe version).  The MSI install didn't work, I used the zip file to put it directly into C:\Program Files\PHP5.  I then added these lines to Apache's httpd.conf file:

LoadModule php5_module "C:/Program Files/PHP5/php5apache2_2.dll"
AddHandler application/x-httpd-php .php
PHPIniDir "C:/Program Files/PHP5"


With PHP now working, I fiddled around for a while trying to work out a way for the same Python CGI script to be run under both Windows and Linux.  There is a way - described here.

  1. Keep the Linux "shebang line".  This is the main problem - with scripts and interpreted programs, Unix systems use the first line of a file to determine what sort of thing it is, as opposed to the Windows method of using the file extension.  Apache by default behaves that way even on Windows systems.  The Linux shebang line is #!/usr/bin/python, and the Windows shebang line would be #!C:\Python27\python.exe - but you can't have both.
  2. In httpd.conf again, enter these lines: 
    ScriptInterpreterSource registry
    PassEnv PYTHONPATH

    SetEnv PYTHONUNBUFFERED 1
  3. Change the name of the script to include the .py extension (which should be already linked to Python).
Bingo!

I then decided that it was better to rewrite the whole thing in PHP, which made most of the above completely unnecessary.

Wednesday, February 16, 2011

Distance

I think I have pretty conclusively shown that getting distance out of the accelerometers and the motionplus is not going to work.  What might work is using the IR camera - I now have a "sensor bar", which contains no sensors and is essentially a couple of banks of LEDs about 20cm apart  (three at each end of the bar, spaced by about 1cm).  I haven't dismantled it, but I suspect they are just bright LEDs with an infra-red filter in front of them (you can see the actual LEDs by pointing a phone camera at them).  The code below checks whether two spots are visible, and if so it works out the apparent distance between them.  When the wiimote is close to the sensor bar, it is able to resolve the individual LEDs so it may pick up more than two spots - a metre away seems to be the "safe" distance where the two groups of three LEDs look like two large infra-red sources.

What we have found is that this works pretty well for distance measurements - placing the sensor bar on a desk and the wiimote on another desk a couple of metres away results in a stable measurement of the apparent separation between the two sources, and moving the wiimote a fairly short distance gives a change in this apparent separation which is also consistent and could be translated into a distance from the sensor bar with about 1cm accuracy (i.e. moving the wiimote forward or back about 1cm causes a change in the apparent separation of one camera pixel).  For example, with d = 1m the apparent separation might be 160 pixels, and with d=1.1m the separation might be 150 pixels (apparently closer together, because the light sources are further away).  With some calibration, and the selection of alternative light sources and positions, this could be used to give accurate position measurement.



#!/usr/bin/python

import cwiid
from time import sleep
from math import sqrt

print "Press 1+2 on the Wiimote now"
wiimote = cwiid.Wiimote()
wiimote.rumble = 1
sleep(0.2)
wiimote.rumble = 0
print "OK"

# Rumble to indicate a connection
wiimote.enable(cwiid.FLAG_MESG_IFC)
wiimote.rpt_mode = cwiid.RPT_BTN | cwiid.RPT_IR 

loop = True
distance = 0
last_distance = 0
last_ir = ""

while (loop):
    sleep(0.01)
    messages = wiimote.get_mesg()
    for mesg in messages:
        # Button:
        if mesg[0] == cwiid.MESG_BTN:
            if mesg[1] & cwiid.BTN_HOME:
                print "Ending Program"
                loop = False
        # Infra-red
        elif mesg[0] == cwiid.MESG_IR:
            sources = mesg[1]
            output = "IR:  "
            spots = []
            for spot in sources:
                if spot:
                    # output = output + "{0}".format(spot)
                    output = output + \
                        "{0} {1:12}".format(spot["size"], spot["pos"])
                    spots.append(spot)
            if len(spots) == 2:
                deltax = spots[0]["pos"][0] - spots[1]["pos"][0]
                deltay = spots[0]["pos"][1] - spots[1]["pos"][1]
                distance = int(sqrt(deltax * deltax + deltay * deltay))
                if distance != last_distance:
                    print "Distance : ", distance
                    last_distance = distance
            else:
                if output != last_ir:
                    print output
                    last_ir = output
        else:
            print mesg

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

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()

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

Friday, December 3, 2010

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"]