How Many Developers Does It Take to Screw In a Lightbulb? Leveraging the Internet of Things!

The Internet of Things (IoT) refers to the increasing number of devices that are now connected to the Internet and can be programmed remotely (typically from your smartphone).  Everything from from Oral-B toothbrushes to Samsung Washer-Dryers, Nest Thermostats, and Philips Light Bulbs are now connected and have their own programming interfaces. Additional products are coming online that enable you to control virtually any electronic device remotely.

This technology revolution opens the door for software developers to produce some truly innovative and immersive experiences. Easy to use API’s and libraries now exist, such as Cylon.js and the forthcoming “Thunder” IoT platform from Salesforce.com that make it quite simple to control devices in the physical world from the virtual one. Of course, all of this power also opens up a myriad of security concerns as well. While I certainly don’t want my blender getting hacked into and ruining my margarita,  I have a dream where I can install NEST thermostats in my sales team’s houses and turn up the heat (literally) when automatic reporting from Salesforce.com indicates that they’re not making their quotas. Because that’s how I roll.

Enlightening yourself about IoT with Philips Hue Lights

For our first IoT trick, we’re going to use several well-proven technologies to produce a simple app to control a Philips Hue lightbulb. I’ve been using Hue bulbs for a couple of years now and they’re fantastic. They use light-emitting diodes to produce energy-efficient light across the RGB palette. Each bulb contains a wi-fi radio that connects to a bridge.

2015-12-13_09-31-21

Philips Hue Starter Kit with Bridge and 3 Connected Bulbs


The Bridge has its own REST API that enables you to easily get a list of the bulbs that have been named/registered as well as send commands to set the color and brightness of each bulb.

Tools of the Trade

We used the following tools to produce our first IoT app. As a bonus, all of the aforementioned products are free and open-source. They also all use JavaScript as their programming language.

  • Node.JS application server
    Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js’ package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
  • Cylon.JS API for Controlling Devices
    Cylon.js is a JavaScript framework for robotics, physical computing, and the Internet of Things. It makes it incredibly easy to command robots and devices.
  • Sencha Ext JS (GPL License) for producing the front-end GUI.
    The most comprehensive JavaScript framework for building feature-rich cross-platform web applications targeting desktop, tablets, and smartphones. Ext JS leverages HTML5 features on modern browsers while maintaining compatibility and functionality for legacy browsers.

Using Node.JS and Cylon.JS – By your Command!

I’ve used Node.JS for a number of projects now and have always been impressed by its simple installation, fast performance, and ease-of-use. In particular, the Express framework for Node makes it ridiculously simple to produce a quick and dirty RESTful api like the one that we produced for this app.

Cylon.JS is an awesome API for connecting to more than 40 different hardware platforms — including Philips Hue bulbs and bridges.

iot2.png

Cylon.JS Supported Devices

To create our Node.JS project, we simply created a new folder (/IoT1/) on our filesystem, opened a command prompt, and issued the following statements which installs the Express framework for Node.JS, creates an express project, installs the Cylon API, and adds-in the cylon-hue library:

npm install express --save
express
npm install cylon --save
npm install cylon-hue --save

Next, we implemented a new route, named hue, by inserting code on lines 10 and 28 of the /IoT1/app.js file as illustrated by the following snippet. In effect, this programs our Node server to respond to HTTP requests to a url of http://serverip/hue


var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');
var hue = require('./routes/hue');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', routes);
app.use('/users', users);
app.use('/hue', hue);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
  app.use(function(err, req, res, next) {
    res.status(err.status || 500);
    res.render('error', {
     message: err.message,
     error: err
    });
  });
}

// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.render('error', {
  message: err.message,
  error: {}
 });
});

module.exports = app;

The Cylon.js library uses a high-level object named “Cylon” to control supported devices. A Cylon object has three major properties:

  • connections – Defines how to connect to devices and pass credentials
  • devices – Defines the types of devices that you want to control and their corresponding device drivers
  • work – a function that is automatically invoked after the device connection and authentication has occurred

Each device type has a series of methods that are enumerated in the Cylon.js documentation. Our application makes the following calls:

  • bridge.getFullState() – returns all information about the system including lights and color themes
  • bulb.toggle() – toggles the on/off state of the bulb
  • bulb.rgb() – Sets the color of the bulb as a red,green,blue 3-tuple
  • bulb.turnOn() – Turns the bulb on.
  • bulb.turnOff() – Turns the bulb off

Uisng this information, we created a node.js file named /IoT1/routes/hue.js to handle http GET/POST/PUT requests. In this particular case, we somewhat arbitrarily decided the following:

  • GET requests should return information about connected Hue bulbs from the Hue Bridge
  • PUT requests should turn on/off a selected bulb and change the bulb color to Fig Leaf’s green brand color
  • POST requests should activate the “Red Alert” mode, causing the light to blink like in Star Trek because, well… of course!
var express = require('express');
var router = express.Router();
var Cylon = require('cylon');

// see notes about host + username in the blog article
var host = '10.0.1.71';
var username = '215c4296227a3247159acafa1258e75b';

var redAlert = null; // red alert interval timer

/*
poll the bridge and return info about the lights
*/
router.get('/', function(req, res, next) {

 Cylon.robot({
  connections: {
  hue: {
   adaptor: 'hue',
   host: host,
   username: username
  }
 },

 devices: {
   bridge: {
    driver: 'hue-bridge'
   }
 },

 work: function(my) {
  
  my.bridge.getFullState(function(err, config) {
 
    if (err) {
     var result = {
      success: false,
      error: err
     }
    } else {
     var lights = [];
     for (var i in config.lights) {
       lights.push({
        'id': i,
        'state': config.lights[i].state.on,
        'reachable': config.lights[i].state.reachable,
        'name': config.lights[i].name
       });
     }
     var result = {
      success: true,
      lights: lights,
      config: config.config
     }
    }
    res.writeHead(200, {
     "Content-Type": "application/json"
    });
    res.write(JSON.stringify(result));
    res.end();
   });
  }
 }).start();

});

/*
* toggle bulb on/off
*/
router.put('/', function(req, res) {

var lightId = req.body.id;
var turnOn = (req.body.turnOn == 'true');

Cylon.robot({
 connections: {
  hue: {
   adaptor: 'hue',
   host: host,
   username: username
  }
 },

 devices: {
  bulb: {
  driver: 'hue-light',
  lightId: lightId
 }
},

 work: function(my) {
   if (turnOn) {
    my.bulb.turnOn();
    my.bulb.rgb(0, 102, 51); // fig leaf green
   } else {
    if (global.redAlert) {
     clearInterval(global.redAlert);
     global.redAlert = null;
    }
    my.bulb.turnOff();
   }

   var result = {
    success: true
   };
   res.writeHead(200, {
     "Content-Type": "application/json"
   });
   res.write(JSON.stringify(result));
   res.end();
   }
  }).start();
});

/*
* "Red Alert" mode - red blinking light
*/

router.post('/', function(req, res) {
  
  var lightId = req.body.id;
  var turnOn = (req.body.turnOn == 'true');

  Cylon.robot({
   connections: {
    hue: {
     adaptor: 'hue',
     host: host,
     username: username
    }
   },

   devices: {
    bulb: {
     driver: 'hue-light',
     lightId: lightId
    }
   },

   work: function(my) {
    
    if (!global.redAlert) {
      my.bulb.turnOn();
      my.bulb.rgb(255, 0, 0);
      global.redAlert = every((0.8).second(), function() {
       my.bulb.toggle();
      });
    }

    var result = { success: true };

    res.writeHead(200, {
      "Content-Type" : "application/json"
    });
    res.write(JSON.stringify(result));
    res.end();
   }
  }).start();
});

module.exports = router;

Building out the Web Client with Sencha Ext JS

On the client-side, we used Ext JS to produce a simple GUI illustrated in the figure below. Left-clicking on the bulb toggles its on/off state. Right-clicking on the bulb puts it into “Red Alert” mode.

Bulb Off Ext JS Bulb Activation UI

The View, which outputs the image of the bulb and the combo box light selector is fairly straightforward:


Ext.define('MyApp.view.Lightbulb', {
    extend: 'Ext.window.Window',
    alias: 'widget.Lightbulb',

    requires: [
        'MyApp.view.LightbulbViewModel',
        'MyApp.view.LightbulbViewController',
        'Ext.Img',
        'Ext.form.field.ComboBox'
    ],

    controller: 'lightbulb',
    viewModel: {
        type: 'lightbulb'
    },
    constrain: true,
    height: 358,
    width: 425,
    bodyPadding: 10,
    title: 'Fig Leaf Software IoT Demo 1',

    layout: {
        type: 'vbox',
        align: 'center',
        pack: 'center'
    },
    items: [
        {
            xtype: 'image',
            reference: 'lightbulbImg',
            disabled: true,
            height: 256,
            width: 256,
            bind: {
                src: '{lightbulbUrl}'
            }
        },
        {
            xtype: 'combobox',
            reference: 'lightscombo',
            width: 200,
            fieldLabel: '',
            displayField: 'name',
            forceSelection: true,
            valueField: 'id',
            bind: {
                store: '{Lights}'
            },
            listeners: {
                change: 'onComboboxChange'
            }
        }
    ],
    listeners: {
        afterrender: 'onWindowAfterRender'
    }

});

Ext JS 5 supports the concept of viewmodels that link data structures to a view. Our viewmodel, as illustrated by the following code snippet, defines lightStatus and lightId properties that indicate the on/off state and the id of the lamp that’s being controlled, respectively.

There’s also a data store (array of records) that makes a GET request to our /hue webservice in Node.JS in order to pull the relevant information about connected bulbs. The bulb listing is bound to the combo box in the view.

The lightbulbUrl formula is used in conjunction with data binding in the view to swap-in the appropriate image on mouseclick.


Ext.define('MyApp.view.LightbulbViewModel', {
    extend: 'Ext.app.ViewModel',
    alias: 'viewmodel.lightbulb',

    requires: [
        'Ext.data.Store',
        'Ext.data.proxy.Ajax',
        'Ext.data.reader.Json',
        'Ext.util.Sorter',
        'Ext.util.Filter',
        'Ext.app.bind.Formula'
    ],

    data: {
        lightStatus: false,
        lightId: 0
    },

    stores: {
        Lights: {
            autoLoad: false,
            model: 'MyApp.model.Light',
            proxy: {
                type: 'ajax',
                url: '/hue',
                reader: {
                    type: 'json',
                    rootProperty: 'lights'
                }
            },
            sorters: {
                property: 'name'
            },
            filters: {
                property: 'reachable',
                value: true
            }
        }
    },
    formulas: {
        lightbulbUrl: function(get) {
            if (get('lightStatus')) {
                return 'resources/images/lightbulb_on.png';
            } else {
                return 'resources/images/lightbulb.png';
            }
        }
    }

});

The Ext JS ViewController handles all the events thrown from the view, performing the following functions:

  • On lightbulb click, invoke our Node.JS service, calling its PUT method
  • On lightbulb right-click, invoke our Node.JS service, calling its POST method
  • On combo box selection, set the appropriate values in the viewmodel and display the appropriate bulb image
  • On startup, attach the left-click and right-click event handlers

Ext.define('MyApp.view.LightbulbViewController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.lightbulb',

    onLightbulbClick: function() {
                var vm = this.getViewModel();
                var lightId = vm.get('lightId');
                if (lightId == 0) {
                    Ext.Msg.alert("Error","You must select a bulb");
                    return;
                }
                var status = vm.get('lightStatus');
                vm.set('lightStatus',!status );
                this.lookupReference('lightscombo').getSelection().set('state', !status );

                Ext.Ajax.request({
                    url: '/hue',
                    method: 'PUT',
                    params: {
                        id: vm.get('lightId'),
                        turnOn: !status
                    },
                    success:function(response) {
                        console.log(response);
                    }
                });
    },

    onLightbulbContextMenuClick: function(e) {
        e.preventDefault();
        var vm = this.getViewModel();
        var lightId = vm.get('lightId');

        if (lightId == 0) {
            Ext.Msg.alert("Error","You must select a bulb");
            return;
        }
        vm.set('lightStatus',true);
        Ext.Ajax.request({
            url: '/hue',
            method: 'POST',
            params: {
                id: vm.get('lightId')
            },
            success:function(response) {
                console.log(response);
            }
        });

    },

    onComboboxChange: function(field, newValue, oldValue, eOpts) {
        var vm = this.getViewModel();
        var selection = field.getSelection();
        vm.set('lightId',selection.get('id'));
        vm.set('lightStatus', selection.get('state'));

    },

    onWindowAfterRender: function(component, eOpts) {
        var l = this.lookupReference('lightbulbImg');
        l.getEl().on('click', this.onLightbulbClick, this);
        l.getEl().on('contextmenu', this.onLightbulbContextMenuClick, this);
    }

});

Where do we go from here?

For my next trick, I’m going to move some bulbs down the basement and put my node.js script on a timer so that they all go into “red alert” mode precisely at 8:30 pm. If that doesn’t get my two boys upstairs for bedtime then nothing will.

You can download the complete sourcecode from github by clicking here.

Check out our Node.JS training, Sencha Ext JS training,  and consulting services!

I hope that you found this article to be illuminating. Have a great holiday, everyone!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s