July 8, 2013 Software leds, rasberrypi, software, webapp, ws2801 The Pi Control Script

The Raspberry Pi was built as a cheap, educational computer platform for students. But it’s exposed GPIO pins, linux support and prices comparable to an Arduino, have all contributed to it’s meteoric rise as an ‘internet of things’ style embedded computer. Many projects have used it’s small form factor to control all types of things. For example, the Cloud Lamp project, that I recently built!

In the past, when I’ve wanted to connect a hardware project to the internet, I’ve gone with Arduino and an ethernet shield. The problem with this solution is that the price was high and the available space for software was limited. This is where the Raspberry Pi really shines! But in order for the Raspberry Pi to provide these great benefits, you’ll need your program to be able to communicate. In particular, a web-based API will make it a snap to communicate with. Python is a popular language for using the GPIO pins and so pairing that with the Twisted networking module makes for a powerful program.

First check out what I was able to accomplish using this technique, then, after the video, stay tuned for a simple, pared down and detailed tutorial of the basics. After that I jump into the code that powers the actual lamp!

A simple example and tutorial of the basics of turning a Raspberry Pi into a networked controller

First, let’s consider a simple example of a single bulb lamp connected to a powerswitch tail, which communicates with a Raspberry Pi on GPIO pin 17.

If you wanted to write a python program that would turn the lamp on, then off the code would look like this:

import RPi.GPIO as GPIO #import the GPIO library GPIO.setmode(GPIO.BCM) #Set the pin naming scheme GPIO.setup(17, GPIO.OUT) #Tell pin 17 to be an output pin GPIO.output(17, true) #Turn the lamp on! GPIO.output(17, false) #Turn the lamp off! 1 2 3 4 5 6 import RPi . GPIO as GPIO #import the GPIO library GPIO . setmode ( GPIO . BCM ) #Set the pin naming scheme GPIO . setup ( 17 , GPIO . OUT ) #Tell pin 17 to be an output pin GPIO . output ( 17 , true ) #Turn the lamp on! GPIO . output ( 17 , false ) #Turn the lamp off!

If you’ve programmed for an Arduino before, this code is really straightforward.

The first few lines are setup, import will load the Raspberry Pi GPIO library

The second sets the pin naming scheme

The third tells pin 17 to be an output pin.

Once the setup is taken care of you can turn the pin off or on now, easily. Which in turn turns the lamp off and on!

Now we can control the hardware, but what good is a controller program if it isn’t listening? Here’s where Twisted comes in, let’s say we wanted to be able to turn the lamp off and on remotely. The code to listen for input over the web looks like this:

from twisted.web.server import Site #import twisted stuff from twisted.web.resource import Resource from twisted.internet import reactor import RPi.GPIO as GPIO #import the GPIO library GPIO.setmode(GPIO.BCM) #Set the pin naming scheme GPIO.setmode(17, GPIO.OUT) #Tell pin 17 to be an output pin class lampAPI(Resource): def render_GET(self, request): if 'light' in request.args: #'light' is the URL variable if request.args['light'][0] == "off": #Did the client put 'off' in the light var? GPIO.output(power_pin, False) #turn the lamp off return " light off " #tell the browser/client that we did it if request.args['light'][0] == "on": #Did the client put 'on' in the light var? GPIO.output(power_pin, True) #turn the lamp on return " light on " #tell the browser/client that we did it root = Resource() #Create a root root.putChild("API", lampAPI()) #Create a child that will handle requests (the second argument must be the class name) factory = Site(root) #Initialize the twisted object reactor.listenTCP(80, factory) #Choose the port to listen on (80 is standard for HTTP) reactor.run() #Start listening, this command is an infinite loop #so don't bother putting anything after it 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from twisted . web . server import Site #import twisted stuff from twisted . web . resource import Resource from twisted . internet import reactor import RPi . GPIO as GPIO #import the GPIO library GPIO . setmode ( GPIO . BCM ) #Set the pin naming scheme GPIO . setmode ( 17 , GPIO . OUT ) #Tell pin 17 to be an output pin class lampAPI ( Resource ) : def render_GET ( self , request ) : if 'light' in request . args : #'light' is the URL variable if request . args [ 'light' ] [ 0 ] == "off" : #Did the client put 'off' in the light var? GPIO . output ( power_pin , False ) #turn the lamp off return " light off " #tell the browser/client that we did it if request . args [ 'light' ] [ 0 ] == "on" : #Did the client put 'on' in the light var? GPIO . output ( power_pin , True ) #turn the lamp on return " light on " #tell the browser/client that we did it root = Resource ( ) #Create a root root . putChild ( "API" , lampAPI ( ) ) #Create a child that will handle requests (the second argument must be the class name) factory = Site ( root ) #Initialize the twisted object reactor . listenTCP ( 80 , factory ) #Choose the port to listen on (80 is standard for HTTP) reactor . run ( ) #Start listening, this command is an infinite loop #so don't bother putting anything after it

After including the twisted modules you’ll recognize the GPIO setup again.

The next chunk is our response class, it will define a standard ‘get’ request that we can use as an API to turn the bulb off and on. ‘lampAPI’ is the class name I chose, name it whatever you like. In the first IF statement, the first term in the condition is the URL variable. So if you want your API call to look like this ( http://127.0.0.1/API?foo=1 ) then you would put ‘foo’ in the quotes instead. Once you’ve confirmed that your variable made it into the request object, you can now check what the variable holds. That’s what the next two IF statements are doing. The variables can contain whatever you like. You could look for a zero or one, yes or no, off or on, etc. Again, you should recognize the GPIO statement from before, that will turn the light off or on, respectively. The return statement should be a string, and whatever it contains is what the client will spit out to the browser!

Lastly, the Twisted object is initialized, and the program enters an infinite loop where we check to see if there’s a new request, forever. Now you can turn your light off and on from anywhere by typing the following URL/commands:

http://[ip of rasberry pi]/API?light=on (Turn the light on)

http://[ip of rasberry pi]/API?light=off (Turn the light off)

Ok, this is great, but an API is an interface for robots, let’s make one for humans.

The first step is to set up the root object of the twisted object to pass files to the client, which is the primary job of a web-server. This can be done simply by changing:

root = Resource() 1 root = Resource ( )

to

root = File("lampwww") 1 root = File ( "lampwww" )

Now, create a directory called ‘lampwww’ in the same directory as your python file. Anything called from the URL of the Pi will be passed along to the browser. The last step here is simply creating an html file with links that will call up our API. In it’s simplest form, it could look like the following (create a file called index.html and put it in the lampwww dir) :

My Lamp Control <a href="http://[ip of lamp]/API?light=on"> Turn On </a> | <a href="http://[ip of lamp]/API?light=off"> Turn Off</a> 1 2 My Lamp Control < a href = "http://[ip of lamp]/API?light=on" > Turn On < / a > | < a href = "http://[ip of lamp]/API?light=off" > Turn Off < / a >

If you browse to the IP of your lamp, you’ll see the heading and the two choices to turn the lamp on or off; Tada! Web-connected lamp.

Getting more sophisticated: showing what I added to get the functionality in my Cloud Lamp project

I crafted this simple example to make an easy to follow tutorial but there’s a whole lot of improvements that could be made. In my Cloud Lamp project for instance, I not only had light bulbs connected, I also had a 60 ‘pixel’ string of ws2801 LED’s. So what could we do with this type of display? How about creating different modes of operation, each with it’s own unique display.

Adding this functionality required a few key changes, first, the end of our example is an infinite loop that looks for changes from the network. We need to change this so that we can do other things besides listening. The statement,

reactor.run() 1 reactor . run ( )

will run the loop, but if we change that to

reactor.startRunning(false) 1 reactor . startRunning ( false )

Now we’re not tied down to only listening for new connections. The end of our program would now be an infinite loop that includes but is not limited to, our listening statement, like this:

while True: reactor.iterate() 1 2 while True : reactor . iterate ( )

Next, how do we animate the LED’s in different modes, which is a continual process, while also listening for new connections. I opted for a simple list of IF’s that choose which animation subroutine to run on every loop iteration. A global ‘mode’ variable will decide which animation to iterate through. Each of the animation sub-routines is contained in a function that can be called on from the main while loop. In my infodisplay mode, I’m showing the state of the house (Whether all the doors are close or not, whether there’s motion in the house), the surf report and the weather report. I accomplished this by using the Beautiful Soup module to scrape a Magic Seaweed widget, Weather Underground’s API for the weather report and MicasaVerde’s json feed, all formatted to output into a serial list of multicolored LED’s.

And finally, I wrote a more sophisticated web interface, taking advantage of the ample speed and space available on the Raspberry Pi. Besides a fancier looking interface, the important part is multi-directional feedback powered by Jquery. Talking to the lamp is easy, i just use standard .click’s on the mode buttons and .get’s to call the API that I created. The web interface is also able to dynamically tell which mode it’s in and whether the bulbs are off or on. I do this by creating another API that spits out a Json feed of the state of all the lamp’s functions, like this:

def getStatus(): global MODE global BULB global STROBE global BRIGHT jsonString = '' jsonString += '{"lamp":{"bulb":' jsonString += str(BULB) jsonString += ',"strobefreq":' jsonString += str(STROBE) jsonString += ',"mode":' jsonString += str(MODE) jsonString += ',"brightness":' jsonString += str(BRIGHT) jsonString += '}}' #print jsonString return jsonString 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def getStatus ( ) : global MODE global BULB global STROBE global BRIGHT jsonString = '' jsonString += '{"lamp":{"bulb":' jsonString += str ( BULB ) jsonString += ',"strobefreq":' jsonString += str ( STROBE ) jsonString += ',"mode":' jsonString += str ( MODE ) jsonString += ',"brightness":' jsonString += str ( BRIGHT ) jsonString += '}}' #print jsonString return jsonString

Then, I use Jquery’s cross-domain capable Jsonp functionality to check the status every X number of seconds and update the interface. It’s fairly simple from a code perspective and the result is that even with multiple interfaces open, they will all update depending on the actual state of the Lamp!

Well, now that I’ve talked about most of what makes my Cloud Lamp Project tick, why not dig into all the code yourself? It’s up on Github, feel free to modify, use, scoff at or appreciate all of it! :)

[ml_raw_html]

[/ml_raw_html]

What’s next?

In addition to turning the light-bulbs off and on and running the LED’s internally, I’ve also got a mode that allows the LED’s to be controlled externally through a TCP stream. It’s too slow to be very functional right now, however. And still requires some debugging. To try that as well, here’s an example Processing.org script:

import processing.net.*; Client myClient; //Put the IP of your Cloud Lamp here in this variable String ip=""; String datamode="-1"; int pcirc[][] = new int[60][3]; int LEDnum = 60; int RGBnum = 3; int xCenter = 300; int yCenter = 300; int r = 250; int LEDdiameter = 15; int count = 0; int count2 = 0; void setup() { setModeData(); delay(2000); myClient = new Client(this, ip, 50007); size(600, 600); ellipseMode(CENTER); //Create empty LED array for (int i = 0; i < LEDnum; i ++ ) { for (int j = 0; j < RGBnum; j ++ ) { // Initialize each object pcirc[i][j] = 0; } } } //Show the RGB spectrum sequentially, similar to the beginning of Lady Ada's ws2801 test script void draw() { background(150.0); if(count2 == 0) { pcirc[count][0] = 255; pcirc[count][1] = 0; pcirc[count][2] = 0; } if(count2 == 1) { pcirc[count][0] = 0; pcirc[count][1] = 255; pcirc[count][2] = 0; } if(count2 == 2) { pcirc[count][0] = 0; pcirc[count][1] = 0; pcirc[count][2] = 255; } count++; if(count == 60) { count = 0; count2++; if(count2 == 3) { count2 = 0; } } refreshCircles(); delay(40); sendData(); } This function draws and refreshes an onscreen simulation of the LED's in the Cloud Lamp void refreshCircles() { for (int i = 0; i < LEDnum; i ++ ) { float xrad = (float) (xCenter + r * Math.cos(2 * Math.PI * i / LEDnum)); float yrad = (float) (yCenter + r * Math.sin(2 * Math.PI * i / LEDnum)); int Col[] = new int[3]; for (int j = 0; j < RGBnum; j ++ ) { // Initialize each object Col[j] = pcirc[i][j]; } //print("(" + Col[0] + ":" + Col[1] + ":" + Col[2] + ")|"); //line(xCenter, yCenter, xrad, yrad); fill(Col[0],Col[1],Col[2]); //stroke(0); ellipse(xrad, yrad, LEDdiameter, LEDdiameter); } } //This function sends the string data prepared by the draw loop void sendData() { String data = ""; for (int i = 0; i < LEDnum; i ++ ) { int Col[] = new int[3]; for (int j = 0; j < RGBnum; j ++ ) { // Initialize each object Col[j] = pcirc[i][j]; } data = data + Col[0] + "," + Col[1] + "," + Col[2] + ","; } myClient.write(data); } //This function puts the Cloud Lamp into 'Data Stream' mode void setModeData() { Client c; String data; String domain = ip; String addr = "/API?mode=" + datamode; print(addr); c = new Client(this, domain, 80); // Connect to server on port 80 c.write("GET "+addr+" HTTP/1.1\r

"); // Can replace / with, eg., /reference/ or similar path c.write("Host: "+domain+"\r

"); // Which server is asked c.write("User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.04 Chromium/10.0.648.205 Chrome/10.0.648.205 Safari/534.16\r

"); c.write("Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r

"); c.write("Accept-Language: en-us,en;q=0.5\r

"); c.write("Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r

"); c.write("\r

"); //c.stop(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 import processing . net . * ; Client myClient ; //Put the IP of your Cloud Lamp here in this variable String ip = "" ; String datamode = "-1" ; int pcirc [ ] [ ] = new int [ 60 ] [ 3 ] ; int LEDnum = 60 ; int RGBnum = 3 ; int xCenter = 300 ; int yCenter = 300 ; int r = 250 ; int LEDdiameter = 15 ; int count = 0 ; int count2 = 0 ; void setup ( ) { setModeData ( ) ; delay ( 2000 ) ; myClient = new Client ( this , ip , 50007 ) ; size ( 600 , 600 ) ; ellipseMode ( CENTER ) ; //Create empty LED array for ( int i = 0 ; i & lt ; LEDnum ; i ++ ) { for ( int j = 0 ; j & lt ; RGBnum ; j ++ ) { // Initialize each object pcirc [ i ] [ j ] = 0 ; } } } //Show the RGB spectrum sequentially, similar to the beginning of Lady Ada's ws2801 test script void draw ( ) { background ( 150.0 ) ; if ( count2 == 0 ) { pcirc [ count ] [ 0 ] = 255 ; pcirc [ count ] [ 1 ] = 0 ; pcirc [ count ] [ 2 ] = 0 ; } if ( count2 == 1 ) { pcirc [ count ] [ 0 ] = 0 ; pcirc [ count ] [ 1 ] = 255 ; pcirc [ count ] [ 2 ] = 0 ; } if ( count2 == 2 ) { pcirc [ count ] [ 0 ] = 0 ; pcirc [ count ] [ 1 ] = 0 ; pcirc [ count ] [ 2 ] = 255 ; } count ++ ; if ( count == 60 ) { count = 0 ; count2 ++ ; if ( count2 == 3 ) { count2 = 0 ; } } refreshCircles ( ) ; delay ( 40 ) ; sendData ( ) ; } This function draws and refreshes an onscreen simulation of the LED 's in the Cloud Lamp void refreshCircles() { for (int i = 0; i < LEDnum; i ++ ) { float xrad = (float) (xCenter + r * Math.cos(2 * Math.PI * i / LEDnum)); float yrad = (float) (yCenter + r * Math.sin(2 * Math.PI * i / LEDnum)); int Col[] = new int[3]; for (int j = 0; j < RGBnum; j ++ ) { // Initialize each object Col[j] = pcirc[i][j]; } //print("(" + Col[0] + ":" + Col[1] + ":" + Col[2] + ")|"); //line(xCenter, yCenter, xrad, yrad); fill(Col[0],Col[1],Col[2]); //stroke(0); ellipse(xrad, yrad, LEDdiameter, LEDdiameter); } } //This function sends the string data prepared by the draw loop void sendData() { String data = ""; for (int i = 0; i < LEDnum; i ++ ) { int Col[] = new int[3]; for (int j = 0; j < RGBnum; j ++ ) { // Initialize each object Col[j] = pcirc[i][j]; } data = data + Col[0] + "," + Col[1] + "," + Col[2] + ","; } myClient.write(data); } //This function puts the Cloud Lamp into ' Data Stream ' mode void setModeData ( ) { Client c ; String data ; String domain = ip ; String addr = "/API?mode=" + datamode ; print ( addr ) ; c = new Client ( this , domain , 80 ) ; // Connect to server on port 80 c . write ( "GET " + addr + " HTTP/1.1\r

" ) ; // Can replace / with, eg., /reference/ or similar path c . write ( "Host: " + domain + "\r

" ) ; // Which server is asked c . write ( "User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Ubuntu/10.04 Chromium/10.0.648.205 Chrome/10.0.648.205 Safari/534.16\r

" ) ; c . write ( "Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r

" ) ; c . write ( "Accept-Language: en-us,en;q=0.5\r

" ) ; c . write ( "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r

" ) ; c . write ( "\r

" ) ; //c.stop(); }

I will also be coding and publishing a Mi Casa Verde plugin that will allow you to control the functions of the Lamp and see it’s status through their home control system.

Finally, a few features I plan on adding to the setup are a brightness setting for all Modes and some kind of mechanism for letting modes have individual settings. Like frequency for the strobe lights, or speed of drops for the rain mode.

Let me know if you have any comments or questions down below! :)

π