Skip to content

Recent Articles

Roll your own Tasteful Twitter Ticker

Twitter’s search widget is really nice. So nice that I use it unmodified on the right side of the front page of pr.ogra.ms. You’re supposed to put it on the sidebar of your blog and it will show a list of a specified number of past tweets vertically. You can have it display any user’s tweets or specify search terms. It has a limited number of other options which are enough for most applications. Sometimes you need something a little different though.

I’ve been working on a project for which a long list of past tweets might not be the best for visibility, layout or usability. The Photobot 3000 is a portable photo booth without the booth. It’s a small box that you set up facing a wall with chairs and a green screen. Party guests sit in the chairs and hit the big red button on the front of the box. A series of lights counts down and the box takes their picture. All of the pictures are then projected onto a screen for everyone to see.

The projected image is a browser running a web page in full screen mode. For certain events, we also want to show tweets adjacent to the photos using a custom hashtag. We’ve been using Twitter’s widget and it’s been working great, but there are a few things that could make it better.

First, the text is very small, making it hard to read from far away. If we’re at a venue that doesn’t have a proper screen and are instead projecting the images on a non-homogenous wall, the tweets are even harder to read. If people can’t read the tweets, they’ll be less likely to use the hashtag themselves and the Twitter widget will just become one big waste of space.

Second, having the list of tweets displayed vertically to the right of the photos limits how large we can display the photos. We need to make sure that the Twitter widget adds to the experience and doesn’t detract from the important portion of the display: the images.

Third, there are some filters that we’d like to put on the tweets to be displayed that the default Twitter widget doesn’t support. For example, we’d like to only show tweets from the same day as the event.

So I decided to start playing around with getting data from Twitter and try to see if I could create my own Twitter widget that only shows one tweet at a time and position it right below each photo.

Before we continue, let me just say that I’ve been skittish about using ticker-type displays ever since the introduction of the second most hated HTML tag: <marquee> (second to <blink>). But I think I’ve made a reasonable case for a Twitter ticker widget as long as I keep it tasteful.
I promise it’ll be better than this!

Twitter’s really easy

It turns out that Twitter is really easy to work with if you’re just reading public tweets. They expose an interface to get search results in JSON format without having to log-in. If you add in the ease of working with JSON data which jQuery provides (which you should because jQuery is awesome,) the code to get Twitter search results becomes pretty trivial:

function getTweets()
{
	var hashTag = "%23flyers";
	var maxTweets = 99;

	var JSONQuery = "http://search.twitter.com/search.json?q=" 
		+ hashTag + "&callback=?&rpp=" + maxTweets;

 	$.getJSON(JSONQuery, getTweetCallback);

}
function getTweetCallback(ob)
{
	tweets = ob;
}

This code’s pretty straightforward. I’m asynchronously calling the Twitter search with an arbitrary hashtag (%23 is the octothorpe character.) When the request is complete, the getTweetCallback method is invoked. The rpp option stands for Results Per Page. The maximum is 100 which is fine for my uses, but if you need more than that, you’ll have to call the JSON search multiple times with the paging query options and aggregate the results.

Ok, but what do we do with it?

So we’ve got all of the tweets, now what do we do? the variable ob now contains a member called results which is an array of tweets. Each element has a bunch of data about each tweet, but the members that we’re going to use are profile_image_url, from_user, and text. So for example, to access the text of the latest tweet, you would do:

ob.results[0].text

It’s not enough to just show the latest tweet though. We want to cycle through the list, showing each tweet for a few seconds. For this, we’re going to use the Javscript’s setTimeout function. setTimeout sets a one-time timer and after the prescribed duration invokes the specified callback method.

timer = setTimeout(showNext, interval);

The final piece of the puzzle is making it look pretty. Another reason that jQeury is a joy to use is jQuery UI. jQuery UI adds a method called .toggle that shows or hides an HTML element with a few different options for visual effects. The effects look great and are really easy to invoke.

var toggleType = "blind"
$( "#TwitterTicker" ).toggle(toggleType, options, "slow");

Pulling it all together, we have the following Javascript:

function getTweets()
{
	refreshNum = 0;

	if(timer)
		clearTimeout(timer);

	var JSONQuery = "http://search.twitter.com/search.json?q=" 
		+ hashTag + "&callback=?&rpp=" + maxTweets;

 	$.getJSON(JSONQuery, getTweetCallback);

}
function getTweetCallback(ob)
{
	tweets = ob;
	showNext();
}
function showNext()
{
	if(tweets.results.length > refreshNum+1)
 	{
 		var toggleType = "blind";
 		var options = {}

 		//Calling toggle from jQuery UI to 
 		//make the previous tweet invisible.
		$( "#TwitterTicker" ).toggle(toggleType, options, "slow", 
		function(){

 			//Using the tweet data
 			var text = 
			   "<img style='vertical-align:middle' src='"
			   + tweets.results[refreshNum].profile_image_url 
			   + "'/> <a href='twitter.com'>"
			   + tweets.results[refreshNum].from_user
			   + "</a> "
			   + tweets.results[refreshNum].text;
			
			$( "#TwitterTicker" ).html(text);

 			if(refreshNum >= maxTweets)
 				getTweets();
 			else	
 				refreshNum = refreshNum + 1;

 		});


		 //Calling toggle from jQuery UI to
 		 //make the new tweet visible
		 $( "#TwitterTicker" ).toggle(toggleType, options, "slow");


		 //Setting the timeout timer
		 timer = setTimeout(showNext, interval);
	 }
	 else
	 	getTweets();
}

The only slightly tricky part here is that I’m setting the text of the div element TwitterTicker in the callback for the toggle method that hides the div. This is so that the text of the tweet doesn’t change until the div is completely hidden. Here’s what it looks like:

One more thing

One more thing. setTimeout has this weird behavior on many browsers where if you switch tabs or the tab otherwise loses focus, when you return the browser tries to “catch up”. If you leave this code as-is, you may see a bunch of tweets flashing by quickly if you leave the tab and then come back. That’s pretty undesirable behavior here, so I added this code to the end of the Javascript file:

 $(document).blur(function(e) {
 	clearTimeout(timer);
 });
 $(document).focus(function(e) {
  	if(timer)
 	 	setTimeout(showNext, interval);
 }); 

This code disables the timer when the tab loses focus and restarts it when it gains focus again. The timer will start over at zero seconds when the focus comes back, but it’s better than playing catch up!

You can download the Javascript source here and an example html file here!

No Comments

Turning it up to 12

I bought a nice Samsung flat screen from Best Buy during a Black Friday sale and have been mostly happy with it. The built-in speakers are terrible though. Don’t get me wrong, I’m no audiophile. I went on a bus tour of Toledo two years ago and still use the free headphones they gave us. The sound on my TV was worse though.
DSC_0004
I lived with it for a while, but eventually decided that I needed to give myself an auditory experience that matched the visual experience of watching my new TV. I had heard good things about the Bose Companion 3 Series II multimedia speaker system, so I grabbed it on sale from Target.

I knew the speaker system was meant for a computer, but didn’t think it made any difference. My TV has an audio-out jack, the speakers have an audio-in jack, what could go wrong? I got the speakers home and connected them to the TV. The sound was amazing. My ears ached retroactively from the abuse they had been subjected to from the built-in speakers. Plugging in the new speakers turned Snooki into a virtuoso, Walter White into Louis Armstrong, and the Bridalplasty contestants into sirens.

There was a problem though. When you hook up external speakers to my TV, the TV assumes that the speakers will now control the volume and locks you out of its volume control.
IMG_7381
That would be fine if the speakers could recognize the volume up and volume down buttons on the TV remote, but this particular set of speakers is geared toward computers and has no IR input whatsoever. In place of an IR reciever it has a big standalone knob, which as an input device is pretty awesome, but you can’t use it remotely.

Turning it up to 12

So for months, I would set the volume to a middle level and only change it in dire situations. It’s only a foot away from the sofa, but it might as well be inside of Yucca Mountain.

This was all just a minor inconvenience, but I would think about the problem a lot. I wasn’t really trying to solve the issue, I was just using it as a brain teaser. I would try to come up with the most Goldbergian contraption I could. The funniest one was something that intercepts the volume signals from the normal TV remote and then turns a gear that turns the big knob in a direction that corresponds to which volume button you pressed. The conversion of mechanical signal (button press) to IR signal to mechanical signal (gear turning) to electrical signal (from the knob to the speakers) to audio signal (change in volume) seemed hilariously complicated.

Plan

It would have stayed just a funny idea if I hadn’t started playing around with an Arduino. Arduinos are tiny programmable microcontrollers that work really well with any sort of electronic component. They use the Processing language which is super simple and fun to code in. Making this crazy contraption was the perfect project to get me started with the Arduino. I ran down to the local Radio Shack and bought the two main components of the system: an IR reciever module and a Servo (servo being a fancy word for turny thing) and got started.

Getting the IR module to respond to my remote

Getting the IR module to work was not as hard as I thought it would be. I basically used an amazing blog post as the outline for my code. I added some Serial.print statements into the pulse_to_bits method so that I could see the codes that the receiver was getting from my particular remote. I ran the code with the Arduino IDE’s built-in Serial Monitor and was able to find the codes for both the Volume Up button and the Volume Down button on my Samsung remote.
Serial Monitor
The real bit sequence is actually much longer, but the length I collected seems to be enough to be basically unique. At least in my living room.

Getting the servo to turn the right amount in the right direction

Getting the servo turning took a little bit more work. Arduino has a Servo library called SoftwareServo and there’s another Arduino Servo library on Google Code, but neither of them worked with the servos I had lying around. So not knowing anything about servos, I did some extensive research and more than a little trial and error to come up with the following code.

void rotateServo(int servoPin, boolean clockwise, int duration)
{
  int pulse = clockwise?1200:1800;
  unsigned long endTime = millis() + duration;
  
  int i = 1;
  while(endTime > millis())
  {
    digitalWrite(servoPin,HIGH);
    delayMicroseconds(pulse); 
    digitalWrite(servoPin,LOW);
    delay(20); 
    i++;
  }
}

If you send the servo I was working with a stream of ones pulsed at 1200 milliseconds, it will turn clockwise. If you send the same digits but with an 1800ms pulse, it will turn counterclockwise. I sincerely hope someone else looking for this information finds this page, it will save them a ton of time.

Tying it all together

Once I had the IR receiver and the servo figured out, I made this circuit.
IMG_7394
Here is a video of it in action.

And this is the processing code that I uploaded to the Arduino.

#include <Servo.h>;
// 0.1 by pmalmsten
// 0.2 by farkinga
//This code is mostly verbatim from this blog post 
//http://goo.gl/R3Zm8 with the primary modifications
//being the addition of a servo and the magic bit strings
//that represent the volume-up and volume-down buttons 
//on my Samsung TV remote.


#define IR_BIT_LENGTH 32
#define BIT_1 1000          //Binary 1 threshold (Microseconds)
#define BIT_0 400           //Binary 0 threshold (Microseconds)
#define BIT_START 2000      //Start bit threshold (Microseconds)
#define DEBUG 1             //Serial connection must be started
                            //to debug

#define IR_PIN 0            //Sensor pin 1 wired through a 
                            //220 ohm resistor

#define LED_PIN 9           //"Ready to Recieve" flag, not
                            //needed but nice
#define POWER_PIN 11       // the red LED that indicates 
                           //if the power button is pressed.

#define SERVO_PIN 9

#define SERVO_INCREMENT 50

int runtime_debug = 0;
int output_key = 1;
int power_button = 0;

int servoPos = 0;

//IR Codes for my remote
int samsungVolumeUp[] = {1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,1};
int samsungVolumeDown[] = {1,1,1,0,0,0,0,0,1,1,1,0,0,0,0,0,1,1,0,1,0,0,0,0,0,0,1,0,1,1,1,1};

Servo volumeKnob;

void setup() {
  pinMode(IR_PIN, INPUT);
  
  volumeKnob.attach(SERVO_PIN);
  volumeKnob.write(servoPos);
  delay(100);
  volumeKnob.detach();
  
  Serial.begin(9600);
}

void loop() {

  int bitStream[IR_BIT_LENGTH];
  
  get_ir_key(bitStream);		    //Fetch the key
  do_response(bitStream);
  
  delay(200);
}

void do_response(int key[])
{
  if(intArraysAreEqual(key, samsungVolumeUp))
  {
    Serial.println("Smasung Volume UP!");
    rotateServo(SERVO_PIN, true, SERVO_INCREMENT);

  }
  else if(intArraysAreEqual(key, samsungVolumeDown))
  {
    Serial.println("Samsung Volume DOWN!");
    rotateServo(SERVO_PIN, false, SERVO_INCREMENT);

  }
  else
    Serial.println("Unrecognized Key.");
    
  
}

void read_pulse(int data[], int num_bits)
{
  for (int i = 0; i < num_bits; i++)
  {
    data[i] = pulseIn(IR_PIN, HIGH);
  }
}

void pulse_to_bits(int pulse[], int bits[], int num_bits)
{
  
  if (DEBUG || runtime_debug) { Serial.println("-----"); }

  for(int i = 0; i < num_bits ; i++)
  {
    //if (DEBUG || runtime_debug) { Serial.println(pulse[i]); }

    if(pulse[i] > BIT_1) //is it a 1?
    {
      bits[i] = 1;
    }
    else if(pulse[i] > BIT_0) //is it a 0?
    {
      bits[i] = 0;
    } 

    else //data is invalid...
    {
      Serial.println("Error");
    }
    Serial.print(bits[i]);
  }
  Serial.println();
  
}

void get_ir_key(int bits[])
{
  int pulse[IR_BIT_LENGTH];

  do {} //Wait for a start bit
  while(pulseIn(IR_PIN, LOW) < BIT_START);

  read_pulse(pulse, IR_BIT_LENGTH);

  pulse_to_bits(pulse, bits, IR_BIT_LENGTH);


  
}
void printBits(int bits[])
{
  for(int i = 0; i < IR_BIT_LENGTH; i++)
    Serial.print(bits[i]);
  Serial.println();
}
boolean intArraysAreEqual(int Array1[], int Array2[])
{ 
  
  for(int i = 0; i < IR_BIT_LENGTH; i++)
  {
    if(Array1[i] != Array2[i])
      return false;
  }
  
  return true;
}
void rotateServo(int servoPin, boolean clockwise, int duration)
{
  int pulse = clockwise?1200:1800;
  unsigned long endTime = millis() + duration;
  
  int i = 1;
  while(endTime > millis())
  {
    digitalWrite(servoPin,HIGH);
    delayMicroseconds(pulse); 
    digitalWrite(servoPin,LOW);
    delay(20); 
    i++;
  }
}
No Comments