sample_pir-lights_rgb

Dependencies:   ChainableLED

Files at this revision

API Documentation at this revision

Comitter:
iv123
Date:
Sun Jun 18 10:14:56 2017 +0000
Commit message:
Initial commit

Changed in this revision

ChainableLED.lib Show annotated file Show diff for this revision Revisions of this file
README.md Show annotated file Show diff for this revision Revisions of this file
docs/2_circuit.md Show annotated file Show diff for this revision Revisions of this file
docs/3_software.md Show annotated file Show diff for this revision Revisions of this file
docs/4_connectivity.md Show annotated file Show diff for this revision Revisions of this file
docs/5_controlling-from-cloud.md Show annotated file Show diff for this revision Revisions of this file
docs/6_cloud-api.md Show annotated file Show diff for this revision Revisions of this file
docs/7_app.md Show annotated file Show diff for this revision Revisions of this file
docs/8_conclusion.md Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights10.jpg Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights11.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights12.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights13.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights14.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights15.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights16.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights17.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights18.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights2.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights3.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights4.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights5.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights6.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights7.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights8.png Show annotated file Show diff for this revision Revisions of this file
docs/assets/lights9.png Show annotated file Show diff for this revision Revisions of this file
docs/index.md Show annotated file Show diff for this revision Revisions of this file
example-nodejs/main.js Show annotated file Show diff for this revision Revisions of this file
example-nodejs/package.json Show annotated file Show diff for this revision Revisions of this file
example-python/lights.py Show annotated file Show diff for this revision Revisions of this file
lighting-system-firmware/.gitignore Show annotated file Show diff for this revision Revisions of this file
lighting-system-firmware/mbed_settings.py Show annotated file Show diff for this revision Revisions of this file
lighting-system-firmware/mbedtls_mbed_client_config.h Show annotated file Show diff for this revision Revisions of this file
lighting-system-firmware/source/led.h Show annotated file Show diff for this revision Revisions of this file
lighting-system-firmware/source/main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed-os.lib Show annotated file Show diff for this revision Revisions of this file
mbed_app.json Show annotated file Show diff for this revision Revisions of this file
mkdocs.yml Show annotated file Show diff for this revision Revisions of this file
webapp/README.md Show annotated file Show diff for this revision Revisions of this file
webapp/main.js Show annotated file Show diff for this revision Revisions of this file
webapp/package.json Show annotated file Show diff for this revision Revisions of this file
webapp/public/ARMmbedLogo.svg Show annotated file Show diff for this revision Revisions of this file
webapp/public/blueband_BG.png Show annotated file Show diff for this revision Revisions of this file
webapp/public/color-picker-element/.bower.json Show annotated file Show diff for this revision Revisions of this file
webapp/public/color-picker-element/LICENSE Show annotated file Show diff for this revision Revisions of this file
webapp/public/color-picker-element/README.md Show annotated file Show diff for this revision Revisions of this file
webapp/public/color-picker-element/bower.json Show annotated file Show diff for this revision Revisions of this file
webapp/public/color-picker-element/dist/color-picker.html Show annotated file Show diff for this revision Revisions of this file
webapp/public/color-picker-element/dist/demo/index.html Show annotated file Show diff for this revision Revisions of this file
webapp/public/color-picker-element/package.json Show annotated file Show diff for this revision Revisions of this file
webapp/public/color-picker.js Show annotated file Show diff for this revision Revisions of this file
webapp/public/helper-functions.js Show annotated file Show diff for this revision Revisions of this file
webapp/public/manifest.json Show annotated file Show diff for this revision Revisions of this file
webapp/public/style.css Show annotated file Show diff for this revision Revisions of this file
webapp/views/index.html Show annotated file Show diff for this revision Revisions of this file
webapp/views/partials/device.html Show annotated file Show diff for this revision Revisions of this file
diff -r 000000000000 -r 7a352727249b ChainableLED.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ChainableLED.lib	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,1 @@
+https://developer.mbed.org/users/Jackson_lv/code/ChainableLED/#e7513df9d7cb
diff -r 000000000000 -r 7a352727249b README.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,3 @@
+# Connected Lights
+
+Example code for the [Building an internet connected lighting system](https://docs.mbed.com/docs/building-an-internet-connected-lighting-system/en/latest/2_circuit/) article.
diff -r 000000000000 -r 7a352727249b docs/2_circuit.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/2_circuit.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,30 @@
+# Building the circuit
+
+The first thing we want to do is build a basic circuit, where we connect the RGB LED and the PIR sensor to the development board.
+
+## Finding suitable pins
+
+For the circuit we need four digital pins. Three of these need to support pulse width modulation (PWM). Through PWM we can control the amount of current flowing through a circuit, and we can use this to dim the colours of the LED on one of the three RGB channels.
+
+To find pins that you can use, look at the [platform page](https://developer.mbed.org/platforms/) for your board and find the pinout. The pinout defines the characteristics of the pins. For example, here is the pinout for the FRDM-K64F, where we can use D5, D6 and D7 as our PWM pins:
+
+<span class="images">![FRDM-K64F pinout showing PWM pins](assets/lights3.png)<span>The pinout for the NXP FRDM-K64F, showing that pins D5, D6 and D7 can be used for PWM.</span></span>
+
+We also need a pin for the PIR sensor. This can be any digital pin, as long as it's not marked as UART (D0 and D1 on the pinout above). In this example, we can use pin D2.
+
+<span class="notes">**Note:** In general it's a good idea not to use any of the I2C and SPI pins for LEDs and basic sensors, as connectivity shields (like Wi-Fi) might need them.</span>
+
+## Hooking up the peripherals on a breadboard
+
+Here is a diagram of hooking up the PIR sensor and the RGB LED to your board. Replace the pins D2, D5, D6 and D7 with the pins you found for your board. The RGB LED has to be positioned so that the longest pin is the second from the left (hold it [like this](http://howtomechatronics.com/wp-content/uploads/2015/09/RGB-LED.png?28ea0f)).
+
+<span class="images">![PIR sensor and RGB LED Fritzing diagram](assets/lights4.png)</span>
+
+<span class="notes">**Note - common anode or common cathode LED:** Four pin RGB LEDs come in two different flavours: *common anode* and *common cathode*. If you have a common cathode LED, connect the second pin to `GND` instead of `3.3V`. If you are unsure, just try both circuits and see which one works.</span>
+
+<span class="notes">**Note:** If you're unsure of the pins on the PIR sensor, and you have a sensor with a 'dome' on it: remove the dome. The pins are described on the PCB.</span>
+
+When you hook everything up, the circuit looks something like this:
+
+<span class="images">![PIR sensor and RGB LED connected to FRDM-K64F](assets/lights5.png)</span>
+
diff -r 000000000000 -r 7a352727249b docs/3_software.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/3_software.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,163 @@
+# Writing the software
+
+Now we can write some software to verify that the circuit works. We'll enable the LED whenever we see movement. We can either build locally - using mbed CLI - or in the cloud by using the mbed Online Compiler. We'll first show how to use the online compiler, and at the end of this section we'll show the corresponding commands in mbed CLI.
+
+## Setting up the online compiler
+
+Part of the ARM mbed IoT Device Platform is an online compiler, which we will use to write and compile software right in the browser. To get started:
+
+1. Find your development board's [platform page](https://developer.mbed.org/platforms/).
+1. In the right hand panel, choose **Add to your mbed Compiler**.
+1. When prompted sign up (or sign in). You are redirected to the platform page.
+1. Choose **Open mbed Compiler**. The online compiler opens.
+1. Click the **Import** button.
+
+    <span class="images">![Import button](assets/lights17.png)</span>
+
+1. Click the "Click here to import from URL" link:
+
+    <span class="images">![Import from URL](assets/lights18.png)</span>
+
+1. Under **Source URL** enter `https://github.com/armmbed/connected-lights`.
+    
+    Do not check the **Update all libraries to the latest revision** box.
+
+    <span class="images">![Creating a program in the online compiler](assets/lights6.png)</span>
+
+The program you just imported already contains some boilerplate, including mbed OS and a configuration file. We'll use this configuration file to configure the pins our software uses, then start writing some code.
+
+## Adding the code
+
+mbed OS comes with a powerful [configuration system](https://docs.mbed.com/docs/mbedmicro-api/en/latest/api/md_docs_config_system.html) that makes it easy to separate configuration and application code. In this application we'll separate the configuration of the LED (cathode, anode or a Grove LED), pins, and connectivity method (next section).
+
+From the mbed Online Compiler's tree, open ``mbed_app.json``. Edit the file to reflect your LED choice, and the pins you used to connect the LED and the PIR sensor:
+
+1. If you have a common cathode LED, set the `value` of `led-type` to `TRICOLOR_CATHODE`.
+1. If you have a Grove Chainable LED, set the `value` of `led-type` to `TRICOLOR_ANODE`.
+1. Replace the pins D2, D5, D6 and D7 with the pins you used when building the circuit.
+
+
+```js
+/* mbed_app.json */
+
+/* snip */
+
+        "pir-pin": {
+            "help": "Pin to which the PIR sensor is connected",
+            "macro_name": "PIR_PIN",
+            "value": "D2"
+        },
+
+        "led-type": {
+            "help": "options are TRICOLOR_ANODE,TRICOLOR_CATHODE,GROVE_CHAINABLE",
+            "value": "TRICOLOR_ANODE"
+        },
+
+        "led-pin-red": {
+            "help": "Only used for TRICOLOR_* LED types",
+            "value": "D5"
+        },
+        "led-pin-green": {
+            "help": "Only used for TRICOLOR_* LED types",
+            "value": "D6"
+        },
+        "led-pin-blue": {
+            "help": "Only used for TRICOLOR_* LED types",
+            "value": "D7"
+        },
+
+        "grove-clock-pin": {
+            "help": "Only used for GROVE_CHAINABLE LED types",
+            "value": "D5"
+        },
+        "grove-data-pin": {
+            "help": "Only used for GROVE_CHAINABLE LED types",
+            "value": "D6"
+        },
+
+/* snip */
+```
+
+Next, create a file called ``main.cpp`` in the `source` directory:
+
+```cpp
+/* lighting-system-firmware/source/main.cpp */
+
+#include "mbed.h"
+#include "led.h"    // Abstracts away the differens between the LED types
+
+// PIR sensor acts as an interrupt - signals us whenever it goes high (or low)
+InterruptIn pir(PIR_PIN);   // This pin value comes out mbed_app.json
+
+// Whenever movement is not detected
+void pir_fall() {
+  setRgbColor(0.0f, 0.0f, 0.0f);
+}
+
+// Whenever movement is detected
+void pir_rise() {
+  // set the color to red
+  setRgbColor(1.0f, 0.0f, 0.0f);
+}
+
+int main(int, char**) {
+  // Blink the LED when the application starts
+  setRgbColor(0.0f, 1.0f, 0.0f);
+  Thread::wait(500);
+  setRgbColor(0.0f, 0.0f, 0.0f);
+
+  // The PIR sensor uses interrupts, no need to poll
+  pir.fall(&pir_fall);
+  pir.rise(&pir_rise);
+}
+```
+
+## Compiling and flashing
+
+### Compiling
+
+Compile the code by clicking the *Compile* button at the top of the screen:
+
+<span class="images">![The compile button](assets/lights7.png)</span>
+
+A successful compilation downloads a `bin` file to your computer. This is the compiled firmware for your development board.
+
+### Flashing
+
+When you connect your board to your computer, it mounts as a USB mass storage device, like a USB drive. To flash the new application onto the board, drag and drop the firmware file onto the mass storage device:
+
+<span class="images">![Flashing the application on Windows](assets/lights8.png)<span>Drag the firmware file onto the mass storage device to flash the application.</span></span>
+
+<span class="notes">**Note:** On some boards you might need to press the *Reset* button to load the program.</span>
+
+### Testing the application
+
+After flashing the application, you can test it by waving your hand in front of the PIR sensor; the red LED should light up.
+
+## Developing using mbed CLI
+
+You can also develop locally using [mbed CLI](http://github.com/armmbed/mbed-cli), a command line tool for mbed OS. First follow [the installation steps](https://github.com/ARMmbed/mbed-cli#installing-mbed-cli), then use the following commands to recreate the flow above:
+
+```bash
+# import the connected-lights project
+$ mbed import connected-lights
+
+# go into the folder
+cd connected-lights/
+
+# now edit the mbed_app.json file, and create the main.cpp file
+
+# detect which board you are using
+$ mbed detect
+
+# build the project, you'll need the GCC ARM cross-compilation toolchain installed
+# optionally, you can also build with ARMCC or IAR
+$ mbed compile -t GCC_ARM -m YOUR_BOARD_NAME
+
+# … building …
+# ends with something like:
+# Image: ./.build/K64F/GCC_ARM/simple-mbed-client-example.bin
+```
+
+Copy the binary file that was generated to your board using drag-and-drop programming (as shown under [Compiling and flashing](#compiling-and-flashing)).
+
diff -r 000000000000 -r 7a352727249b docs/4_connectivity.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/4_connectivity.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,306 @@
+# Adding connectivity
+
+Now that we've built our basic circuit and wrote the code to control that circuit, we can start adding connectivity to the project. Part of the ARM mbed IoT Device Platform is mbed Device Connector, a unified solution to connect devices to the internet and communicate with them regardless of *how* these devices connect to the internet. There are libraries available for a variety of connectivity methods, including Ethernet, Wi-Fi, 6LoWPAN, Thread and Cellular.
+
+## Obtaining a device certificate
+
+All data that goes from the device to mbed Device Connector (and vice-versa) is end-to-end encrypted by [mbed TLS](https://tls.mbed.org). To set up secure communications we need a security certificate. We can get one from the mbed Device Connector website:
+
+1. Go to the [mbed Device Connector homepage](https://connector.mbed.com) and click *Sign in to get connected*.
+1. If prompted for your login credentials, use your mbed developer account (the same account you use to log into the mbed Online Compiler).
+1. Go to *My Devices* > *Security Credentials*.
+1. Click *Get my security credentials*.
+1. Copy the contents of the gray box. This is your certificate.
+
+    <span class="images">![The certificate is located in the gray box](assets/lights16.png)</span>
+
+1. Go back to the online compiler.
+1. Create a new file ``security.h`` in your application's ``source`` directory. 
+1. Paste the certificate into this file.
+
+## Adding connectivity to the board
+
+### Ethernet
+
+If you have a development board that connects over Ethernet, just plug in an Ethernet cable. We’re assuming that the network has DHCP enabled and the firewall does not block connections to *http://connector.mbed.com*.
+
+### ESP8266 Wi-Fi module
+
+To wire the ESP8266 module up to your development board, look at the [ESP8266 Cookbook page](https://developer.mbed.org/users/4180_1/notebook/using-the-esp8266-with-the-mbed-lpc1768/). Broadly, this means hooking up the ESP8266's TX pin to `D0` and RX pin to `D1`.
+
+<span class="notes">**Note about ESP8266 on NUCLEO boards:** On the NUCLEO boards pins `D0` and `D1` are used for serial communication with the computer. Use pins `D8` and `D2` instead.</span>
+
+### 6LoWPAN
+
+First connect your 6LoWPAN gateway to an IPv6-enabled network by following the steps under 'Gateway Configuration' on [this page](https://github.com/ARMmbed/mbed-client-example-6lowpan#gateway-configuration). 
+
+Then attach the 6LoWPAN shield to the top of your development board.
+
+## Adding libraries with the online compiler
+
+For the device and mbed Device Connector to talk we need the [mbed Client library](https://docs.mbed.com/docs/mbed-client-guide/en/latest/). This library is already included in mbed OS, and is very powerful, but can also be daunting for new users. In this example we'll use an additional library built on top of mbed Client: SimpleClient. This library is designed to easily expose variables and resources to the cloud.
+
+We will also use [EasyConnect](https://github.com/ARMmbed/easy-connect) to handle connectivity.
+
+To add these libraries to your project:
+
+1. Go back to the online compiler.
+1. Right click on your program in the tree and select *Import Library* > *From URL*.
+1. Under *Source URL* enter: ``https://github.com/armmbed/easy-connect``.
+1. Do **not** tick 'Update all sub-libraries to the latest version'.
+1. Click *Import*.
+1. Again, right click on your program and select *Import Library* > *From URL*.
+1. Under *Source URL* enter: ``https://developer.mbed.org/teams/sandbox/code/simple-mbed-client/``.
+1. Click *Import*.
+
+## Adding libraries with mbed CLI
+
+If you are using mbed CLI, run the following commands to add the libraries:
+
+```bash
+$ mbed add easy-connect
+$ mbed add http://developer.mbed.org/teams/sandbox/code/simple-mbed-client/
+```
+
+## Updating configuration
+
+We need to tell EasyConnect which connectivity method to use. Open ``mbed_app.json`` and locate the `network-interface` field. Change the `value` to the connectivity method used:
+
+```json
+/* mbed_app.json */
+
+/* snip */
+
+        "network-interface":{
+            "help": "options are ETHERNET,WIFI_ESP8266,MESH_LOWPAN_ND,MESH_THREAD",
+            "value": "ETHERNET"
+        },
+        "esp8266-tx": {
+            "help": "Pin used as TX (connects to ESP8266 RX)",
+            "value": "D1"
+        },
+        "esp8266-rx": {
+            "help": "Pin used as RX (connects to ESP8266 TX)",
+            "value": "D0"
+        },
+        "esp8266-ssid": {
+            "value": "\"SSID\""
+        },
+        "esp8266-password": {
+            "value": "\"Password\""
+        },
+        "esp8266-debug": {
+            "value": true
+        }
+    }
+
+/* snip */
+```
+
+If you:
+
+* Are using Wi-Fi: also set your Wi-Fi SSID and your password. 
+* Used pins other than `D0`/`D1`: also change the pin names.
+
+## Writing code
+
+### Setting up a connection
+
+We need to add some code to the application so it connects to the internet and sets up a connection to mbed Device Connector.
+
+Replace ``main.cpp`` with:
+
+```cpp
+#include "mbed.h"
+#include "led.h"        // Abstracts away the differens between the LED types
+#include "security.h"   // Security configuration
+#include "easy-connect.h"
+#include "simple-mbed-client.h"
+
+EventQueue eventQueue;  // An event queue
+Thread eventThread;     // An RTOS thread to process events in
+
+SimpleMbedClient client;
+
+// PIR sensor acts as an interrupt - signals us whenever it goes high (or low)
+InterruptIn pir(PIR_PIN);   // This pin value comes out mbed_app.json
+
+
+// YOUR CODE HERE
+void pir_rise() { }
+// END OF YOUR CODE HERE
+
+// Use the built-in LED as a status LED
+DigitalOut statusLed(LED1);
+int        statusLedBlinkId;    // Callback ID
+void blink_builtin_led() {
+  statusLed = !statusLed;
+}
+
+void registered() {
+  // When we registered with mbed Device Connector, blink faster
+  eventQueue.cancel(statusLedBlinkId);
+
+  statusLedBlinkId = eventQueue.call_every(300, &blink_builtin_led);
+}
+
+int main(int, char**) {
+  // Using an event queue is a very useful abstraction around many microcontroller 'problems', like dealing with ISRs
+  // see https://developer.mbed.org/blog/entry/Simplify-your-code-with-mbed-events/
+  eventThread.start(callback(&eventQueue, &EventQueue::dispatch_forever));
+
+  // Blink the built-in LED every 1000ms. After registering we'll blink every 300ms.
+  statusLedBlinkId = eventQueue.call_every(1000, &blink_builtin_led);
+
+  // Disable the LED
+  setRgbColor(0.0f, 0.0f, 0.0f);
+
+  // The PIR sensor uses interrupts, no need to poll
+  pir.rise(eventQueue.event(&pir_rise));
+
+  NetworkInterface* network = easy_connect(true);
+  if (!network) {
+    printf("Connect to internet failed... See serial output.\r\n");
+    return 1;
+  }
+
+  struct MbedClientOptions options = client.get_default_options();
+  options.DeviceType = "light-system";
+  if (!client.setup(options, network)) {
+    printf("Setting up mbed_client failed...\r\n");
+    return 1;
+  }
+
+  client.on_registered(eventQueue.event(&registered));
+
+  // We can just let the main thread end here, no need to busy-loop
+}
+```
+
+### Program logic
+
+The code sample above does not do much, except for setting up the connection. We can now define some logic for this program:
+
+1. The color of the LED should be configurable.
+1. The period between the moment of motion detection to the moment lights go out should be configurable.
+1. There should be a permanent-on mode for the lights.
+1. We should notify mbed Device Connector whenever we detect movement.
+
+We can implement these actions by defining *resources*: pieces of information the device makes available. We can read or write to them from the cloud, and the device can use a resource's value to determine the correct action to perform. We can reach a resource with a URI and access modifier (for example, only write allowed), and we can also subscribe to them, so we'll be notified when a resource changes.
+
+Let's define a resource for each of our actions:
+
+* `led/0/color` - the color of the LED.
+* `led/0/timeout` - the timeout (in seconds) after detection; lights are disabled when this period ends.
+* `led/0/permanent_status` - whether we should have the lights permanently on (or off).
+* `pir/0/count` - the number of times the PIR sensor was triggered. Read only, and should allow notifications.
+
+We can use SimpleClient to define these resources and attach actions to each resource.
+
+Replace the following section in ``main.cpp``:
+
+```cpp
+// YOUR CODE HERE
+void pir_rise() { }
+// END OF YOUR CODE HERE
+```
+
+with (comments inline):
+
+```cpp
+// Fwd declaration
+void putLightsOn();
+void colorChanged(int newColor);
+
+// Variable that holds whether the light is on because the PIR sensor triggered (and timeout didn't happen yet)
+bool ledOnBecauseOfPir = false;
+
+// Timeout based on led/0/timeout, disables the light after a set interval
+Timeout pirTimeout;
+
+// Permanent statuses (set by led/0/permanent_status)
+#define STATUS_NONE     0
+#define STATUS_ON       1
+#define STATUS_OFF      2
+
+// clear the lights
+void putLightsOff() {
+  setRgbColor(0.0f, 0.0f, 0.0f);
+}
+
+// Status changes
+void statusChanged(int newStatus) {
+  switch (newStatus) {
+    case STATUS_ON: // Permanently on? OK.
+      putLightsOn();
+      break;
+    case STATUS_NONE: // Otherwise listen to PIR sensor
+    case STATUS_OFF:  // Or be off forever
+      putLightsOff();
+      break;
+  }
+}
+
+// Here are our resources:
+// We encode color in 3 bytes [R, G, B] and put it in an integer by providing the color as an hex value (default color: green)
+SimpleResourceInt ledColor = client.define_resource("led/0/color", 0x00ff00, &colorChanged);
+SimpleResourceInt ledTimeout = client.define_resource("led/0/timeout", 5);
+SimpleResourceInt ledStatus = client.define_resource("led/0/permanent_status", STATUS_NONE, &statusChanged);
+SimpleResourceInt pirCount = client.define_resource("pir/0/count", 0, M2MBase::GET_ALLOWED);
+
+// As said above, color is encoded in three bytes
+void putLightsOn() {
+  // parse the individual channels
+  int redCh   = ledColor >> 16 & 0xff;
+  int greenCh = ledColor >> 8 & 0xff;
+  int blueCh  = ledColor & 0xff;
+
+  // our color is 0..255, but we need a float between 0..1, cast it.
+  float red = static_cast<float>(redCh) / 255.0f;
+  float green = static_cast<float>(greenCh) / 255.0f;
+  float blue = static_cast<float>(blueCh) / 255.0f;
+  setRgbColor(red, green, blue);
+}
+
+// Color updated from the cloud,
+// if the LED is on because of the PIR, or if the LED is on permanently -> Set the color.
+void colorChanged(int newColor) {
+  if (ledOnBecauseOfPir || ledStatus == STATUS_ON) {
+    putLightsOn();
+  }
+}
+
+// Timeout (from led/0/timeout) happened after PIR sensor was triggered...
+void onPirTimeout() {
+  // if we're not permanent on
+  if (ledStatus != STATUS_ON) {
+    // clear the lights
+    putLightsOff();
+
+    ledOnBecauseOfPir = false;
+  }
+}
+
+// When the PIR sensor fires...
+void pir_rise() {
+  // Update the resource
+  pirCount = pirCount + 1;
+
+  // Permanent off? Don't put the lights on...
+  if (ledStatus == STATUS_OFF) return;
+
+  // Otherwise do it!
+  ledOnBecauseOfPir = true;
+  putLightsOn();
+
+  // And attach the timeout
+  pirTimeout.attach(eventQueue.event(&onPirTimeout), static_cast<float>(ledTimeout));
+}
+```
+
+When you compile and flash this program, you'll see that when you wave your hand in front of the PIR sensor the color of the LED changes to green, and the LED always goes off after 5 seconds.
+
+When the connection to mbed Device Connector is created, the onboard LED will blink faster. We can now control this device from the cloud.
+
+<span class="notes">**Note:** No connection? [Inspect the logs on the device](https://developer.mbed.org/handbook/SerialPC#host-interface-and-terminal-applications).</span>
+
diff -r 000000000000 -r 7a352727249b docs/5_controlling-from-cloud.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/5_controlling-from-cloud.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,67 @@
+# Controlling the device from mbed Device Connector
+
+At this moment the device is connected through mbed Device Connector. In the code sample that we gave in the previous section, we defined a number of resources using calls to `client.define_resource()`. These resources are automatically exposed to mbed Device Connector, from where you can read and write resources. Then, changes are automatically synced back to the device. That means that we already have a remote management interface for this device.
+
+## Seeing the status of a device
+
+Each device that you connect to mbed Device Connector has an endpoint name. This is a GUID (in the form of 8874e0eb-96ef-4799-b948-e91d05147bfe), which is the unique identifier of your device. If you don't know the endpoint name of your device, check the ``security.h`` file in the online compiler.
+
+We need the know the endpoint's name to check the device's status in the mbed Device Connector's online interface. The [endpoint](https://connector.mbed.com/#endpoints) page lists all devices associated with your account, with their current status.
+
+<span class="tips">**Tip:** The mbed Device Connector interface lists your devices by type. You can categorize devices by setting the device type in the application running on the device. See `options.DeviceType = "light-system";` in ``main.cpp``.</span>
+
+<span class="images">![Two connected devices](assets/lights11.png)<span>The mbed Device Connector Connected Devices page, showing two connected devices: our light-system and another device.</span></span>
+
+
+## Controlling the device
+
+We created four resources before (see ``main.cpp``):
+
+* `led/0/color` - the color of the LED, encoded as three bytes.
+* `led/0/timeout` - the timeout (in seconds) after detection; lights are disabled when this period ends.
+* `led/0/permanent_status` - whether we should have the lights permanently on (status 1) or off (status 2), or just let the PIR sensor figure it out (status 0).
+* `pir/0/count` - the number of times the PIR sensor was triggered. Read only, and should show notifications.
+
+These resources can be controlled through the mbed Device Connector web interface. For instance, when we write the value `1` to `led/0/permanent_status` the lights will stay on indefinitely.
+
+### Turning the lights on
+
+To test this out, in mbed Device Connector go to *Device Connector* > *API Console*, and select *Endpoint directory lookups*. This gives you access to a management console where you can quickly test out interactions with resources.
+
+To enable the lights:
+
+1. Choose *PUT*.
+1. Select your endpoint and the resource path `/led/0/permanent_status`.
+
+    <span class="images">![Preparing a PUT request to a resource in mbed Device Connector](assets/lights12.png)</span>
+
+1. Switch to the *PUT data* tab and enter `1`.
+1. Click the *TEST API* button.
+
+
+    <span class="images">![Setting PUT data for a request in mbed Device Connector](assets/lights13.png)</span>
+
+Now your lights will stay on until you change the status of `permanent_status` to 0 (listen to PIR sensor) or 2 (always off).
+
+### Setting the color
+
+We can control the color of the lights the same way. The color is encoded in an integer where we store three channels: red, green and blue. Each of the channels can have a value between 0 (off) and 255 (completely on).
+
+To encode the value of a color:
+
+```js
+red = 0;
+green = 255;
+blue = 255;
+
+// alternatively: encode the color as a hex value, via encoded = 0x00ffff
+
+encoded = (red << 16) + (green << 8) + blue;
+// 65380
+```
+
+Use the API Console to write this value to resource `/led/0/color` and change the color of the LED to turquoise.
+
+### Other variables
+
+We can also change the value of the timeout (in a real light system you probably want at least 30 seconds) and read the number of times the PIR sensor triggered (in the GET tab).
diff -r 000000000000 -r 7a352727249b docs/6_cloud-api.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/6_cloud-api.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,188 @@
+# Using the mbed Device Connector API
+
+The mbed Device Connector web interface that we used in the previous section is a wrapper around the mbed Device Connector API. Through this API we can connect any app to any device. We can use this API to build an app that allows us to control any of the lighting systems that we deploy in our house or office.
+
+## Obtaining an access key
+
+To talk to the API we need to obtain an access key. This key is used to authenticate with the API. To create a new access key, go to the [Access Keys](https://connector.mbed.com/#accesskeys) page in the mbed Device Connector web interface.
+
+Click *CREATE NEW ACCESS KEY* to create a new access key, and give it a descriptive name.
+
+
+![Creating a new access key in mbed Device Connector](assets/lights14.png)
+
+## Testing the API
+
+We can quickly test out if the access key works by doing a call to the API to query for all our devices. To retrieve a list of all devices, make a GET request to https://api.connector.mbed.com/endpoints/. You'll need to send an authorization header with this request:
+
+```
+Authorization: Bearer <your_access_key>
+```
+
+You can make this request with any request library, but if you're using curl, use the following command:
+
+
+
+```
+curl -v -H "Authorization: Bearer <your_access_key>" https://api.connector.mbed.com/endpoints/
+```
+
+This will return something like this:
+
+```
+< HTTP/1.1 200 OK
+< Date: Tue, 14 Jun 2016 10:37:01 GMT
+< Server: mbed Device Server
+< Content-Type: application/json
+< Content-Length: 169
+<
+* Connection #0 to host api.connector.mbed.com left intact
+
+[
+  {
+    "name": "9e3a37ae-e74f-465f-b04c-f105c8f69f1c",
+    "type": "test",
+    "status": "ACTIVE"
+  },
+  {
+    "name": "8874e0eb-96ef-4799-b948-e91d05147bfe",
+    "type": "light-system",
+    "status": "ACTIVE"
+  }
+]
+```
+
+<span class="notes">**Note:** The official API documentation for the mbed Device Connector REST interface is [located here](https://docs.mbed.com/docs/mbed-device-connector-web-interfaces/en/latest/).</span>
+
+## Using the official libraries
+
+You don't need to write raw HTTP requests to deal with the mbed Device Connector REST interface, as there are official libraries available for node.js, Python and Java. This is especially nice because the APIs to interact with resources are [asynchronous](https://docs.mbed.com/docs/mbed-device-connector-web-interfaces/en/latest/#asynchronous-requests), because for many functions it's not guaranteed that an action (such as writing to a device) will happen straight away, as the device might be in deep sleep or otherwise slow to respond. Therefore, you need to listen to callbacks on a [notification channel](https://docs.mbed.com/docs/mbed-device-connector-web-interfaces/en/latest/api-reference/#notifications). If you're using any of the official libraries, notification channels are abstracted away, making it easier to write applications on top of mbed Device Connector.
+
+An additional feature in the libraries is that they support subscriptions. We can subscribe to resources and get a notification whenever they change. This is useful for our `/pir/0/count` resource, as we can get notified whenever someone moves in front of the sensor.
+
+The following sections show an example of changing the color of the light, and receiving a notification whenever someone waves in front of the PIR sensor, in both node.js and Python.
+
+### node.js
+
+First, make sure you have installed [node.js](http://nodejs.org). Then create a new folder, and install the mbed Device Connector node.js library via npm:
+
+```bash
+$ npm install mbed-connector-api
+```
+
+Next create a new file ``main.js`` in the same folder where you installed the library, and fill it with the following content (replace `YOUR_ACCESS_KEY` with your access key):
+
+```js
+var CloudApi = require('mbed-connector-api');
+var api = new CloudApi({
+  accessKey: 'YOUR_ACCESS_KEY'
+});
+
+// Start notification channel
+api.startLongPolling(function(err) {
+  if (err) throw err;
+
+  // Find all lights
+  api.getEndpoints(function(err, devices) {
+    if (err) throw err;
+
+    console.log('Found', devices.length, 'lights', devices);
+
+    devices.forEach(function(d) {
+      // For every light, we will request notifications on the PIR resource
+      api.putResourceSubscription(d.name, '/pir/0/count');
+
+      // and set the color to orange, as your's truly is Dutch
+      var orange = 0xff6400;
+      api.putResourceValue(d.name, '/led/0/color', orange, function(err) {
+        if (err) console.error('Setting led/0/color for', d.name, 'failed', err);
+        console.log('Set color of', d.name, 'to orange!');
+      });
+
+    });
+
+  }, { parameters: { type: 'light-system' } });
+});
+
+// Notifications
+api.on('notification', function(notification) {
+  console.log('Got a notification', notification);
+});
+```
+
+When you run this program, and you wave your hand in front of the PIR sensor, you'll see something like this:
+
+```
+$ node main.js
+Found 1 lights [ { name: '8874e0eb-96ef-4799-b948-e91d05147bfe',
+    type: 'light-system',
+    status: 'ACTIVE' } ]
+Set color of 8874e0eb-96ef-4799-b948-e91d05147bfe to orange!
+Got a notification { ep: '8874e0eb-96ef-4799-b948-e91d05147bfe',
+  path: '/pir/0/count',
+  ct: 'text/plain',
+  payload: '7',
+  'max-age': 0 }
+```
+
+See here for the [full docs](https://github.com/ARMmbed/mbed-connector-api-node) on how to use the node.js library.
+
+### Python
+
+First make sure that you have installed [Python 2.7 or Python 3](https://www.python.org/downloads/) and [pip](https://pip.pypa.io/en/stable/installing/). Then create a new folder, and install the mbed Device Connector library through pip:
+
+```bash
+$ pip install -U mbed-connector-api
+```
+
+Next create a new file - ``lights.py`` - in the same folder where you installed the library, and fill it with the following content (replace `YOUR_ACCESS_KEY` with your access key):
+
+```python
+import mbed_connector_api
+import time
+import base64
+
+connector = mbed_connector_api.connector("YOUR_ACCESS_KEY")
+
+def notificationHandler(data):
+    for n in data['notifications']:
+        print "Got a notification for %s: %s -> new value %s" % (n['ep'], n['path'], base64.b64decode(n['payload']))
+
+connector.startLongPolling()
+connector.setHandler('notifications', notificationHandler)
+
+e = connector.getEndpoints("light-system")
+while not e.isDone():
+    None
+if e.error:
+    raise(e.error.error)
+print("Found %d lights: %s" % (len(e.result), str(e.result)))
+
+for endpoint in e.result:
+    # Get a notification whenever the PIR count changes
+    connector.putResourceSubscription(endpoint['name'], "/pir/0/count")
+
+    # And change the color to pink, because that's nice
+    pink = 0xff69b4
+    x = connector.putResourceValue(endpoint['name'], "/led/0/color", pink)
+    while not x.isDone():
+        None
+    if (x.error):
+        print("Setting pink color for %s failed: %s" % (endpoint['name'], x.error.error))
+    else:
+        print("Set color of %s to pink!" % endpoint['name'])
+
+while 1:
+    time.sleep(1.0)
+```
+
+When we run this program, and you wave your hand in front of the PIR sensor, we'll see something like this:
+
+```
+$ python lights.py
+Found 1 lights: [{u'status': u'ACTIVE', u'type': u'light-system', u'name': u'8874e0eb-96ef-4799-b948-e91d05147bfe'}]
+Set color of 8874e0eb-96ef-4799-b948-e91d05147bfe to pink!
+Got a notification for 8874e0eb-96ef-4799-b948-e91d05147bfe: /pir/0/count -> new value 49
+```
+
+See here for the [full docs](https://github.com/ARMmbed/mbed-connector-api-python) on how to use the Python library.
diff -r 000000000000 -r 7a352727249b docs/7_app.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/7_app.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,45 @@
+# Bringing it all together
+
+We’ve already built almost all of the pieces required for our lighting system. We now have:
+
+* The circuitry.
+* The firmware.
+* A connection to mbed Device Connector.
+* A way of talking from a node.js or Python app to the device.
+
+We can now combine everything by building a web app that allows us to control the lights from anywhere in the world. For convenience we built a node.js mobile web app for you, which is located here: [ARMmbed/connected-lights](https://github.com/ARMmbed/connected-lights/tree/master/webapp). The web app has the following features:
+
+* Lists all the lights under your account.
+* Changes the status of the light (from motion sensing to on/off).
+* Quickly changes colors.
+* Gets notifications whenever the PIR sensor detects movement.
+
+Here is a screenshot of the app running on an Android phone:
+
+
+![Screenshot of the light control interface](assets/lights15.png)
+
+*Mobile interface with two lights connected. Tap the color to change it.*
+
+By tapping on the endpoint name we can quickly rename the light to something more friendly (like “living room”), and by clicking on the color we can pick a new light color from a color wheel.
+
+## Installing the application
+
+To start the application, first download the application, either by:
+
+1. Downloading the [application as a ZIP archive](https://github.com/ARMmbed/connected-lights/archive/master.zip).
+1. Or by running `git clone https://github.com/armmbed/connected-lights` in your terminal.
+
+Then open ``main.js`` and paste in your mbed Device Connector Access Token on the first line. Next, open a terminal or the command prompt and navigate to the location where you extracted the application, and run:
+
+```
+$ cd connected-lights/webapp
+$ npm install
+$ node main.js
+```
+
+Open your web browser and go to http://localhost:5265 to see the application running.
+
+## Konekuta
+
+The web application is built on top of [Konekuta](https://github.com/armmbed/konekuta), a framework for building dynamic web applications on top of mbed Device Connector. Konekuta solves a number of common problems with building connected applications, including state syncing, going offline/online, handling errors, and updating the UI when devices connect or disconnect from mbed Device Connector. Konekuta is open source and available on [GitHub](https://github.com/armmbed/konekuta).
diff -r 000000000000 -r 7a352727249b docs/8_conclusion.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/8_conclusion.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,7 @@
+# Conclusion
+
+The ARM mbed IoT Device Platform is a very diverse set of tools, all aimed at making developing and managing IoT appliances easier. In this article we went over all the basics required to build connected devices, and how to address and manage these devices over the internet. There is more to mbed than what's in this article: we have connectivity methods for a variety of other standards, like [LoRa](https://docs.mbed.com/docs/lora-with-mbed/en/latest/intro-to-lora/), [Thread](https://docs.mbed.com/docs/arm-ipv66lowpan-stack/en/latest/thread_overview/) and [Bluetooth](https://docs.mbed.com/docs/ble-intros/en/latest/), and a complete set of security libraries to build more secure IoT applications with [uVisor](https://docs.mbed.com/docs/uvisor-and-uvisor-lib-documentation/en/latest/) and [mbed TLS](https://tls.mbed.org). mbed Cloud also has some more tricks up its sleeve, including services for [device management](https://cloud.mbed.com/product-overview) (like firmware updates over the air) and telemetry services. If you want more information, [let us know](https://cloud.mbed.com/contact).
+
+If you want to continue iterating on the light system, a very good start would be to attach a brighter (or bigger) light source. [Neopixel LEDs](https://www.adafruit.com/category/168) can be sourced in a variety of forms (like LED strips) and are super bright - a great choice for your home or office. [Here's documentation](https://developer.mbed.org/components/NeoPixel-LED-chain-using-high-speed-SPI/) on how to use the Neopixel LEDs from mbed OS.
+
+Happy hacking!
diff -r 000000000000 -r 7a352727249b docs/assets/lights10.jpg
Binary file docs/assets/lights10.jpg has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights11.png
Binary file docs/assets/lights11.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights12.png
Binary file docs/assets/lights12.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights13.png
Binary file docs/assets/lights13.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights14.png
Binary file docs/assets/lights14.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights15.png
Binary file docs/assets/lights15.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights16.png
Binary file docs/assets/lights16.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights17.png
Binary file docs/assets/lights17.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights18.png
Binary file docs/assets/lights18.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights2.png
Binary file docs/assets/lights2.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights3.png
Binary file docs/assets/lights3.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights4.png
Binary file docs/assets/lights4.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights5.png
Binary file docs/assets/lights5.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights6.png
Binary file docs/assets/lights6.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights7.png
Binary file docs/assets/lights7.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights8.png
Binary file docs/assets/lights8.png has changed
diff -r 000000000000 -r 7a352727249b docs/assets/lights9.png
Binary file docs/assets/lights9.png has changed
diff -r 000000000000 -r 7a352727249b docs/index.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/index.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,27 @@
+# Building an internet connected lighting system
+
+In this tutorial we'll use the ARM mbed IoT Device Platform to build a complete connected lighting system that you can deploy in your house or office. The system consists of one (or more) extra-bright RGB LEDs hooked up to an mbed OS development board. The lights can be turned on or off by a motion sensor, but are also connected to the internet, so you can change their color. The system uses mbed Device Connector to connect to the internet, and is secured end-to-end.
+
+We'll go over all the steps required to build the hardware, the cloud integration, and the application. At the end of the tutorial you'll have a firm understanding of building complete connected IoT solutions on the mbed IoT Device Platform. You'll also have a cool light.
+
+## Requirements
+
+We need the following hardware:
+
+* A [development board](https://developer.mbed.org/platforms/?software=16) capable of running mbed OS.
+* A way of connecting to the internet. Choose one:
+    * Ethernet, by using a development board with an Ethernet connection.
+    * Wi-Fi, by using an [ESP8266 Wi-Fi module](https://developer.mbed.org/teams/ESP8266/).
+    * 6LoWPAN, by using a [6LoWPAN gateway](https://firefly-iot.com/product/firefly-6lowpan-gateway-2-4ghz/) and a [6LoWPAN shield](https://firefly-iot.com/product/firefly-arduino-shield-2-4ghz/).
+* A breadboard, to hook up the components.
+* A PIR sensor to detect motion.
+* An RGB LED - preferably an extra-bright one.
+    * For a better effect you can also use a [Grove Chainable LED](http://wiki.seeed.cc/Grove-Chainable_RGB_LED/).
+    * After building the original application, you can easily swap out the LED for something [fancier](https://www.adafruit.com/product/1138).
+* Some jumper wires. Both male-male and male-female.
+* Resistors: 1x 100 Ohm, 2x 220 Ohm.
+
+
+<span class="images">![Components needed](assets/lights2.png)<span>Components required to build our lighting system. Top row: RGB LED, PIR sensor, ESP8266 Wi-Fi module. Bottom row: breadboard, NXP FRDM-K64F, jumper wires.</span></span>
+
+We also need an account on [developer.mbed.org](http://developer.mbed.org/) to access the online compiler and mbed Device Connector.
\ No newline at end of file
diff -r 000000000000 -r 7a352727249b example-nodejs/main.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/example-nodejs/main.js	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,39 @@
+var TOKEN = 'YOUR_ACCESS_TOKEN';
+
+var CloudApi = require('mbed-connector-api');
+var api = new CloudApi({
+  accessKey: process.env.TOKEN || TOKEN
+});
+
+// Start notification channel
+api.startLongPolling(function(err) {
+  if (err) throw err;
+
+  // Find all lights
+  api.getEndpoints(function(err, devices) {
+    if (err) throw err;
+
+    console.log('Found', devices.length, 'lights', devices);
+
+    devices.forEach(function(d) {
+      // For every light, we will request notifications on the PIR resource
+      api.putResourceSubscription(d.name, '/pir/0/count', function(err) {
+        console.log('subscribed to resource', err);
+      });
+
+      // and set the color to orange, as your's truly is Dutch
+      var orange = 0xff6400;
+      api.putResourceValue(d.name, '/led/0/color', orange, function(err) {
+        if (err) console.error('Setting led/0/color for', d.name, 'failed', err);
+        console.log('Set color of', d.name, 'to orange!');
+      });
+
+    });
+
+  }, { parameters: { type: 'light-system' } });
+});
+
+// Notifications
+api.on('notification', function(notification) {
+  console.log('Got a notification', notification);
+});
diff -r 000000000000 -r 7a352727249b example-nodejs/package.json
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/example-nodejs/package.json	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,14 @@
+{
+  "name": "mbed-cloud-lighting-node",
+  "version": "1.0.0",
+  "description": "Talking to lighting system over mbed Cloud from node.js",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "Jan Jongboom <jan.jongboom@arm.com>",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "mbed-connector-api": "^2.0.0"
+  }
+}
diff -r 000000000000 -r 7a352727249b example-python/lights.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/example-python/lights.py	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,39 @@
+import mbed_connector_api
+import time
+import base64
+import os
+
+TOKEN = "YOUR_ACCESS_TOKEN"
+
+connector = mbed_connector_api.connector(os.environ['TOKEN'] or TOKEN)
+
+def notificationHandler(data):
+    for n in data['notifications']:
+        print "Got a notification for %s: %s -> new value %s" % (n['ep'], n['path'], base64.b64decode(n['payload']))
+
+connector.startLongPolling()
+connector.setHandler('notifications', notificationHandler)
+
+e = connector.getEndpoints("light-system")
+while not e.isDone():
+    None
+if e.error:
+    raise(e.error.error)
+print("Found %d lights: %s" % (len(e.result), str(e.result)))
+
+for endpoint in e.result:
+    # Get a notification whenever the PIR count changes
+    connector.putResourceSubscription(endpoint['name'], "/pir/0/count")
+
+    # And change the color to pink, because that's nice
+    pink = 0xff69b4
+    x = connector.putResourceValue(endpoint['name'], "/led/0/color", pink)
+    while not x.isDone():
+        None
+    if (x.error):
+        print("Setting pink color for %s failed: %s" % (endpoint['name'], x.error.error))
+    else:
+        print("Set color of %s to pink!" % endpoint['name'])
+
+while 1:
+    time.sleep(1.0)
diff -r 000000000000 -r 7a352727249b lighting-system-firmware/.gitignore
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lighting-system-firmware/.gitignore	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,3 @@
+.build/
+*.pyc
+security.h
diff -r 000000000000 -r 7a352727249b lighting-system-firmware/mbed_settings.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lighting-system-firmware/mbed_settings.py	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,45 @@
+"""
+mbed SDK
+Copyright (c) 2016 ARM Limited
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+
+from os.path import join, abspath, dirname
+
+#ROOT = abspath(join(dirname(__file__), "."))
+
+##############################################################################
+# Build System Settings
+##############################################################################
+#BUILD_DIR = abspath(join(ROOT, "build"))
+
+# ARM
+#ARM_PATH = "C:/Program Files/ARM"
+
+# GCC ARM
+#GCC_ARM_PATH = ""
+
+# GCC CodeRed
+#GCC_CR_PATH = "C:/code_red/RedSuite_4.2.0_349/redsuite/Tools/bin"
+
+# IAR
+#IAR_PATH = "C:/Program Files (x86)/IAR Systems/Embedded Workbench 7.0/arm"
+
+# Goanna static analyser. Please overload it in private_settings.py
+#GOANNA_PATH = "c:/Program Files (x86)/RedLizards/Goanna Central 3.2.3/bin"
+
+#BUILD_OPTIONS = []
+
+# mbed.org username
+#MBED_ORG_USER = ""
diff -r 000000000000 -r 7a352727249b lighting-system-firmware/mbedtls_mbed_client_config.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lighting-system-firmware/mbedtls_mbed_client_config.h	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,119 @@
+/**
+ *  Minimal configuration for using mbedtls as part of mbed-client
+ *
+ *  NOTE! This is an optimized, minimal configuration for mbed Client.
+ *  We know it works with mbed Client but if you want to add more
+ *  services/communications to the application yourself - please ensure
+ *  you update this configuration accordingly. The default configuration
+ *  can be found from mbedTLS Github:
+ *
+ *  https://github.com/ARMmbed/mbedtls/blob/development/include/mbedtls/config.h
+ *
+ *
+ *  Copyright (C) 2006-2016, ARM Limited, All Rights Reserved
+ *  SPDX-License-Identifier: Apache-2.0
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *  not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  This file is part of mbed TLS (https://tls.mbed.org)
+ */
+
+
+#ifndef MBEDTLS_CUSTOM_CONFIG_H
+#define MBEDTLS_CUSTOM_CONFIG_H
+
+/* Enable entropy for K64F and K22F. This means entropy is disabled for all other targets. */
+/* Do **NOT** deploy this code in production on other targets! */
+/* See https://tls.mbed.org/kb/how-to/add-entropy-sources-to-entropy-pool */
+#if defined(TARGET_K64F) || defined(TARGET_K22F)
+#undef MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES
+#undef MBEDTLS_TEST_NULL_ENTROPY
+#endif
+
+/* System support */
+#define MBEDTLS_HAVE_ASM
+
+/* mbed TLS feature support */
+#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
+#define MBEDTLS_ECP_NIST_OPTIM
+#define MBEDTLS_SSL_MAX_FRAGMENT_LENGTH
+#define MBEDTLS_SSL_PROTO_TLS1_2
+#define MBEDTLS_SSL_PROTO_DTLS
+#define MBEDTLS_SSL_DTLS_ANTI_REPLAY
+#define MBEDTLS_SSL_DTLS_HELLO_VERIFY
+#define MBEDTLS_SSL_EXPORT_KEYS
+
+/* mbed TLS modules */
+#define MBEDTLS_AES_C
+#define MBEDTLS_ASN1_PARSE_C
+#define MBEDTLS_ASN1_WRITE_C
+#define MBEDTLS_BIGNUM_C
+#define MBEDTLS_CIPHER_C
+#define MBEDTLS_CTR_DRBG_C
+#define MBEDTLS_ECP_C
+#define MBEDTLS_ENTROPY_C
+#define MBEDTLS_MD_C
+#define MBEDTLS_OID_C
+#define MBEDTLS_PK_C
+#define MBEDTLS_PK_PARSE_C
+#define MBEDTLS_SHA256_C
+#define MBEDTLS_SSL_COOKIE_C
+#define MBEDTLS_SSL_CLI_C
+#define MBEDTLS_SSL_SRV_C
+#define MBEDTLS_SSL_TLS_C
+
+// XXX mbedclient needs these: mbedtls_x509_crt_free, mbedtls_x509_crt_init, mbedtls_x509_crt_parse
+#define MBEDTLS_X509_USE_C
+#define MBEDTLS_X509_CRT_PARSE_C
+
+// XXX: clean these up!!
+#define MBEDTLS_SHA512_C
+#define MBEDTLS_ECDH_C
+#define MBEDTLS_GCM_C
+
+#define MBEDTLS_ECDH_C
+#define MBEDTLS_ECDSA_C
+#define MBEDTLS_X509_CRT_PARSE_C
+
+// Remove RSA, save 20KB at total
+#undef MBEDTLS_RSA_C
+#undef MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED
+
+// Remove error messages, save 10KB of ROM
+#undef MBEDTLS_ERROR_C
+
+// Remove selftesting and save 11KB of ROM
+#undef MBEDTLS_SELF_TEST
+
+// Reduces ROM size by 30 kB
+#undef MBEDTLS_ERROR_STRERROR_DUMMY
+#undef MBEDTLS_VERSION_FEATURES
+#undef MBEDTLS_DEBUG_C
+
+// needed for parsing the certificates
+#define MBEDTLS_PEM_PARSE_C
+// dep of the previous
+#define MBEDTLS_BASE64_C
+
+// Reduce IO buffer to save RAM, default is 16KB
+#define MBEDTLS_SSL_MAX_CONTENT_LEN 2048
+
+// define to save 8KB RAM at the expense of ROM
+#undef MBEDTLS_AES_ROM_TABLES
+
+// Save ROM and a few bytes of RAM by specifying our own ciphersuite list
+#define MBEDTLS_SSL_CIPHERSUITES MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
+
+#include "mbedtls/check_config.h"
+
+#endif /* MBEDTLS_CUSTOM_CONFIG_H */
diff -r 000000000000 -r 7a352727249b lighting-system-firmware/source/led.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lighting-system-firmware/source/led.h	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,27 @@
+#ifndef CONNECTED_LIGHTS_LED_H_
+#define CONNECTED_LIGHTS_LED_H_
+
+#if MBED_CONF_APP_LED_TYPE == GROVE_CHAINABLE
+#include "ChainableLED.h"       // Library to talk to the Grove Chainable LED
+static ChainableLED led(MBED_CONF_APP_GROVE_CLOCK_PIN, MBED_CONF_APP_GROVE_DATA_PIN, 1);
+#else
+static PwmOut redLed(MBED_CONF_APP_LED_PIN_RED);
+static PwmOut greenLed(MBED_CONF_APP_LED_PIN_GREEN);
+static PwmOut blueLed(MBED_CONF_APP_LED_PIN_BLUE);
+#endif
+
+void setRgbColor(float red, float green, float blue) {
+#if MBED_CONF_APP_LED_TYPE == GROVE_CHAINABLE
+    led.setColorRGB(0, static_cast<uint8_t>(red * 255.0f), static_cast<uint8_t>(green * 255.0f), static_cast<uint8_t>(blue * 255.0f));
+#elif MBED_CONF_APP_LED_TYPE == TRICOLOR_ANODE
+    redLed = 1.0f - red;
+    greenLed = 1.0f - green;
+    blueLed = 1.0f - blue;
+#else
+    redLed = red;
+    greenLed = green;
+    blueLed = blue;
+#endif
+}
+
+#endif // CONNECTED_LIGHTS_LED_H_
diff -r 000000000000 -r 7a352727249b lighting-system-firmware/source/main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lighting-system-firmware/source/main.cpp	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,29 @@
+/* lighting-system-firmware/source/main.cpp */
+
+#include "mbed.h"
+#include "led.h"    // Abstracts away the differens between the LED types
+
+// PIR sensor acts as an interrupt - signals us whenever it goes high (or low)
+InterruptIn pir(PIR_PIN);   // This pin value comes out mbed_app.json
+
+// Whenever movement is not detected
+void pir_fall() {
+  setRgbColor(0.0f, 0.0f, 0.0f);
+}
+
+// Whenever movement is detected
+void pir_rise() {
+  // set the color to red
+  setRgbColor(1.0f, 0.0f, 0.0f);
+}
+
+int main(int, char**) {
+  // Blink the LED when the application starts
+  setRgbColor(0.0f, 1.0f, 0.0f);
+  Thread::wait(500);
+  setRgbColor(0.0f, 0.0f, 0.0f);
+
+  // The PIR sensor uses interrupts, no need to poll
+  pir.fall(&pir_fall);
+  pir.rise(&pir_rise);
+}
\ No newline at end of file
diff -r 000000000000 -r 7a352727249b mbed-os.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed-os.lib	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,1 @@
+https://github.com/ARMmbed/mbed-os/#c3b9436e12610acaab723f730ab15b48a539a5ac
diff -r 000000000000 -r 7a352727249b mbed_app.json
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed_app.json	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,85 @@
+{
+    "config": {
+
+        "pir-pin": {
+            "help": "Pin to which the PIR sensor is connected",
+            "macro_name": "PIR_PIN",
+            "value": "D2"
+        },
+
+        "led-type": {
+            "help": "options are TRICOLOR_ANODE,TRICOLOR_CATHODE,GROVE_CHAINABLE",
+            "value": "TRICOLOR_ANODE"
+        },
+
+        "led-pin-red": {
+            "help": "Only used for TRICOLOR_* LED types",
+            "value": "D5"
+        },
+        "led-pin-green": {
+            "help": "Only used for TRICOLOR_* LED types",
+            "value": "D6"
+        },
+        "led-pin-blue": {
+            "help": "Only used for TRICOLOR_* LED types",
+            "value": "D7"
+        },
+
+        "grove-clock-pin": {
+            "help": "Only used for GROVE_CHAINABLE LED types",
+            "value": "D5"
+        },
+        "grove-data-pin": {
+            "help": "Only used for GROVE_CHAINABLE LED types",
+            "value": "D6"
+        },
+
+
+        "network-interface":{
+            "help": "options are ETHERNET,WIFI_ESP8266,MESH_LOWPAN_ND,MESH_THREAD",
+            "value": "ETHERNET"
+        },
+        "esp8266-tx": {
+            "help": "Pin used as TX (connects to ESP8266 RX)",
+            "value": "D1"
+        },
+        "esp8266-rx": {
+            "help": "Pin used as RX (connects to ESP8266 TX)",
+            "value": "D0"
+        },
+        "esp8266-ssid": {
+            "value": "\"SSID\""
+        },
+        "esp8266-password": {
+            "value": "\"Password\""
+        },
+        "esp8266-debug": {
+            "value": true
+        },
+        "mesh_radio_type": {
+            "help": "options are ATMEL, MCR20",
+            "value": "ATMEL"
+        }
+
+    },
+
+    "macros": ["MBEDTLS_USER_CONFIG_FILE=\"mbedtls_mbed_client_config.h\"", "MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES", "MBEDTLS_TEST_NULL_ENTROPY"],
+
+    "target_overrides": {
+        "*": {
+            "target.features_add": ["NANOSTACK", "LOWPAN_ROUTER", "COMMON_PAL"],
+            "mbed-mesh-api.6lowpan-nd-channel-page": 0,
+            "mbed-mesh-api.6lowpan-nd-channel": 12,
+            "mbed-trace.enable": 0
+        },
+        "NUCLEO_F401RE": {
+            "esp8266-tx": "D8",
+            "esp8266-rx": "D2"
+        },
+        "NUCLEO_F411RE": {
+            "esp8266-tx": "D8",
+            "esp8266-rx": "D2"
+        }
+    }
+
+}
diff -r 000000000000 -r 7a352727249b mkdocs.yml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mkdocs.yml	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,15 @@
+site_name: Building an internet connected lighting system
+
+docs_dir: docs
+
+pages:
+- ['index.md','Requirements']
+- ['2_circuit.md', 'Building the circuit']
+- ['3_software.md', 'Writing the software']
+- ['4_connectivity.md', 'Adding connectivity']
+- ['5_controlling-from-cloud.md', 'Controlling the device from mbed Cloud']
+- ['6_cloud-api.md', 'Using the mbed Cloud API']
+- ['7_app.md', 'Bringing it all together']
+- ['8_conclusion.md', 'Conclusion']
+
+copyright: © ARM Ltd. Copyright 2016 – ARM mbed IoT Device Platform
diff -r 000000000000 -r 7a352727249b webapp/README.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/README.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,24 @@
+# Connected Lights Web Application
+
+This is the web application that goes with the article [Building an internet connected lighting system](https://docs.mbed.com/docs/building-an-internet-connected-lighting-system/en/latest/), in which we build a lighting system on top of the ARM mbed IoT Device Platform.
+
+To get started, install a recent version of [node.js](https://nodejs.org/en/). Then:
+
+```bash
+$ npm install
+$ TOKEN=xxx node main.js    # xxx = mbed Cloud Access Token
+```
+
+Now go to http://localhost:5265 to see your connected lights.
+
+## Architecture
+
+This application is built on top of [Konekuta](https://github.com/armmbed/konekuta), a node.js library to quickly build dynamic web applications on top of mbed Cloud.
+
+The server side source code is relatively compact. In [main.js](main.js) we declare which devices we want to find (`deviceType`) and which resources on these devices to retrieve, which resources to subscribe to, and which resources can be updated. We also tell Konekuta how to map the data from mbed Cloud into a view (used to render the web application). Konekuta will now set up a socket connection to every connected client, sync changes from one client to another, store data in mbed Cloud, and will notify clients when a device changes it status (either update the `/pir/0/count`, disappears or comes back online).
+
+On the client we'll connect to the server, and subscribe to events from the server. We then update the UI based on these updates. This ensures that every connected client will always see the same UI. Changing the color of a light on your desktop will also update the color on your phone.
+
+To update data we can send events to the server over the same socket. The server will send the data back to Connector and tell the sender whether the action succeeded. If the action succeeded the server will also inform all other clients.
+
+Konekuta also has logic for handling disconnects, going from offline to online, and other features to synchronize data.
diff -r 000000000000 -r 7a352727249b webapp/main.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/main.js	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,94 @@
+var TOKEN = 'YOUR_ACCESS_TOKEN';
+
+var konekuta = require('konekuta');
+var express = require('express');
+var app = express();
+var server = require('http').Server(app);
+var io = require('socket.io')(server);
+var hbs = require('hbs');
+
+// Some options for express (node.js web app library)
+hbs.registerPartials(__dirname + '/views/partials');
+app.use(express.static(__dirname + '/public'));
+app.set('view engine', 'html');
+app.set('views', __dirname + '/views');
+app.engine('html', hbs.__express);
+
+if (!process.env.TOKEN && TOKEN === 'YOUR_ACCESS_TOKEN') {
+  throw 'Please set your access token first in main.js!';
+}
+
+// This is how we go from device => view model
+// we'll use this to render the device view, this will also be sent to the client
+// when a device connects (so it knows what to render)
+function mapToView(d) {
+  var hex = Number(d.color).toString(16);
+  var model = {
+    name: d.endpoint,
+    rawColor: d.color,
+    color: '#' + '000000'.substring(0, 6 - hex.length) + hex,
+    motionClass: d.status == 0 ? 'selected' : '',
+    onClass: d.status == 1 ? 'selected' : '',
+    offClass: d.status == 2 ? 'selected' : '',
+    timeout: d.timeout,
+    status: d.status
+  };
+
+  // create the device HTML
+  var html = hbs.handlebars.compile('{{> device}}')(model);
+
+  return { model: model, html: html };
+}
+
+var options = {
+  endpointType: 'light-system',     // what endpoint types to look for
+  token: TOKEN,
+  io: io,
+  deviceModel: {                    // building the initial device model (w/ 4 properties)
+    status: {
+      retrieve: 'led/0/permanent_status',   // when device registers, retrieve value
+      update: {
+        method: 'put',                      // update actions
+        path: 'led/0/permanent_status'
+      }
+    },
+    timeout: {
+      retrieve: 'led/0/timeout',
+      update: {
+        method: 'put',
+        path: 'led/0/timeout'
+      }
+    },
+    color: {
+      retrieve: 'led/0/color',
+      update: {
+        method: 'put',
+        path: 'led/0/color'
+      }
+    },
+    count: {
+      retrieve: 'pir/0/count',
+      subscribe: true                       // subscribe to updates
+    }
+  },
+  mapToView: mapToView,
+  verbose: true                         // Verbose logging
+};
+
+// Start konekuta (connects to mbed Device Connector, and retrieves initial device model)
+konekuta(options, (err, devices, ee, connector) => {
+  if (err) {
+    throw err;
+  }
+
+  // Now we can start the web server
+  server.listen(process.env.PORT || 5265, process.env.HOST || '0.0.0.0', function() {
+    console.log('Web server listening on port %s!', process.env.PORT || 5265);
+  });
+
+  // And handle requests
+  app.get('/', function(req, res, next) {
+    // Render index view, with the devices based on mapToView function
+    res.render('index', { devices: devices.map(d => mapToView(d).model) });
+  });
+});
diff -r 000000000000 -r 7a352727249b webapp/package.json
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/package.json	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,17 @@
+{
+  "name": "mbed-cloud-lighting-webapp",
+  "version": "1.0.0",
+  "description": "Talking to lighting system over mbed Cloud from node.js",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "Jan Jongboom <jan.jongboom@arm.com>",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "express": "^4.13.4",
+    "hbs": "^4.0.0",
+    "konekuta": "^0.3.4",
+    "socket.io": "^1.4.6"
+  }
+}
diff -r 000000000000 -r 7a352727249b webapp/public/ARMmbedLogo.svg
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/ARMmbedLogo.svg	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 459.6 65.6" enable-background="new 0 0 459.6 65.6" xml:space="preserve">
+<g>
+	<path fill="#0A7185" d="M56.6,65.1h17.6L45.3,0H26.5L0,65.1h17.5l4.9-13h28.8L56.6,65.1z M26.7,40.8l9.4-24.8l10.4,24.8H26.7z"/>
+	<path fill="#0A7185" d="M126.3,49.1c-1.4-2.9-3.4-10.6-10.3-14.1c0,0,11-3.6,11-15.9c0-11-8.6-19-22.5-19C93.5,0,77,0,77,0v65.1
+		h16.1V38.4c0,0,9.5-1.8,13,5.4l9.8,21.2h17.8C133.7,65.1,127.6,51.9,126.3,49.1z M103.6,27.1L103.6,27.1l-10.6,0v-15h10.6v0
+		c3.9,0.2,7.1,3.5,7.1,7.5S107.6,26.8,103.6,27.1z"/>
+	<polygon fill="#0A7185" points="138.6,65.1 154.6,65.1 154.6,24.6 172.1,44.9 174.5,44.9 191.7,24.6 191.7,65.1 208,65.1 208,0 
+		194.1,0 173.3,24.5 152.7,0 138.6,0 	"/>
+	<g>
+		<path fill="#0A7185" d="M218.9,0.1c1.2,0,2.2,0.3,3.2,0.9c1,0.6,1.8,1.4,2.4,2.4c0.6,1,0.9,2.1,0.9,3.2c0,1.2-0.3,2.2-0.9,3.2
+			c-0.6,1-1.4,1.8-2.4,2.4c-1,0.6-2.1,0.9-3.2,0.9c-1.2,0-2.2-0.3-3.2-0.9c-1-0.6-1.8-1.4-2.4-2.4c-0.6-1-0.9-2.1-0.9-3.2
+			s0.3-2.2,0.9-3.2c0.6-1,1.4-1.8,2.4-2.4C216.7,0.4,217.7,0.1,218.9,0.1z M218.9,1.2c-1.5,0-2.8,0.5-3.8,1.6s-1.6,2.3-1.6,3.8
+			c0,1.5,0.5,2.8,1.6,3.8c1.1,1,2.3,1.6,3.8,1.6c1.5,0,2.8-0.5,3.8-1.6c1.1-1.1,1.6-2.3,1.6-3.8s-0.5-2.8-1.6-3.8
+			S220.4,1.2,218.9,1.2z M217.4,10h-1.1V3.1h2.4c0.9,0,1.5,0.1,1.8,0.2c0.4,0.1,0.6,0.4,0.8,0.7c0.2,0.3,0.3,0.6,0.3,1
+			c0,0.5-0.2,1-0.6,1.3c-0.4,0.4-0.8,0.6-1.4,0.6c0.3,0.1,0.5,0.3,0.7,0.5c0.2,0.2,0.5,0.7,0.9,1.2l0.8,1.4h-1.4l-0.6-1.1
+			c-0.4-0.8-0.8-1.3-1-1.5c-0.3-0.2-0.6-0.3-1-0.3h-0.7V10z M217.4,4v2.1h1.4c0.6,0,1-0.1,1.3-0.3c0.3-0.2,0.4-0.4,0.4-0.8
+			c0-0.4-0.1-0.7-0.4-0.8c-0.3-0.2-0.7-0.2-1.4-0.2H217.4z"/>
+	</g>
+	<g>
+		<g>
+			<path fill="#179CB6" d="M266.8,34.1v30.8h-8.4V41.3c0-4.7-0.6-8-1.9-9.8c-1.3-1.9-3.5-2.8-6.6-2.8c-1.8,0-3.4,0.4-4.8,1.2
+				c-1.5,0.8-3.1,2.2-5,4.2v30.8h-8.4V22.4h8.4V28c4.3-4.3,8.5-6.4,12.5-6.4c5.4,0,9.5,2.6,12.5,7.7c4.5-5.2,9.2-7.7,14.1-7.7
+				c4.1,0,7.5,1.5,10.2,4.5c2.7,3,4,7.6,4,13.8v25h-8.4V39.7c0-3.5-0.7-6.2-2.2-8.1s-3.5-2.8-6.2-2.8
+				C273.1,28.8,269.8,30.6,266.8,34.1z"/>
+			<path fill="#179CB6" d="M304.5,0.4h8.4v25c3.6-2.6,7.5-4,11.6-4c5.4,0,9.8,2,13.2,6.1c3.4,4.1,5.1,9.3,5.1,15.6
+				c0,6.8-2,12.3-6.1,16.3s-9.5,6.1-16.4,6.1c-2.7,0-5.6-0.3-8.6-0.9s-5.4-1.3-7.2-2.2V0.4z M312.9,33.5v23c2.9,0.9,5.8,1.3,8.8,1.3
+				c3.7,0,6.8-1.3,9.1-4c2.4-2.6,3.5-6,3.5-10.2c0-4.2-1.1-7.6-3.3-10.3c-2.2-2.6-5.1-4-8.6-4C319.3,29.4,316.1,30.7,312.9,33.5z"/>
+			<path fill="#179CB6" d="M388.3,44.4h-29.8c0.2,4.1,1.6,7.3,4.1,9.7c2.5,2.4,5.7,3.6,9.7,3.6c5.5,0,10.6-1.7,15.3-5.2v8.2
+				c-2.6,1.7-5.1,3-7.7,3.7c-2.5,0.7-5.5,1.1-8.9,1.1c-4.7,0-8.5-1-11.3-2.9c-2.9-1.9-5.2-4.5-6.9-7.8c-1.7-3.3-2.6-7.1-2.6-11.4
+				c0-6.5,1.8-11.7,5.5-15.8c3.7-4,8.4-6.1,14.3-6.1c5.6,0,10.1,2,13.5,5.9c3.4,3.9,5,9.2,5,15.8V44.4z M358.6,39.4H380
+				c-0.2-3.4-1.2-5.9-3-7.7s-4.2-2.7-7.2-2.7s-5.5,0.9-7.4,2.7S359.2,36,358.6,39.4z"/>
+			<path fill="#179CB6" d="M425.7,0.4h8.4v64.4h-17.9c-6.3,0-11.3-2-15-6c-3.7-4-5.6-9.3-5.6-16c0-6.2,2-11.4,5.9-15.4
+				c3.9-4,8.9-6,15-6c2.8,0,5.9,0.6,9.2,1.8V0.4z M425.7,57.7V30.8c-2.6-1.3-5.3-2-7.9-2c-4.2,0-7.5,1.4-10,4.1
+				c-2.5,2.7-3.7,6.4-3.7,11.1c0,4.4,1.1,7.7,3.2,10.1c1.3,1.4,2.7,2.4,4.1,2.9c1.4,0.5,4,0.7,7.8,0.7H425.7z"/>
+		</g>
+		<g>
+			<path fill="#179CB6" d="M447.2,9.1h-1.5V1.7H443V0.4h7.1v1.3h-2.8V9.1z M452.4,9.1H451V0.4h2.1l2.2,6.9l2.1-6.9h2.1v8.7h-1.3V1.8
+				L456,9.1h-1.3l-2.3-7.4V9.1z"/>
+		</g>
+	</g>
+</g>
+</svg>
diff -r 000000000000 -r 7a352727249b webapp/public/blueband_BG.png
Binary file webapp/public/blueband_BG.png has changed
diff -r 000000000000 -r 7a352727249b webapp/public/color-picker-element/.bower.json
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/color-picker-element/.bower.json	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,35 @@
+{
+  "name": "color-picker-element",
+  "main": "dist/color-picker.html",
+  "version": "0.0.4",
+  "authors": [
+    "bbrewer97202@gmail.com"
+  ],
+  "description": "Custom HTML color picker element",
+  "keywords": [
+    "custom-element",
+    "web-components",
+    "color",
+    "colorpicker",
+    "color-picker"
+  ],
+  "license": "MIT",
+  "homepage": "https://github.com/bbrewer97202/color-picker-element",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ],
+  "_release": "0.0.4",
+  "_resolution": {
+    "type": "version",
+    "tag": "0.0.4",
+    "commit": "5fc92c29ab45fa98410fc6f6f1c47bec629c3d47"
+  },
+  "_source": "git://github.com/bbrewer97202/color-picker-element.git",
+  "_target": "~0.0.4",
+  "_originalSource": "color-picker-element",
+  "_direct": true
+}
\ No newline at end of file
diff -r 000000000000 -r 7a352727249b webapp/public/color-picker-element/LICENSE
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/color-picker-element/LICENSE	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Ben Brewer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff -r 000000000000 -r 7a352727249b webapp/public/color-picker-element/README.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/color-picker-element/README.md	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,41 @@
+# &lt;color-picker&gt;
+
+A custom HTML element that provides a color picker.
+
+## Demo
+[See demo](http://bbrewer97202.github.io/color-picker-element/demo/index.html)
+
+## Overview
+This is a generic custom element that is not built on third-party frameworks (Polymer, x-tag, etc). It will run in a browser that supports custom elements, shadow DOM and HTML imports.  For browsers that do not support these features, use the [webcomponents.js](https://github.com/WebComponents/webcomponentsjs) polyfills (the demo page uses a cdn hosted instance).
+
+## Install
+
+Install using [Bower](http://bower.io/):
+
+```sh
+$ bower install color-picker-element --save
+```
+
+## Usage
+
+1. Import Web Components polyfill:
+
+    ```html
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.20/webcomponents.min.js"></script>
+    ```
+
+2. Import Custom Element:
+
+    ```html
+    <link rel="import" href="bower_components/color-picker-element/dist/color-picker.html">
+    ```
+
+3. Embed on page, optionally providing width and height attributes
+
+    ```html
+    <color-picker width="200" height="200"></color-picker>
+    ```
+
+## License
+
+[MIT License](http://opensource.org/licenses/MIT)
diff -r 000000000000 -r 7a352727249b webapp/public/color-picker-element/bower.json
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/color-picker-element/bower.json	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,25 @@
+{
+  "name": "color-picker-element",
+  "main": "dist/color-picker.html",
+  "version": "0.0.4",
+  "authors": [
+    "bbrewer97202@gmail.com"
+  ],
+  "description": "Custom HTML color picker element",
+  "keywords": [
+    "custom-element",
+    "web-components",
+    "color",
+    "colorpicker",
+    "color-picker"
+  ],
+  "license": "MIT",
+  "homepage": "https://github.com/bbrewer97202/color-picker-element",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ]
+}
diff -r 000000000000 -r 7a352727249b webapp/public/color-picker-element/dist/color-picker.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/color-picker-element/dist/color-picker.html	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,179 @@
+<script>
+
+/**
+ * color-picker custom element
+ */
+
+var colorPickerPrototype = Object.create(HTMLElement.prototype);
+
+colorPickerPrototype.onMouseDown = function(e) {
+    this.onMouseMove(e);
+    this.addEventListener('mousemove', this.onMouseMove);
+}
+
+colorPickerPrototype.onMouseUp = function(e) {
+    this.removeEventListener('mousemove', this.onMouseMove);
+}
+
+colorPickerPrototype.onTouchStart = function(e) {
+    this.onTouchMove(e);
+    this.addEventListener('touchmove', this.onTouchMove);
+}
+
+colorPickerPrototype.onTouchEnd = function(e) {
+    this.removeEventListener('touchmove', this.onTouchMove);
+}
+
+colorPickerPrototype.onTouchMove = function(e) {
+    var touch = e.touches[0]; 
+    this.onColorSelect(e, {
+        x: touch.clientX,
+        y: touch.clientY
+    });
+}
+
+colorPickerPrototype.onMouseMove = function(e) {
+    e.preventDefault();
+    if (this.mouseMoveIsThrottled) {                    
+        this.mouseMoveIsThrottled = false;
+        this.onColorSelect(e);
+        setTimeout(function() {
+            this.mouseMoveIsThrottled = true;
+        }.bind(this), 100);
+    }
+}                        
+
+colorPickerPrototype.onColorSelect = function(e, coords) {
+
+    if (this.context) {
+
+        coords = coords || this.relativeMouseCoordinates(e);
+        var data = this.context.getImageData(coords.x, coords.y, 1, 1).data;
+
+        this.setColor({
+            r: data[0], 
+            g: data[1], 
+            b: data[2]
+        });
+    }
+};
+
+colorPickerPrototype.pickerDraw = function() {
+
+    this.canvas = document.createElement('canvas');
+    this.canvas.setAttribute("width", this.width);
+    this.canvas.setAttribute("height", this.height);
+    this.canvas.setAttribute("style", "cursor:crosshair");
+    this.canvas.setAttribute("class", "simpleColorPicker");
+
+    this.context = this.canvas.getContext('2d');    
+
+    var colorGradient = this.context.createLinearGradient(0, 0, this.width, 0);
+    colorGradient.addColorStop(0, "rgb(255,0,0)");
+    colorGradient.addColorStop(0.16, "rgb(255,0,255)");
+    colorGradient.addColorStop(0.32, "rgb(0,0,255)");
+    colorGradient.addColorStop(0.48, "rgb(0,255,255)");
+    colorGradient.addColorStop(0.64, "rgb(0,255,0)");
+    colorGradient.addColorStop(0.80, "rgb(255,255,0)");
+    colorGradient.addColorStop(1, "rgb(255,0,0)");
+    this.context.fillStyle = colorGradient;
+    this.context.fillRect(0, 0, this.width, this.height);
+
+    var bwGradient = this.context.createLinearGradient(0, 0, 0, this.height);
+    bwGradient.addColorStop(0, "rgba(255,255,255,1)");
+    bwGradient.addColorStop(0.5, "rgba(255,255,255,0)");
+    bwGradient.addColorStop(0.5, "rgba(0,0,0,0)");
+    bwGradient.addColorStop(1, "rgba(0,0,0,1)");
+
+    this.context.fillStyle = bwGradient;
+    this.context.fillRect(0, 0, this.width, this.height); 
+
+}
+
+colorPickerPrototype.setColor = function(rgb) {
+
+    //save calculated color
+    this.color = {
+        hex: this.rgbToHex(rgb),
+        rgb: rgb
+    };
+
+    //update element attribute
+    this.setAttribute('color', this.color.hex);
+
+    //broadcast color selected event
+    var event = new CustomEvent('colorselected', {
+        detail: {
+            rgb: this.color.rgb,
+            hex: this.color.hex
+        }
+    });
+
+    this.dispatchEvent(event);
+}
+
+/**
+ * given red, green, blue values, return the equivalent hexidecimal value
+ * base source: http://stackoverflow.com/a/5624139
+ */
+colorPickerPrototype.componentToHex = function(c) {
+    var hex = c.toString(16);
+    return hex.length == 1 ? "0" + hex : hex;
+};
+
+colorPickerPrototype.rgbToHex = function(color) {
+    return "#" + colorPickerPrototype.componentToHex(color.r) + colorPickerPrototype.componentToHex(color.g) + colorPickerPrototype.componentToHex(color.b);
+};
+
+/**
+ * given a mouse click event, return x,y coordinates relative to the clicked target
+ * @returns object with x, y values
+ */
+colorPickerPrototype.relativeMouseCoordinates = function(e) {
+
+    var x = 0, y = 0;
+
+    if (this.canvas) {
+        var rect = this.canvas.getBoundingClientRect();
+        x = e.clientX - rect.left;
+        y = e.clientY - rect.top;
+    } 
+
+    return {
+        x: x,
+        y: y
+    };
+};
+
+colorPickerPrototype.createdCallback = function(e) {
+
+    //parse attributes
+    var attrs = {
+        width: this.getAttribute('width'),
+        height: this.getAttribute('height')
+    };
+
+    //initialization
+    this.canvas = null;
+    this.context = null;
+    this.color = null;
+    this.mouseMoveIsThrottled = true;
+    this.width = attrs.width || 300;
+    this.height = attrs.height || 300;
+
+    //create UI
+    this.shadowRoot = this.createShadowRoot();    
+    this.pickerDraw();    
+    this.shadowRoot.appendChild(this.canvas);
+
+    ///event listeners
+    this.addEventListener('mousedown', this.onMouseDown);
+    this.addEventListener('mouseup', this.onMouseUp);
+    this.addEventListener('touchstart', this.onTouchStart);
+    this.addEventListener('touchend', this.onTouchEnd);
+
+}
+
+document.registerElement('color-picker', { prototype: colorPickerPrototype });
+
+</script>
diff -r 000000000000 -r 7a352727249b webapp/public/color-picker-element/dist/demo/index.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/color-picker-element/dist/demo/index.html	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+    <head>
+        <meta name="viewport" content="width=device-width,user-scalable=no">
+        <title>color-picker custom element demo</title>
+
+        <!-- import polyfils for browsers without native support -->
+        <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.20/webcomponents.min.js"></script>
+
+        <!-- import the custom element -->
+        <link rel="import" href="../color-picker.html">
+
+    </head>
+    <body>
+
+        <!-- define the custom element -->
+        <color-picker width="300" height="300"></color-picker>
+
+        <!-- for demo output purposes -->
+        <h3>&lt;color-picker&gt;</h3>
+        <div style="width: 200px; height: 30px; border: solid 1px #eee;" id="colorDisplay"></div>
+        <div id="colorValues"></div>
+
+        <script type="text/javascript">
+
+            var cp = document.querySelector('color-picker');
+            var display = document.getElementById('colorDisplay');
+            var output = document.getElementById('colorValues');
+
+            //listen for 'colorselected' event on the color-picker element which returns a details param with hex and rgb values
+            cp.addEventListener('colorselected', function(e) {
+                output.innerHTML = e.detail.hex + ' (r=' + e.detail.rgb.r + ', g=' + e.detail.rgb.g + ', b=' + e.detail.rgb.b + ')';
+                display.style.backgroundColor = e.detail.hex;
+            });
+        </script>
+
+    </body>
+</html>
diff -r 000000000000 -r 7a352727249b webapp/public/color-picker-element/package.json
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/color-picker-element/package.json	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,25 @@
+{
+  "name": "color-picker-element",
+  "version": "0.0.4",
+  "description": "Custom HTML color picker element",
+  "main": "dist/color-picker.html",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git@github.com:bbrewer97202/color-picker-element.git"
+  },
+  "keywords": [
+    "custom-element",
+    "web-components",
+    "color",
+    "colorpicker",
+    "color-picker"
+  ],
+  "author": "bbrewer97202@gmail.com",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/bbrewer97202/color-picker-element/issues"
+  }
+}
diff -r 000000000000 -r 7a352727249b webapp/public/color-picker.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/color-picker.js	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,25 @@
+(function() {
+  /*global changeColor */
+
+  var cpOverlay = document.querySelector('#color-picker-overlay');
+  // this is needed by color-picker component unfortunately (does reflow)
+  var vw = window.innerWidth * 0.8 | 0;
+  // so this thing reads its attributes when creating, not when adding to body
+  // that's a mess... dont feel like patching the library
+  cpOverlay.innerHTML +=
+    '<color-picker width="' + vw + '" height="' + vw + '"></color-picker>';
+
+  document.querySelector('color-picker').addEventListener('colorselected', function(e) {
+    if (!cpOverlay.dataset.endpoint) return console.error('No data-endpoint attribute found...');
+
+    var rgb = e.detail.rgb;
+    var v = (rgb.r << 16) + (rgb.g << 8) + rgb.b;
+
+    changeColor(cpOverlay.dataset.endpoint, v);
+  });
+
+  cpOverlay.onclick = function(e) {
+    if (e.target !== e.currentTarget) return;
+    this.style.display = 'none';
+  };
+})();
diff -r 000000000000 -r 7a352727249b webapp/public/helper-functions.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/helper-functions.js	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,38 @@
+function getElement(endpoint) {
+  return document.querySelector('li[data-endpoint="' + endpoint + '"]');
+}
+
+function updateStatusUi(endpoint, status) {
+  var statusEl = getElement(endpoint).querySelector('.status');
+  [].forEach.call(statusEl.children, function(el) {
+    el.classList.remove('selected');
+  });
+  statusEl.querySelector('*[data-action="' + status + '"]').classList.add('selected');
+}
+
+function getDeviceName(endpoint, trim) {
+  var name = localStorage.getItem(endpoint + '-name') || endpoint;
+  if (trim && name.length > 20) name = name.substr(0, 20) + '...';
+  return name;
+}
+
+var notificationTo;
+function showNotification(msg) {
+  clearTimeout(notificationTo);
+
+  var el = document.querySelector('#notification');
+  el.textContent = msg;
+  el.style.opacity = 1;
+  el.style.visibility = 'visible';
+
+  notificationTo = setTimeout(function() {
+    el.style.opacity = 0;
+    el.style.visibility = 'hidden';
+  }, 6000);
+
+  el.onclick = function() {
+    clearTimeout(notificationTo);
+    el.style.opacity = 0;
+    el.style.visibility = 'hidden';
+  };
+}
diff -r 000000000000 -r 7a352727249b webapp/public/manifest.json
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/manifest.json	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,7 @@
+{
+  "short_name": "Connected Lights",
+  "name": "ARM mbed Connected Lights",
+  "start_url": "/",
+  "display": "standalone",
+  "orientation": "portrait"
+}
\ No newline at end of file
diff -r 000000000000 -r 7a352727249b webapp/public/style.css
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/public/style.css	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,108 @@
+#header { margin-top: 20px; }
+#logo { width: 167px; }
+body {
+  font-family: 'Open Sans', Arial, sans-serif;
+  margin: 0;
+  padding: 0 20px;
+  font-weight: 300;
+  color: rgb(80, 79, 77);
+  font-size: 16px;
+}
+h1 {
+  background-color: #159ab5;
+  background-image: url('/blueband_BG.png');
+  background-position-x: right;
+  background-size: 140%;
+  color: white;
+  font-weight: 400;
+  font-size: 48px;
+  margin: 20px -20px;
+  padding: 30px 20px;
+}
+a {
+  color: rgb(232, 146, 45);
+  text-decoration: none;
+}
+a:hover { color: #008CBA; }
+p, li {
+  line-height: 23px;
+}
+h2 {
+  font-weight: 300;
+}
+#lights { padding: 0; margin-top: -20px; }
+#lights li {
+  display: flex;
+  flex-direction: row;
+  margin: 0;
+  padding: 20px 0;
+  border-bottom: solid 1px lightgray;
+}
+.color {
+  min-width: 25vw;
+  height: 25vw;
+  margin-right: 5vw;
+}
+.right-col {
+  width: 100%;
+  max-width: calc(100% - 30vw);
+}
+.endpoint {
+  margin: 0;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  font-weight: 400;
+  font-size: 20px;
+  padding-bottom: 2px;
+}
+.timeout {
+  margin: 8px 0;
+}
+.status {
+  display: flex;
+  flex-direction: row;
+  border: solid 1px gray;
+  border-radius: 4px;
+}
+.status div {
+  padding: 6px;
+  flex-grow: 1;
+  width: 33%;
+  text-align: center;
+  border-right: solid 1px gray;
+}
+.status div.selected {
+  background: lightgray;
+}
+.status div:last-child {
+  border-right: none;
+}
+#color-picker-overlay {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 100%;
+  background: rgba(0, 0, 0, 0.7);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-direction: column;
+}
+#color-picker-overlay h2 {
+  color: white;
+}
+#notification {
+  position: fixed;
+  bottom: 20px;
+  background: rgba(0,0,0,0.6);
+  color: white;
+  border-radius: 10px;
+  display: inline-block;
+  padding: 10px 20px;
+  cursor: pointer;
+  transition: all 0.6s ease-out;
+  left: 50%;
+  transform: translateX(-50%);
+}
diff -r 000000000000 -r 7a352727249b webapp/views/index.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/views/index.html	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Light System</title>
+  <link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300,600' rel='stylesheet' type='text/css'>
+  <link rel="stylesheet" href="/style.css">
+  <link rel="import" href="/color-picker-element/dist/color-picker.html">
+  <link rel="manifest" href="/manifest.json">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+</head>
+
+<body>
+  <div id="header">
+    <a href="/"><img id="logo" src="/ARMmbedLogo.svg"></a>
+  </div>
+
+  <h1>Lights</h1>
+
+  <ul id="lights">
+  {{#devices}}
+    {{> device}}
+  {{/devices}}
+  </ul>
+
+  <div id="color-picker-overlay" style="display: none">
+    <h2>Change color</h2>
+  </div>
+  <div id="notification" style="opacity: 0; visibility: hidden;"></div>
+
+  <script src="/helper-functions.js"></script>
+  <script src="/color-picker.js"></script>
+  <script src="/socket.io/socket.io.js"></script>
+  <script>
+    /*global io getElement updateStatusUi getDeviceName showNotification localStorage */
+
+    // Here is how we connect back to the server
+    var socket = io.connect(location.origin);
+
+    // ==== BEGIN SERVER EVENTS ====
+
+    var createDeviceEv, deleteDeviceEv, changeStatusEv, changeColorEv, changeTimeoutEv;
+
+    // Device came online, add it to the UI
+    socket.on('created-device', createDeviceEv = function(viewModel) {
+      document.querySelector('#lights').insertAdjacentHTML('beforeend', viewModel.html);
+    });
+
+    // Device was deleted, remove it from the UI
+    socket.on('deleted-device', deleteDeviceEv = function(endpoint) {
+      var li = getElement(endpoint);
+      li.parentNode.removeChild(li);
+
+      showNotification(getDeviceName(endpoint, true) + ' was de-registered');
+    });
+
+    // Status of a device was updated
+    socket.on('change-status', changeStatusEv = function(endpoint, status) {
+      updateStatusUi(endpoint, status);
+    });
+
+    // Color of a device was updated
+    socket.on('change-color', changeColorEv = function(endpoint, color) {
+      var hex = Number(color).toString(16);
+      getElement(endpoint).querySelector('.color').style.backgroundColor =
+        '#' + '000000'.substring(0, 6 - hex.length) + hex;
+    });
+
+    // Timeout of a device was updated
+    socket.on('change-timeout', changeTimeoutEv = function(endpoint, timeout) {
+      getElement(endpoint).querySelector('.change-timeout').textContent =
+        timeout + ' seconds';
+    });
+
+    // When you go offline=>online, server will automatically send current state
+    // So you're always up to date with other clients.
+    socket.on('device-list', function(list) {
+      list.forEach(function(device) {
+        // element not in the UI yet? Create it!
+        if (!getElement(device.endpoint)) {
+          return createDeviceEv(device.view);
+        }
+
+        // otherwise, update the properties
+        changeStatusEv(device.endpoint, device.view.model.status);
+        changeColorEv(device.endpoint, device.view.model.rawColor);
+        changeTimeoutEv(device.endpoint, device.view.model.timeout);
+      });
+
+      // find devices that are still in the UI, but no longer in the list...
+      var validEndpoints = list.map(function(device) { return device.endpoint; });
+      [].forEach.call(document.querySelectorAll('li[data-endpoint]'), function(li) {
+        if (validEndpoints.indexOf(li.dataset.endpoint) === -1) {
+          deleteDeviceEv(li.dataset.endpoint);
+        }
+      });
+
+    });
+
+    // ==== END OF SERVER EVENTS ====
+
+
+
+    // ==== BEGIN UI EVENTS ====
+
+    // When someone clicks on an element in the device area...
+    document.querySelector('#lights').addEventListener('click', function(e) {
+
+      // We look at which endpoint we're clicking on...
+      var parent = e.target;
+      while (e.target.parentElement && !((parent = parent.parentElement).dataset.endpoint)) {
+        // noop
+      }
+      if (!parent.dataset.endpoint) return console.error('Could not find parent for', e.target);
+
+      var endpoint = parent.dataset.endpoint;
+
+      // Now we can check which element we clicked on:
+      if (e.target.classList.contains('change-timeout')) {
+        changeTimeout(endpoint, e.target);
+        return false;
+      }
+
+      if (e.target.classList.contains('change-status')) {
+        changeStatus(endpoint, e.target);
+        return false;
+      }
+
+      // Names are stored locally...
+      if (e.target.classList.contains('change-endpoint-name')) {
+        var n = prompt('Enter new name');
+        if (n) {
+          e.target.textContent = n;
+          localStorage.setItem(endpoint + '-name', n);
+        }
+        else if (n === '') {
+          e.target.textContent = endpoint;
+          localStorage.setItem(endpoint + '-name', '');
+        }
+        return false;
+      }
+
+      // Click on color => show color picker
+      if (e.target.classList.contains('color')) {
+        var cp = document.querySelector('#color-picker-overlay');
+        cp.dataset.endpoint = endpoint;
+        cp.style.display = 'flex';
+        return false;
+      }
+    });
+
+    // ==== END UI EVENTS ====
+
+
+
+    // ==== BEGIN UI ACTIONS ====
+
+    function changeTimeout(endpoint, el) {
+      var old = el.textContent.split(' ')[0];
+
+      // Ask for a new value and verify that it's a number
+      var v = prompt('Enter a new value for the motion timeout');
+      if (v === false) return;
+      if (!v || isNaN(v)) {
+        return showNotification('Updating timeout failed: not a number');
+      }
+
+      // Update the UI
+      el.textContent = v + ' seconds';
+      // Send to the server
+      socket.emit('change-timeout', endpoint, Number(v), function(err) {
+        // If it fails, reset to old value and show notification
+        if (err) {
+          el.textContent = old + ' seconds';
+          showNotification('Updating timeout failed: ' + err);
+        }
+      });
+    }
+
+    function changeStatus(endpoint, el) {
+      // get the currently selected value
+      var old = el.parentNode.querySelector('.selected').dataset.action;
+
+      // update the UI
+      updateStatusUi(endpoint, el.dataset.action);
+
+      // send to server
+      socket.emit('change-status', endpoint, Number(el.dataset.action), function(err) {
+        // if it fails, reset to old value and show notification
+        if (err) {
+          updateStatusUi(endpoint, old);
+          showNotification('Updating status failed: ' + err);
+        }
+      });
+    }
+
+    function changeColor(endpoint, newColor) {
+      var colorEl = getElement(endpoint).querySelector('.color');
+      var oldHex = Number(colorEl.dataset.value).toString(16);
+      var newHex = Number(newColor).toString(16);
+
+      if (newColor === 0) return;
+
+      console.log('new value is', newColor);
+
+      // Update UI
+      colorEl.style.backgroundColor = '#' + '000000'.substring(0, 6 - newHex.length) + newHex;
+
+      // Send to server
+      socket.emit('change-color', endpoint, newColor, function(err) {
+        // If all fails, reset back to old color
+        if (err) {
+          colorEl.style.backgroundColor = '#' + '000000'.substring(0, 6 - oldHex.length) + oldHex;
+          showNotification('Updating color failed: ' + err);
+        }
+      });
+    }
+
+    // ==== END UI ACTIONS ====
+
+  </script>
+
+</body>
+</html>
diff -r 000000000000 -r 7a352727249b webapp/views/partials/device.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/webapp/views/partials/device.html	Sun Jun 18 10:14:56 2017 +0000
@@ -0,0 +1,12 @@
+<li data-endpoint="{{name}}">
+  <div class="color" data-value="{{rawColor}}" style="background-color: {{color}}"></div>
+  <div class="right-col">
+    <p class="endpoint change-endpoint-name">{{name}}</p>
+    <p class="timeout">Motion timeout: <a href="#" class="change-timeout">{{timeout}} seconds</a></p>
+    <div class="status">
+      <div class="change-status {{motionClass}}" data-action="0">Motion</div>
+      <div class="change-status {{onClass}}" data-action="1">On</div>
+      <div class="change-status {{offClass}}" data-action="2">Off</div>
+    </div>
+  </div>
+</li>
\ No newline at end of file