A Groovy Time with UPnP and WeMo

How to control a WeMo device using Groovy.

Brendon Anderson

Universal Plug and Play (UPnP) is a networking technology used to discover devices and services on a network.  Chances are, if you have any sort of device on your home network it is probably a device that understands UPnP.  UPnP uses HTTP over UDP, SOAP, and XML for performing its various interactions.  Finding a device involves sending out a discovery packet (using SSDP) to a multicast address and waiting for responses.  Within the response, a device description and interface is defined.  From there, an application can use the interfaces provided by the device to configure or control it.  Furthermore, devices can subscribe to other devices to be notified when various state changes.

For a gift, I received a Belkin WeMo outlet device.  It’s an outlet that you plug into an existing outlet that you control using an app on your smartphone.  Through the app you can setup a schedule to turn on your living room lamp every day at 6pm and then turn it off at 10pm for example.  There are other options using other websites to configure your WeMo device to turn on your exterior lights when you are a certain distance from your house (based on the GPS on your phone), turn on a light when a WeMo motion detector is tripped, and other wild things.

I like the device, except the schedule does not work for me.  No matter what I do it will not follow the schedule.  So, I did a little digging to see if I could figure out an alternative way to control this thing.  It turns out the WeMo is a UPnP device and has a SOAP API that can be used to control it.  That’s easy enough to do using a client that can send XML and some headers, but I wanted to learn a little more about it and hopefully at the end of this investigation I would have a utility that I could use with cron to turn my outlet on and off.

Since my WeMo is already on the network and configured, the first thing I needed to do was send out a discovery packet and listen to responses from the devices on my network.

InetSocketAddress socketAddress
    = new InetSocketAddress(InetAddress.getByName("239.255.255.250"), 1900)
MulticastSocket socket = new MulticastSocket(null)
try {
    socket.bind(new InetSocketAddress("192.168.1.150", 1901))
    StringBuilder packet = new StringBuilder()
    packet.append( "M-SEARCH * HTTP/1.1\r\n" )
    packet.append( "HOST: 239.255.255.250:1900\r\n" )
    packet.append( "MAN: \"ssdp:discover\"\r\n" )
    packet.append( "MX: ").append( "5" ).append( "\r\n" )
    packet.append( "ST: " ).append( "ssdp:all" ).append( "\r\n" ).append( "\r\n" )
    //packet.append( "ST: " ).append( "urn:Belkin:device:controllee:1" ).append( "\r\n" ).append( "\r\n" )
    byte[] data = packet.toString().bytes
    socket.send(new DatagramPacket(data, data.length, socketAddress))
} catch (IOException e) {
    throw e
} finally {
    socket.disconnect()
    socket.close()
}
  • 1 - Address and port to send the packet to.  This is a special IP address and port reserved for UPnP.
  • 3, 5 - Create a multicast socket and bind the local IP address and a port.
  • 7 - M-SEARCH - Search method
  • 8 - HOST - IP address and port reserved for UPnP.
  • 9 - MAN - Defines a namespace.
  • 10 - MX - Maximum wait time in seconds the device should respond.  The device should reply a random number of seconds between 0 and this number.
  • 11, 12 - ST - Search target - Line 11 looks for anything that will respond.  Line 12 is specifically for Belkin devices.
  • 14 - Send the packet.
MulticastSocket recSocket = new MulticastSocket(null)
recSocket.bind(new InetSocketAddress(InetAddress.getByName("0.0.0.0"), 1901))
recSocket.setTimeToLive(10)
recSocket.setSoTimeout(1000)
recSocket.joinGroup(InetAddress.getByName("239.255.255.250"))
while (inService) { //inService is a variable controlled by a thread to stop the listener
    byte[] buf = new byte[2048]
    DatagramPacket input = new DatagramPacket(buf, buf.length)
    try {
        recSocket.receive(input)
        String originaldata = new String(input.data)
    } catch (SocketTimeoutException e) {
    }
}
recSocket.disconnect()
recSocket.close()
  • 1-5 - Setup the socket for receiving notifications.
  • 8 - Data structure to hold the notification.
  • 10 - Receive the notification into the packet.
  • 11 - Convert the notification into a String for further processing.

Generally you will receive several notifications from each device on your network.  Here are some examples of responses received on my network:

//router
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1801
DATE: Wed, 19 Mar 2014 08:33:08 GMT
EXT:
LOCATION: http://192.168.1.1:49152/wps_device.xml
SERVER: Unspecified, UPnP/1.0, Unspecified
ST: upnp:rootdevice
USN: uuid:bbbbbbb-aaaa-zzzz-yyyy-xxxxxxxxxx::upnp:rootdevice

//Chromecast
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Wed, 19 Mar 2014 14:33:09 GMT
EXT:
LOCATION: http://192.168.1.136:8008/ssdp/device-desc.xml
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 11111111-2222-3333-4444-5555555555
SERVER: Linux/3.8.13, UPnP/1.0, Portable SDK for UPnP devices/1.6.18
X-User-Agent: redsonic
ST: upnp:rootdevice
USN: uuid:11111111-2222-3333-4444-555555555::upnp:rootdevice
BOOTID.UPNP.ORG: 125
CONFIGID.UPNP.ORG: 1

//WeMo
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=86400
DATE: Wed, 19 Mar 2014 10:24:58 GMT
EXT:
LOCATION: http://192.168.1.120:49153/setup.xml
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 11111111-2222-2222-2222-3333333333
SERVER: Unspecified, UPnP/1.0, Unspecified
X-User-Agent: redsonic
ST: urn:Belkin:device:controllee:1
USN: uuid:Socket-1_0-11111111111::urn:Belkin:device:controllee:1

The part we care about is the LOCATION header.  Copying and pasting the value of the location header into your web browser will display the description of that particular device.  For my WeMo it also displays the services provided.  Here is an abridged version of the XML returned from my WeMo:

<root xmlns="urn:Belkin:device-1-0">
    <device>
        <deviceType>urn:Belkin:device:controllee:1</deviceType>
        <friendlyName>Livingroom Lamp</friendlyName>
        <modelDescription>Belkin Plugin Socket 1.0</modelDescription>
        <modelName>Socket</modelName>
        <binaryState>0</binaryState>
        <serviceList>
            <service>
                <serviceType>urn:Belkin:service:basicevent:1</serviceType>
                <serviceId>urn:Belkin:serviceId:basicevent1</serviceId>
                <controlURL>/upnp/control/basicevent1</controlURL>
                <eventSubURL>/upnp/event/basicevent1</eventSubURL>
                <SCPDURL>/eventservice.xml</SCPDURL>
            </service>
        </serviceList>
    </device>
</root>

The XML contains basic information about the device including the various services it provides.  An interesting element here is the SCPDURL element (line 14).  This has the definition of the various actions that this particular service provides. Replacing /setup.xml in the URL with the value of SCPDURL will give you those actions.  Here is an action from the truncated XML from the WeMo that we can use to find out if it is on or off:

<scpd xmlns="urn:Belkin:service-1-0">
    <specVersion>
        <major>1</major>
        <minor>0</minor>
    </specVersion>
    <actionList>
        <action>
            <name>GetBinaryState</name>
            <argumentList>
                <argument>
                    <retval/>
                    <name>BinaryState</name>
                    <relatedStateVariable>BinaryState</relatedStateVariable>
                    <direction>out</direction>
                </argument>
            </argumentList>
        </action>
    </actionList>
</scpd>

Using this information, we can build the XML to make our SOAP call to find out the status of the WeMo.  Using Groovy’s HTTPBuilder we can send the XML and get the response back.  It should be easy to see where the various elements of the XML and the request headers come from.

HTTPBuilder http = new HTTPBuilder("http://192.168.1.120:49153/upnp/control/basicevent1")
http.request(Method.POST, ContentType.XML) {
    body = '<?xml version="1.0" encoding="utf-8"?>' +
            '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
                '<s:Body>' +
                    '<u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1">' +
                        '<BinaryState>1</BinaryState>' +
                    '</u:GetBinaryState>' +
                '</s:Body>' +
            '</s:Envelope>'
    headers.'SOAPACTION' = "\"urn:Belkin:service:basicevent:1#GetBinaryState\""
    headers.'Content-Type' = "text/xml; charset=\"utf-8\""
    headers.'Accept' = ""
    response.success = { resp, xml ->
        println xml.Body.GetBinaryStateResponse.BinaryState.text()
    }
}

The actual XML response looks like this:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:GetBinaryStateResponse xmlns:u="urn:Belkin:service:basicevent:1">
            <BinaryState>0</BinaryState>
        </u:GetBinaryStateResponse>
    </s:Body>
</s:Envelope>

Here is the request to turn the device on:

HTTPBuilder http = new HTTPBuilder("http://192.168.1.120:49153/upnp/control/basicevent1")
http.request(Method.POST, ContentType.XML) {
    body = '<?xml version="1.0" encoding="utf-8"?>' +
           '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
               '<s:Body>' +
                   '<u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1">' +
                       '<BinaryState>1</BinaryState>' +
                   '</u:SetBinaryState>' +
               '</s:Body>' +
           '</s:Envelope>'
    headers.'SOAPACTION' = "\"urn:Belkin:service:basicevent:1#SetBinaryState\""
    headers.'Content-Type' = "text/xml; charset=\"utf-8\""
    headers.'Accept' = ""
}

Checking the status now, produces this XML:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:GetBinaryStateResponse xmlns:u="urn:Belkin:service:basicevent:1">
            <BinaryState>1</BinaryState>
        </u:GetBinaryStateResponse>
    </s:Body>
</s:Envelope>

Putting all these bits of code together, I now have a basic program that I can use to discover WeMo devices on my network, find out the status of the device, and turn it on or off.  It should be fairly straightforward to implement functions for setting up schedules and other things the WeMo can do.

My code can be found here: https://github.com/brendonanderson/wemocontrol

Share this Post

Related Blog Posts

JVM

GUM Recap - Enterprise Grails with Spring Batch

February 13th, 2014

Recap of John Engelmans presentation at the January 2014 Groovy Users of Minnesota (GUM) meeting on utilizing Spring Batch with Grails applications.

Object Partners
JVM

Memoization in Groovy

January 28th, 2014

The performance of expensive calculations in Groovy can be increased with the use of memoization.

Brendon Anderson
JVM

Simpler Stored Procedures with Groovy

January 24th, 2014

Using Groovy almost makes calling Stored Procedures an enjoyable process. See how to process Sql Output Parameters and ResultSets easily in Groovy

Jeff Sheets

About the author

Brendon Anderson

Sr. Consultant

Brendon has over 10 years of software development experience at organizations large and small.  He craves learning new technologies and techniques and lives in and understands large enterprise application environments with complex software and hardware architectures.