Arduino Yún + Twitter Streaming API blink example

This is a simple example on how to run some code on the Arduino side of the Arduino Yún on specific events fired through the Twitter Streaming API

i.e. when a tweet containing a specific #hashtag is twitted, Arduino blinks an LED

I love this experiment because it’s near real time and it gives a lot of possibilities, and it’s very easy to set up

Requirements

  • An Arduino Yún (obviously) with an external SD card (required for hosting the Python libraries you’ll need)
  • A twitter app you own
  • The Tweepy library for Python
  • And, finally… this code
  • 🙂

Instructions

“I love instructions, they give me confidence.”
me

  1. In case you haven’t done it yet, upgrade your Arduino Yún to the latest version and expand it’s disk space using the SD card
  2. Log to your Arduino using ssh
  3. Install a bunch of useful stuff
     $ opkg update
     $ opkg install git
     $ opkg install python-expat
     $ opkg install python-openssl
     $ opkg install python-bzip2
    

    easy_install

     $ opkg install distribute
    

    pip

     $ easy_install pip
    

    tweepy

     $ pip install tweepy
    

    or install it manually cloning it from github

      $ git clone https://github.com/tweepy/tweepy.git
      $ cd tweepy
      $ python setup.py install
    
  4. If you don’t have one, create your twitter app
    and get your consumer key and consumer secret. Then, generate an access token and get your access token and access token secret

  5. Create a working folder for all the scripts (i.e. /root/python)

     $ mkdir /root/python
     $ cd /root/python
    

    and, while in the python folder, clone the Twitter Blink example

      $ git clone git@github.com:amicojeko/TwitterBlink.git
    

    don’t forget to make the kill_processes.sh script executable!

      $ chmod +x kill_processes.sh
    
  6. Then use vi or nano (I personally installed vim) to put your twitter app configuration data into the streaming.py script, and to configure the string to be searched inside the Twitter Stream Maelstrom
     $ nano streaming.py
    

    now everything should be ready.

  7. Open the Arduino IDE and load the TwitterBlink.ino script that is in the Arduino folder (you can download it from GitHub )

  8. Configure the script folder (in case you created a folder different from root/python/ in the previuos steps)

    You can customize the script for your needings, in it’s initial configuration it will blink the LED attached to pin 13 (and the built in LED as well) so it’s perfect for testing

  9. Upload the script to Arduino, it should start working as soon as it’s uploaded

  10. Have fun!!

cheers!

Jeko

The social media hamster

(la muerte peluda is back)


Every hackathon we host at Mikamai usually means a lot of leftovers. From pizza crusts to burned LEDs, you can find a lot of useful and useless stuff even in the deepest and darkest corners of our huge open space.

And in one of these dark corners I found a scared little friend, which I suddenly decided to adopt.

image

We became BFFs, and we do everything together, coding, drinking, pranking the teammates and cycling, but he wanted to be useful to me, so I went into my cove and start thinking… what an ex cartoon eating plush hamster can do for me?

The answer was quite obvious! He can warn me whenever someone tweets me or cites me in a tweet! Genius!

So, here are the ingredients

  • Twitter streaming APIs
  • Electric IMP (I decided to use an Electric Imp just because I had a spare one, you can even use a spark core or an Arduino Yún)
  • A servo
  • Chopsticks
  • Some screws
  • Cable ties
  • Something heavy
  • Obviously, absulutely essential, a plush hamster
  • An USB power adapter or a USB battery pack if you want your project to be portable

image

I also used an LED for visual debugging, I tried to put a pair of LEDs instead of my hamster’s eyes, but it was too scary, and it remembered a lot my creepy doll, so I decided not to use them

Then I had to make the hamster move with the servo. It’s just a prototype, so I decided to keep it very very simple, and I secured the hamster to the servo with a chopstick, some copper wire and some screws to keep the hamster from doing a barrel roll. The surgery was not so traumatic, so my hamster is still happy. Then I fixed the servo to an heavy metal plate with a cable tie, just to provide a solid base.

I drilled some holes to the chopstick

image

Then put some screws in it and i secured the chopstick to the servo

image

And I used a bit of copper wire to secure the hamster to the screws

image

image

And a cable tie to fix the servo to the metal plate

image

Now everything’s in place!

image

The circuit is dead simple. Just connect the power pins of the servo to the GND-VIN pins of the Electric Imp, and the third pin to the Imp’s pin number 7

image

The Electric Imp is one of the easiest platforms to configure, thanks to the Blink app, so you can have it online in a couple of minutes. You can even use your phone as hotspot, it works perfectly and it makes your social hamster fully portable.

image

Now, the code. The Electric imp platform gives you all the instruments to interact with the whole world of APIs and webservices. Basically, the code is splitted in two: An agent, that runs on the Electric Imp Cloud servers, and a device, which is your electric Imp

[Copy & Paste from the official Electric Imp documentation]

The agent object represents the imp’s agent: the server-side Squirrel, running in the Electric Imp’s cloud servers, that deals with Internet requests and responses on behalf of the imp. The agent object is used to mediate communication between the imp and its agent.

The device object represents the server-based agent’s view of the imp and is used to mediate communication between the agent and the imp.

[End of copy&paste]

So here we go with our agent code. It’s based on the useful twitter library included in the Electric Imp webservices reference. I also added this piece of code to generate the tweet event manually

// test function for manual hamster shaking
function requestHandler(request, response) {
  try {
    // check if the user sent led as a query parameter
    if ("tweet" in request.query) {
        device.send("tweet", null); 
    }
    // send a response back saying everything was OK.
    response.send(200, "tweet test ok");
  } catch (ex) {
    response.send(500, "Internal Server Error: " + ex);
  }
}

// register the HTTP handler
http.onrequest(requestHandler);

To enable the twitter stream real time parsing, I just needed to configure these twitter constants

// Twitter Keys
const API_KEY = "";
const API_SECRET = "";
const AUTH_TOKEN = "";
const TOKEN_SECRET = "";

and this line

twitter.stream("@jeko", onTweet);

which basically tells to the stream object to catch all the tweets containing the string “@jeko”

So, here’s the complete agent code

// Copyright (c) 2013 Electric Imp
// This file is licensed under the MIT License
// http://opensource.org/licenses/MIT

// Twitter Keys
const API_KEY = "";
const API_SECRET = "";
const AUTH_TOKEN = "";
const TOKEN_SECRET = "";

class Twitter {
    // OAuth
    _consumerKey = null;
    _consumerSecret = null;
    _accessToken = null;
    _accessSecret = null;

    // URLs
    streamUrl = "https://stream.twitter.com/1.1/";
    tweetUrl = "https://api.twitter.com/1.1/statuses/update.json";

    // Streaming
    streamingRequest = null;
    _reconnectTimeout = null;
    _buffer = null;


    constructor (consumerKey, consumerSecret, accessToken, accessSecret) {
        this._consumerKey = consumerKey;
        this._consumerSecret = consumerSecret;
        this._accessToken = accessToken;
        this._accessSecret = accessSecret;

        this._reconnectTimeout = 60;
        this._buffer = "";
    }

    /***************************************************************************
     * function: Tweet
     *   Posts a tweet to the user's timeline
     * 
     * Params:
     *   status - the tweet
     *   cb - an optional callback
     * 
     * Return:
     *   bool indicating whether the tweet was successful(if no cb was supplied)
     *   nothing(if a callback was supplied)
     **************************************************************************/
    function tweet(status, cb = null) {
        local headers = { };

        local request = _oAuth1Request(tweetUrl, headers, { "status": status} );
        if (cb == null) {
            local response = request.sendsync();
            if (response && response.statuscode != 200) {
                server.log(format("Error updating_status tweet. HTTP Status Code %i:rn%s", response.statuscode, response.body));
                return false;
            } else {
                return true;
            }
        } else {
            request.sendasync(cb);
        }
    }

    /***************************************************************************
     * function: Stream
     *   Opens a connection to twitter's streaming API
     * 
     * Params:
     *   searchTerms - what we're searching for
     *   onTweet - callback function that executes whenever there is data
     *   onError - callback function that executes whenever there is an error
     **************************************************************************/
    function stream(searchTerms, onTweet, onError = null) {
        server.log("Opening stream for: " + searchTerms);
        // Set default error handler
        if (onError == null) onError = _defaultErrorHandler.bindenv(this);

        local method = "statuses/filter.json"
        local headers = { };
        local post = { track = searchTerms };
        local request = _oAuth1Request(streamUrl + method, headers, post);


        this.streamingRequest = request.sendasync(

            function(resp) {
                // connection timeout
                server.log("Stream Closed (" + resp.statuscode + ": " + resp.body +")");
                // if we have autoreconnect set
                if (resp.statuscode == 28) {
                    stream(searchTerms, onTweet, onError);
                } else if (resp.statuscode == 420) {
                    imp.wakeup(_reconnectTimeout, function() { stream(searchTerms, onTweet, onError); }.bindenv(this));
                    _reconnectTimeout *= 2;
                }
            }.bindenv(this),

            function(body) {
                 try {
                    if (body.len() == 2) {
                        _reconnectTimeout = 60;
                        _buffer = "";
                        return;
                    }

                    local data = null;
                    try {
                        data = http.jsondecode(body);
                    } catch(ex) {
                        _buffer += body;
                        try {
                            data = http.jsondecode(_buffer);
                        } catch (ex) {
                            return;
                        }
                    }
                    if (data == null) return;

                    // if it's an error
                    if ("errors" in data) {
                        server.log("Got an error");
                        onError(data.errors);
                        return;
                    } 
                    else {
                        if (_looksLikeATweet(data)) {
                            onTweet(data);
                            return;
                        }
                    }
                } catch(ex) {
                    // if an error occured, invoke error handler
                    onError([{ message = "Squirrel Error - " + ex, code = -1 }]);
                }
            }.bindenv(this)

        );
    }

    /***** Private Function - Do Not Call *****/
    function _encode(str) {
        return http.urlencode({ s = str }).slice(2);
    }

    function _oAuth1Request(postUrl, headers, data) {
        local time = time();
        local nonce = time;

        local parm_string = http.urlencode({ oauth_consumer_key = _consumerKey });
        parm_string += "&" + http.urlencode({ oauth_nonce = nonce });
        parm_string += "&" + http.urlencode({ oauth_signature_method = "HMAC-SHA1" });
        parm_string += "&" + http.urlencode({ oauth_timestamp = time });
        parm_string += "&" + http.urlencode({ oauth_token = _accessToken });
        parm_string += "&" + http.urlencode({ oauth_version = "1.0" });
        parm_string += "&" + http.urlencode(data);

        local signature_string = "POST&" + _encode(postUrl) + "&" + _encode(parm_string);

        local key = format("%s&%s", _encode(_consumerSecret), _encode(_accessSecret));
        local sha1 = _encode(http.base64encode(http.hash.hmacsha1(signature_string, key)));

        local auth_header = "oauth_consumer_key=""+_consumerKey+"", ";
        auth_header += "oauth_nonce=""+nonce+"", ";
        auth_header += "oauth_signature=""+sha1+"", ";
        auth_header += "oauth_signature_method=""+"HMAC-SHA1"+"", ";
        auth_header += "oauth_timestamp=""+time+"", ";
        auth_header += "oauth_token=""+_accessToken+"", ";
        auth_header += "oauth_version="1.0"";

        local headers = { 
            "Authorization": "OAuth " + auth_header
        };

        local url = postUrl + "?" + http.urlencode(data);
        local request = http.post(url, headers, "");
        return request;
    }

    function _looksLikeATweet(data) {
        return (
            "created_at" in data &&
            "id" in data &&
            "text" in data &&
            "user" in data
        );
    }

    function _defaultErrorHandler(errors) {
        foreach(error in errors) {
            server.log("ERROR " + error.code + ": " + error.message);
        }
    }

}

twitter <- Twitter(API_KEY, API_SECRET, AUTH_TOKEN, TOKEN_SECRET);

function onTweet(tweetData) {
    // log the tweet, and who tweeted it (there is a LOT more info in tweetData)
    server.log(format("%s - %s", tweetData.text, tweetData.user.screen_name));
    device.send("tweet", null); 
}


// test function for manual hamster shaking
function requestHandler(request, response) {
  try {
    // check if the user sent led as a query parameter
    if ("tweet" in request.query) {
        device.send("tweet", null); 
    }
    // send a response back saying everything was OK.
    response.send(200, "tweet test ok");
  } catch (ex) {
    response.send(500, "Internal Server Error: " + ex);
  }
}

twitter.stream("yoursearchstring", onTweet);

// register the HTTP handler
http.onrequest(requestHandler);

Then, here’s the device code, based on the electric Imp PWM Servo example

// These values may be different for your servo
const SERVO_MIN = 0.03;
const SERVO_MAX = 0.1;

// create global variable for servo and configure
servo <- hardware.pin7;
servo.configure(PWM_OUT, 0.02, SERVO_MIN);

// assign pin9 to a global variable
led <- hardware.pin9;
// configure LED pin for DIGITAL_OUTPUT
led.configure(DIGITAL_OUT);

// global variable to track current state of LED pin
state <- 0;
// set LED pin to initial value (0 = off, 1 = on)
led.write(state);



// expects a value between 0.0 and 1.0
function SetServo(value) {
  local scaledValue = value * (SERVO_MAX-SERVO_MIN) + SERVO_MIN;
  servo.write(scaledValue);
}

// expects a value between -80.0 and 80.0
function SetServoDegrees(value) {
  local scaledValue = (value + 81) / 161.0 * (SERVO_MAX-SERVO_MIN) + SERVO_MIN;
  servo.write(scaledValue);
}

// current position (we'll flip between 0 and 1)
position <- 0;

function HamsterDance() {
  SetServoDegrees(-10);
  imp.sleep(0.5);
  SetServoDegrees(0);
  imp.sleep(0.2);
  SetServoDegrees(-15);
  imp.sleep(0.2);
  SetServoDegrees(5);  
  imp.sleep(0.2);
  SetServoDegrees(-20);
  imp.sleep(0.2);
  SetServoDegrees(0);
  imp.sleep(0.2);
  SetServoDegrees(-78);
} 


function ShakeRattleAndRoll(ledState){
    server.log("Let's shake the hamster!");  
    // turn on the led for visual debugging
    led.write(1);
    // 
    HamsterDance();
    imp.sleep(2);
    // turn off the led
    led.write(0);

} 

// initialize the servo to the start position
SetServoDegrees(-78);

//shake the hamster when got a tweet message from the Agent
agent.on("tweet", ShakeRattleAndRoll);

And… that’s it! Just connect the Electric Imp to a power source, configure the wifi via the Electric Imp mobile App, and start tweeting! Your hamster will be soo happy to warn you whenever a tweet is coming!!

Ciao and happy hacking!

Stefano

Tweet highlighted text with TweetEverything

If readers of your wordpress site/blog come across a snippet of text they want to Tweet, you know the drill: they have to copy it, open Twitter, create a new message, then paste in the text there and then hit the send button…it’s not that difficult, but what if there was an even easier way? Well, there is, and I’ve just published a WordPress plugin, TweetEverything, to allow tweeting highlighted text with a single click.

Here is the plugin’s page on WordPress.org: http://wordpress.org/plugins/tweeteverything/

Want to contribute? Visit TweetEverything on GitHub .