Advertisement

How to Build a Tweet Controlled RGB LCD

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →
What You'll Be Creating

In this tutorial I’ll show you how to use a Raspberry Pi with an RGB LCD to monitor tweets. Tweets containing specific keywords are displayed in defined colours. You’ll learn how to create a Twitter application to use the stream API to push data to your program, giving you an opportunity to learn about multi-threaded Python programs.

Required Hardware

Setup

  1. Follow the steps on the Adafruit site for setting up and testing your RGB LCD.
  2. Install Tweepy like so:

sudo pip install tweepy

Create a Twitter Application

Create a Twitter Application, on the Twitter web site, to allow the Raspberry Pi program to connect to the stream. To do this navigate to https://apps.twitter.com and sign in.

Create an application by completing the form, as shown below. Note: you can put a placeholder for the website–it's a required field but not required for this Raspberry Pi project.

Creating a Twitter application on the Twitter website

When you submit the form an application will be created. On the API Keys tab you'll see an API Key and API secret–these are unique to you and will be required later.

Since the API Key and Secret are unique, I've redacted mine on this screenshot

Scroll down and click Create my access token:

Creating the Access Token

Refresh the page if you need to. You'll now have the Access token and Access token secret. Again, these are unique to you and will be required later.

Tokens completed.  They can also be regenerated, to create new ones, or revoked

That's the application created.

The Code

In the code for this project, below, you'll need to edit to edit to enter your API Key, API Secret, Access Token Key and Access Token Secret. You may also need to change the path to the Adafruit_CharLCDPlate so it matches where you've installed it on your Pi.

When you run the program, the Pi will connect to the Twitter Stream. In this example, any tweet containing the word Jeremy will be pushed to the Pi and displayed on the RGB LCD. This is any public tweet from anyone in the world–nothing to do with who you're following–it's anyone!

Remember to use sudo when running the Python script–root privileges are required for the i2c access to the LCD. For example:

 sudo python /path/to/script/script.py

Note: The program will not close cleanly by pressing Control-C. Instead, open another shell and use ps ax and kill to terminate the program.

The program looks for other words in the tweet to set the colour of the RGB backlight. If it finds the work Clarkson it will set the colour to red, pearl is green, love is blue.

Finally, if there's time between Tweets, the program will scroll the word-wrapped text so that you can see it all. 

import sys
sys.path.append('/home/pi/py/'+
    'Adafruit-Raspberry-Pi-Python-Code/Adafruit_CharLCDPlate')
from Adafruit_CharLCDPlate import Adafruit_CharLCDPlate
from tweepy.streaming import StreamListener
from tweepy import OAuthHandler
from tweepy import Stream
import json
import threading
import time
import textwrap
import subprocess

api_key='###'
api_secret='###'

access_token_key='###'
access_token_secret='###'


class DisplayLoop(StreamListener):
    """
    This class is a listener for tweet stream data.
    It's also callable so it can run the main display 
    thread loop to update the display.
    """
    def __init__(self):
        self.lcd = Adafruit_CharLCDPlate()
        self.lcd.backlight(self.lcd.RED)
        self.lcd.clear()
        self.backlight_map = {'clarkson':self.lcd.RED,
                              'pearl':self.lcd.GREEN,
                              'love':self.lcd.BLUE,
                              'hate':self.lcd.YELLOW,
                              'kyle':self.lcd.TEAL,
                              'like':self.lcd.VIOLET}
        self.msglist = []
        self.pos = 0
        self.tweet = 'Nothing yet'
        
    def set_backlight(self):
        words = self.tweet.lower().split(' ')
        use_default = True
        for w in words:
            if w in self.backlight_map:
                self.lcd.backlight(self.backlight_map[w])
                use_default = False
                break    
        if use_default:
            self.lcd.backlight(self.lcd.WHITE)
        
    def on_data(self, data):
        tweet_data = json.loads(data)
        self.tweet = tweet_data['text'].encode('ascii',
                        errors='backslashreplace')
        self.msglist = [x.ljust(16) for x in
                        textwrap.wrap(str(self.tweet),16)]
        self.pos = 0
        self.set_backlight()
        self.scroll_message()
        return True

    def on_error(self, status):
        print status
        
    def write_message(self,msg):
        self.lcd.home()
        self.lcd.message(msg)

    def scroll_message(self):
        """
        Displays the page of text and updates
        the scroll position for the next call
        """
        if len(self.msglist) == 0:
            self.write_message(''.ljust(16)+'\n'+''.ljust(16))
        elif len(self.msglist) == 1:
            self.write_message(self.msglist[0]+'\n'+''.ljust(16))
        elif len(self.msglist) == 2:
            self.write_message(self.msglist[0]+'\n'+
                               self.msglist[1])
        else:
            if self.pos >= len(self.msglist)-1:
                self.pos = 0
            else:
                self.write_message(self.msglist[self.pos]+'\n'+
                                   self.msglist[self.pos+1])
                self.pos+=1        
            
    def get_ip_address(self,interface):
        "Returns the IP address for the given interface e.g. eth0"
        try:
            s = subprocess.check_output(
                    ["ip","addr","show",interface])
            return s.split('\n')[2].strip().split(
                    ' ')[1].split('/')[0]
        except:
            return '?.?.?.?'        
        
    def __call__(self):
        while True:
            if self.lcd.buttonPressed(self.lcd.LEFT):
                self.write_message(
                    self.get_ip_address('eth0').ljust(16)+'\n'+
                    self.get_ip_address('wlan0').ljust(16))
            else:
                self.scroll_message()
            time.sleep(1)

display_loop_instance = DisplayLoop()

# Start the thread running the callable
threading.Thread(target=display_loop_instance).start()

# Log in to twitter and start the tracking stream
auth = OAuthHandler(api_key, api_secret)
auth.set_access_token(access_token_key, access_token_secret)
stream = Stream(auth, display_loop_instance)
stream.filter(track=['jeremy'])

The Code Explained

Tweet display

There are a few key things happening in the code. You can adapt the code for your own purposes, and I encourage you to experiment. There are two threads:

  1. the main program thread that connects to Twitter and receives the tweets, and
  2. the display thread that manages the LCD screen–it displays and scrolls the text.

Main Thread

There's a simple example on the Tweepy github page–this connects to the stream and prints out any tweet containing the word basketball. You can see in that example, more clearly, how a StreamListener class is defined and then passed to the Stream constructor. 

I've done the same in my script here: stream = Stream(auth, display_loop_instance) with display_loop_instance

The instance of StreamListener can define a few event methods for Tweepy to call. Just like the example, I'm only using on_data and on_error. Once the connection to Twitter is established on_data is called whenever a new tweet comes in. The tweet data that's received is a UTF-8 encoded JSON document like this:

{"created_at":"Sun May 18 11:07:53 +0000 2014","id":467984918237437952,"id_str":"467984918237437952","text":"Here's a test message to Jeremy (@jerbly) from controlmypi","source":"web","truncated":false,"in_reply_to_status_id":null,"in_reply_to_status_id_str":null,"in_reply_to_user_id":null,"in_reply_to_user_id_str":null,"in_reply_to_screen_name":null,"user":{"id":2413327915,"id_str":"2413327915","name":"ControlMyPi","screen_name":"ControlMyPi","location":"","url":null,"description":null,"protected":false,"followers_count":0,"friends_count":0,"listed_count":0,"created_at":"Wed Mar 26 23:39:27 +0000 2014","favourites_count":0,"utc_offset":null,"time_zone":null,"geo_enabled":false,"verified":false,"statuses_count":7,"lang":"en","contributors_enabled":false,"is_translator":false,"is_translation_enabled":false,"profile_background_color":"C0DEED","profile_background_image_url":"http:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_image_url_https":"https:\/\/abs.twimg.com\/images\/themes\/theme1\/bg.png","profile_background_tile":false,"profile_image_url":"http:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_5_normal.png","profile_image_url_https":"https:\/\/abs.twimg.com\/sticky\/default_profile_images\/default_profile_5_normal.png","profile_link_color":"0084B4","profile_sidebar_border_color":"C0DEED","profile_sidebar_fill_color":"DDEEF6","profile_text_color":"333333","profile_use_background_image":true,"default_profile":true,"default_profile_image":true,"following":null,"follow_request_sent":null,"notifications":null},"geo":null,"coordinates":null,"place":null,"contributors":null,"retweet_count":0,"favorite_count":0,"entities":{"hashtags":[],"symbols":[],"urls":[],"user_mentions":[{"screen_name":"jerbly","name":"Jeremy Blythe","id":106417803,"id_str":"106417803","indices":[33,40]}]},"favorited":false,"retweeted":false,"filter_level":"medium","lang":"en"}

I'm only interested in the text attribute from this document so I'll use Python's json library to parse the json into Python objects. I can then grab the text and encode it as ascii so that it's compatible with the LCD screen. 

In a list comprehension I've word wrapped the tweet into a space padded 16 character width list, ready to display on the screen: [x.ljust(16) for x in textwrap.wrap(str(self.tweet),16)]

Next, I've calledset_backlight–this looks for a secondary word within the tweet text and sets the RGB backlight colour. If it doesn't find a word in the map then the display defaults to white.

Finally I've called scroll_message to force an immediate update on the display.

Display thread

There are two independent things happening in the program: receiving tweets from the stream and scrolling through them on the display. The display thread takes care of the scrolling and it also monitors the keypad for a secondary function.

The two threads need to work over the same data, the tweets, so to keep things simple the display thread also calls methods on the display_loop_instance

Python has a neat way to start a thread that calls a method on an object. You just have to make your class callable by defining a __call__ method. 

In the code this method has a never-ending while loop with a 1 second sleep. This sleep is vital, without it the text will scroll through too fast. Also the thread has to yield so it doesn't needlessly use a lot of CPU time.

An extra useful thing here, and possibly a point where this program could be expanded, is the keypad monitoring. I like to run this program from boot-up so you can take it to work or somewhere and plug it into a LAN on DHCP and it just works. 

Sometimes though, you need to find out the IP address you've been assigned so you can ssh into it. In the display thread never-ending loop I've added a simple statement to check if the left keypad button is pressed. If it is, I display the wired and wireless IP addresses on the screen. You can test this by pressing left key.

To run the program automatically at boot simply edit the /etc/rc.local file and add a line to run your program:

python /home/pi/path/to/program/program.py &

Reboot and it should start automatically.

Conclusion

In this tutorial, I have shown you how to construct a device, using a Raspberry Pi and an Adafruit RGB LCD, to display tweets and to display them in certain colours dependent upon any keywords defined in the tweet itself.

Let me know if you have any particularly interesting ideas for the use of this project or ways in which you have adapted it for your own purposes.


Advertisement