Braindump

Get this stuff out of my head.

Racism Stinks

| Comments

A few weeks ago I asked (on twitter) for ideas for simple Arduino project idea just to give me a bit of a challenge and get me started using my Arduino, which after being tinkered with for a week when I first got it had been sat in a box for over a year.

My friend Hannah Goraya (@yorkhannah) supplied a problem that was just the right mix of hardware hacky, coding and silliness to get me interested.  In her blog post she asked whether it it would be possible to train people to stop making racist comments in her workplace in the same way that dogs can be trained to stop barking by fitting them with a collar that lets of a pungent pong whenever they woof.

I thought that it would be entirely possible to implement such a system using one of those powered air fresheners that fires off (usually a revolting whiff of a chemical approximation to pine) automatically on a regular period.

The hardware hack
The first task was to buy one - a Glade Fresh Matic (not a ‘Sense and Smell’, which contains a motion sensor) - and take it apart.  With some a bit of poking about it became clear that all of the logic for the unit was built into a single chip on one of the boards.  A second board had the light sensor that feedback when the canister nozzle had been fully depressed, but everything else was hidden inside a single logic chip.  It did not look like I was going to be able to find an interface point to let me fire the spray from an Arduino.

Then I noticed that whenever the unit was switched on (using a simple slider switch exposed to the outside of the unit, the spray would always fire about 10 seconds later.  So if I could just control whether unit was on or off, I could cause it to spray when I wanted it to.  A little more poking (literally) about and a test with an ammeter proved to me that on/off switch was not a power switch but in fact a logic signal into the chip.  And even better, it required a LOW signal to activate so I wouldn’t even need to match signal voltages between the Glade unit and an Arduino.

Racism Stinks

The authorisation problem
As well as meeting the needs for Hannah’s racism training programme, I wanted to make my system as general purpose as possible.  I figured that it could be used at more public events, meetups and un-conferences.  In many of those circumstances it would not be appropriate to allow anyone on the Internet to fire off a pungent puff, so I needed some mechanism to say who should be allowed to fire the device.
I also wanted the management of the authorised user list to be done somewhere that doesn’t need technical Arduino knowledge to do.  It just wouldn’t work if to allow someone new to fire the spray you needed to reprogramme the Arduino.
The obvious choice seemed to be to use a twitter list.  These can be managed from any twitter client and are a logical place to manage a list of people whose tweets will be listened to (by the air freshener).  The only problem was that there did not seem to be an easy way to search a twitter list for a hashtag.
  • twitter’s search API only searches the whole twitter-sphere
  • ifttt.com doesn’t do any filtering over and above the twitter API
  • Yahoo! Pipes has some community connectors to Twitter but they kept falling over on me


The software service
My most viable option seemed to be to create a service that would do what I wanted. But I wanted it to be general

ised and reusable.  This meant that if I want others to use it I should be prepared for them to use it lots.  I needed to create the service on a platform that would scale and also not cost me anything when not in use.
The Google App Engine seemed ideal. Free for low usage, able to scale enormously if necessary.  Only problem - GAE doesn’t support any languages that I’ve written in before.  Never mind - how difficult can it be.
Surprisingly, for Python, the answer is not very.  In a few evenings I created a service that will search a twitter list for specified set of hashtags and return a true/false and a last tweet checked id.  It even allows someone to authorise it with twitter so that it uses their API request allocation (150 requests/minute) rather than one shared by the whole of the Google App Engine.

The Arduino bit
Obviously to be able to listen for and react to tweets, the Arduino needs access to the network.  I originally intended the Arduino to use a wifi shield so that, with a battery to power it, it would not need to be plugged in anywhere.  The cost of the wifi shields was just a little too much for me to fork out, so I went for a plain old ethernet shield.

After another few evenings poring over the Arduino library documentation and a lot of debug code, I created an Arduino sketch that does what I need it to:

  • initialised the Glade (makes sure it is not activated while the Arduino network is getting initialised)
  • initialises the Arduino network using the standard Ethernet/DHCP libraries
  • sits in a loop checking to see if it’s time to check twitter for updates again
  • calls the listhashtag service on appspot
  • capturing and remembering the last tweet check id (so we can ask for updates since then next time)
  • firing the spray if the hashtag was found.
  • rinse and repeat.

The Arduino code looks like this

#include <SPI.h>
#include <Ethernet.h>
 
byte mac[] = {  0x90, 0xA2, 0xDA, 0x00, 0x7A, 0x6D }; //must be set t the MAC address of your Ethernet Shield
 
int gladePin=9; // for testing purposes we're setting it to the LED pin
 
const unsigned long checkPeriod=60000UL;  // how often to check for tweets in mS
unsigned long checkAgainAt=millis()+checkPeriod;
char twitterCheckDomain[] = "listhashtag.appspot.com";
char twitterCheckPath[] = "/saulcozens/racismstinks/badracist,racismstinks";
 
void setup(){
  // start the serial library:
  Serial.begin(9600);
 
  initGlade();
  Serial.println("initGlade done");
 
  initTwitterCheck();
  Serial.println("initTwitterCheck done");
}
 
void loop() {
  for(;;) {
    if(millis() > checkAgainAt) {
      checkAgainAt=millis()+checkPeriod;
      if(twitterCheck(twitterCheckDomain, twitterCheckPath)) {
        activateGlade();
      }
      if(checkAgainAt < millis()) { // this happens when millis() is near to rolling over, once every 51 days or so
        delay(checkPeriod);
        checkAgainAt=millis();
      }
    }
  }
}
 
void initTwitterCheck() {
 
  // start the Ethernet connection:
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // no point in carrying on, so do nothing forevermore:
    for(;;) {}
  }
  // give the Ethernet shield a second to initialize:
  delay(1000);
}
 
char lastTweetChecked[22];
 
boolean twitterCheck(char* serverName, char* path) {
  EthernetClient client;
 
  Serial.println("connecting...");
  if(client.connect(serverName, 80)) {
    Serial.print("connected to ");
    Serial.println(serverName);
 
 
    String request = "GET ";
    request += path;
 
    if(lastTweetChecked[0] != 0) {
      request += "/";
      request += lastTweetChecked;
    }
 
    request += " HTTP/1.0\n";
    request += "Host: ";
    request += serverName;
    Serial.print("Requesting: ");
    Serial.println(request);
 
    client.print(request);
    client.println();
    client.println();
    String buffer;
    int i;
    while(client.connected()) {
      if(client.available()) {
        buffer += (char)client.read();
      }
    }
    client.stop();
 
    String status_response = buffer.substring(0, buffer.indexOf("\n"));
 
    int body_p = buffer.indexOf("[") ;
    String body = buffer.substring(body_p);
 
    if(body.indexOf('[')>=0 && body.indexOf(',')>=0 && body.indexOf(']')>=0) { // we got all the bits we're expecting
      body.substring(body.indexOf('\'')+1, body.lastIndexOf('\'')).toCharArray(lastTweetChecked, 21);
 
      return (body.charAt(body.indexOf('[')+1) == 'T');
    } else {
      Serial.print("unexepected response: ");
      Serial.println(body);
    }
  } else {
    Serial.print("Failed to connect with status:");
    Serial.println(client.status());
  }
}
 
 
void initGlade() {
  pinMode(gladePin, OUTPUT);
  digitalWrite(gladePin, HIGH);
}
 
void activateGlade() {
  digitalWrite(gladePin, LOW);
  delay(20*1000); // switch Glade on for 20 seconds
  digitalWrite(gladePin, HIGH);  // then turn it off again
}
 


Meet Mr Stinks
After staring at the Glade Fresh Matic for long evenings of debugging it seem to take on a bit of a personality.  Its acrid emissions were disturbing even when I was expecting them and its disapproving manner made me think that I needed to give it a stern and severe look.

I raided the kids craft box and cut up some gaffer tape, 10 minutes later Mr Stinks was here!

Racism Stinks

Comments