Category Archives: Tech

Multipart HTTP Post requests with Volley on Android

It’s been a little while since I’ve done any really serious Android development, but a couple of projects have brought me back to it.

Early on in one of those projects I had to make some HTTP requests, my first thought was to make use of the Apache HTTP Client classes as I had done many times before on Android. Which is why I was a little surprised when the usual ctrl-space didn’t auto complete any of the expected class names.

It turns out the classes were removed in Android 6.0 and the notice suggests using the HttpURLConnection class. A little bit more digging turned up a wrapper for this called Volley.

Volley is a wrapper round the HttpURLConnection class to provides a neat asynchronous interface that does IO in the background and then delivers results to the Main thread so UI updates can be done with out further faffing around switching threads. There is also a nice set of tutorials on the Android Developers pages.

The first few requests all worked fine, but there was one which was a little bit more tricky. The HTTP endpoint in question accepts a multipart-form payload. A bit of googling/searching on Stackoverflow turned up a number of approaches to this and best seamed to be documented in this gist

This was close to what I wanted but not quite what I needed so I have taken some of the core concepts and built my own MultipathRequest object.

...
MultipartRequest request = new MultipartRequest(url, headers, 
    new Response.Listener<NetworkResponse>() {
        @Override
        public void onResponse(NetworkResponse response) {
        ...
        }
    },
    new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
        ...
        }
    });
    
request.addPart(new FormPart(fieldName,value));
request.addPart(new FilePart(fileFieldName, mimeType, fileName, data);

requestQueue.add(request);
...

I’ve stuck the code up on github here. You can include it in your Android Project by adding the following to the build.gradle in the root of the project:

allprojects {
  repositories {
    ...
    maven { url 'https://jitpack.io' }
  }
}

And then this to the dependencies section of the modules build.gradle:

dependencies {
  compile 'com.github.hardillb:MultiPartVolley:0.0.3'
}

Fist pass TRÅDFRI MQTT Bridge

I’ve been working on integrating the new IKEA TRÅDFRI Lights into my Home Automation system. I’d really like a native NodeJS system so I can plug it directly into Node-RED, but I’ve not found a working CoAP over DTLS setup just yet.

So in the mean time I’ve been working on a very basic MQTT to CoAP client bridge in Java using the Eclipse Californium library.

It still needs some work, but here is the first pass:

package uk.me.hardill.coap2mqtt;

import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.logging.Level;

import org.eclipse.californium.core.CaliforniumLogger;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.scandium.ScandiumLogger;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.pskstore.StaticPskStore;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 * @author hardillb
 *
 */
public class Main {
  
  static {
    CaliforniumLogger.disableLogging();
    ScandiumLogger.disable();
//    ScandiumLogger.initialize();
//    ScandiumLogger.setLevel(Level.FINE);
  }
  
  private DTLSConnector dtlsConnector;
  private MqttClient mqttClient;
  private CoapEndpoint endPoint;
  
  private String ip;
  
  private HashMap<String, Integer> name2id = new HashMap<>();
  
  Main(String psk, String ip) {
    this.ip = ip;
    DtlsConnectorConfig.Builder builder = new DtlsConnectorConfig.Builder(new InetSocketAddress(0));
    builder.setPskStore(new StaticPskStore("", psk.getBytes()));
    dtlsConnector = new DTLSConnector(builder.build());
    endPoint = new CoapEndpoint(dtlsConnector, NetworkConfig.getStandard());
    
    MemoryPersistence persistence = new MemoryPersistence();
    try {
      mqttClient = new MqttClient("tcp://localhost", MqttClient.generateClientId(), persistence);
      mqttClient.connect();
      mqttClient.setCallback(new MqttCallback() {
        
        @Override
        public void messageArrived(String topic, MqttMessage message) throws Exception {
          // TODO Auto-generated method stub
          System.out.println(topic + " " + message.toString());
          String parts[] = topic.split("/");
          int id = name2id.get(parts[1]);
          System.out.println(id);
          String command = parts[3];
          System.out.println(command);
          try{
            JSONObject json = new JSONObject("{\"9001\":\"Living Room Light\",\"9020\":1491515804,\"9002\":1491158817,\"9003\":65537,\"9054\":0,\"5750\":2,\"3\":{\"0\":\"IKEA of Sweden\",\"1\":\"TRADFRI bulb E27 opal 1000lm\",\"2\":\"\",\"3\":\"1.1.1.0-5.7.2.0\",\"6\":1},\"9019\":1,\"3311\":[{\"5850\":1,\"5851\":10,\"9003\":0}]}");
            JSONObject settings = json.getJSONArray("3311").getJSONObject(0);
            if (command.equals("dim")) {
              settings.put("5851", Integer.parseInt(message.toString()));
            } else if (command.equals("on")) {
              if (message.toString().equals("0")) {
                settings.put("5850", 0);
                settings.put("5851", 0);
              } else {
                settings.put("5850", 1);
                settings.put("5851", 128);
              }
            }
            String payload = json.toString();
            Main.this.set("coaps://" + ip + "//15001/" + id, payload);
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
        
        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
          // TODO Auto-generated method stub
        }
        
        @Override
        public void connectionLost(Throwable cause) {
          // TODO Auto-generated method stub
        }
      });
      mqttClient.subscribe("TRÅDFRI/+/control/+");
    } catch (MqttException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  private void discover() {
    try {
      URI uri = new URI("coaps://" + ip + "//15001");
      CoapClient client = new CoapClient(uri);
      client.setEndpoint(endPoint);
      CoapResponse response = client.get();
      JSONArray array = new JSONArray(response.getResponseText());
      for (int i=0; i<array.length(); i++) {
        String devUri = "coaps://"+ ip + "//15001/" + array.getInt(i);
        this.watch(devUri);
      }
      client.shutdown();
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  private void set(String uriString, String payload) {
    System.out.println("payload\n" + payload);
    try {
      URI uri = new URI(uriString);
      CoapClient client = new CoapClient(uri);
      client.setEndpoint(endPoint);
      CoapResponse response = client.put(payload, MediaTypeRegistry.TEXT_PLAIN);
      if (response.isSuccess()) {
        System.out.println("Yay");
      } else {
        System.out.println("Boo");
      }
      
      client.shutdown();
      
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
  
  private void watch(String uriString) {
    
    try {
      URI uri = new URI(uriString);
      
      CoapClient client = new CoapClient(uri);
      client.setEndpoint(endPoint);
      CoapHandler handler = new CoapHandler() {
        
        @Override
        public void onLoad(CoapResponse response) {
          System.out.println(response.getResponseText());
          JSONObject json = new JSONObject(response.getResponseText());
          if (json.has("3311")){
            MqttMessage message = new MqttMessage();
            int state = json.getJSONArray("3311").getJSONObject(0).getInt("5850");
            message.setPayload(Integer.toString(state).getBytes());
            message.setRetained(true);
            String topic = "TRÅDFRI/" + json.getString("9001") + "/state/on";
            String topic2 = "TRÅDFRI/" + json.getString("9001") + "/state/dim";
            name2id.put(json.getString("9001"), json.getInt("9003"));
            MqttMessage message2 = new MqttMessage();
            int dim = json.getJSONArray("3311").getJSONObject(0).getInt("5851");
            message2.setPayload(Integer.toString(dim).getBytes());
            message2.setRetained(true);
            try {
              mqttClient.publish(topic, message);
              mqttClient.publish(topic2, message2);
            } catch (MqttException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
          } else {
            System.out.println("not bulb");
          }
        }
        
        @Override
        public void onError() {
          // TODO Auto-generated method stub
          
        }
      };
      client.observe(handler);
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    
  }

  /**
   * @param args
   */
  public static void main(String[] args) throws InterruptedException {
    String psk = args[0];
    String ip = args[1];
    Main m = new Main(psk, ip);
    
    m.discover();
  }

}

I’ve tagged the code onto the gist as well for now, but I’ll check the whole thing in as a separate project soon.

EDIT: now with it’s own Github repo here

More improvements to the WeMo Node-RED Nodes

Having got all my bulbs working again with my WeMo Link device it was time to have another play with the Node-RED WeMo nodes.

  • First up close the gap between what the doc claims the nodes do and what they actually do. This is mainly the WeMo Out node which claimed to support an input object that could set both the state and the brightness at the same time for light bulbs and groups. In actuality it only set the state. This update fixes this along with supporting setting colour and colour temperature as well if the bulb supports those capabilities.
  • The WeMo in node (event node) now includes the capability name as well as it’s code when a bulb or group changes.
  • Fixed Light groups to actually work
  • Automatically set the node name to the discovered device name to stop you having to set it via the name field in the config

The biggest change is the addition on the new WeMo Lookup node. This node queries a given device for it’s current state.

For Sockets the node sets the msg.payload to something very similar to the event node’s staus field, so 0 for off and 1 for on (and 8 for on/standby in the case of the insight socket.). For lights/light groups it outputs an object similar to the Event node, with keys for each capability the light/group has. At the moment the color field is still in X,Y values not RGB.

This makes it possible to implement flows that carry out relative changes without having to keep a permanent record of the state of the device in the context. This let’s you do fun things like this:

Powermate dimmer with Node-RED

This flow looks up the current brightness level and then increases or decreases it based on the direction the Powermate is turned and toggles the light on/off when it’s pressed.

Version 0.1.11 should have gone live on npmjs.org today.

TRÅDFRI – Ikea’s new Smart lighting system

This week Ikea announced a new set of Smart Lighting products called TRÅDFRI (translates to ‘Wire Free’).

Reading the various articles it sounds like it’s a Zigbee Light Link based system so I was interested in having a play to see if I could make the bulbs work with the WeMo kit I already have, but also given how cheap the gateway is I thought I’d grab one of those as well to see if I could work out what the network protocol is so I can write a similar Node-RED node to the existing WeMo one.

A (remarkably) short trip to the local branch and I came a way with 2 items:

A E27 Blub that can be dimmed and a remote dimmer £15

E27 LED Bulb + Dimmer
E27 LED Bulb + Dimmer

The dimmer looks very cool. It looks like it’s accelerometer based, comes with a little magnetic mount that can be stuck or screwed to the wall or it can be just stuck to the front of the fridge. If you turn it slowly it dims/brightens or if you turn it faster it turns the light on and off. You can pair each remote with up to 10 bulbs, so it can control a whole room (all be it all at once).

Bulbs on their own start at £9 and they are doing E14, GU10 formats as well as several versions of E27.

A Network Gateway that plugs into the router £25

TRÅDFRI Ethernet bridge
TRÅDFRI Ethernet bridge

The bridge is USB powered and comes with little USB power supply and a length of ethernet cable to connect it to your router.

I’m going to set the bridge up on a separate network and capture the traffic between the Android App and the bridge to see what the network traffic looks like. I’ll post again with the captured data and my progress as well as sticking it all up on github so others can build libraries for other systems, like OpenHab.

EDIT:
I’m keeping the notes on how I’m getting on with working out how to drive the gateway here

node-red-contrib-alexa-home-skill

It’s finally ready. I’ve been working on a Node-RED node to act on Amazon Alexa Home Skill directives since November last year. The skill was approved some time very early this morning and now should be available in the UK, US and Germany.

I’ll be mailing all the folks that have already signed up some time later today to let them know they can finally start using the skill, but for the rest of you here is a brief introduction (full details in earlier post).

Alexa Home Skill’s allow you to say the much more natural “Alexa, turn on the kitchen light” rather than “Alexa, ask Jeeves to turn on the kitchen light”, where “Jeeves” is the name of skill you have to remember. Some of the basic commands are:

  • Turn On/Off
  • Dim/Brighten
  • Set/Get Temperature
  • Lock/Unlock

With this node and service you can wire those commands to nearly anything you can control via Node-RED.

Node-RED - Alexa Smart Home Skill

You can install the node with the following commands:

cd ~/.node-red
npm install node-red-contrib-alexa-home-skill

Or via the Manage Palette option in the Node-RED editor.

If you have already installed this module please make sure you update to the latest version (0.1.13) to get the best support for all the voice commands.

There are detailed instructions on how to set everything up here.

Here is an example flow using the node. This turns a light on then automatically turns it off after 5mins. It uses the switch node to detect if it’s a request to turn the light on or off. When following the On branch it uses a trigger node to first send a payload of true then, 5 minutes later it sends false to the WeMo node.

On then Auto Off flow

This sort of flow would be great for a set of outside lights or maybe an electric heater. I also have some updates to the node-red-nodes-wemo package to make dimming/brightening by specific amounts easier, I’ll try and get them out by the weekend.

Making the WeMo Link work with 3rd party bulbs

innr bulb
I’ve been playing with my WeMo Link device again. A couple of weekends ago I made a mistake, I used the Belkin WeMo Android app to turn a light on (rather than ask Alexa to turn it on via Node-RED). While doing this the app suggested I update the firmware on the Link device. I decided to let it do the update, this led to whole host of issues with the device not wanting to connect to the WiFi.

In the end I had to reset the device, and re-pair the bulbs. The re-pairing worked, but the Osram bulbs didn’t show up in the WeMo android app any more. It turns out the app no longer show bulbs which haven’t been “WeMo Certified”. They still show up when you query the API directly and I can control them via my Node-RED nodes but I couldn’t add the bulbs to groups. The WeMo Link supports the ZigBee Light Link protocol just like Osram Lightify, Philips Hue and the Innr lights so there is no reason why all of these things shouldn’t be able to play nicely with each other.

This sent me back to reverse engineering the SOAP API to interact with the WeMo Link.

I’ve had a pretty good go at working out the protocol already, resulting in the wemo-control.js script and the Node-RED WeMo nodes, but this is just basic discovery and control not really “admin” tasks. I decided to break this work out into it’s own script.

There are 2 main tasks that this script will have to do, add new bulbs and create groups.

Adding bulbs

This was actually pretty easy, the API end points were pretty obvious in the list.

  • OpenNetwork – allows bulbs to join the mesh
  • GetEndDevices – when used with the UNPAIRED_LIST filter it shows just the new bulbs.
  • IdentifyDevice – makes a bulb flash so you can work out which bulb is which if you discover new bulbs
  • AddDevice – adds a new bulb to the mesh
  • CloseNetwork – stops bulbs joining the mesh

None of these calls take any complex arguments and all are available either via discovery responses or other simple calls. If you chain them together in the order above you end up with your new bulbs available (at least to my scripts even if not in the WeMo app).

Creating Groups

This one was a little harder, while the API list a CreateGroup endpoint, it says that it takes as a single argument if type ReqCreateGroup which is listed to be a string. Now from experience I can guess that this string is actually a URL encoded XML fragment. There are no hints as to what this XML fragment might look like. This led to a slight diversion down a rabbit hole to set up a raspberry pi as a WiFi AccessPoint bridged on to my local network so I could run tcpdump to make sure I captured all sides of the conversation between my tablet and the WeMo Link. A little bit of formatting and collating in Wireshark and we hit pay dirt:

<?xml version="1.0" encoding="UTF-8"?>
<CreateGroup>
    <GroupID>1489757700</GroupID>
    <GroupName>Lighting Group</GroupName>
    <DeviceIDList>94103EA2B278030F,94103EA2B27803ED</DeviceIDList>
    <GroupCapabilityIDs>10008,10006,30008,30009,3000A</GroupCapabilityIDs>
    <GroupCapabilityValues>255:0,0,,,</GroupCapabilityValues>
</CreateGroup>

The full capture can be found here.

It all looks pretty self explanatory,

  • A unique id for the group (looks like it’s epoch time in seconds)
  • Name of the group (probably should be XML escaped, but we’ll keep them simple)
  • The list of device IDs to include in the group
  • The subset of capabilities that all the devices in the group support
  • Some starting values for those capabilities

Now I had the format of the messages I need to send it’s time to actually write some code. The first pass is up here, it’s a little rough and ready but I’ll try and clean it up a bit later and add a command to rename devices, but it’s Friday afternoon and I’m typing this up in a bar…

To add a bulb you would follow this flow of commands:

node bridge.js open
...
node bridge.js unpaired
...
<?xml version="1.0" encoding="utf-8"?><DeviceLists><DeviceList><DeviceListType>Unpaired</DeviceListType><DeviceInfos><DeviceInfo><DeviceIndex>5</DeviceIndex><DeviceID>00158D0001696252</DeviceID><FriendlyName>OnOff Light</FriendlyName><IconVersion>1</IconVersion><FirmwareVersion>01</FirmwareVersion><CapabilityIDs>10006,10008,30008,30009,3000A</CapabilityIDs><CurrentState>,,,,</CurrentState><Manufacturer>innr</Manufacturer><ModelCode>BY 165</ModelCode><productName>lighting</productName><WeMoCertified>NO</WeMoCertified></DeviceInfo></DeviceInfos></DeviceList></DeviceLists>
node bridge.js indentify <device id>
...
node bridge.js add <device id>
...
node bridge.js close
...

To create a new group:

node bridge.js createGroup "Group Name" "00158D0001696252,00158D0001696253" "10006,10008,30008,30009,3000A", "0,255:0,,,"

Updated Pi Zero Gadgets

Following on from my last post I’ve continued to work on improving my instructions for a USB connectable gadget based on a Raspberry Pi Zero.

Firstly I’ve got a slight improvement to the dnsmasq config I mentioned last time. This removes the dnsmasq.leases file which can cause issues if you plug the Zero into multiple computers. This can be a problem because while I had managed to fix the mac address for Host computer end of the connection the OS still pushes down the host name and it’s own unique id when it requests a DHCP address from dnsmasq, this causes dnsmasq to cycle through it’s small pool of addresses. This combined with the fact the clock on Zero is not battery backed up so only gets set correctly when it can reach internet can cause strangeness with addresses getting expired in strange ways. Anyway there is a pretty simple fix.

Adding leasefile-ro to the dnsmasq config causes it to not write lease information to disk, but rely on the dhcp-script to keep track of things. To do this I needed to add handling for a new option to the script to deal with dnsmasq wanting to read the current lease state at startup.

#!/bin/bash
op="${1:-op}"
mac="${2:-mac}"
ip="${3:-ip}"
host="${4}"

if [[ $op == "init" ]]; then
  exit 0
fi

if [[ $op == "add" ]] || [[ $op == "old" ]]; then
  route add default gw $ip usb0
fi

Now on to getting things working better with Windows machines.

To do this properly we need to move from the g_ether to the g_multi kernel module, this lets the Zero be a USB Mass Storage device, a network device (and a serial device) at the same time. This is useful because it lets me bundle .inf files that tell Windows which drivers to use on the device it’s self so it they can be installed just by plugging it in.

The first order of business is to fix the cmdline.txt to load the right module, after making the changes in the last post it looks like this:

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether

The g_ether needs replacing with g_multi so it looks like this:

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_multi

next we need to fix the options passed to the module, these are in the /etc/modprobe.d directory probably in a file called g_ether.conf. We should don’t need to, but to make it obvious when we come back to this a lot later on we’ll rename it to g_multi.conf. Now we’ve renamed it we need to add a few more options.

It currently looks like this:

options g_ether host_addr=00:dc:c8:f7:75:15 dev_addr=00:dd:dc:eb:6d:a1

It needs the g_ether needs changing to e_multi and some options adding to point to a disk image.

options g_multi file=/opt/disk.iso cdrom=y ro=y host_addr=00:dc:c8:f7:75:15 dev_addr=00:dd:dc:eb:6d:a1

Now we have the config all sorted we just need to build the disk image. First we need to create a directory called disk-image to use as the root of the file system we will share from the Zero. Then we need to get hold of the 2 .inf files, they ship with the Linux Kernel doc, but can be found online (serial port, RNDIS Ethernet).

Once we have the files place them in a directory called disk-image/drivers. We should also create a README.txt to explain what’s going on, put that in the root of disk-image. Along side that we want to create a file called Autorun.inf, this tell Windows about what’s on the “cd” when it’s inserted and where it should search for the driver definitions.

[AutoRun]
open="documentation\index.html"
icon=clock.ico,0

[DeviceInstall]
DriverPath=drivers

[Content]
MusicFiles=no
PictureFiles=no
VideoFiles=no

Full details of what can go in the Autorun.inf file can be found here, but the interesting bits are the DriverPath=drivers which points to the directory that we put the .inf files in earlier. Also the open=”documentation\index.html” which should open documentation/index.html when the device is plugged in which explains how to install the drivers. I also added an icon file so the drive shows up looking like a clock in the file manager.

That should be the bare minimum that needs to be on the image, but I ran into an issue with the g_multi module complaining the disk image being too small, to get round this I just stuck a 4mb image in the directory as well. To build the iso image run the following command:

mkisofs -o disk.iso -r -J -V "Zero Clock" disk-image

This will output a file called disk.iso that you should copy to /opt/disk.iso on the Zero (I built the image on my laptop as it’s easier to edit files and mkisofs is not installed in the default raspbian image).

This is working well on Linux and Windows, but I’m still getting errors on OSx to do with the file system. It’s not helped by the fact I don’t have a Mac to test on so I end up having to find friends that will let me stick a random but of hardware in to the side of their MacBook.

Once I’ve got the OSx bits sorted out I’ll put together script to build images for anything you want.

So now we have a Pi Zero that should look like a CD-ROM drive and a Network adapter when plugged into pretty much any machine going, it brings the driver information with it for windows, sets up a network connection with a static IP address and a Avahi/Bonjour/mDNS address to access it. I’m planning on using this to set up my Linear Clock on the local WiFi but there are all manner of interesting things that could be done with a device like this. e.g. an offline Certificate Authority, a 2FA token, a Hardware VPN solution or a Web Controllable display device.

brr

Auto Launch Webpages full screen on Android

While waiting for Amazon to get round to reviewing my Node-RED Alexa Smart Home Skill I’ve needed something to hack on.

I’ve been keeping an eye on what folk have been doing with Node-RED Dashboard and a common use case keeps coming up. That is running the UI on a Android tablet mounted on a wall as a way to keep track of and control things around the house. This got me thinking about how to set something like this up.

Icon

For the best results you really want to run this totally full screen, there are ways to get this to happen with Chrome, but it’s a bit convoluted. Sure you can add it as a shortcut on the home screen but I thought there had to be a easier/better way.

So I started to have a bit of a play and came up with a new app. It’s basically just a full screen Activity with a WebView, with a few of extra features.

Settings
Settings
  • Set URL – Pick a URL to load when the phone boots, or the app is launched.
  • Launch on boot – The app can be configured to start up as soon as the phone finishes booting.
  • Take Screen Lock – This prevents the screen from powering off or locking so the page is always visible.

You can change the URL by swiping from the left hand edge of the screen, this will cause the action bar to appear, from this you can select “Settings”.

Set URL to Load
Set URL to Load

The app is in the Google Play store here, the code is on Github here

Google Home

Having got hold of a Amazon Echo Dot just over a week ago, I was eager to get my hands on a Google Home to compare. Luckily a I had a colleague in the US on release day who very kindly brought me one back.

Google Home

It looks like the developer program will not be opening up until December, which is just as well as I need to get the my Alexa project finished first.

My initial impression from a couple of hours of playing is it’s very similar to the Echo, but knows a lot more about me out of the box (due Google already knowing all my secrets), I really do like the Chromecast integration for things like Youtube. I need to try the Echo with my FireTV stick to see if it can do anything similar. It’s still early days for the Google Home and it has only been released in the US so it wouldn’t be totally fair compare them too closely just yet.

I’ll keep this brief for now until I can get into the developer tools. It’s going to be fun time working out just what I can get these two devices to do.

Alexa Home Skill for Node-RED

Following on from my last post this time I’m looking at how to implement Alexa Home Skills for use with Node-RED.

Home Skills provide ON/OFF, Temperature, Percentage control for devices which should map to most home automation tasks.

To implement a Home Skill there are several parts that need to created.

Skill Endpoint

Unlike normal skills which can be implemented as either HTTP or Lambda endpoints, Home Skills can only be implemented as a Lambda function. The Lambda function can be written in one of three languages, Javascript, Python and Java. I’ve chosen to implement mine in Javascript.

For Home Skills the request is passed in a JSON object and can be one of three types of message:

  • Discovery
  • Control
  • System

Discovery

These messages are triggered when you say “Alexa, discover devices”. The reply this message is when the skill has the chance to tell the Echo what devices are available to control and what sort of actions they support. Each device section includes it’s name and a description to be shown in the Alexa phone/tablet application.

The full list of supported actions:

  • SetTargetTemperature
  • IncrementTargetTemperature
  • DecrementTargetTemperature
  • SetPercentage
  • IncrementPercentage
  • DecrementPercentage
  • TurnOff
  • TurnOn
  • GetTemperatureReading 1
  • GetTargetTemperature 1
  • GetLockState 1
  • SetLockState 1

1 These actions are listed as only available in the US at the moment.

Control

These are the actual control messages, triggered by something like “Alexa, set the bedroom lights to 20%”. It contains one of the actions listed earlier and the on/off or value of the change.

System

This is the Echo system checking that the skill is all healthy.

Linking Accounts

In order for the skill to know who’s echo is connecting we have to arrange a way to link an Echo to an account in the Skill. To do this we have to implement a oAuth 2.0 system. There is a nice tutorial on using passport to provide oAuth 2.0 services here, I used this to add the required HTTP endpoints needed.

Since there is a need to set up oAuth and to create accounts in order to authorise the oAuth requests this means that is makes sense to only do this once and to run it as a shared service for everybody (just got to work out where to host it and how to pay for it).

A Link to the Device

For this the device is actually Node-RED which is probably going to be running on people’s home network. This means something that can connect out to the Skill is probably best to allow or traversing NAT routers. This sounds like a good usecase for MQTT (come on, you knew it was coming). Rather than just use the built in MQTT nodes we have a custom set of nodes that make use of some of the earlier sections.

Firstly a config node that uses the same authentication details as account linking system to create oAuth token to be used to publish device details to the database and to authenticate with the MQTT broker.

Secondly a input node that pairs with the config node. In the input node settings a device is defined and the actions it can handle are listed.

Hooking it all together

At this point the end to end flow looks something like this:

Alexa -> Lambda -> HTTP to Web app -> MQTT to broker -> MQTT to Node-RED

At this point I’ve left the HTTP app in the middle, but I’m looking at adding direct database access to the Lambda function so it can publish control messages directly via MQTT.

Enough talk, how do I get hold of it!

I’m beta testing it with a small group at the moment, but I’ll be opening it up to everybody in a few days. In the mean time the code is all on github, the web side of all of this can be found here, the Lambda function is here and the Node-RED node is here, I’ll put it on npm as soon as the skill is public.

EDIT

The skill should now be generally available in the UK,US and Germany