Category Archives: Tech

DNS-Over-HTTPS

I saw the recent announcements from Mozilla, Cloudflare and Google about running a trial to try and make DNS name resolution more secure.

The basic problem is that most users get their DNS server set via DHCP which is controlled by who ever runs the network (at home this tends to be their ISP, but when using public wifi this could be anybody). The first approach to help with this was Google’s 8.8.8.8 public DNS service (followed by the IBM’s 9.9.9.9 and Cloudflares 1.1.1.1). This helps if people are technically literate enough know how to change their OS’s DNS settings and fix them to one of these providers. Also DNS is UDP based protocol which makes it particularly easy for a bad actor on the network to spoof responses.

The approach the 3 companies are taking is to run DNS over an existing secure protocol, in this case HTTPS. From Firefox version 60 (currently in beta) it is possible to set it up to do name host name resolution via DNS-Over-HTTPS.

There are currently 2 competing specifications for how to actually implement DNS-Over-HTTPS.

DNS Wireformat

This uses exactly the same data structure as existing DNS. Requests can be made via a HTTP GET or POST. For a POST the body is the binary request and the Content-Type is set to application/dns-udpwireformat.

For GET requests the payload is BASE64 encoded and passed as the dns query parameter.

In both cases the response is the same binary payload as would be made by a normal DNS server.

This approach is currently covered by this draft RFC

JSON

For this approach the request are made as a HTTP GET request with the hostname (or IP address) being passed as the name and the query type being passed as the type query parameters.

A response looks like this:

{
    "Status": 0,
    "RA": true,
    "RD": true,
    "TC": false,
    "AD": false,
    "CD": true,
    "Additional": [],
    "Answer": [
        {
            "TTL": 86400,
            "data": "93.184.216.34",
            "name": "example.com",
            "type": 1
        }
    ],
    "Question": [
        {
            "name": "example.com",
            "type": 1
        }
    ]
}

With a Content-Type of application/dns-json

You can find the spec for this scheme from Google here and Cloudflare here.

Both of these schemes have been implemented by both Google and Cloudflare and either can be used with Firefox 60+.

Privacy Fears

There has already been a bit of a backlash against this idea, mainly around privacy fears. The idea of Google/CloudFlare being able to collect information about all the hosts your browser resolves scared some people. Mozilla has an agreement in place with CloudFlare about data retention for the initial trial.

Given these fears I wondered if people might still want to play with DNS-Over-HTTPS but not want to share data with Google/Cloudflare. With this in mind I thought I’d try and see how easy it would be to implement a DNS-Over-HTTPS server. Also people may want to try this out on closed networks (for things like performance testing or security testing).

It turned out not to be too difficult, I started with a simple ExpressJS based HTTP server and then started to add DNS support. Initially I tried a couple of different DNS NodeJS nodes to get all the require details and in the end settled on dns-packet and actually sending my own UDP packets to the DNS server.

I’ve put my code up on github here if anybody wants a play. The README.md should include details about how to set up Firefox to use an instance.

Logging request & response body and headers with nginx

I’ve been working a problem to do with oAuth token refresh with the Amazon Alexa team recently and one of the things they have asked for is a log of the entire token exchange stage.

Normally I’d do this with something like Wireshark but as the server is running on a Amazon EC2 instance I didn’t have easy access to somewhere to tap the network so I decided to look for another way.

The actual oAuth code is all in NodeJS + Express but the whole thing is fronted by nginx. You can get nginx to log the incoming request body relatively simply, there is a $request_body variable that can be included in the logs, but there is no equivalent $resp_body.

To solve this I turned to Google and it turned up this answer on Server Fault which introduced me to the embedded lua engine in nginx. I’ve been playing with lua for some things at work recently so I’ve managed to get my head round the basics.

The important bit of the answer is:

lua_need_request_body on;

set $resp_body "";
body_filter_by_lua '
  local resp_body = string.sub(ngx.arg[1], 1, 1000)
  ngx.ctx.buffered = (ngx.ctx.buffered or "") .. resp_body
  if ngx.arg[2] then
     ngx.var.resp_body = ngx.ctx.buffered
  end
';

I also wanted the request and response headers logging so a little bit more lua got me those as well:

set $req_header "";
  set $resp_header "";
  header_filter_by_lua ' 
  local h = ngx.req.get_headers()
  for k, v in pairs(h) do
      ngx.var.req_header = ngx.var.req_header .. k.."="..v.." "
  end
  local rh = ngx.resp.get_headers()
  for k, v in pairs(rh) do
      ngx.var.resp_header = ngx.var.resp_header .. k.."="..v.." "
  end
';

This combined with a custom log format string gets me everything I need.

log_format log_req_resp '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" $request_time req_header:"$req_header" req_body:"$request_body" resp_header:"$resp_header" resp_body:"$resp_body"';

Adding audio to the CCTV of your burgular

In a full on throw back to the 2nd ever post to this blog, back in February 2010, I’ve recently been updating the system that sends me a video when there is movement in my flat via MMS and email.

I thought I’d try and add audio to the video that gets sent. A quick Google turned up two options, one was to use the sox command and it’s silence option, the second uses the on_event_start triggers in motion as a way to record the audio at the same time as capturing the movement video. I went with the second option and tweaked it a bit to make it pick the right input for my system and to direct encode the audio to MP3 rather than WAV to save space.

on_event_start arecord --process-id-file /var/motion/arecord.pid -D sysdefault:CARD=AF -f S16_LE -r 22050 - | lame  -m m - /var/motion/%Y%m%d_%H%M%S.mp3

The other useful addition was the –process-id-file /var/motion/arecord.pid which writes the process id to a file so I can just use this to stop the recording rather than having to use grep and awk to find the process in the ps output.

on_event_end kill `cat /var/motion/arecord.pid`

Now it’s just a case of combining the video from motion with the audio. I can do this with ffmpeg when I re-encode the video into the 3gp container to make it suitable for sending via a MMS message.

ffmpeg -i movement.avi -i movement.mp3 -map 0:v -map 1:a -c:a aac -strict -2 -s qcif -c:v h263 -y /var/www/html/cam1/intruder.3gp

This seams to work but the output file is a little large. The default audio encoding seams to be at 160k bitrate, I wound it down to 32k and the file size got a lot better.

ffmpeg -i movement.avi -i movement.mp3 -map 0:v -map 1:a -c:a aac -b:a 32k -strict -2 -s qcif -c:v h263 -y /var/www/html/cam1/intruder.3gp

I’d like to try the AMR audio codec but I can’t get ffmpeg to encode with it at the moment, so I’m just going to email the mp3 of the audio along with the high res AVI version of the video and just send the low res 3GP version via MMS.

Update on Garmin Forerunner 935

It’s been a few weeks since I picked up my Garmin Forerunner 935. I must say I’m pretty impressed.

Step counting

I’ve been using it to record my day to day step count and all day heart rate data as well as all my training and the London Triathlon.

The battery life is great, I’m getting a good 2 weeks out of a charge even when using it to record activities with GPS and ANT+ sensor data. It seams to take about 2 hours to fully charge.

Having it sync with the phone is useful as it means I don’t need to keep a Windows box kicking around just (OK, I do still need one for Zwift but that is less regular) to run the Garmin Connect application to upload my workouts to the web and Strava. There is built in WiFi support as well which can allow it to sync without having the phone, I’ve not enabled this at the moment as even if I’m not always carrying my phone while training it is pretty much always going to be around when I get back.

Another change is that the ANT+ sensors now live in a collective pool rather than being bound to something like a bike profile so you don’t need to remember to pick the right profile if you have multiple bikes. The watch will just pick all the relevant sensors it can see as you select the activity type. The only downside I can see to this is if you lend somebody a bike and both go riding at the same time. To get round this you can force it to pick one if it can see multiple versions of the same sensor. But it does mean I don’t need 3 different profiles, one for the Propel, Defy and the Defy on the turbo trainer.

Rest indicator

The new training tracking feature is also helpful, giving indications of how much rest time you should take between activities and also a training load number. The training load number is supposed to unique to each user so not something you can compare with others, but should show if the system thinks you are over training (looks like I need to back off a little)

Training Load

The only extra I have purchased is a glass screen protector as I managed to get a very small scratch in the plastic face on the first day wearing it. I’ve no idea how it did it as I doing remember knocking or catching it against anything. The protector is very thin and fits nearly flush with the bezel and you can’t tell it’s there. Given I’m planning on wearing this as my day to day watch as well as for activity tracking this is a little disappointing, but this is probably why it’s cheaper than the equivalently spec’d Fenix 5.

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.

EDIT:
If you have problems with this node please do not post comments here, it really isn’t the best place to work issues. Open a issue on github here then it can be properly tracked.

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,,,"