Twitter-controlled Mood Lamp

The source code of the video tutorial

#include <SPI.h>
#include <WiFi.h>

char ssid[] = "FABLAB TORINO2"; //  your network SSID (name)
char pass[] = "password";    // your network password (use for WPA, or use as key for WEP)
int keyIndex = 0;            // your network key Index number (needed only for WEP)
int status = WL_IDLE_STATUS; // status of the wifi connection


const int pin_r = 3;
const int pin_g = 6;
const int pin_b = 5;

const int maxTweets = 5; // Limit tweets printed; avoid runaway output

const unsigned long // Time limits, expressed in milliseconds:
pollingInterval = 10L * 1000L, // Note: Twitter server will allow 150/hr max
connectTimeout = 15L * 1000L, // Max time to retry server link
responseTimeout = 15L * 1000L; // Max time to wait for data from server

byte sleepPos = 0; // Current "sleep throb" table position
byte resultsDepth; // Used in JSON parsing
// Ethernet MAC address is found on sticker on Ethernet shield or board:

WiFiClient client;
char *serverName = "search.twitter.com",
// queryString can be any valid Twitter API search string, including
// boolean operators. See https://dev.twitter.com/docs/using-search
// for options and syntax. Funny characters do NOT need to be URL
// encoded here -- the sketch takes care of that.
*queryString = "#arduinoRGB",
lastId[21], // 18446744073709551615\0 (64-bit maxint as string)
timeStamp[32], // WWW, DD MMM YYYY HH:MM:SS +XXXX\0
fromUser[16], // Max username length (15) + \0
msgText[141], // Max tweet length (140) + \0
name[11], // Temp space for name:value parsing
value[141]; // Temp space for name:value parsing
PROGMEM byte
sleepTab[] = { // "Sleep throb" brightness table (reverse for second half)
  0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
  1, 1, 2, 3, 4, 5, 6, 8, 10, 13,
  15, 19, 22, 26, 31, 36, 41, 47, 54, 61,
  68, 76, 84, 92, 101, 110, 120, 129, 139, 148,
  158, 167, 177, 186, 194, 203, 211, 218, 225, 232,
  237, 242, 246, 250, 252, 254, 255 };

// Function prototypes -------------------------------------------------------

boolean
jsonParse(int, byte),
readString(char *, int);
int
unidecode(byte),
timedRead(void);

// ---------------------------------------------------------------------------

void setup() {
  pinMode(pin_r, OUTPUT);
  pinMode(pin_g, OUTPUT);
  pinMode(pin_b, OUTPUT);


  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);
  }
  // you're connected now, so print out the status:
  printWifiStatus();

  // Clear all string data
  memset(lastId , 0, sizeof(lastId));
  memset(timeStamp, 0, sizeof(timeStamp));
  memset(fromUser , 0, sizeof(fromUser));
  memset(msgText , 0, sizeof(msgText));
  memset(name , 0, sizeof(name));
  memset(value , 0, sizeof(value));
}

// ---------------------------------------------------------------------------

void loop() {
  unsigned long startTime, t;
  int i;
  char c;

  startTime = millis();


  // Attempt server connection, with timeout...
  Serial.print("Connecting to server...");
  while((client.connect(serverName, 80) == false) &&
    ((millis() - startTime) < connectTimeout));

  if(client.connected()) { // Success!
    Serial.print("OK\r\nIssuing HTTP request...");
    // URL-encode queryString to client stream:
    client.print("GET /search.json?q=");
    for(i=0; c=queryString[i]; i++) {
      if(((c >= 'a') && (c <= 'z')) ||
        ((c >= 'A') && (c <= 'Z')) ||
        ((c >= '0') && (c <= '9')) ||
        (c == '-') || (c == '_') ||
        (c == '.') || (c == '~')) {
        // Unreserved char: output directly
        client.write(c);
      }
      else {
        // Reserved/other: percent encode
        client.write('%');
        client.print(c, HEX);
      }
    }
    client.print("&result_type=recent&include_entities=false&rpp=");
    if(lastId[0]) {
      client.print(maxTweets); // Limit to avoid runaway printing
      client.print("&since_id="); // Display tweets since prior query
      client.print(lastId);
    }
    else {
      client.print('1'); // First run; show single latest tweet
    }
    client.print(" HTTP/1.1\r\nHost: ");
    client.println(serverName);
    client.println("Connection: close\r\n");

    Serial.print("OK\r\nAwaiting results (if any)...");
    t = millis();
    while((!client.available()) && ((millis() - t) < responseTimeout));
    if(client.available()) { // Response received?
      // Could add HTTP response header parsing here (400, etc.)
      if(client.find("\r\n\r\n")) { // Skip HTTP response header
        Serial.println("OK\r\nProcessing results...");
        resultsDepth = 0;
        jsonParse(0, 0);
      }
      else Serial.println("response not recognized.");
    }
    else Serial.println("connection timed out.");
    client.stop();
  }
  else { // Couldn't contact server
    Serial.println("failed");
  }

  // Sometimes network access & printing occurrs so quickly, the steady-on
  // LED wouldn't even be apparent, instead resembling a discontinuity in
  // the otherwise smooth sleep throb. Keep it on at least 4 seconds.
  t = millis() - startTime;
  if(t < 4000L) delay(4000L - t);

  // Pause between queries, factoring in time already spent on network
  // access, parsing, printing and LED pause above.
  t = millis() - startTime;
  if(t < pollingInterval) {
    Serial.print("Pausing...");
    sleepPos = sizeof(sleepTab); // Resume following brightest position
    delay(pollingInterval - t);
    Serial.println("done");
  }
}

// ---------------------------------------------------------------------------

boolean jsonParse(int depth, byte endChar) {
  int c, i;
  boolean readName = true;

  for(;;) {
    while(isspace(c = timedRead())); // Scan past whitespace
    if(c < 0) return false; // Timeout
    if(c == endChar) return true; // EOD

    if(c == '{') { // Object follows
      if(!jsonParse(depth + 1, '}')) return false;
      if(!depth) return true; // End of file
      if(depth == resultsDepth) { // End of object in results list


        if (strlen(msgText) < 20) {
          int r = (hex2dec(msgText[12]) * 16) + hex2dec(msgText[13]);
          int g = (hex2dec(msgText[14]) * 16) + hex2dec(msgText[15]);
          int b = (hex2dec(msgText[16]) * 16) + hex2dec(msgText[17]);
          analogWrite(pin_r,r);
          analogWrite(pin_g,g);
          analogWrite(pin_b,b);

          Serial.print(r);
          Serial.print(" ");
          Serial.print(g);
          Serial.print(" ");
          Serial.println(b);

        }

        // Dump to serial console as well
        Serial.print("User: ");
        Serial.println(fromUser);
        Serial.print("Text: ");
        Serial.println(msgText);
        Serial.print("Time: ");
        Serial.println(timeStamp);

        // Clear strings for next object
        timeStamp[0] = fromUser[0] = msgText[0] = 0;
      }
    }
    else if(c == '[') { // Array follows
      if((!resultsDepth) && (!strcasecmp(name, "results")))
        resultsDepth = depth + 1;
      if(!jsonParse(depth + 1,']')) return false;
    }
    else if(c == '"') { // String follows
      if(readName) { // Name-reading mode
        if(!readString(name, sizeof(name)-1)) return false;
      }
      else { // Value-reading mode
        if(!readString(value, sizeof(value)-1)) return false;
        // Process name and value strings:
        if (!strcasecmp(name, "max_id_str")) {
          strncpy(lastId, value, sizeof(lastId)-1);
        }
        else if(!strcasecmp(name, "created_at")) {
          strncpy(timeStamp, value, sizeof(timeStamp)-1);
        }
        else if(!strcasecmp(name, "from_user")) {
          strncpy(fromUser, value, sizeof(fromUser)-1);
        }
        else if(!strcasecmp(name, "text")) {
          strncpy(msgText, value, sizeof(msgText)-1);
        }
      }
    }
    else if(c == ':') { // Separator between name:value
      readName = false; // Now in value-reading mode
      value[0] = 0; // Clear existing value data
    }
    else if(c == ',') {
      // Separator between name:value pairs.
      readName = true; // Now in name-reading mode
      name[0] = 0; // Clear existing name data
    } // Else true/false/null or a number follows. These values aren't
    // used or expected by this program, so just ignore...either a comma
    // or endChar will come along eventually, these are handled above.
  }
}

// ---------------------------------------------------------------------------

// Read string from client stream into destination buffer, up to a maximum
// requested length. Buffer should be at least 1 byte larger than this to
// accommodate NUL terminator. Opening quote is assumed already read,
// closing quote will be discarded, and stream will be positioned
// immediately following the closing quote (regardless whether max length
// is reached -- excess chars are discarded). Returns true on success
// (including zero-length string), false on timeout/read error.
boolean readString(char *dest, int maxLen) {
  int c, len = 0;

  while((c = timedRead()) != '\"') { // Read until closing quote
    if(c == '\\') { // Escaped char follows
      c = timedRead(); // Read it
      // Certain escaped values are for cursor control --
      // there might be more suitable printer codes for each.
      if (c == 'b') c = '\b'; // Backspace
      else if(c == 'f') c = '\f'; // Form feed
      else if(c == 'n') c = '\n'; // Newline
      else if(c == 'r') c = '\r'; // Carriage return
      else if(c == 't') c = '\t'; // Tab
      else if(c == 'u') c = unidecode(4);
      else if(c == 'U') c = unidecode(8);
      // else c is unaltered -- an escaped char such as \ or "
    } // else c is a normal unescaped char

    if(c < 0) return false; // Timeout

    // In order to properly position the client stream at the end of
    // the string, characters are read to the end quote, even if the max
    // string length is reached...the extra chars are simply discarded.
    if(len < maxLen) dest[len++] = c;
  }

  dest[len] = 0;
  return true; // Success (even if empty string)
}

// ---------------------------------------------------------------------------

// Read a given number of hexadecimal characters from client stream,
// representing a Unicode symbol. Return -1 on error, else return nearest
// equivalent glyph in printer's charset. (See notes below -- for now,
// always returns '-' or -1.)
int unidecode(byte len) {
  int c, v, result = 0;
  while(len--) {
    if((c = timedRead()) < 0) return -1; // Stream timeout
    if ((c >= '0') && (c <= '9')) v = c - '0';
    else if((c >= 'A') && (c <= 'F')) v = 10 + c - 'A';
    else if((c >= 'a') && (c <= 'f')) v = 10 + c - 'a';
    else return '-'; // garbage
    result = (result << 4) | v;
  }

  // To do: some Unicode symbols may have equivalents in the printer's
  // native character set. Remap any such result values to corresponding
  // printer codes. Until then, all Unicode symbols are returned as '-'.
  // (This function still serves an interim purpose in skipping a given
  // number of hex chars while watching for timeouts or malformed input.)

  return '-';
}

// ---------------------------------------------------------------------------

// Read from client stream with a 5 second timeout. Although an
// essentially identical method already exists in the Stream() class,
// it's declared private there...so this is a local copy.
int timedRead(void) {
  int c;
  unsigned long start = millis();

  while((!client.available()) && ((millis() - start) < 5000L));

  return client.read(); // -1 on timeout

}
int hex2dec(byte c) { // converts one HEX character into a number
  if (c >= '0' && c <= '9') {
    return c - '0';
  }
  else if (c >= 'A' && c <= 'F') {
    return c - 'A' + 10;
  }
}

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");
}

Share