Control Your Home Lights with a Raspberry Pi


In the past, when we’ve wanted to connect a hardware project to the internet, we’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. 

 Control Your Home Lights with a Raspberry Pi


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:

If you’ve programmed for an Arduino before, this code is really straightforward.
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

GPIO.output(17, true)           #Turn the lamp on!
GPIO.output(17, false)          #Turn the lamp off!

 

  • 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

 

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()
to
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>

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. home lights

Getting more sophisticated

“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()

will run the loop

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()

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.

Control Your Home Lights with a Raspberry Pi


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

 

Then, 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!

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! :)

The Cloud Lamp

Dig into all the code Lamp project or grab the whole package for your modifying pleasure!

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\n"); // Can replace / with, eg., /reference/ or similar path
  c.write("Host: "+domain+"\r\n"); // 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\n");
  c.write("Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5\r\n");
  c.write("Accept-Language: en-us,en;q=0.5\r\n");
  c.write("Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n");
  c.write("\r\n");

  //c.stop();
}

home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights home lights

Project Source: TnX && CrediT


Visitors Rating
Rate Here
Ease Of Use
Features
Value
Overall Rating
50%
58%
Visitors Rating
1 rating
You have rated this
What's your reaction?
OWND
0%
Cool
0%
Nice
100%
WHAT ?
100%
MEH
0%
zzzZZzz
0%
Rage
0%

You must log in to post a comment