How to Build a Tweet Controlled RGB LCD
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
- Raspberry Pi model B ($40)
- Wired or Wireless Internet connection
- Adafruit RGB Negative 16x2 LCD+Keypad Kit for Raspberry Pi ($25)
Setup
- Follow the steps on the Adafruit site for setting up and testing your RGB LCD.
- 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.



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.



Scroll down and click Create my 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.



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.
1 |
import sys |
2 |
sys.path.append('/home/pi/py/'+ |
3 |
'Adafruit-Raspberry-Pi-Python-Code/Adafruit_CharLCDPlate') |
4 |
from Adafruit_CharLCDPlate import Adafruit_CharLCDPlate |
5 |
from tweepy.streaming import StreamListener |
6 |
from tweepy import OAuthHandler |
7 |
from tweepy import Stream |
8 |
import json |
9 |
import threading |
10 |
import time |
11 |
import textwrap |
12 |
import subprocess |
13 |
|
14 |
api_key='###' |
15 |
api_secret='###' |
16 |
|
17 |
access_token_key='###' |
18 |
access_token_secret='###' |
19 |
|
20 |
|
21 |
class DisplayLoop(StreamListener): |
22 |
"""
|
23 |
This class is a listener for tweet stream data.
|
24 |
It's also callable so it can run the main display
|
25 |
thread loop to update the display.
|
26 |
"""
|
27 |
def __init__(self): |
28 |
self.lcd = Adafruit_CharLCDPlate() |
29 |
self.lcd.backlight(self.lcd.RED) |
30 |
self.lcd.clear() |
31 |
self.backlight_map = {'clarkson':self.lcd.RED, |
32 |
'pearl':self.lcd.GREEN, |
33 |
'love':self.lcd.BLUE, |
34 |
'hate':self.lcd.YELLOW, |
35 |
'kyle':self.lcd.TEAL, |
36 |
'like':self.lcd.VIOLET} |
37 |
self.msglist = [] |
38 |
self.pos = 0 |
39 |
self.tweet = 'Nothing yet' |
40 |
|
41 |
def set_backlight(self): |
42 |
words = self.tweet.lower().split(' ') |
43 |
use_default = True |
44 |
for w in words: |
45 |
if w in self.backlight_map: |
46 |
self.lcd.backlight(self.backlight_map[w]) |
47 |
use_default = False |
48 |
break
|
49 |
if use_default: |
50 |
self.lcd.backlight(self.lcd.WHITE) |
51 |
|
52 |
def on_data(self, data): |
53 |
tweet_data = json.loads(data) |
54 |
self.tweet = tweet_data['text'].encode('ascii', |
55 |
errors='backslashreplace') |
56 |
self.msglist = [x.ljust(16) for x in |
57 |
textwrap.wrap(str(self.tweet),16)] |
58 |
self.pos = 0 |
59 |
self.set_backlight() |
60 |
self.scroll_message() |
61 |
return True |
62 |
|
63 |
def on_error(self, status): |
64 |
print status |
65 |
|
66 |
def write_message(self,msg): |
67 |
self.lcd.home() |
68 |
self.lcd.message(msg) |
69 |
|
70 |
def scroll_message(self): |
71 |
"""
|
72 |
Displays the page of text and updates
|
73 |
the scroll position for the next call
|
74 |
"""
|
75 |
if len(self.msglist) == 0: |
76 |
self.write_message(''.ljust(16)+'\n'+''.ljust(16)) |
77 |
elif len(self.msglist) == 1: |
78 |
self.write_message(self.msglist[0]+'\n'+''.ljust(16)) |
79 |
elif len(self.msglist) == 2: |
80 |
self.write_message(self.msglist[0]+'\n'+ |
81 |
self.msglist[1]) |
82 |
else: |
83 |
if self.pos >= len(self.msglist)-1: |
84 |
self.pos = 0 |
85 |
else: |
86 |
self.write_message(self.msglist[self.pos]+'\n'+ |
87 |
self.msglist[self.pos+1]) |
88 |
self.pos+=1 |
89 |
|
90 |
def get_ip_address(self,interface): |
91 |
"Returns the IP address for the given interface e.g. eth0"
|
92 |
try: |
93 |
s = subprocess.check_output( |
94 |
["ip","addr","show",interface]) |
95 |
return s.split('\n')[2].strip().split( |
96 |
' ')[1].split('/')[0] |
97 |
except: |
98 |
return '?.?.?.?' |
99 |
|
100 |
def __call__(self): |
101 |
while True: |
102 |
if self.lcd.buttonPressed(self.lcd.LEFT): |
103 |
self.write_message( |
104 |
self.get_ip_address('eth0').ljust(16)+'\n'+ |
105 |
self.get_ip_address('wlan0').ljust(16)) |
106 |
else: |
107 |
self.scroll_message() |
108 |
time.sleep(1) |
109 |
|
110 |
display_loop_instance = DisplayLoop() |
111 |
|
112 |
# Start the thread running the callable
|
113 |
threading.Thread(target=display_loop_instance).start() |
114 |
|
115 |
# Log in to twitter and start the tracking stream
|
116 |
auth = OAuthHandler(api_key, api_secret) |
117 |
auth.set_access_token(access_token_key, access_token_secret) |
118 |
stream = Stream(auth, display_loop_instance) |
119 |
stream.filter(track=['jeremy']) |
The Code Explained



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:
- the main program thread that connects to Twitter and receives the tweets, and
- 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:
1 |
{"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.