The new HTML5 tag built atop AngularJS and Lelylan
that makes it easier and faster to create a complete application
across desktop, tablet, and mobile for the Connected Home.

Introduction

Control anything

The device directive gives you a ready to use solution to control any physical device in the real world. This is possible thanks to Lelylan, a simple and open REST API for the Connected Home.

Mobile first

Works across desktop, tablet and mobile, including iPhones, iPads and Android devices.

Fully customizable

The device directive comes to life with customization in mind. It offers you a default template, but if it doesn't fit your needs, you can easily create new ones only changing few lines of CSS and HTML.

Overview

Install the device directive using Bower.

$ bower install device-directive-ng --save

Use the <device> directive and set the desired device ID.

<device device-id="DEVICE_ID"></device>

Getting Started

In this section you will learn how to use the device directive to monitor and control all the devices you have defined in Lelylan.

The oauth directive has beed tested with AngularJS 1.2.x (legacy version). Open an issue on Github if any problem occurs when using the AngularJS 1.3.x or 2.x versions.

Setup

To build our app we'll use Yeoman, a collection of tools and frameworks helping developers to quickly build web applications.

Installation

With a recent version of Node.js installed, install the yo package. In this way you have Yo, Grunt and Bower and can run them directly from the command-line.

$ npm install -g yo

With Yeoman you can install additional generators with npm. For this tutorial you need to install the AngularJS generator.

$ npm install -g generator-angular

Create your AngularJS app

To begin, go to the terminal, make a new directory and cd into it.

$ mkdir new-project && cd $_

You can now kick-start your AngularJS app.

$ yo angular

It will also ask you if you would like to include Twitter Bootstrap and other stuff. Once you've decided, just hit Enter. It will take a while to complete.

To preview what the app looks like run the serve command.

$ grunt serve

The server supports LiveReload, meaning you can fire up a text editor, edit a custom element and the browser will reload on save.

Install the device directive

Install the device directive using Bower.

$ bower install device-directive-ng --save

Now you have <device> directive and all its dependencies ready to be used. Restart the server to automatically add the needed javascript files to your index page.

$ grunt serve

The setup is now completed.

Add the device directive

Inject the device directive into your AngularJS app.js.

// app/scripts/app.js
angular.module('app', ['lelylan.directives.device', ... ])

OAuth 2.0 Server

Lelylan uses an OAuth 2.0 server for authentication and authorization. Register a new application setting a name (e.g Lelylan App) and the Redirect URI (e.g. http://localhost:9000).

Add the oauth-ng directive

To get an authorization token you need to use oauth-ng, an AngularJS directive for OAuth 2.0 (already installed as dependency). Open your index page and configure the oauth directive by setting the client-id and redirect-uri previously defined.

// app/index.html
<oauth
  site="http://people.lelylan.com"
  client-id="CLIENT_ID"
  redirect-uri="REDIRECT_URI"
  profile-uri="http://api.lelylan.com/me"
  scope="devices">
</oauth>

The oauth directive works just straight when the HTML5 mode is active. Inject the $locationProvider into a new AngularJS config block and set the HTML5 mode.

// app/scripts/app.js
.config(function ($locationProvider) {
  $locationProvider.html5Mode(true).hashPrefix('!');
});

The oauth directive works also without HTML5 mode. Check out the oauth-ng docs to better understand how OAuth 2.0 works with AngularJS.

Create your first device

If you never used Lelylan before you need to create your first device using Lelylan Dashboard. The fastest way to get started is to simulate a device as showed below. In this way you don't need any hardware (perfect for app development).

Get your devices

To get all your devices you can use the Lelylan client for AngularJS (already installed as dependency). Open the controller main.js, inject the Device service and place the Device#all method.

// app/scripts/controllers/main.js
angular.module('newProjectApp')
  .controller('MainCtrl', function ($scope, $timeout, Device) {
    $timeout(function() {
      Device.all()
        .success(function(data) { $scope.devices = data })
        .error(function(data) { $scope.error = 'Unauthorized. Sign in first.' })
    }) // digest for the access token to be loaded
  });

This is what happens. You inject the Device service and call the Device.all() method to get all of your devices and save them in $scope.devices.

Show your devices

Supposing you have created at least one device with Lelylan you are ready to show them. Open the main.html view and list your devices by using the device directive.

This is what happens. You iterate between all devices and show them using the device directive. To let the directive know which device it has to render you use the attribute device-json that accepts any valid device representation.

See the configurations section to see the accepted attributes.

You're done!

Open your index page, click to the Login link and authorize your application to get a new access token. You are now ready to monitor and control all your devices.

Configurations

The directive accepts the following attributes.

device-json optional Device JSON. (required if device-id is not used)
device-id optional Device ID. (required if device-json is not used)
device-template optional Custom template to render. Learn more about.

Examples

// Load the device by setting its id
<device device-id="DEVICE_ID"></device>

// Load the device by setting its json structure and a specific template
<device device-json="DEVICE_JSON" device-template="TEMPLATE_URI"></device>

Tips

As good habit, fetch the all your devices first and then render them usig the device-json attribute. This will make the system more efficient than using the device-id attribute which fires an HTTP request per device.

Custom Templates

The device directive comes to life to be easily customizable. If the default template does not satisty your needs, you can create new ones (e.g light specific). All you need to do is to personalize the HTML and CSS code.

All examples below are created with Codepen. This makes it easy for you to create new templates by forking and editing the existing code.

Configuration

To set a new template you need to tell to the device directive where the HTML file is located.

1. Template attribute

Set the template path (or uri) to the device-template attribute.

<device device-template="TEMPLATE_PATH" ... ></device>

2. Template event

Fires the update template event passing an object with the template uri and the optional device id (when not set all devices are affected by the change).

$rootScope.$broadcast(
  'lelylan:directives:device:template',
  { template: 'TEMPLATE_PATH', id: 'DEVICE_ID'}
);

Included templates

Default | HTML | CSS

<device device-id="DEVICE_ID"></device>

See the Pen Gxgae by Andrea Reginato (@andreareginato) on CodePen.

Compact | HTML | CSS

<device
  device-id="DEVICE_ID"
  device-template="bower_components/device-directive-ng/dist/views/templates/compact.html">
</device>

See the Pen pGrcD by Andrea Reginato (@andreareginato) on CodePen.

Community templates

Follows a list of templates created by the community. To use them, as convention, download the HTML template and CSS style into views/templates and use them as described in the code examples.

Switch | HTML | CSS

Valid for any device type.

When you click on the switch we apply the default function of the device in a specific status of its life (e.g if a light is on, the default function is turn off and viceversa).

<link rel="stylesheet" href="views/templates/switch.css">
<device device-id="ID" device-template="views/templates/switch.html"></device>

See the Pen czodF by Andrea Reginato (@andreareginato) on CodePen.


Mail, Tweet or send us a pull request for any new template. We'll be happy to add them to the project.

Type specific templates

With the device directive we want to define the best user experience when controlling the physical world. To make this possible we need to create type specific templates for lights, thermostats, locks and more like the ones below.

To reach these results we need to create custom templates for every specific type. To easily get started we created the following pens for the most important types.


Switch Light Lock Thermostat IP Camera Alarm System Roller Shutter Alarm Clock Sprinkler System Kettle Temperature Sensor Fire Sensor City pollution


If you want to experiment on a type not listed here mail or tweet us.

To deeply understand how templates can be created, read the next sections.

Create your first (basic) template

When the existing templates does not satisfy your needs, you can create a new one. In this section we'll explain how to create the template below step by step.

The code that makes it possible is this.

Let's study step by step what this code does.

Default function

<div class="ly-execute" ng-click="execute(status.function)">
  ...
</div>

As convention, the blue circle is used to execute the device default function (the expected functionality you want to execute when a device is in a specific state). For example when a light is "turned on" the default function is "turn off" and viceversa.

To execute the defualt function use execute(status.function). As you can get from the naming it will execute the default function based on the current device status.

The defualt function is kind of a complex topic. You only need to get the general concept, but if you want to dig in deep on how it works, check out the Status API.

Lelylan circle

The blue circle is rendered by the device directive on every element with ly-button class.

<button class="ly-button"></button>

In this example we also set a loader identified by the ly-spinner class visible only when the device is in a pending status, meaning that the request is sent to the physical device and you are waiting for its execution to be completed.

<div class="ly-spinner ly-animation" ng-if="device.pending">
  <div class="ly-bounce-1"></div>
  <div class="ly-bounce-2"></div>
  <div class="ly-bounce-3"></div>
</div>

Device info

The last part lets you see the device name (e.g front door lock, kitchen thermostat, etc.) and status, which will give you a descriptive overview of the device status in a specific moment of its life (e.g. on and off for a basic light, open and close for a lock, etc.).

<div class="ly-description">
  <p class="ly-name" ng-bind="device.name"></p>
  <p class="ly-extras ly-updated-animation" ng-bind="status.name"></p>
</div>

You are done

Save the new template under your project and set the path using the template attribute and you are done. Congrats. You have created your first custom template

<device device-json="DEVICE_JSON" device-template="TEMPLATE_PATH"></device>

Create your first type specific template

The device directive defines a user interface that adapts to any device. This means that you can control lights, locks, thermostats or any other device without any effort. While this flexibility is perfect for general purpose solutions, it becomes a weakness when you want to monitor and control a specific device. Take a light for example. In the real world we control it using a switch. Why should we change this convention?

Light switch

That said, we now want to build a template that can be used to control a basic light having only the turn on and turn off functionalities.

Switch Template

The template is pretty simple. You have a checkbox label that when clicked executes the default function. This means that when the light is on the switch turns it off and when the light is off the switch turns it on.

You also have the spinner which is visible only when the device is pending, meaning that the request is sent to the physical device and you are waiting for its execution to be completed.

Switch CSS

This is the CSS you need to get the final switch.

Many thanks to Billy Crist for his work.

Assigning the new template

Save the new HTML and CSS template files under your project. Now you need to set this template only for your lights (not other devices like locks or thermostats). To get all devices of a specific type (in this case all lights) you have two solutions.

// Using Device API passing the Type ID in the query string
Device.all({ type: TYPE_ID }).success(function(devices) {
  scope.devices = devices;
})

// Using JSON filtering (this example uses underscore)
Device.all().success(function(devices) {
  scope.devices = _.where(devices, { type.id: TYPE_ID }
});

Now use the template event to set the new template to all previously filtered devices.


_.each(scope.devices, function(device) {
  $rootScope.$broadcast(
    'lelylan:device:template:update',
    { template: 'TEMPLATE_PATH', id: device.id}
  );
});

Events

[Listen] Device loaded

lelylan:device:load(device)

Listen for an event fired when the directive has completed the requests to the API.
As param it receives the loaded device JSON representation.

$scope.$on('lelylan:device:load', function(event, device) { ... })

[Listen] Device update

lelylan:device:update:get(device)

Listen for a device name or phyiscal uri update event.
As param it receives the updated device JSON representation.

$scope.$on('lelylan:device:update:get', function(event, device) { ... })

[Fire] Device update

lelylan:device:update:set(device)

Fires an event to update a device representation.
As param you need to send a valid device JSON representation.

$rootScope.$broadcast('lelylan:device:update:set', device);

[Listen] Properties update

lelylan:device:properties:get(properties)

Listen for an event fired when a function starts and some properties are sent to be updated.
As param it receives the properties to update.

$scope.$on('lelylan:device:load', function(event, device) { ... })

[Listen] Device delete

lelylan:device:deleted(device)

Listen for device deletion.
As param it sends the deleted JSON device representation.

$scope.$on('lelylan:device:delete', function(event, device) { ... });

[Fire] Update device(s) template

lelylan:device:template({template: "TEMPLATE_PATH", id: "DEVICE_ID"})

Fires the update of the directive template. The event accepts an object with the following options.

template required Template path (or uri) value.
id optional Device ID indicating the device targeted to update the template.
(when missing all devices are targeted to update the template).
$rootScope.$broadcast(
  'lelylan:directives:device:template',
  { template: 'TEMPLATE_PATH', id: 'DEVICE_ID'}
);

[Listen] Custom events

lelylan:device:custom:EVENT_NAME(device)

Listen for a custom event (as param it sends the JSON device representation). This is a special event tha can be used when defining a new template and interacting with specific parts of it.

$scope.$on('lelylan:device:custom:EVENT_NAME', function(event, device) { ... });

Example

An example is the custom event lelylan:device:custom:open(device) that is fired in the compact template when you click the right arrow.


[reset]


To make this possible we attach the fire('open') function to the right arrow click event. In this way the directive will automatically fire the open custom event that we can easily catch with the following code.

$scope.$on('lelylan:device:custom:open', function(event, device) {
  $scope.text = 'The custom:open event was fired from the ' + device.name;
});

Internal API (Advanced)

Any template is composed by mix of HTML (structure), CSS (visual) and AngularJS (logic). In the previous section you saw the template using some JavaScript functions (e.g. execute(status.function)) or varialbles (e.g device.pending). To give you full control during the template definition we now describe the internal API offered by the directive.

device variable JSON object representing a device. Keys used quite often are.
  • device.id - device identifier
  • device.name - device name
  • device.pending - true when requests are being resolved
type variable JSON object representing a device type.
privates variable JSON object representing a device private info. Keep in mind that privates are loaded only when showSettings() is called and the logged user is the creator.

functions variable JSON object representing the device functions. Each of them can be executed using the execute() method.
status variable JSON object representing the current device status.
view.path variable String representing the active view. Above you can see the keys used by the default template to switch between different views.
  • /loading - directive is loading (resolving the API requests)
  • /default - default view (control and monitoring)
  • /settings - settings view (device info)
  • /message - generic message
  • /delete - directive deleted message
execute(function) function This function executes a function. When params are required from the user the function.visibleForm variable is set to true. This let you show the form that needs to be filled
update() function Update the device name or the device physical URI.
destroy(name) function Delete the device passing the device name as security check.

This function is available only for the device maker. If you did not create the device, you will not be able to delete the device.

isMaker() function Returns true if the logged user is the creator of the device.
fire(EVENT_NAME) function Fires an event where the prefix is lelylan:device:custom: and the suffix is the EVENT_NAME. Supposing the event name is open, the custom event will be lelylan:device:custom:open.
showSettings() function Sets the path to /settings and load the device privates info (if the logged user is the creator).

Links

Thanks

Mail or Tweet us for any idea that can improve the project.