Welcome back! In the previous article, we explored smart home automation via a garage door opener service using LiftMaster, PubNub, and Node.js. The context is the growing popularity of two main areas, the Smart Car and Smart Home. Integration of Smart Home door and access control seems like a natural tie-in with future Smart Car location tracking and geo-fencing events.
Smart Cars are able to communicate their location, perform certain tasks autonomously, interact with other vehicles and traffic features and access critical data on an almost constant basis to keep drivers informed. At the same time, Smart Home capabilities are improving exponentially year over year. The number of home-connected devices has grown tremendously: to cameras and baby monitors, fire alarms and security, lighting, door locks, gate and garage door openers, and even the world of appliances.
In this article, we build on our previous service and create a User Interface for Real-time Garage Door Control using LiftMaster, PubNub and AngularJS. With 120 lines of HTML & JavaScript, we create a capable UI that is easily extensible to new components and device types. If you don’t have a LiftMaster-compatible Garage Door opener, don’t fret – the patterns and techniques we use will be easily adaptable to other devices (or even your own custom-built integration).
Going back to the context of Smart Home and Smart Cars, there are 3 primary requirements that come to mind. The first is High Availability. For services that provide infrastructure to homes, cars, and commercial and industrial applications, there can be no downtime. Secondly, High Performance is a must. When coordinating activities between vehicles or other location-based devices, response time is critical for user experience and building trust. Lastly and perhaps most importantly, High Security is essential. As these services will be carrying payloads for device control, vehicle and home access, there must be clear capabilities for locking down and controlling access to authorized users and applications.
These 3 requirements, Availability, Performance, and Security, are exactly where the PubNub Data Stream Network comes into the picture. PubNub is a global data stream network that provides “always-on” connectivity to just about any device with an internet connection (there are now over 70+ SDKs for a huge range of programming languages and platforms). PubNub’s Publish-Subscribe messaging provides the mechanism for secure channels in your application, where servers and devices can publish and subscribe to structured data message streams in real time: messages propagate worldwide in under a quarter of a second (250ms for the performance enthusiasts out there).
So to summarize, in this article, we’ll be making the garage door respond to our UI events:
You might not want your garage doors to be quite so active. But you get the idea…
Here’s the rough sketch of what we needed from the previous article:
Hopefully you were able to get everything going from the previous article, so hooking up the UI from this one should be a snap!
PubNub plays together really well with Node.js because the PubNub Node.js SDK (part of the PubNub JavaScript SDK family) is extremely robust and has been battle-tested over the years across a huge number of mobile and backend installations. The SDK is currently on its 4th major release, which features a number of improvements such as isomorphic JavaScript, new network components, unified message/presence/status notifiers, and much more. NOTE: for compatibility with the PubNub AngularJS SDK, this UI code will use the PubNub JavaScript v3 API syntax.
The PubNub JavaScript SDK is distributed via Bower or the PubNub CDN (for Web) and NPM (for Node), so it’s easy to integrate with your application using the native mechanism for your platform. In our case, it’s as easy as including the CDN link from a `script` tag.
That note about API versions bears repeating: the Garage Door service we implement in this article uses the PubNub JS API v4, but the user interface in the upcoming article uses the v3 API (since it needs the AngularJS API, which still runs on v3). We expect the AngularJS API to be v4-compatible soon. In the meantime, please stay alert when jumping between the backend and front-end JS code!
The first things you’ll need before you can create a real-time application with PubNub are publish and subscribe keys from PubNub (you probably already took care of this if you already followed the steps in the previous article). If you haven’t already, you can create an account, get your keys and be ready to use the PubNub network in less than 60 seconds.
The publish and subscribe keys look like UUIDs and start with “pub-c-” and “sub-c-” prefixes respectively. Keep these handy – you’ll need to plug them in when initializing the PubNub object in your JavaScript application.
That’s it, nicely done!
Let’s look at the application at the high level before we dive in:
We mentioned this in the previous article, but if all we wanted to do was make the doors go up and down on command, we probably wouldn’t need a data stream integration layer. However, as soon as we start connecting more and more devices and expect them to be able to talk to each other, that’s where PubNub is the big win. PubNub provides the standard APIs, patterns and security that we can use to implement a solution that scales to thousands and millions of devices.
That said, let’s move onto the code itself!
You’ll want to grab these 121 lines of HTML & JavaScript and save them to a file, say, garage_ui.html.
The first thing you should do after saving the code is to replace two values in the JavaScript:
If you don’t, the UI will not be able to communicate with anything and probably clutter your console log with entirely too many errors.
For your convenience, this code is also available as a Gist on GitHub, and a Codepen as well. Enjoy!
<!doctype html> <html> <head> <script src="https://cdn.pubnub.com/pubnub-3.15.1.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script> <script src="https://cdn.pubnub.com/sdk/pubnub-angular/pubnub-angular-3.2.1.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" /> </head> <body> <div class="container" ng-app="PubNubAngularApp" ng-controller="MyHomeCtrl"> <h3>MyHome Controls</h3><br /><br /> <ul class="list-unstyled"> <li ng-repeat="(uuid, data) in devices"> <b>{{data.name}} (id:{{uuid}})</b> - {{STATES[data.state]}}<br/> <small style="color:gray">since {{getDate(data.updated)}}</small><br /> <div> <i class="fa fa-home fa-5x"></i> <i class="fa fa-car fa-2x"></i> <span ng-click="toggleDoor(uuid)"> <i class="fa fa-4x fa-spinner fa-spin" ng-show="data.state == -1" style="color:gray"></i> <i class="fa fa-4x fa-toggle-on" ng-show="data.state == 1"></i> <i class="fa fa-4x fa-toggle-off" ng-show="data.state == 2"></i> <i class="fa fa-4x fa-spinner fa-spin" ng-show="data.state == 4"></i> <i class="fa fa-4x fa-spinner fa-spin" ng-show="data.state == 5"></i> </span> </div><br /> <pre>{{data}}</pre><br /><br /> </li> </ul> </div> <script> angular.module('PubNubAngularApp', ["pubnub.angular.service"]) .controller('MyHomeCtrl', function($rootScope, $scope, Pubnub) { $scope.devices = {}; $scope.STATES = { '-1':'Sending Command...', '1': 'Open', '2': 'Closed', '4': 'Opening', '5': 'Closing' }; $scope.msgChannel = 'MyHome'; $scope.prsChannel = 'MyHome-pnpres'; $scope.ctrlChannel = 'MyHome_Ctrl'; if (!$rootScope.initialized) { Pubnub.init({ publish_key: 'YOUR_PUB_KEY', subscribe_key: 'YOUR_SUB_KEY', ssl:true }); $rootScope.initialized = true; } var msgCallback = function(payload) { if (payload.uuids) { _(payload.uuids).forEach(function (v) { $scope.$apply(function() { if (v.state && v.state.type == "Garage Door") { $scope.devices[v.uuid] = v.state; } }); }); } else if (payload.action == "state-change" && payload.uuid) { $scope.$apply(function() { if (payload.data && payload.data.type == "Garage Door") { $scope.devices[payload.uuid] = payload.data; } }); } }; $scope.getDate = function(ts) { return new Date(parseInt(ts)).toISOString(); }; $scope.toggleDoor = function(uuid) { var cbFn = function(result) { var targetState = null; if (result.state == "2") { targetState = "open"; } else if (result.state == "1") { targetState = "closed"; } if (!targetState) { return; } Pubnub.publish({ channel: $scope.ctrlChannel, message: { target : uuid, newState : targetState } }); $scope.$apply(function() { $scope.devices[uuid].state = -1; }); }; Pubnub.state({channel:$scope.msgChannel, uuid:uuid, callback:cbFn}); }; Pubnub.subscribe({ channel: [$scope.msgChannel, $scope.prsChannel], message: msgCallback, presence:msgCallback }); Pubnub.here_now({ channel:$scope.msgChannel, state:true, callback: msgCallback }); }); </script> </body> </html>
OK, that’s a lot to digest all at once – let’s take a look at the code piece by piece.
First up, we have the JavaScript code & CSS dependencies of our application.
<!doctype html> <html> <head> <script src="https://cdn.pubnub.com/pubnub-3.15.1.min.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script> <script src="https://cdn.pubnub.com/sdk/pubnub-angular/pubnub-angular-3.2.1.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script> <link rel="stylesheet" href="https://netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" /> </head> <body>
For folks who have done front-end implementation with AngularJS before, these should be the usual suspects:
In addition, we bring in 2 CSS features:
Overall, we were pretty pleased that we could build a nifty UI with so few dependencies. And with that… on to the UI!
Here’s what we intend the UI to look like:
The UI is pretty straightforward – everything is inside a div
tag that is managed by a single controller that we’ll set up in the AngularJS code. That h3
heading should be pretty self-explanatory.
<div class="container" ng-app="PubNubAngularApp" ng-controller="MyHomeCtrl"> <h3>MyHome Controls</h3><br /><br />
Our UI consists of a list with garage door switches in each row. We iterate over the devices in the devices
map in the controller scope using a trusty ng-repeat
.
<ul class="list-unstyled"> <li ng-repeat="(uuid, data) in devices">
The first couple lines of text in each list item provide the door’s name, id, state, and the time the state last changed. That is, the last time the door transitioned, not the last time we received a status refresh (which would be less than exciting since the status refreshes every 3s).
<b>{{data.name}} (id:{{uuid}})</b> - {{STATES[data.state]}}<br/> <small style="color:gray">since {{getDate(data.updated)}}</small><br />
We display a house and a car icon for each device, just to make it look nice.
<div> <i class="fa fa-home fa-5x"></i> <i class="fa fa-car fa-2x"></i>
The following code implements the toggle button in the UI. When the state is “-1”, “4”, or “5” (meaning “Pending Command”, “Opening”, or “Closing” respectively), it displays a spinner for the pending command. Otherwise, it displays a “toggle-off” switch when the door is closed, or a “toggle-on” switch when the door is open. Thanks to Font Awesome for making it easy!
<span ng-click="toggleDoor(uuid)"> <i class="fa fa-4x fa-spinner fa-spin" ng-show="data.state == -1" style="color:gray"></i> <i class="fa fa-4x fa-toggle-on" ng-show="data.state == 1"></i> <i class="fa fa-4x fa-toggle-off" ng-show="data.state == 2"></i> <i class="fa fa-4x fa-spinner fa-spin" ng-show="data.state == 4"></i> <i class="fa fa-4x fa-spinner fa-spin" ng-show="data.state == 5"></i> </span>
Finally, we close out the list element with a pre
tag containing the state (we figure it’ll help with debugging, but you’ll likely remove it in your app). Then, all we have to do is close out the ul
and the enclosing div
tags!
</div><br /> <pre>{{data}}</pre><br /><br /> </li> </ul> </div>
Wow, that’s a lot of UI in just a handful of code (thanks, AngularJS)!
Right on! Now we’re ready to dive into the AngularJS code. It’s not a ton of JavaScript, so this should hopefully be pretty straightforward.
The first lines we encounter set up our application (with a necessary dependency on the PubNub AngularJS service) and a single controller (which we dub MyHomeCtrl
). Both of these values correspond to the ng-app
and ng-controller
attributes from the preceding UI code.
<script> angular.module('PubNubAngularApp', ["pubnub.angular.service"]) .controller('MyHomeCtrl', function($rootScope, $scope, Pubnub) {
Next up, we initialize a bunch of values. First is a map of device UUIDs to device state JSON objects, which starts out empty. Then, we have a constant map from LiftMaster API codes to their friendly String counterparts.
After that, we set up three channel name variables:
$scope.devices = {}; $scope.STATES = { '-1':'Sending Command...', '1': 'Open', '2': 'Closed', '4': 'Opening', '5': 'Closing' }; $scope.msgChannel = 'MyHome'; $scope.prsChannel = 'MyHome-pnpres'; $scope.ctrlChannel = 'MyHome_Ctrl';
We initialize the Pubnub
object with our PubNub publish and subscribe keys mentioned above (they should be the same as the ones running on the server). We set a scope variable to make sure the initialization only occurs once. NOTE: this uses the v3 API syntax.
if (!$rootScope.initialized) { Pubnub.init({ publish_key: 'YOUR_PUB_KEY', subscribe_key: 'YOUR_SUB_KEY', ssl:true }); $rootScope.initialized = true; }
The next thing we’ll need is a real-time message callback called msgCallback
; it takes care of all the real-time messages we need to handle from PubNub. In our case, we have 2 main scenarios.
In the first case, the inbound message has a uuids
property: that means it is a here_now()
function payload (from upcoming code below), and we should update the device state with all of the JSON objects in the payload.
Alternatively, the message is a state-change
event, and the payload has a uuid
property. This means it is a real-time state-change
event for a single member of the channel, so we update the corresponding device’s state if its type equals the “Garage Door” string literal.
var msgCallback = function(payload) { if (payload.uuids) { _(payload.uuids).forEach(function (v) { $scope.$apply(function() { if (v.state && v.state.type == "Garage Door") { $scope.devices[v.uuid] = v.state; } }); }); } else if (payload.action == "state-change" && payload.uuid) { $scope.$apply(function() { if (payload.data && payload.data.type == "Garage Door") { $scope.devices[payload.uuid] = payload.data; } }); } };
The getDate()
function is just a tiny helper that takes the “epoch seconds” value from the LiftMaster API and turns it into an ISO timestamp string. For your application, you’ll probably use a nifty JavaScript library and turn it into friendlier string.
$scope.getDate = function(ts) { return new Date(parseInt(ts)).toISOString(); };
We create a toggleDoor()
function that takes the Garage Door UUID (which is the same as the door.id
in the LiftMaster API), and toggles the door’s state. That is, opens it when closed, or closes it when open. We call the PubNub.state()
function to know what the latest state of the door is. If the door is in an intermediate state, the function returns and the command is ignored.
To send the door operation command, we just publish a message to the control channel with the specified door and target state. The Garage Door component server will receive this message and send the request to the LiftMaster MyQ API.
Lastly, we also temporarily set the door state to “Sending Command…”; this way, there will only be one pending command from the UI, and the status will refresh on the next update (which should be around 3s, depending on the server’s refresh rate from the API).
$scope.toggleDoor = function(uuid) { var cbFn = function(result) { var targetState = null; if (result.state == "2") { targetState = "open"; } else if (result.state == "1") { targetState = "closed"; } if (!targetState) { return; } Pubnub.publish({ channel: $scope.ctrlChannel, message: { target : uuid, newState : targetState } }); $scope.$apply(function() { $scope.devices[uuid].state = -1; }); }; Pubnub.state({channel:$scope.msgChannel, uuid:uuid, callback:cbFn}); };
In the main body of the controller, we subscribe()
to the message and presence channels (using the JavaScript v3 API syntax) and bind the events to the callback function we just created.
Then, we call Pubnub.here_now()
to initialize the user interface with the currently connected devices (which hopefully already includes the garage doors if you have the node.js server from the previous article running).
Pubnub.subscribe({ channel: [$scope.msgChannel, $scope.prsChannel], message: msgCallback, presence:msgCallback }); Pubnub.here_now({ channel:$scope.msgChannel, state:true, callback: msgCallback }); }); </script> </body> </html>
And that’s it! Not bad for a hundred-odd lines of HTML & JavaScript!
Running the code is super simple – just upload the HTML file to your favorite web server and navigate to the corresponding URL. NOTE: you’ll want to make sure it’s protected by some kind of access control, otherwise you’ll potentially have unauthorized folks tampering with your doors!
For testing, we used http-server with node.js.
# install http-server (requires node.js + npm) sudo npm install -g http-server # run the server, serving files in current directory on localhost # (which should include garage_ui.html of course) http-server -a localhost -p 8000 .
Thanks to this post for helping us find a localhost
replacement for Python SimpleHTTPServer!
We think this is pretty neat, but there is nothing special here about the process of integrating LiftMaster with PubNub. We saved some time because there is already an unofficial Node.js-enabled API for the LiftMaster, but if you dive into that code you’ll see that it’s not too tough to adapt it to other devices, even running embedded on a device itself.
It’s also worth noting that in the code for the UI, there is nothing specifically tied to Garage Doors there. This pattern and technique of using a status channel and control channel could work just as easily for lighting control, door lock operation, and many of the other Smart Home applications you might think of!
Thank you so much for staying with us this far! Hopefully it’s been a useful endeavor. The goal was to convey our experience in how to build a Node.js app and AngularJS UI that:
If you’ve been successful thus far, you should be able to start using PubNub as an integration hub and real-time communications layer for all of your Smart Home needs.
In future articles, we hope to dive deeper into more components for the Smart Home and Smart Car, plus other things we haven’t thought of yet. Can’t wait!
Stay tuned, and please reach out anytime if you feel especially inspired or need any help!
There are common underlying technologies for a dating app, and in this post, we’ll talk about the major technologies and designs...
Michael Carroll
How to use geohashing, JavaScript, Google Maps API, and BART API to build a real-time public transit schedule app.
Michael Carroll
How to track and stream real-time vehicle location on a live-updating map using EON, JavaScript, and the Mapbox API.
Michael Carroll