Make Your Own Instavision

Recently I built the backend for the Instavision TV and wanted to give a quick tutorial on how to build one yourself. Brian Fouhy, George Tong, Jesse Weaver and Pedro Sorrentino actually made this TV a reality.

Materials

  • Retro looking TV
  • VGA to Coaxial adapter
  • Old Laptop
  • Instagram account
  • Instagram API Client ID, Client Secret and an Access Token
  • Node.js
  • Shell access to your server

Getting Started…

The first step is to get the backend working. Register a new client on the Instagram API, just register for the url you’ll be using to setup your feed.

Next you need to store an access token to use later on in our node.js file. Once you have your Client registered with Instagram, simply input the following into your browser’s url (replacing with your newly registered client ID and your specific redirect URI):

https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token

Once you visit this page, simply login with your instagram account credentials and you should be redirected. You don’t necessarily need to have your redirect URI do anything when you land on it, what your interested in is the access token.

Copy this access token and save it for later, we’ll use it to gain access to Instagram and pull information for our TV to display.

Coding…

Lets get our code up and running. The basic idea is to have a client side page calling our server about every 5 seconds to get an update on the most recent photo. We’ll then also need something on our server to go out and get the most recent photos from Instagram, this script will also run about every 5 seconds. It would be hard to do this with PHP, sleep() and a chron job…so we’ll turn to node.js. Copy the following script and put it in the root of your public_html folder (make sure you replace your access token with the access token you got in the previous step also, you’ll need to make sure and replace all paths and urls with your own custom paths and domains).

/*
	INSTRUCTIONS

	shell: cd /home/instavis/public_html/
	shell: node instavision.js
	shell: kill ##### (process number)

	call the url: http://instavision.tv:8080/feed.html

	Needs:
			find out rate limit with instagram = 5000 requests per hour
*/
var sys = require("sys"),
    http = require("http"),
    https = require("https"),
    url = require("url"),
    path = require("path"),
    fs = require("fs"),
    util = require("util"),
    events = require("events");  

function load_static_file(uri, response) {
    var filename = path.join(process.cwd(), uri);
    path.exists(filename, function(exists) {
        if(!exists) {
            response.writeHead(404, {"Content-Type": "text/plain"});
            response.write("404 Not Foundn" + " filename: " + filename);
            response.end();
            return;
        }  

        fs.readFile(filename, "binary", function(err, file) {
            if(err) {
                response.writeHead(500, {"Content-Type": "text/plain"});
                response.write(err + "n");
                response.end();
                return;
            }  

            response.writeHead(200);
            response.write(file, "binary");
            response.end();
        });
    });
}

//var twitter_client = http.createClient(80, "api.instagram.com");  

var tweet_emitter = new events.EventEmitter();  

function get_tweets() {
    //var request = twitter_client.request("GET", "/v1/tags/bdwinstavision/media/recent?access_token=3036112.f59def8.4708bbe9fe9e43639c8d9fe93c5c41df", {"host": "api.instagram.com"}); 

    var options = {
    // public search - unauthenticated
	//host: 'api.instagram.com',
	//path: '/v1/tags/bdwinstavision/media/recent?access_token=3036112.f59def8.4708bbe9fe9e43639c8d9fe93c5c41df',

	// authenticated feed - https://api.instagram.com/v1/users/self/feed?access_token=3036112.c90c34d.411d18988857490c9032a2bac30e6943

	// instavisionTV access token
	// access_token = 3036112.c90c34d.411d18988857490c9032a2bac30e6943

	// instavision access token
	// access_token = 3060511.0b03a1e.fff65e339b694cf0844306131ab82b97
	host: 'api.instagram.com',
	path: '/v1/users/self/feed?access_token=3060511.0b03a1e.fff65e339b694cf0844306131ab82b97',
    } 

    https.get(options, function (res) {
		var raw = "";
		res.on('data', function (chunk) {
		    raw += chunk;
		});
		res.on('end', function () {
		    var response = JSON.parse(raw);

		   //sys.puts(response); 

		    if(raw.length > 0) {
	/*         	sys.puts("tweets:" + response);  */
	            tweet_emitter.emit("tweets", response);
	        }
	        //res.removeListener('connection', callback);
		});
    }).on('error', function(e) {
	  //console.error(e);
	  sys.puts(e);
	});

    /*request.addListener("response", function(response) {
        var body = "";
        response.addListener("data", function(data) {
            body += data;
            sys.puts("tweets:" + data);
        });  

        response.addListener("end", function() {
            var tweets = JSON.parse(body);
            sys.puts(body);
            if(tweets.length > 0) {
            	//sys.puts("tweets:" + tweets);
                tweet_emitter.emit("tweets", tweets);
            }
        });
    }); */ 

    //request.end();
}

setInterval(get_tweets, 5000);

http.createServer(function(request, response) {
    var uri = url.parse(request.url).pathname;
    //var uri = request.url;

    var callback = function(stream) {
	  sys.puts('listener removed!');
	};

    if(uri === "/stream") {  

        //var listener = tweet_emitter.addListener("tweets", function(tweets) {
        var listener = tweet_emitter.on("tweets", function(tweets) {
            response.writeHead(200, { "Content-Type" : "text/plain" });

            response.write(JSON.stringify(tweets));

            //sys.puts("stringify tweets:" + JSON.stringify(tweets));

            response.end();

            //var listenersForTeets = tweet_emitter.listeners(listener);

        	//sys.puts(util.inspect(listener));

        	//tweet_emitter.removeListener(tweets);
        	//tweet_emitter.removeListener(tweet_emitter);
        	//tweet_emitter.removeListener(listener);
        	//tweet_emitter.removeListener("tweets");
  			//tweet_emitter.removeListener("listener");
  			//tweet_emitter.removeListener("tweet_emitter");

  			listener.removeAllListeners("tweets");

            clearTimeout(timeout);  

        });

        //tweet_emitter.removeListener(tweets);

        //sys.puts(util.inspect(tweet_emitter.listeners(listener), true, null));

        var timeout = setTimeout(function() {
            response.writeHead(200, { "Content-Type" : "text/plain" });
            response.write(JSON.stringify([])); 

            response.end();  

            tweet_emitter.removeListener(listener);
        }, 10000); 

    }
    else {
        load_static_file(uri, response);
    }
}).listen(8080);  

sys.puts("Server running at http://instavision.com:8080/");

Note: I am pulling a user feed in this case, you can also do a search for specific photos and other users’ feeds if you call different urls. For more information read the Instagram API.

Once you have this copied and named instavision.js in your root, you need to make an html page with a special script to call instavision.js and get the most recent photos. Copy the following and save this into a file named feed.html, also in the root of your public_html folder:

<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link href="css/styleDark.css" rel="stylesheet" type="text/css">
<title>Instavision TV</title>
</head>

<body style="">
    <div id="wrap">

    	<div id="tweets"></div>
    </div>

	<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
	<script type="text/javascript">
	    var tweet_list = $("#tweets");
	    var tweet_caption = $("#caption"); 

	    function load_tweets() {
	        $.getJSON("/stream", function(tweets) {
	            var imageurl = tweets.data[0].images.standard_resolution.url;
	            var imageUser = tweets.data[0].user.username;
	            var imageCaption = '';
	            if (tweets.data[0].caption != null){
	            var imageCaption = tweets.data[0].caption.text;
	            }

	            console.log(imageUser);
	            console.log(imageCaption);
	            imageurl = "<img id='tweet' src='"+imageurl+"' /><br/>";
	            imageCaption = "<div id='caption'><img id='captionImage' src='/images/ColorBars.png' height='18px' width='29px'/><h1>"+imageUser+"</h1><h3>"+imageCaption+"</h3></div>";
	            console.log(imageurl);
	            $(tweet_list).html(imageurl + imageCaption);
	            load_tweets();
	        });
	    }  

	    setTimeout(load_tweets, 5000); 

	    $(document).ready(function() {
	    	load_tweets();
	    });
	</script>

</body>
</html>

Once you have feed.html saved, you can try it out! You have to run the instavision.js script as a process on your server, simply run these commands to get it running:

cd /home/instavis/public_html
node instavision.js&

Make sure you have node installed. If you find that you want this process to stop, look at its process ID and kill it by:

kill 76234

Once you have the process running, visit the feed.html page at port 8080 (place the url with your custom url):

http://your-custom-domain.com:8080/feed.html

If it worked, then you’re good to go! If it didn’t, leave a comment below and I’ll try to help out!

Once you have it up and running, its simply a matter of plugging the TV in and formatting the image, which is totally a case by case situation and thus beyond the scope of this tutorial.

DIY Parking Sensor Tutorial

The last time I was home visiting my parents I noticed bumper imprints caused by my mother suburban on the stairs leading up from the garage. Their garage it turns out is just barely long enough to fit their gigantic vehicles. So I decided it would be nice to have some visual cue for parking. Out came the arduino and a sonar range finder from Radio Shack and the result was this tutorial.

Materials…

  • Arduino (I had a duemilanove available)
  • Ultrasonic Range Finder
  • Wire
  • Small box
  • 9V power supply (You can find old power supplies for cheap at thrift stores)
  • Tri Color LED
  • Hot Glue Gun
  • Breadboard

Assembly…

  1. Hot glue the arduino to the bottom of the box and run the power supple to it
  2. Connect the 5V and Ground wires to the range finder.
  3. Connect the Pulse cable from the range finder to a “PWM” input on the arduino (this is necessary because we’ll be sending pulses through the same leed that we listen for a return on). I used digital pin 7 with PWM for the pulse connection.
  4. Test the tri color LED to find out which connectors make which colors. You will need to keep track of which wire creates which colors and connect them to three digital pins on the arduino. Keep track of the pin numbers. For instance, I connected to digital pins 11, 12 and 13 with red, green and blue respectively.
  5. Once you have everything connected we’ll start writing the program. After your finished with the programming and you’re sure it works, it’s a good idea to seal everything up in the box to make sure none of the wires get disconnected before you mount the sensor to your wall.

Programming…

Luckily, Arduino already provides an example of how to use the pulse sonar sensor. Select the File -> Examples -> Sensors -> Ping example in the Arduino programming kit. Once open, select the code and copy it to a blank sketch. Save the new project under sketches I used the name “parking Example”.

Now we have something to work with. Begin customizing the code. First, we are only going to be measuring inches, not cm so lets comment out the code snippet about halfway down that runs a function to calculate cm. Comment out:

//cm = microsecondsToCentimeters(duration);

Next we don’t need to send sonar pings out at such a high interval. We only need to ping about every second because the car will (hopefully) be moving slowly into the garage. So at the bottom of the loop function set the delay to 1000:

delay(1000);

Next we need to tell arduino which pins we’ll be using for our LED output. At the top where we have:

const int pingPin = 7;

We’ll add:

pinMode(13, OUTPUT); // blue
pinMode(12, OUTPUT); // green
pinMode(11, OUTPUT); // red

Now that ardiuno has a setup for those pins, we’ll need to send a signal whenever we want that color to show. So After receiving the signal from our ping, we’ll compute the distance and if it falls within certain ranges we’ll show a specific color. I want the driver to see green until they get within 24 inches of the wall, at that point, I want the light to turn blue, signaling that they are getting closer. Then when they are within 6 inches of the wall, the red light should turn on, indicating to the driver that they should stop.

Include the following code below the inches distance calculation:

inches = microsecondsToInches(duration);
// show LED colors
  if(inches > 0 && inches <= 6) {
    // show red, all other leds are off
    digitalWrite(13, LOW);
    digitalWrite(12, LOW);
    digitalWrite(11, HIGH);
  } else if(inches <= 24 && inches > 6) {
    // show blue, all other leds are off
    digitalWrite(12, LOW);
    digitalWrite(11, LOW);
    digitalWrite(13, HIGH);
  } else {
    // show green led, all other leds are off
    digitalWrite(13, LOW);
    digitalWrite(11, LOW);
    digitalWrite(12, HIGH);
  }

This code tells arduino to send a signal to a specific pin when an object comes within a certain distance. The entire script should look like:

/* Ping))) Sensor
  
   This sketch reads a PING))) ultrasonic rangefinder and returns the
   distance to the closest object in range. To do this, it sends a pulse
   to the sensor to initiate a reading, then listens for a pulse 
   to return.  The length of the returning pulse is proportional to 
   the distance of the object from the sensor.
     
   The circuit:
	* +V connection of the PING))) attached to +5V
	* GND connection of the PING))) attached to ground
	* SIG connection of the PING))) attached to digital pin 7

   http://www.arduino.cc/en/Tutorial/Ping
   
   created 3 Nov 2008
   by David A. Mellis
   modified 30 Jun 2009
   by Tom Igoe
 
   This example code is in the public domain.

 */

// this constant won't change.  It's the pin number
// of the sensor's output:
pinMode(13, OUTPUT); // blue
pinMode(12, OUTPUT); // green
pinMode(11, OUTPUT); // red
const int pingPin = 7;

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
}

void loop()
{
  // establish variables for duration of the ping, 
  // and the distance result in inches and centimeters:
  long duration, inches, cm;

  // The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
  // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
  pinMode(pingPin, OUTPUT);
  digitalWrite(pingPin, LOW);
  delayMicroseconds(2);
  digitalWrite(pingPin, HIGH);
  delayMicroseconds(5);
  digitalWrite(pingPin, LOW);

  // The same pin is used to read the signal from the PING))): a HIGH
  // pulse whose duration is the time (in microseconds) from the sending
  // of the ping to the reception of its echo off of an object.
  pinMode(pingPin, INPUT);
  duration = pulseIn(pingPin, HIGH);

  // convert the time into a distance
  inches = microsecondsToInches(duration);
  // show LED colors
  if(inches > 0 && inches <= 6) {
    // show red, all other leds are off
    digitalWrite(13, LOW);
    digitalWrite(12, LOW);
    digitalWrite(11, HIGH);
  } else if(inches <= 24 && inches > 6) {
    // show blue, all other leds are off
    digitalWrite(12, LOW);
    digitalWrite(11, LOW);
    digitalWrite(13, HIGH);
  } else {
    // show green led, all other leds are off
    digitalWrite(13, LOW);
    digitalWrite(11, LOW);
    digitalWrite(12, HIGH);
  }
  //cm = microsecondsToCentimeters(duration);
  
  Serial.print(inches);
  Serial.print("in, ");
  Serial.print(cm);
  Serial.print("cm");
  Serial.println();
  
  delay(1000);
}

long microsecondsToInches(long microseconds)
{
  // According to Parallax's datasheet for the PING))), there are
  // 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
  // second).  This gives the distance travelled by the ping, outbound
  // and return, so we divide by 2 to get the distance of the obstacle.
  // See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
  return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds)
{
  // The speed of sound is 340 m/s or 29 microseconds per centimeter.
  // The ping travels out and back, so to find the distance of the
  // object we take half of the distance travelled.
  return microseconds / 29 / 2;
}

All that’s needed after that is upload the code to your arduino, mount the device to the front wall of your garage and hang the led somewhere where the driver can see it! Let me know how these instructions work for you. I can always adjust this tutorial later.