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.

 

Friday, July 26, 2013

Shift Registers

I bought a "Nano 3.0 for Arduino" for eight quid - a neat little thing that reminds me of the Hexbug Nanos I bought my niece and nephew for Christmas a couple of years ago, but is rather more interesting for grownups.  It fits directly into a breadboard, and is powered and programmed through a USB lead (standard "mini A" connector).


The pins are:

Top row: digital pins D12 to D2 (D2 is just above the TX LED), ground, reset, RX and TX (RX and TX are D0 and D1 on an Arduino Uno).

Bottom row: digital 13, 3.3 V, REF (analogue reference, labelled AREF on bigger boards), analogue inputs A0 to A7 (two more than on the Uno, A7 is just below the LED), 5 V, reset, ground, and input voltage (VIN).

This is almost, but not quite, the same layout as the official Arduino Nano, and considerably cheaper.  I stuck it on a breadboard with a couple of shift register chips (74HC595), and used it to control some RGB LED units.

The RGB units have three LEDs with a common anode, so controlling four of them requires twelve bits.  I've used two shift registers, so that gives sixteen bits to play with.

A diagram is below.

Things to note: the board shown in the diagram doesn't have the same pin order as mine, and my breadboard (see below) has a very different layout but the circuit is the same.  The connections from the board are: 5V pin to the top power rail (which is then linked to the lower power rail in the middle, and the bottom power rail), ground pin to the top ground rail (similarly linked to the middle and bottom), and the SPI connection to the first shift register: blue wire links D2 to the data pin on the 74HC595, green wire links D4 to the latch pin, and yellow wire links D3 to the clock pin.

The two 74HC595 chips have the clock and latch pins linked (horizontal green and yellow wires), and the serial output from the first goes to the data pin on the second (horizontal blue wire below the yellow one).  This means that when two bytes are sent into the first chip, the first byte is displaced into the second chip (you can chain these things even further - so if you had four linked chips and sent four bytes into the first one, the fourth chip would contain the first byte sent, the third chip the second byte, the second chip the third byte and the first chip the fourth byte).

I have only shown the connections for one of the LED units - the horizontal red, blue and green wires connect from pins Q3, Q2 and Q1 of the first 74HC595 to the cathodes of one of the LED units.  Pins Q7-Q5 of the first 74HC595 go to the RGB cathodes of the second unit, and the third and fourth units are connected to the same pins on the second 74HC595.  On my breadboard (see below) there are no wires making these connections - I just bridged the pins of the 74HC595 chips to the LED units with resistors (270 Ω).

I'm not using pins Q0 or Q4 on either 74HC595 - they aren't needed (and Q0 is in a slightly inconvenient position, on the other side of the chip to the other seven output pins, next to the data pin).


Code:

const int latchPin = 4;
const int clockPin = 3;
const int dataPin  = 2;

const byte red     = 0x08;
const byte green   = 0x02;
const byte blue    = 0x04;
const byte yellow  = red   | green;
const byte magenta = red   | blue;
const byte cyan    = green | blue;
const byte white   = red   | green | blue;
const byte black   = 0x00;

const byte sequence[8] = {black, red, yellow, green, 
  cyan, blue, magenta, white};

void put_four_leds(byte leds[4])
{
    byte a = leds[0] | (leds[1] << 4);
    byte b = leds[2] | (leds[3] << 4);
    
    digitalWrite(latchPin, LOW);
    // Flip bits because low is on, high is off
    // Second pair goes first because that byte is shifted
    // through to the second shift register. 
    shiftOut(dataPin, clockPin, MSBFIRST, ~b);
    shiftOut(dataPin, clockPin, MSBFIRST, ~a);
    digitalWrite(latchPin, HIGH);
}

void setup() 
{
  //set pins to output so you can control the shift register
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
}

void loop() 
{
  byte led_array[4];
  int  i, j;
  
  for (i=0; i<8; i++)
  {
    for (j=0; j<4; j++)
    {
      led_array[j] = sequence[(i + j) % 8];
    }
    put_four_leds(led_array);
    delay(500);
  }
}
 
 

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.

Wednesday, June 12, 2013

Raspberry Pi

I haven't done much with the Raspberry Pi since I bought it, but having semi-volunteered to give some talks at a forthcoming open day, I thought I would use it as a web server to interface with an Arduino and some hardware.  So I set it up again.

SD Card

I bought a new 16 gigabyte SD card for a tenner from the students' union shop. I think I paid about £130 for a 2 gigabyte hard disk back in the mid-nineties (I have a pile of them propping up a bookshelf in my office).  Downloaded the raspbian image on to my Linux netbook, popped the SD card into the slot, and created the image (the card was automounted as /dev/sdb1, hence the first command):
    umount /dev/sdb1
    dd if=2013-05-25-wheezy-raspbian.img of=/dev/sdb bs=4M

Network

The RPi was already registered for the campus network, so I haven't bothered with any of the static network address recipes - it picks up its address through DHCP.

Additional software

I installed (using apt-get) the following packages:
  • apache2
  • php5
  • libapache2-mod-php5
  • xfce4
  • tightvnc
  • arduino

VNC

I want to run the RPi headless most of the time, and don't want to have to muck about with monitor cables and spare keyboards when I do need to use the GUI.  Initially, I took the RPi up to one of the labs where I could pinch a monitor with DVI input, and work in peace.  These instructions worked to get VNC up and running.  I used vinagre and remmina to test the setup - VNC is now starting reliably at boot time.

Web server

I want the web server to use one of my subdirectories as root (documents in /home/pi/www/htdocs, CGI scripts in /home/pi/www/cgi-bin), so I edited /etc/apache2/sites-enabled/000-default to change the values of DocumentRoot, ScriptAlias, and the permissions on /home/pi/www/htdocs/.

Desktop

I use xfce as my standard desktop (I like Gnome 4 on netbooks, have never got on with Unity).  Selecting that as the default is simple (like most of the commands above, this has to be done using sudo to give yourself root privileges):
    update-alternatives --config x-session-manager
A menu appears, and I was able to choose /usr/bin/startxfce4 as my session manager.

Wednesday, April 17, 2013

Gmail

I've been using Python to email .MOBI files to the Kindle "Personal Documents" service, which adds them to your documents list on Amazon and allows them to be downloaded, synchronised between reading devices, etc.  I've been sending via Gmail's SMTP server rather than the University server (because my Gmail account is authorised to send to Kindle, and my University account is not).  That stopped working today, with the mail server reporting an incorrect password.

The answer: https://accounts.google.com/DisplayUnlockCaptcha

This doesn't actually display a Captcha, but it opens a ten minute window during which, if you use the application which is getting password rejections, the password will be accepted and the application added to a "whitelist".