Physical Computing Group Project: Your Tweet Has Been Scent

For my physical computing class’s media controller group project, I worked with Gavin, Michael, and Yucef.  Our task was to build a controller that interacted with human interface and media.  For our project, we chose to work with web interaction, whereas other groups chose to work with video or sound or light.

Your Tweet Has Been Scent/Odoruino (unofficial name)

Our team has built a device that sprays out certain scents based on which friend or family member sends you a tweet.

Origin

Originally our team’s idea was to build a game.  We were thinking it might be like the prisoner’s dilemma game theory examples, where two people would go into a room they’d not seen the layout of and then try to figure out puzzles in order to escape.  Each person would have someone outside the room who would use a computer to interface with the person inside the room, via the web and Arduino.  The person in the room would not be able to see much feedback at all, especially not from his teammate, and he might find that the person outside the room was not his partner but in fact the opponent’s partner.  We had all kinds of crazy ideas, like the person inside the room having to use an Indiana Jones-like sceptre on a model temple to figure out where to go next.

In the end though, we could never figure out a way to make a project out of this.  We wanted to have an Arduino unlocking a wall in the maze or puzzle that the web user would need in order to progress, and for unlocks for items in the room for the person there.  But it just never progressed.

Working with Smell

Yucef came up with an idea to use smell, where if you received a tweet from someone you knew, you would smell the presence of their message for you, so you could employ your sense of smell in case you were using your other senses for other projects.  Or if you came home, the room would have a lingering smell that reminded you of someone close to you.  Yucef liked the idea that smells are so powerfully linked to memory.

This idea, we were far more quick to turn into something we knew how to build.  Gavin had worked with the Twitter API for his first project, and I had familiarity with the Twitter API and PHP.

We found the Glade Sense & Spray device in the store.  The device is actually pretty clever.  It has a motion sensor inside that detects movement and sprays when it senses something.  The canister top is depressed by a geartrain in the back of the housing, spraying out a scent very quickly.  It operates on 2 AA batteries.

By the end, we chopped off the front of the housing after removing the front lid, so we’d have more room to house 4 different diffusers.  We opened the device up and cut the wires leading to the mini breadboard with the motion sensor on it.  Then we soldered the toy motor (which powers the plastic geartrain) to our power and ground wires to plug in to our own breadboard to be powered with a 4-AA battery pack.

Here’s someone else’s teardown of the device.

I wrote a jQuery script originally to post values to a web page that the Arduino and its ethernet shield could connect to the internet and parse, in order to figure out which spray diffusers to activate.  The jQuery script would parse JSON results from Twitter’s URL-based search.  The problem, which of course I realized after finishing it, was that the code that you can write for Arduino to connect and process data of course doesn’t render a JavaScript model.  So the Arduino was trying to parse the JS code.  Duh!  So then I rewrote the script in PHP so that only the exact values we wanted outputted would show in the page.

This worked fairly well because PHP makes it just as easy to turn JSON into an array that it can use.  But I ended up having to put in a last-checked checker, because we didn’t want the Odoruino to see ALL past tweets, just the ones that were new from the last time it checked.  It took me a long time of debugging to figure out why my checker wasn’t working right.  It was a question of making sure each tweet was being checked against the last time-check — originally my for..loop was just a simple test of whether any new tweets had come in, but then each tweet wasn’t checked.  I’m guessing that makes zero sense to anyone.

Our code checked for an initial token, “*”, and an end token, “b” (just chosen randomly).  In between were our four digits, showing binary results.  For instance, “0101” meant that the 2nd and 4th diffusers had new data and would activate.

First Demo

We demo’d our project in class with just two scents (we could only find “bed linen” and “apple & cinnamon” scents in stores).  We made Twitter accounts for the user (“Dano”, i.e. Dan O’Sullivan, an ITP badass who was rumored to have patched into local Manhattan cable access TV) and for his mother and “Woz”, as in Steve Wozniak.  His mother would smell like linens while Woz would of course smell like Apple (he looks like he smells like an apple, doesn’t he?  so furry!).

@ Hey can you fix my QuickTime VR on my Mac? I broke it :-(
@ITP_Smell_Woz
ITP_Smell_Woz

Final Demo

Adding two more sensors was problematic because we didn’t increase the numSensors variable in our Arduino code until we found it, plus we had issues with power — we tried 2 AA’s and 4 AA’s and found the 2 didn’t draw enough, but the 4 were probably pushing too much power.  We ended up using 4 AA’s.  We experimented with multiple resistors till we found that a 10Ohm resister was the highest resistance we could add and still get the diffusers to be powered correctly.

Our circuits were basically thus: a diffuser would be linked to a resistor and battery pack on one side, while an output pin controlled it, along with a diode to keep power from flowing back into the toy motor and burning it out.

We also found that the diffusers would not work well once we sawed off the fronts, because the lower part of the diffuser no longer had any screw threading.  So Gavin and Michael had to wrap the bottom of the two sides of the casing tightly so that the plastic geartrain would not come loose when activated.

Design

We were thinking of using a big blue plushy Angry Bird, since the official Twitter plushies were too small to house our diffusers and the Arduino + breadboard.  Then we would cut off the tail of a plushy skunk and stick the diffusers in the bird’s butt, so that it would tweet-spray out of its butt.  Here’s a quick Photoshop of what it might have looked like.

But we ended up using a bird/owl lunchpack which was actually a tighter, more snug fit for our device.  We took a thin piece of wood and nailed some foam board onto one side of it, to provide a base for the diffusers above and a compartment below to hold the Arduino + ethernet shield and breadboard.  We cut away the top of the lunchpack to make an opening for the diffusers to spray.  We wanted it to be a neater job up top but there was barely enough room to fit all 4 diffusers so we ended up cutting the whole thing away.

We added two more accounts, GF (girlfriend) and Gram (grandma), which were the scents of Hawai’ian Breeze and Vanilla & Lavender, which I found at K-Mart.  So we had Dano being tweeted by Woz, his Mom, his GF, and his Gram.

You’ll notice that this is probably not something we’ll want to leave on the subway.

Finally, here’s the video of the device/Odoruino, taken in the ITP workshop:

Nerdcode

Code is below:

<?php

// converts Twitter's format (verbose) to UNIX timestamp
function fromApacheDate($date) {
	list($D, $d, $M, $y, $h, $m, $s, $z) = sscanf($date, "%3s, %2d %3s %4d %2d:%2d:%2d %5s");
	return strtotime("$d $M $y $h:$m:$s $z");
}

// gets JSON info from Twitter API
$jsonurl = "http://search.twitter.com/search.json?result_type=mixed&callback=?&q=ITP_Smell_Dano";
$json = file_get_contents($jsonurl,0,null,null);
$json_output = json_decode($json); // converts to PHP array

// need booleans to check if tweets happened, to set 0/1 values
$found1 = false;
$found2 = false;
$found3 = false;
$found4 = false;

// writeable .txt file holding last checked UNIX timestamp
$lastCheckTime = file_get_contents('notarealpathname.txt');

// converts Twitter time to UNIX timestamp
$convertedTwitterTime = fromApacheDate($json_output->results[0]->created_at);

// ethernet shield needs beginning marker for parsing
echo "*";

if ($lastCheckTime < $convertedTwitterTime) {
  foreach ($json_output->results as $result) {
  	if ($result->to_user == 'ITP_Smell_Dano') {
      if ($result->from_user == 'ITP_Smell_Mom' && $lastCheckTime < fromApacheDate($result->created_at)) {
        $found1 = true;
      }
      if ($result->from_user == 'ITP_Smell_Woz' && $lastCheckTime < fromApacheDate($result->created_at)) {
        $found2 = true;
      }
      if ($result->from_user == 'ITP_Smell_GF' && $lastCheckTime < fromApacheDate($result->created_at)) {
        $found3 = true;
      }
      if ($result->from_user == 'ITP_Smell_Gram' && $lastCheckTime < fromApacheDate($result->created_at)) {
        $found4 = true;
      }
    }
  }

	if ($found1 == true) {
  	echo "1";
	}
	elseif ($found1 == false) {
  	echo "0";
	}

	if ($found2 == true) {
  	echo "1";
	}
  elseif ($found2 == false) {
    echo "0";
  }

  if ($found3 == true) {
    echo "1";
  }
  elseif ($found3 == false) {
    echo "0";
  }

  if ($found4 == true) {
    echo "1";
  }
  elseif ($found4 == false) {
    echo "0";
  }

}
else {
	echo "0000";
}

// writes results to .txt file if newer tweet/s found
fwrite(fopen("itpsmell_last.txt", "w"), $convertedTwitterTime);

// ethernet shield needs ending marker for parsing
echo "b";

//print_r($json_output)

?>

Here’s the Arduino sketch (adapted from bildr’s helpful blog):

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

////////////////////////////////////////////////////////////////////////
//CONFIGURE
////////////////////////////////////////////////////////////////////////
byte ip[] = {x,x,x,x}; //ip address to assign the arduino

byte server[] = {x,x,x,x}; //ip Address of the server you will connect to

//The location to go to on the server
//make sure to keep HTTP/1.0 at the end, this is telling it what type of file it is
String location = "/notarealpath.php /HTTP/1.0";

//WAP subnet mask is 255.255.255.0
byte subnet[] = {
  255, 255, 255, 0 };

// if need to change the MAC address for a different arduino shield

byte mac[] = {
  0x90, 0xA2, 0xDA, 0x00, 0x6E, 0x2F };

Client client(server, 80); // port 80 is typical www page

////////////////////////////////////////////////////////////////////////

//these are the pins that the scent dispensers are connected to
int scentPins[] = {2, 5, 7, 9};

char inString[32]; // string for incoming serial data
int stringPos = 0; // string index counter
boolean startRead = false; // is reading?

void setup(){
  Ethernet.begin(mac, ip);
  Serial.begin(9600);

  //turn on scent pins
  for (int i = 0; i < 4; i ++){
    pinMode(scentPins[i], OUTPUT);
  }
}

void loop(){
  String pageValue = connectAndRead(); //connect to the server and read the output
  Serial.println(pageValue); //print out the findings.
  delay(10000); //wait 10 seconds before connecting again
}

String connectAndRead(){
  //connect to the server
  Serial.println("connecting...");
  if (client.connect()) {
    Serial.println("connected");
    client.print("GET ");
    client.println(location);
    client.println();
    //Connected - Read the page
    return readPage(); //go and read the output
  }
  else{
    return "connection failed";
  }
}

String readPage(){
  //read the page, and capture & return everything between '<' and '>'
  stringPos = 0;
  memset( &inString, 0, 32 ); //clear inString memory

  while(true){
    if (client.available()) {
      char c = client.read();

      if (c == '*' ) { //'*' is our begining character
        startRead = true; //Ready to start reading the part
      }
      else if(startRead){
        if(c != 'b'){ //'b' is our ending character
          inString[stringPos] = c;
          //check if the substring is 1 or 0
          if ( c == '1'){
            Serial.println(scentPins[stringPos] + " is the current string position");
            digitalWrite(scentPins[stringPos], HIGH);
            Serial.println("Sending HIGH on " + scentPins[stringPos]);
            delay(200);
            digitalWrite(scentPins[stringPos], LOW);
            Serial.println("Sending LOW on " + scentPins[stringPos]);
            delay(1000);
          }
          else {
            Serial.println("This pin will not be turned HIGH");
          }
          stringPos++;
        }
        else{
          //got what we need here! We can disconnect now
          startRead = false;
          client.stop();
          client.flush();
          Serial.println("disconnecting.");
          return inString;
        }
      }
    }
  }
}