How to control a LED strip with Twitter

Several months after my Twitterdoorbell adventures I decided it was time for a new (not so) useful Arduino project. After I bought 5 meters of RGB LED strip ("IClite Digital RGB-LED Flex Band") at the Aldi in Germany and successfully connected this to an Arduino Uno, I knew it's destiny was to hang in my christmas tree in December to act as Twitter controlled christmas lights.

Usage

To change the color of the LED strip, a twitter user has to send a tweet with hashtag #twitmaslights, followed by a colon (:) and a six digit hexadecimal number (red-green-blue). For example: #twitmaslights:ff0000 would make the LEDs turn red.

Connecting the RGB strip to an Arduino Uno

(Source: http://www.tns-labs.org/iclite-digital-rgb-led-flex-band-aldi-arduino/ [German])

Because the Aldi LED strip doesn't work with the AdaFruit Neopixel library out of the box, I needed to use this modified library.

The Aldi LED strip comes with a 12V power supply which connects to a controller. This is a little white box which has 4 outputs: V (VCC), G (ground), another G (ground) and D (data), which all connect to the four corresponding inputs (called VCC, GND, GND and DI) on the LED strip.

To use the Arduino instead of the supplied one as a controller, you have to connect it in the folowing way:

  • The DI connection on the LED strip goes to pin 6 on the Arduino.
  • The GND connection next to DI on the LED strip goes to GND on the Arduino.
  • Because the Arduino Uno can only supply 5V, you have to leave the VCC and the GND next to it connected to the original controller.

Hopefully the following picture makes this a bit clearer. (Notice that this is an Arduino Uno with a WiFi shield on top of it.)

IMG_20131028_213604

To test the LED strip, just use the "Strandtest" example that comes with the Neopixel library.

Checking Twitter for a specific hastag

Been there, done that! But to ensure that you stay on the current page, I will explain it again 🙂

Using the Twitter API to search for specific tweets used to be a simple matter of an http GET request which you could paste in the url field of your browser. But nowadays you must use 0auth to connect to the API and use something called Apllication-only authentication. The drawback of this method is that you have to connect to the Twitter servers through SSL. Unfortunately, an Arduino Uno is too slow and does not have enough resources to be capable of this task.

I came up with the following solution: I created a PHP script on my own web server which makes the request to search for a specific tweet. Then the PHP script parses the JSON response from Twitter and outputs the search result on a web page: twitmaslights.crutzen.eu. The only thing the Arduino does is connecting to this page and read the output: the tweet which was searched for.

This is the PHP script:

 "",
    'oauth_access_token_secret' => "",
    'consumer_key' => "",
    'consumer_secret' => ""
);

/** Perform a GET request and echo the response **/
/** Note: Set the GET field BEFORE calling buildOauth(); **/
$url = 'https://api.twitter.com/1.1/search/tweets.json';
$getfield = '?q=%23twitmaslights&count=1&include_entities=false';
$requestMethod = 'GET';
$twitter = new TwitterAPIExchange($settings);
$json = $twitter->setGetfield($getfield)
             ->buildOauth($url, $requestMethod)
             ->performRequest();
//echo $json;
$obj = json_decode($json, true);
$tweet = $obj["statuses"][0]["text"];
echo "Tweet: $tweet
"; $user = $obj["statuses"][0]["user"]["screen_name"]; echo "User: @$user"; ?>

This script outputs the most recent tweet with hashtag #twitmaslights betweet <tweet> tags and the user who sent the tweet between <user> tags. It uses the TwitterApiExchange class, which you can find here.

So, for example, the page on my web server outputs the following

Change color to red! #twitmaslights:FF0000
@RalpCrutzen

Now it's the Arduino's task to connect to twitmaslights.crutzen.eu, look for the text between the <tweet> and <user> tags, filter the hex color code from the tweet and change the color of the LED lights accordingly.

The Arduino part

As mentioned before, I use an Arduino Uno with a WiFi shield. I had issues connecting to a server with version 1.0.5 of the Arduino IDE. But using IDE 1.0.3 instead solved this problem.

I'm going to explain the sketch piece by piece. If you want to download the sketch as a whole, you can do this here.

/*
 Twitmaslights
 
 Control an Adafruit NeoPixel strip via Twitter.
 
 created 27 October 2013
 by Ralph Crutzen for the @twitmaslights project
*/

#include 
#include 
#include 
#include 

First, we include the necessary libraries. TextFinder is used to search for a specific string in a stream. You can download it here. The AdaFruit NeoPixel library can be found here. If you, just like me, use the German Aldi LED strips, you have to use this library. Put the downloaded folders in the sketchbook/libraries folder.

#define PIN 6

// Parameter 1 = number of pixels in strip
// Parameter 2 = pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(49, PIN, NEO_GRB + NEO_KHZ800);

The last line is the declaration of the strip object. The first parameter of the constructor is the number of LEDs in the strip. (Because I messed up my own strip a little, I had to cut out a piece and now I only have 49 lights left.) The second parameter is the pin number (6) to which the strip's DI input is connected to.

char ssid[] = ""; //  your network SSID (name)
char pass[] = "";    // your network password (use for WPA, or use as key for WEP)

int status = WL_IDLE_STATUS;
// if you don't want to use DNS (and reduce your sketch size)
// use the numeric IP instead of the name for the server:
//IPAddress server(74,125,232,128);  // numeric IP for Google (no DNS)
char server[] = "twitmaslights.crutzen.eu";

// Initialize the Ethernet client library
// with the IP address and port of the server
// that you want to connect to (port 80 is default for HTTP):
WiFiClient client;

These variable declarations are used to make a connection with the WiFi shield. I copied the most of the code that handles the WiFi part from the WifiWebClient example on the official Arduino website.

char tweet[141] = "", oldTweet[141] = "";
char user[141];
String hashtag = "#twitmaslights:";
char color[7];

I think that most of the above variables are self-explanatory. oldTweet is used to check if the detected tweet is a new one.

void setup() {
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'
  //Initialize serial and wait for port to open:
  Serial.begin(9600);
 
  // check for the presence of the shield:
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("WiFi shield not present");
    // don't continue:
    while(true);
  }
 
  // attempt to connect to Wifi network:
  while (status != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network. Change this line if using open or WEP network:    
    status = WiFi.begin(ssid, pass);
 
    // wait 10 seconds for connection:
    delay(10000);
  }
  Serial.println("Connected to wifi");
  printWifiStatus();
}

The setup() routine initializes the LED strip, the serial window (used for some debugging info) and connects to the Wifi network. The WiFi part is from the WifiWebClient example again. The printWifiStatus function puts some info about the connection in the serial window.

void loop() {
  int i;
  TextFinder finder(client);
  
  Serial.println("\nStarting connection to server...");
  
  // if you get a connection, report back via serial:
  if (client.connect(server, 80)) {
    Serial.println("connected to server");
    // Make a HTTP request:
    client.println("GET /index.php HTTP/1.1");
    client.print("Host: ");
    client.println(server);
    client.println("User-Agent: Arduino/1.0");
    client.println("Connection: close");
    client.println();
  }  

The loop() routine starts with connecting to the webserver and then makes a http request for index.php: the file that returns the tweet and user.

  // if there are incoming bytes available
  // from the server, look for the right tags:
  while (client.connected()) {
    if (client.available()) {
      if ( (finder.getString("", "", tweet, 141) != 0) &&
           (finder.getString("", "",   user,  141) != 0) ) {
        for (i = 0; i < 141; i++)
          if (oldTweet[i] != tweet[i])
            break;
        if (i != 141) {
          //Serial.println(tweet);
          foundTweet(tweet, user);
          for (i = 0; i < 141; i++)
            oldTweet[i] = tweet[i];
          break;
        }
        else {
          Serial.println("No new tweets :-(");
        }
      }
    }
  }

After that comes one the fun parts: look for text between <tweet> and <user> tags. If found, check if the tweet is not the same as the previous one (oldTweet). If it's a new tweet, call the function foundTweet, which handles the color code parsing.

  // if the server's disconnected, stop the client:
  if (!client.connected()) {
    Serial.println("Disconnecting from server.");
    client.flush();
    client.stop();
  }
  
  // wait to reconnect
  Serial.println("Wait...");
  delay (30000); // Max. 30000
}

The loop() routine ends with disconnecting from the server and waiting for 30 seconds to make a new request. These 30 seconds matter, because Twitter won't be amused when you make too much requests and word goes around you even can get banned for connecting too often...

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your WiFi shield's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

This function is used by the setup() routine to give you some info about the established WiFi connection. Again, this code is from WifiWebClient example.

void foundTweet(char* tweet, char* user) {
  int i;

  Serial.println("New tweet!");
  Serial.print("Tweet: ");
  Serial.println(tweet);
  Serial.print("From: ");
  Serial.println(user);

  String sTweet(tweet);
  int iBeginHashtag = sTweet.indexOf(hashtag);
  if (iBeginHashtag == -1) {
    Serial.println("Hashtag not found or without colon");
  }
  else {
    // Get color code from tweet
    sTweet.substring(iBeginHashtag + hashtag.length()).toCharArray(color,7);
    Serial.println(color);
    for (i=0; i<6; i++)
      if (((color[i] < 'A') || (color[i] > 'F')) &&
          ((color[i] < 'a') || (color[i] > 'f')) &&
          ((color[i] < '0') || (color[i] > '9')))
        break;
    if (i == 6) {
      char red[] = {color[0],color[1]};
      char green[] = {color[2],color[3]};
      char blue[] = {color[4],color[5]};
      byte r = hexToByte(red);
      byte g = hexToByte(green);
      byte b = hexToByte(blue);
      if (b == 255) b = 254; // Fixes bug in (modified for Aldi) AdaFruit_Neopixel library
      for(uint16_t i=0; i

The foundTweet() function is my favorite part of the sketch! It checks for the hashtag #twitmaslights, followed by a colon (:) and six hexadecimal digits. These digits are then stored in a character array and each of the three pairs (which represent red, green and blue) are converted to a numeric values with the hexToByte() function - see below. These numeric values are used as parameters in the strip.SetPixelColor() function, which turns the lights on.

byte hexToByte(char hexString[]) {
  
  // https://github.com/benrugg/Arduino-Hex-Decimal-Conversion
  
  byte decValue = 0;
  char nextChar;
  
  for (int i = 0; i < 2; i++) {
    
    nextChar = char(hexString[i]);
    if (nextChar >= 48 && nextChar <= 57) nextChar = map(nextChar, 48, 57, 0, 9);
    if (nextChar >= 65 && nextChar <= 70) nextChar = map(nextChar, 65, 70, 10, 15);
    if (nextChar >= 97 && nextChar <= 102) nextChar = map(nextChar, 97, 102, 10, 15);
    nextChar = constrain(nextChar, 0, 15);
    
    decValue = (decValue * 16) + nextChar
  }
  
  return decValue;
}

So what's next?

First of all, I could think of some improvements:

  • Not everyone knows what hexadecimal numbers and html color codes are, so I could also make the Arduino search for #twitmaslights followed by the official names of some of the most used html colors.
  • By polling my server only once in 30 seconds and only using the color in the most recent tweet, it could happen that some tweets don't get handled. Polling Twitter more often than once in 30 seconds could result in a ban. So I have to change the sketch so that every tweet is put in a queue. But because I don't expect that my Twitmaslights will be that popular, this doesn't really have a high priority.
  • How cool would it be to connect a photo camera to the Arduino and tweet a picture of the lights back to the person who requested a new color? The Arduino Uno can't handle processing the camera image, so I ordered an Arduino Yún. I expect the final result with the camera to be really cool, so stay tuned to my blog and twitter account if you want to know how I'm going to make that work!

Final words

I really hope that you found my explanation clear and useful. Feel free to ask anything, but please be patient when you wait for an answer...

Share Button

4 gedachten over “How to control a LED strip with Twitter”

  1. Hi,
    I have tried your code on a similar arduino/twitter project but your initial PHP code is giving me an error of "Notice: Undefined offset: 0 in index.php at line 24" and "Notice: Undefined offset: 0 in index.php at line 26".

    What can I do to fix this?

    I hope that you understand my question and are able to help.

    I loom forward to hear from you.

    Gary

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *

Deze website gebruikt Akismet om spam te verminderen. Bekijk hoe je reactie-gegevens worden verwerkt.