Twitch

From JR
Jump to: navigation, search

Contents

Purpose

This was probably the easiest part of the write up. I had a need to not travel across town, arrive at the space and find it locked and closed. And Jigsaw had a need for general members and the public to know when the space is being held open by a keyed member. A perfect chance to expand my limited electronics knowledge and make a useful thing!

Example

Sometimes an example is the best way to understand. See Twitch on twitter. TwitchPicture.png

How to use the Twitch

  • The left hand switch is the one to use. Flipping it up will indicate send an "#open" post to the twitter account. Whilst flipping it down will send a "#closed" post. The right hand switch does not serve a function at the moment.
  • Once the left hand switch has been flipped, there is a 2 second delay before the post is sent to Twitter. This is so allow you to turn the switch back to it's previous position if you hit it on accident, without posting to Twitter.

Materials & Origins

  • Arduino Diecimila (Atmel ATmega328 version)
Purchased from http://metrixcreatespace.com
  • Arduino Ethernet Shield
Purchased on sale from http://store.fungizmos.com/index.php?main_page=product_info&cPath=65&products_id=360
  • Cat5 Cable
Already in the space (free)
  • 120 VAC -> 12 VDC Power Supply
Purchased in local thrift store for less than $3.
  • Standard White Toggle Light Switch 2x
http://yelp.com/biz/pacific-supply-seattle
  • White PVC Double-Wide Junction Box
Also from Pacific Supply.
  • White Double Switch Junction Box Faceplate
Also from Pacific Supply.
  • Misc. Resistor

Pitfalls

Oh so many. I'll try to list here, some of the major hang ups I had and how I worked around them.

Debounce!

Not being a programmer by trade and not having worked on any physical computing projects before, I was unfamiliar with the concept of Debounce. It is the act of rectifying erratic user input from physical switches/buttons/etc. either digitally (using software) or manually (at the component level). This problem arises anytime the state of a spring loaded switch/button/etc. is read and it cropped up in this project as erratic feedback from the spring loaded light switch. A beginning introduction to Debounce and a more advanced discussion on it.

Twitter Proxy

Ya know that thing Facebook implemented where every other site that uses your Facebook ID requires you to open up a mini-FB frame and authorize through that? That is called OAuth and it's a pain in the but for projects like this where a web browser cannot be used to authenticate the Arduino. Suffice it to say that OAuth is a bit much for the Arduino to handle. Back in the day, you could simply sign in to Twitter via their API. But as of August 31st 2010, Twitter.com has moved away from that old style, and admittedly less secure, basic authentication and now uses OAuth

Thankfully, enough people still have a need for basica authentication that many sites/services exist to bridge the gap between basic auth and OAuth for simpler devices.

For instance, here's a Twitter proxy, providing basic auth -> OAuth

A TCP Packet for Every Character

This was the most annoying pitfall about the project and its (partial) solution was so outside my realm of knowledge that I had to have help from Dug in figuring this one out. The standard Arduino Ethernet library was generating a separate packet for each character when Client.print was being called. This resulted in 140 packets for a 140 character message and some other wacky stack overflow shenanigans with the Arduino restarting and what not. Dug was very helpful in telling me about the C++ function (accessible in the Arduino IDE) called sprintf; which stands for String PRINT Formatted. A hand way to concatenate all of the text you'd like into a single string to pass along to Client.print in order to mitigate the number of packets generated by the crummy Arduino IP stack.

DIY POE

After thinking everything was squared away with the Twitch, I noticed that it was sometimes simply turned off or the Arduino base board would remain on while the shield would shut down. I already knew that combining power and ethernet on an ethernet cable might be running the risk of creating interference or extending the used wallwart's abilities. But I ended up going for it any for its ease and convenience. Well, that came back to bite me in the ass. So I've replaced the homemade power-over-ethernet with a different wallwart plugged in correctly. If I had the extra money, I would spring for the new Arduino + Ethernet Shield w/ PoE!

Buffer OverKJHF)JHOIAJOIJSJJDJDJJDJD)FE)))

It's crucial to remember that the Arduino platform/IDE is built on C which harkens back to a golden age of computer in which life was simpler and strings that were concatenated character arrays didn't exist. While debugging the Twitch code, I found that I was prone to crutching on converting character arrays to strings in order to easily output them for deug purpose. This is a mistake and can often lead to overtaxing your poor development board's puny memory. Never do this:
sBuffer = (String)nameOfCharBuffer

Retweeting

Since the Twitch has been live, I've found many people have attempted to mention it with messages they expected to be retweeted. A perfectly reasonable assumption on their part given that the main Jigsaw twitter does this retweeting. But now, thanks to Kav, it does! Retweeting is accomplished via a service he cooked up called Lyrebd

Source Code

#if defined(ARDUINO) && ARDUINO > 18   // Arduino 0019 or later
#include <SPI.h>
#endif
#include <Ethernet.h>
#include <EthernetDHCP.h>
#include <avr/pgmspace.h>

byte mac[] = { 0x4A, 0x69, 0x67, 0x73, 0x61, 0x77 }; // Jigsaw in Hex

const char* ipToString(const uint8_t*);
byte alixsysIP[] = { 92, 243, 14, 243 }; //twitter.alixsys.com/ 92.243.14.243
//byte server[] = { 72, 2, 118, 214 }; //api.supertweet.net 72.2.118.214
//byte freenodeIP[] = { 130, 237, 188, 200 }; //freenode.net
//byte jigbotIP[] = { 205, 201, 45, 29 }; //http://irc.jigren.org 205.201.45.29

const int switch0Pin = 8;
//const int switch1Pin = 2;

int switch0State = 0;
int reading = 0;

long lastDebounceTime = 0;
long debounceDelay = 2500;
long loopCount = 0;

boolean firstLoop = true;

Client twitter(alixsysIP, 80);

const int tweetCount = 6;

int randomNumber = 42;

char buffer[200];

//char* debugTweet = " \\&_~!@#$%^&*()+QWERTYUIOP{}|[]-=`;:?/Well you made it this far..";

char* alixsysToken = "4d03053671de7"; //4e49c13d5a99d:debugTwitch
                     //4d03053671de7:JigTwitch

prog_char *tweetStrings[2][tweetCount] = {
//{"#closed","Not Open. #closed","Door's locked. #closed","Nope. #closed","Cerrado. #closed","Time to go home for dinner. #closed","Nobody's here!. #closed","I'm sorry, I can't do that Dave. #closed","Portal Closed. #closed","Shut. #closed","Worm Hole collapsed. #closed","Le Clos-ed. #closed",   }  ,  {"Coffee's on. #open","#open","Come on over! #open","Yes, We're open. #open","Someone is here #open","Door is unlocked. #open","Abierto. #open","Come in and play! #open","Openinsky. #open","Portal Open. #open","Worm hole detected. #open","Ooooooooooooopen. #open"}
//{"%23closed","Not%20Open.%20%23closed","Door%27s%20locked.%20%23closed","Nope.%20%23closed","Cerrado.%20%23closed","Time%20to%20go%20home%20for%20dinner.%20%23closed","Nobody%27s%20here%21.%20%23closed","I%27m%20sorry%2C%20I%20can%27t%20do%20that%20Dave.%20%23closed","Portal%20Closed.%20%23closed","Shut.%20%23closed","Worm%20Hole%20collapsed.%20%23closed","Le%20Clos-ed.%20%23closed"}};
{
    "%23closed",
    "Not%20Open.%20%23closed",
    "Door%27s%20locked.%20%23closed",
    "Nope.%20%23closed",
    "Cerrado.%20%23closed",
    "Time%20to%20go%20home%20for%20dinner.%20%23closed"}
  ,
  {
    "Coffee%27s%20on.%20%23open",
    "%23open",
    "Come%20on%20over%21%20%23open",
    "Yes%2C%20We%27re%20open.%20%23open",
    "Someone%20is%20here%20%23open",
    "Door%20is%20unlocked.%20%23open"}
};

void setup() {
  delay(1000);
  //Serial.begin(9600);
  randomSeed(analogRead(0));

  pinMode(switch0Pin, INPUT); 

  EthernetDHCP.begin(mac);
  //Serial.println("[DHCP] Ethernet started.");

  const byte* ipAddr = EthernetDHCP.ipAddress();
  const byte* gatewayAddr = EthernetDHCP.gatewayIpAddress();
  const byte* dnsAddr = EthernetDHCP.dnsIpAddress();

  //Serial.print("[DHCP] My IP address is ");
  //Serial.println(ipToString(ipAddr));
  ////Serial.print("[DHCP] Gateway IP address is ");
  ////Serial.println(ipToString(gatewayAddr));
  ////Serial.print("[DHCP] DNS IP address is ");
  ////Serial.println(ipToString(dnsAddr));

  ////Serial.println(urlEscape(debugTweet));

  randomNumber = random(0, (tweetCount-1));
  //Serial.print("[RAND] New random number: ");
  //Serial.println(randomNumber);
}

void loop() { 
  
  if (lastDebounceTime == 0) {
    reading = digitalRead(switch0Pin);
    if (reading != switch0State) {
      lastDebounceTime = millis();
    };
  } 
  else if (((millis() - lastDebounceTime) >= debounceDelay) || (millis() < lastDebounceTime)) {
    reading = digitalRead(switch0Pin);
    if (reading != switch0State) {
      randomNumber = random(0, (tweetCount-1));
      //Serial.print("[RAND] New random number: ");
      //Serial.println(randomNumber);
      switch (reading) {
      case HIGH:
        //Serial.println("[SWCH] Left: On");
        
        postToTwitter(tweetStrings[reading][randomNumber]);
        //        checkAvail();
        break;
      case LOW:
        //Serial.println("[SWCH] Left: Off");
        postToTwitter(tweetStrings[reading][randomNumber]);
        //        checkAvail();
        break;
      }
    }
    switch0State = reading;
    lastDebounceTime = 0;
  }
  EthernetDHCP.maintain();
} 

const char* ipToString(const uint8_t* ipAddr) {
  static char buf[16];
  sprintf(buf, "%d.%d.%d.%d\0", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]);
  return buf;
}

/* char* urlEscape(char* src) {

  static char bufferPickle[200];
  String buffer = src;
  //Serial.print("[URLE] src: ");
  //Serial.println(src);
  //Serial.print("[URLE] buffer: ");
  //Serial.println(buffer);
  buffer = buffer.replace(" ", "%20");
  buffer = buffer.replace("&", "%26");
  buffer = buffer.replace("\\", "%5C");
  buffer = buffer.replace("#", "%23");
  buffer = buffer.replace("!", "%21");
  buffe
  r = buffer.replace("'", "%27");
  buffer = buffer.replace(",", "%2C");

  buffer.toCharArray(bufferPickle, 200);
  //Serial.print("[URLE] bufferPickle- ");
  
      //Serial.println(sizeof(bufferPickle));
  return bufferPickle;
} */

void postToTwitter(char* tweet) {

  //Serial.print("[TWIT] ");
  //Serial.println((String)tweet);
  //tweet = urlEscape(tweet);
  
  ////Serial.println("[TWIT] Got past urlEscape(tweet)");
  sprintf(buffer,"GET /update/?axk=%s&status=%s HTTP/1.1\nHost: twitter.alixsys.com\n\n",alixsysToken,tweet);
  //Serial.print("[TWIT] buffer: ");
  //Serial.println(buffer);
  ////Serial.println("[TWIT] Got past sprintf");
  if (twitter.connect()) {
    //Serial.println("[TWIT] Twitter connected, sending GET request.");
    twitter.print(buffer);
    
    //Serial.println("[TWIT] GET method sent.");
    if (twitter.available()) {
      char c = twitter.read();
      //Serial.print(c);
    
      //twitter.flush();
    }
    twitter.stop();
  } else if (!twitter.connect()) {
    //Serial.println("[TWIT] Twitter failed to connect.");
    twitter.stop();
  }
}

Changes to make

  • MAC address (should be on the Arduino itself)
  • Authenticate with http://twitter.alixsys.com/ using the Twitter account that you want to tweet from; use the code in the GET request
 sprintf(buffer,"GET /update/?axk=4d1082d7e1a71&status=%s HTTP/1.1\nHost: twitter.alixsys.com\n\n",urlEscape(tweet));
  • Change the IRC channel hostname

Optional

  • if you want to change the number of tweets for #open and #close, change tweetCount to match.

External Links

Other Twittering Hackerspace Objects

Process log

7.24.11 - Put some time into the Twitch tonight. (Re) Found a power supply that will work for it and set about making a crossover cable to get it tethered when I decided to step back and access the situation a bit more. It's been inactive in the "new" Inscape space since we moved in because it requires an ethernet cable and the building is not wired for ethernet. There is wifi, the rub is in procuring a bridge between the building's wifi and the Twitch's ethernet. There is of course, more than one way around this problem. It may seem like a no brainer to just swap out a WiShield for the Ethernet Shield, but that would just be too easy. AsyncLabs, the people who sold the wifi enabling WiShield, are kaput. In addition, the thing itself is 55$, an unacceptable price. Another option is to tether the Twitch to a computer in the space that is always on. Dug has several computers around that could fulfill this role, but it completely negates the aim of the project for it to standalone and I view it as one of my last options, especially considering said computer would only be serving as a gateway to the Twitch and would be wasting energy and computing power in the interim. Worth noting here is the fact that Arduino Twitter Library has been updated to play nice with the updated Ethernet Library and various IDEs (that was a pitfall before wasn't it?)

7.26.11 - A note here that I'm also noticing that the DHCP libraries have also been updated to play nicely with the Arduino IDE 0019 and above. The current version of the Arduino IDE is 0022

8.18.11 - Sank some time into the Twitch recently. FINALLY found the source code, pulled down the updated DHCP libraries and got it compiles with them. A word to the wise: beware of typecasting to string variables for debug output. for it is the source of much frustration and buffer overflow. In fact I think I'll add it to the Pitfalls section. Spent most of the work session battling a defunkt url escaping function, similar to javascript's escape() for use with the Alixsys api, since the twitter update submitted through it is identical to just an HTTP GET. I also spent a little bit of time toying with the idea of implementing the twitter library, but I think that te control the Arduino standard Client() affords you outweighs te fact that you don't get an error code from using the Alixsys api. The next steps, apart from making the urlEscape() function.. function, will be to implement storage of the update strings (or character buffers) in the ATMega328P's 32KByte flash memory instead of trying to cram it into the 2048 byte SRAM with the rest of the binary. This is where the Arduino IDE starts to stretch thin and show it's C underbelly a little bit more. More on the subject of what memory is available where on the Arduino can be found here and it's specific applications using the keyword PROGMEM. Also of note, but not particularly useful for this project, is an oler Arduino emulator projet I happened on called Emulino. It's not useful here because it doesn't handle the functions of the Ethernet Shield. And in trying to use randomSeed(), I found out that it chokes on analogRead(). I know that Parallax offers a very well documented and useful emulator for their propeller microcontrollers, I wish Arduino could d the same. :( Also of note, if you happen to be on a mac, there just so happens to be a Textmate bundle, which includes syntax highlighting and binary upload. I'm already too used to the Arduino IDE, but poking around in the bundle I found a nice bit of unix/command line voodoo:
osascript -e 'tell application "Terminal" to do script "screen /dev/tty.usbserial* 9600"'

9.3.11 - Popping the strings into PROGMEM is proving difficult due to the fact that the 2 dimensional array they are in is in fact a 3 dimensional array because C does not natively support strings as datatypes, except as arrays of characters. Thankfully, lots of information on program space is available.

9.4.11 - Dropped off and set up the Twitch at Jigsaw! Hopefully no one will mess with the mac-min which feeds it internet. I have a slight fear that it will go to sleep and cut off the Twitch. If that happens, I'll have to look into how exactly to keep it awake and forwarding the internet to the Twitch. In the mean time (if I have time for it), I can continue to make changes to the software at home using an alternate Arduino + Ethernet Shield I've got. My debug version of the firmware uses the second switch to toggle output to either the [twitter.com/jigtwitch JigTwitch] account or the debugTwitch accounts. The debug firmware also has a function for polling the switches instead of it being the main body of logic. But for now, IT'S BACK!!

9.5.11 - Looks like there are still several options for adding wifi to the Arduino platform in the 55-80$ range. Today, I was poking around and found some options at Cutedigi, like this black widow-ish clone with wifi built in. They also still seem to have some WiShields which go for 35$ cheaper than Sparkfun's WiFly.

11.26.11 - Toyed around with the new github repo for the twitch code and updated it so that the Twitch's current firmware, this wiki page and the github repro have the same code. Also toyed with a Fritzing file to try and make a decent schematic of the internal workings of the Twitch, but got hung up on making a vector light switch Fritzing part (as none exist). Having an accurate schematic might make future contributors less inclined to tear it apart without re-assembling it. But rendering complex, reflective brass screws doesn't seem feasible tonight.. I have obtained some double-pull light switches, which would be used in an entirely different circuit. Maybe it's time for Twitch 2.0.

11.26.11 - Went with a partial re-write tonight. Eliminated the DHCP library & set the code up to use the Twitter library, but no dice. Not only did it not work when compiling via the new Arduino IDE 1.0, but also when attempting with the Arduino 0022 IDE. After several successful updates/connections, it simply refuses to connect. Not sure it is Twitter refusing the tweets or what. The error handling is too poor to be sure of exactly what the problem is. The Twitter.twitter function doesn't even provide the same available() and read() functions that the client library does. I think the next step is to try the "simpler" method or handing over all the complicated twitter interaction to a computer. Too bad I know no scripting - .-'

09.19.11 (By Danny Dunn and Michael Park) Many moons have passed since the coming of a tweet from the JigTwitch. We have been doing some research and getting the ball rolling on a new version using PushingBox.

4.6.14 - It has been a while.. The original intent of the jigtwitch was a to have a way to tell if the old, first hill space was open or not. A purpose it has aptly served. But there was a secondary goal: true participating in the internet of things. An object who physical and digital representations matched and influenced each other. I never got around to this secondary purpose. In practice, this would look like some kind of mechanical actuation applied to the switch portion of twitch such that, tweeting "closed" or "open" to its twitter account would alter the physical state of the switch; versus the other way round (as it functions now). Just making some notes so I don't lose the thought.

Personal tools
Namespaces

Variants
Actions
Puzzle Pieces
Wiki tools
Operations plan
For staff
Toolbox