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.
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.
Setup
To build our app we'll use Yeoman, a collection of tools and frameworks helping developers to quickly build web applications.
- yo - perform ripetitive tasks.
- grunt - build, preview and test your project.
- bower - solve the frontend package management.
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 main.html view and configure the oauth directive by setting the client-id
and redirect-uri
previously defined.
The oauth directive works just straight when the HTML5 mode is active.
Inject the $locationProvider
into your AngularJS app.js
config block and set the HTML5 mode.
.config(function ($routeProvider, $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.
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
// Load the device by setting its json structure and a specific template
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.
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
Compact | HTML | CSS
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).
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
<div class="ly-device">
<div class="ly-content">
<div class="ly-status">
<div class="ly-execute" ng-click="execute(status.function)">
<button class="ly-button"></button>
<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>
</div>
<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>
</div>
</div>
</div>
view raw device-directive-simple-compact-template.html hosted with ❤ by GitHub
Let's study step by step what this code does.
Default function
...
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.
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.
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.).
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
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.
1 2 3 4 5 6 7 8 9
<div class="ly-switch">
<input type="checkbox" id="ly-checkbox" />
<label class="ly-slider" for="ly-checkbox" ng-click="execute(status.function)"></label>
<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>
</div>
view raw device-directive-switch.html hosted with ❤ by GitHub
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.
1 2 3 4 5 6 7 8
.ly-switch input { position: absolute; left: -9999px; }
.ly-switch input:checked + .ly-slider::before { box-shadow: 0 0.08em 0.15em -0.1em rgba(0, 0, 0, 0.5) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7), 3em 0 0 0 #239cbb inset; }
.ly-switch input:checked + .ly-slider::after { left: 3em; }
.ly-switch .ly-slider { position: relative; display: block; width: 5.5em; height: 3em; cursor: pointer; border-radius: 1.5em; transition: 350ms; background: linear-gradient(rgba(0, 0, 0, 0.07), rgba(255, 255, 255, 0)), #dddddd; box-shadow: 0 0.07em 0.1em -0.1em rgba(0, 0, 0, 0.4) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7);}
.ly-switch .ly-slider::after { position: absolute; content: ''; width: 2em; height: 2em; top: 0.5em; left: 0.5em; border-radius: 50%; transition: 250ms ease-in-out; background: linear-gradient(#f5f5f5 10%, #eeeeee); box-shadow: 0 0.1em 0.15em -0.05em rgba(255, 255, 255, 0.9) inset, 0 0.2em 0.2em -0.12em rgba(0, 0, 0, 0.5); }
.ly-switch .ly-slider::before { position: absolute; content: ''; width: 4em; height: 1.5em; top: 0.75em; left: 0.75em; border-radius: 0.75em; transition: 250ms ease-in-out; background: linear-gradient(rgba(0, 0, 0, 0.07), rgba(255, 255, 255, 0.1)), #d0d0d0; box-shadow: 0 0.08em 0.15em -0.1em rgba(0, 0, 0, 0.5) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7), 0 0 0 0 #239cbb inset; }
.ly-switch .ly-spinner { padding-left: 1.5em; }
.ly-switch .ly-spinner > div { background-color: #ccc; }
view raw device-directive-switch.css hosted with ❤ by GitHub
1 2 3 4 5 6 7 8
.ly-switch input { position: absolute; left: -9999px; }
.ly-switch input:checked + .ly-slider::before { box-shadow: 0 0.08em 0.15em -0.1em rgba(0, 0, 0, 0.5) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7), 3em 0 0 0 #239cbb inset; }
.ly-switch input:checked + .ly-slider::after { left: 3em; }
.ly-switch .ly-slider { position: relative; display: block; width: 5.5em; height: 3em; cursor: pointer; border-radius: 1.5em; transition: 350ms; background: linear-gradient(rgba(0, 0, 0, 0.07), rgba(255, 255, 255, 0)), #dddddd; box-shadow: 0 0.07em 0.1em -0.1em rgba(0, 0, 0, 0.4) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7);}
.ly-switch .ly-slider::after { position: absolute; content: ''; width: 2em; height: 2em; top: 0.5em; left: 0.5em; border-radius: 50%; transition: 250ms ease-in-out; background: linear-gradient(#f5f5f5 10%, #eeeeee); box-shadow: 0 0.1em 0.15em -0.05em rgba(255, 255, 255, 0.9) inset, 0 0.2em 0.2em -0.12em rgba(0, 0, 0, 0.5); }
.ly-switch .ly-slider::before { position: absolute; content: ''; width: 4em; height: 1.5em; top: 0.75em; left: 0.75em; border-radius: 0.75em; transition: 250ms ease-in-out; background: linear-gradient(rgba(0, 0, 0, 0.07), rgba(255, 255, 255, 0.1)), #d0d0d0; box-shadow: 0 0.08em 0.15em -0.1em rgba(0, 0, 0, 0.5) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7), 0 0 0 0 #239cbb inset; }
.ly-switch .ly-spinner { padding-left: 1.5em; }
.ly-switch .ly-spinner > div { background-color: #ccc; }
view raw device-directive-switch.css hosted with ❤ by GitHub
1 2 3 4 5 6 7 8
.ly-switch input { position: absolute; left: -9999px; }
.ly-switch input:checked + .ly-slider::before { box-shadow: 0 0.08em 0.15em -0.1em rgba(0, 0, 0, 0.5) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7), 3em 0 0 0 #239cbb inset; }
.ly-switch input:checked + .ly-slider::after { left: 3em; }
.ly-switch .ly-slider { position: relative; display: block; width: 5.5em; height: 3em; cursor: pointer; border-radius: 1.5em; transition: 350ms; background: linear-gradient(rgba(0, 0, 0, 0.07), rgba(255, 255, 255, 0)), #dddddd; box-shadow: 0 0.07em 0.1em -0.1em rgba(0, 0, 0, 0.4) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7);}
.ly-switch .ly-slider::after { position: absolute; content: ''; width: 2em; height: 2em; top: 0.5em; left: 0.5em; border-radius: 50%; transition: 250ms ease-in-out; background: linear-gradient(#f5f5f5 10%, #eeeeee); box-shadow: 0 0.1em 0.15em -0.05em rgba(255, 255, 255, 0.9) inset, 0 0.2em 0.2em -0.12em rgba(0, 0, 0, 0.5); }
.ly-switch .ly-slider::before { position: absolute; content: ''; width: 4em; height: 1.5em; top: 0.75em; left: 0.75em; border-radius: 0.75em; transition: 250ms ease-in-out; background: linear-gradient(rgba(0, 0, 0, 0.07), rgba(255, 255, 255, 0.1)), #d0d0d0; box-shadow: 0 0.08em 0.15em -0.1em rgba(0, 0, 0, 0.5) inset, 0 0.05em 0.08em -0.01em rgba(255, 255, 255, 0.7), 0 0 0 0 #239cbb inset; }
.ly-switch .ly-spinner { padding-left: 1.5em; }
.ly-switch .ly-spinner > div { background-color: #ccc; }
view raw device-directive-switch.css hosted with ❤ by GitHub
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.
Now use the template event to set the new template to all previously filtered devices.
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.
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).
Comments