Friday 16 March 2012

Discovering UPnP devices



Here I present to you a piece of code that does a very basic UPnP discovery of devices on the network. This is normally the starting point for any UPnP control program.

Discovery is an important part of UPnP. It allows devices to be truly "plug 'n' play". It negates the need for the user to ave to configure the network settings on devices before they plug them in. This relies on the network supporting IP address allocation using DHCP, but DHCP is almost universally used in home networks as well as most corporate networks.

One potential problem with DHCP is that it is difficult to know in advance what IP addresses are ging to be assigned to devices. It is possible to configure DHCP to statically map IP addresses to specific devices, but this requires a configuraion step and removes the "plug 'n' play" element. Discovery solves this problem.

The code I present requires the Gnome GUPnP libraries (as well as dependent libraries such as GSSDP). These are readily available on most Linux distributions. You will need the development packages.

The code below is a very primitive control point. When run it will discover all of the UPnP devices on the local network, and then exit. This code is based on the GUPnP example control point which has been extended slightly. There are comments in the code explaining each major part:
//============================================================================
// Name : UPnPDiscovery.cpp
// Author : Majik 
// Version : 2.0 
//============================================================================ 
#include  
static GMainLoop *main_loop; 
/* This is our callback method to terminate the main loop
 * after the timeout has expired */

 static gboolean main_loop_timeout(void *data)
 {
   g_main_loop_quit (main_loop);
   return 0;
 }
 /* This is our callback method to handle new devices
 * which have been discovered. It simply prints the
 * device model and friendly name to to console */
 
 static void device_proxy_available_cb(GUPnPControlPoint *cp, GUPnPDeviceProxy *proxy)
 {
    GUPnPDeviceInfo* gupnp_device_info = GUPNP_DEVICE_INFO(proxy);
    g_print("Device model: %s", gupnp_device_info_get_model_name(gupnp_device_info));
    g_print("\tFriendly name: %s\n", gupnp_device_info_get_friendly_name(gupnp_device_info));
 }
 /*
  * This is the main program */
 
 int main (int argc, char **argv)
 {
    GUPnPContext *context;
    GUPnPControlPoint *cp;
    /* Required initialisation */
    g_thread_init (NULL);
    g_type_init ();
    /* Create a new GUPnP Context. By here we are using the default GLib main context, and connecting to the current machine's default IP on an automatically generated port. */
    context = gupnp_context_new (NULL, NULL, 0, NULL);

    /* Create a Control Point targeting UPnP Root devices */
   
    cp = gupnp_control_point_new(context, "upnp:rootdevice");
   /* The device-proxy-available signal is emitted when any devices which match our target are found, so connect to it */
    g_signal_connect (cp, "device-proxy-available", G_CALLBACK (device_proxy_available_cb), NULL);
   
    /* Tell the Control Point to start searching */
    gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);

    /* Set a timeout of 2 seconds to finish processing */   
    g_timeout_add_seconds (2, main_loop_timeout, NULL);

    /* Enter the main loop. This will start the search and result in callbacks to device_proxy_available_cb. */
    main_loop = g_main_loop_new (NULL, FALSE);
    g_main_loop_run (main_loop);

    /* Clean up */
    g_main_loop_unref (main_loop);
    g_object_unref (cp);
    g_object_unref (context);
    return 0;
 } 
The way this works is using "callbacks", which is a very common technique in UPnP development as well as in other types of asynchronous programming. There are two callback functions:

device_proxy_available_cb - This is called whenever a new device is discovered on the network.
main_loop_timeout - This is called by a timer after a set period. It is used to terminate the program, otherwise it would run forever waiting for new devices to appear on the network

A typical output when this is run is as follows:

Device model: MiniUPnPd Friendly name: WANConnectionDevice
Device model: Sonos ZonePlayer 100 Friendly name: 192.168.0.203 - Sonos ZonePlayer
Device model: Sonos ZonePlayer S5 Friendly name: 192.168.0.204 - Sonos ZonePlayer
Device model: Sonos ZonePlayer S5 Friendly name: 192.168.0.206 - Sonos ZonePlayer
Device model: Sonos WD100 Friendly name: 192.168.0.205 - Sonos Wireless Dock


As you can see, this discovers not only Sonos devices but other UPnP devices on the network. In my case it discovers my UPnP IGD device (my firewall).


Why is programming Sonos control apps so complex?


Before I launch into actual code, it's worth an explanation about the apparent complexity of the code being presented. To someone more familiar with "macro" programming and scripting than actual development, the code I am publishing may seem unnecessarily complex. The fact is this is what most real world software apps look like because most real world problems are a lot more complex than you might at first think.

In particular, here we are having to deal with asynchronous network communication. If your experience is limited from simple integration tasks such as configuring an "all-in-one" remote control device, you've probably not come across this. There are many simple control systems commonly used in Home Automation. Often these are one way (for example, IR remote controls) or use simple command-response (as do many RS232 controlled devices). These are relatively easy to program, but suffer from being inflexible and limited in terms of control capabilities.

One way control systems have no way to pull back status from the devices they control. This means they can often be unreliable, confusing to user, or that they require devices to be in a known starting state. They also cannot pull back useful information to the user, such as track data or album art.

Simple two-way control systems are better, but normally only allow one controller at a time, and that controller can only do one thing at a time. A good example of this is the iPod dock interface which can support album art transfer, but whilst the album art is being transferred over it's rather slow serial interface, you cannot perform any other actions.

The other problem with many other "simpler" HA protocols is that they require distinct work per device: you have to know there is a device of a specific type, and with a specific protocol. In the case of RS232/485 control protocols you also have to physically install the piece of wire to the device. Even if the device has an Ethernet interface, you have to statically map the IP address on the network and then point something at that IP address. This is hardly "plug and play".

UPnP is designed to support a much richer feature set than simple one/two way command protocols whilst supporting multiple simultaneous controllers which can each perform multiple concurrent actions. It's also designed to support automatic registration and announcement of new devices as well as a description of their capabilities.

This is a far richer integration and control capability than the ones that most HA installers deal with, and it requires a much richer environment and it comes at the expense of some added complexity at the coding level. This may be foreign to most HA installers and end-users, but it will be very familiar to IT Integrators who deal with similar complex protocols in Enterprise and Service Provider environments all the time.

I have heard HA installers ask: "why can't Sonos control be integrated into all-in-one remote control systems?". Worse still, some will claim it's because the protocols are somehow "secret" or "protected". In doing so they are showing their gross lack of understanding of technology: such a view is incompatible with a basic level of competence in HA installations. The real answer is that most commonly used HA systems are too limited to cope with control of complex UPnP AV devices. For instance, very few remote control devices could cope with the demands of browsing the Spotify music library.

Of course, that doesn't preclude some basic level of control, such as volume control. This would require some sort of bridge between the legacy control protocol and UPnP. It's perfectly possible to do this and some of the better HA installers have demonstrated capability in this area, but it does require someone who knows what they are doing to do this integration.