Home Automation with Raspberry Pi

CherryPy is a Web framework module for Python. It is easily extendible to support WebSocket using the ws4py module. CherryPy and ws4py also can be installed using pip:


pip install cherrypy
pip install ws4py

Examples of using the CherryPy framework and the ws4py plugin can be found in the CherryPy docs and the ws4py docs. A basic CherryPy server can be spawned using the code shown in Listing 2.

Listing 2. Spawning a Basic CherryPy Server


# From the CherryPy Docs at
# https://cherrypy.readthedocs.org/en/latest/tutorials.html

import cherrypy    # import the cherrypy module

class HelloWorld(object):      #
    @cherrypy.expose           # Make the function available
    def index(self):           # Create a function for each request
        return "Hello world!"  # Returned value is sent to the browser

if __name__ == '__main__':
   cherrypy.quickstart(HelloWorld())    # start the CherryPy server 
                                        # and pass the class handle
                                        # to handle request

Slightly more advanced code would pass the quickstart method an object with configuration. The partial code in Listing 3 illustrates this. This code serves requests to /js from the js folder. The js folder resides in the home directory of the server code.

Listing 3. Passing the quickstart Method


cherrypy.quickstart(HelloWorld(), '', config={
   '/js': {          # Configure how to serve requests for /js
   'tools.staticdir.on': True,     # Serve content statically 
                                   # from a directory
   'tools.staticdir.dir': 'js'     # Directory with respect to 
                                   # server home.
   }
});

To add WebSocket support to the CherryPy server, modify the code as shown in Listing 4. The WebSocket handler class needs to implement three methods: opened, closed and received_message. Listing 4 is a basic WebSocket server that has been kept small for the purpose of explaining the major functional parts of the code; hence, it does not actually do anything.

Listing 4. Basic WebSocket Server


import cherrypy                 # Import CherryPy server module
# Import plugin modules for CherryPy
from ws4py.server.cherrypyserver  import WebSocketPlugin, WebSocketTool
from ws4py.websocket import WebSocket   # Import modules for 
                                        # the ws4py plugin.
from ws4py.messaging import TextMessage

class ChatWebSocketHandler(WebSocket):
        def received_message(self, m):
                msg=m.data.decode("utf-8")
                print msg
                cherrypy.engine.publish('websocket-broadcast', 
                 ↪"Broadcast Message: Received a message")

        def closed(self, code, reason="A client left the room 
         ↪without a proper explanation."):
                cherrypy.engine.publish('websocket-broadcast', 
                 ↪TextMessage(reason))

class Root(object):
    @cherrypy.expose
    def index(self):
        return "index"

    @cherrypy.expose
    def ws(self):
        print "Handler created: %s" % repr(cherrypy.request.ws_handler)


if __name__ == '__main__':
    WebSocketPlugin(cherrypy.engine).subscribe()   # initialize websocket
                                                   # plugin
    cherrypy.tools.websocket = WebSocketTool()          #
    cherrypy.config.update({'server.socket_host': '0.0.0.0',
        'server.socket_port': 9003,
        'tools.staticdir.root': '/home/pi'})
    cherrypy.quickstart(Root(), '', config={
             '/ws': {
                     'tools.websocket.on': True,
                     'tools.websocket.handler_cls': ChatWebSocketHandler
               }
        });

On the client side, the HTML needs to implement a function to connect to a WebSocket and handle incoming messages. Listing 5 shows simple HTML that would do that. This code uses the jQuery.ready() event to start connecting to the WebSocket server. The code in this Listing implements methods to handle all events: onopen(), onclose(), onerror() and onmessage(). To extend this example, add code to the onmessage() method to handle messages.

Listing 5. Connecting to WebSocket and Handling Incoming Messages


<html>
    <head></head>
    <body>

    <script src="/js/jquery.min.js"></script>
    <script type="text/javascript">
    var ws;
    var addr="ws://127.0.0.1:9000";
    $(document).ready(function (){
            connectWS();
    });
    function dbg(m){
            console.log(m);
    }
    function connectWS(){
            dbg('Connecting...');
            if (window.WebSocket) {
                    ws = new WebSocket(addr);
            }
            else if (window.MozWebSocket) {
                    ws = MozWebSocket(addr);
            }
            else {
                    alert('Your archaic browser does not support
                     ↪WebSockets.');
                    dbg('WebSocket Not Supported');
                    return;
            }

            /* on websocket close */
            ws.onclose=function(){
                    dbg('Connection Closed.');
                    reconnect=setTimeout(connectWS,6000); //try to 
                                                          //reconnect
                                                          //every 6 secs.
            }

            /* on websocket connection */
            ws.onopen=function(){
                    dbg('Connected.');
                    ws.send('Some message to send to the 
                     ↪WebSocket server.');
            }

            /* on websocket error */
            ws.onerror=function(e){
                    dbg("Socket error: " + e.data);
            }

            /* on websocket receiving a message */
            ws.onmessage = function (evt) {
                    dbg(evt.data);
                    //add functions to handle messages.
            }
            return 0;
    }
    </script>
    </body>
</html>

Pi Home Automation

Now that you've seen the basics of WebSockets, CherryPy and the HTML front end, let's get to the actual code. You can get the code from the Git repository at https://bitbucket.org/lordloh/pi-home-automation. You can clone this repository locally on your RPi, and execute it out of the box using the command:


git clone https://bitbucket.org/lordloh/pi-home-automation.git
git fetch && git checkout LinuxJournal2015May
cd pi-home-automation
python relay.py

The relayLabel.json file holds the required configuration, such as labels for relays, times for lights to go on and off and so on. Listing 6 shows the basic schema of the configuration. Repeat this pattern for each relay. The dow property is formed by using one bit for each day of the week starting from Monday for the LSB to Sunday for the MSB.

Listing 6. Basic Schema of the Configuration


{
  "relay1": {
    "times": [
      {
        "start": [
          <hour>,
          <minute>,
          <second>
        ],
        "end": [
          <hour>,
          <minute>,
          <second>
        ],
        "dow":
<Monday<<0|Tuesday<<1|Wednesday<<2|Thursday<<3|
↪Friday<<4|Saturday<<5|Sunday<<6>
      }
    ],
    "id": 1,
    "label": "<Appliance Name>"
  }
}

Figure 4 shows the block diagram of the system displaying the major functional parts. Table 2 enumerates all the commands the client may send to the server and the action that the server is expected to take. These commands are sent from the browser to the server in JSON format. The command schema is as follows:


{
    "c":"<command form TABLE 2>",
    "r":<relay Number>
}

The update and updateLabels commands do not take a relay number. Apart from relay.py and relayLabel.json, the only other file required is index.html. The relay.py script reads this file and serves it in response to HTTP requests. The index.html file contains the HTML, CSS and JavaScript to render the UI.

Figure 4. Block Diagram of the System

Table 2. Commands

Command Description
on Switch a relay on
off Switch a relay off
update Send status of GPIO pins and relay labels
updateLabels Save new labels to JSON files

Once the system is up and running, you'll want to access it from over the Internet. To do this, you need to set a permanent MAC address and reserved IP address for the Raspberry Pi on your local network, and set up port forwarding on your router. The process for doing this varies according to router, and your router manual is the best reference for it. Additionally, you can use a dynamic domain name service so that you do not need to type your IP address to access your Pi every time. Some routers include support for certain dynamic DNS services.

Conclusion

I hope this article helps you to build this or other similar projects. This project can be extended to add new features, such as detecting your phone connected to your Wi-Fi and switching on lights. You also could integrate this with applications, such as OnX and Android Tasker. Adding password protection for out-of-network access is beneficial. Feel free to mention any issues, bugs and feature requests at http://code.lohray.com/pi-home-automation/issues.

______________________

Bharath Bhushan Lohray is a PhD student working on his dissertation on image compression techniques at the Department of Electrical & Computer Engineering, Texas Tech University. He is interested in machine learning.