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.

Thursday 19 January 2012

Myth 2: Sonos doesn't have an open API

This myth is usually repeated in several different forms, including "Sonos has a closed API" and even "Sonos won't let you use it's proprietary API".

I have heard these statements from people who claim to be "integrators" when what their job title should really be "installer" (they wouldn't know what integration was if it hit them in the eye).

I have heard this from ignorant people who assume that because there aren't dozens of Sonos apps written by third parties that this must be the reason.

I have heard it from fans and employees of other vendors who are trying to spread FUD about a competing product.

The bottom line...
It isn't true!


The Sonos control API is based on UPnP which is one of the most open commercial home control protocols there is. All of the specifications can be downloaded for free from www.upnp.org.

Furthermore, much of the functionality is based on UPnP-AV, which is the AV specification from www.upnp.org. Again, the specification documents for this are available free of charge from www.upnp.org. Here's an example:
UPnP Device AV Architecture 1.1

With these specifications, and these specifications alone, you can perform a whole host of activities with your Sonos setup including:

  • Getting a list of devices on the network, their name, device type, IP address, etc.
  • Getting the "tranport state" of a Sonos zone (whether it's playing, paused,. etc.)
  • Getting track information about what any zone is playing, including cover art (if available)
  • Setting a track to play, or pausing it on a specific zone, or all zones.
  • Changing the volume,muting and unmuting on any zone
  • Browsing the Sonos index by Track, Artists, Album, Folder, etc.
  • Browsing Sonos playlists
  • Seeing changes in the status of any zone, such as the player changing from paused to playing
That's a pretty good range of integration capabilities, right?
On top of that, there's a range of Sonos-specific capabilities that, whilst not standard UPnP, are published via the UPnP interface. That means they are visible to end users and some idea of how to use them is presented. Most of these are fairly obviously named so it doesn't take much more than a little experimentation to work out how to use them. It would be nice to have documentation, but as none exists part of my aim here is to use this blog as an unofficial documentation of some of these features, as and when I get around to addressing them.
The sort of things that are available:

  • Adding songs to the Sonos queue (note you can play tracks using standard UPnP-AV, the queue is slightly different)
  • Setting alarms
  • Grouping and ungrouping zones, and seeing how they are grouped
  • Creating Sonos playlists
  • Changing Sonos device configuration (e.g. Line In settings)
As I say, I'll try to address these eventually in this blog, but it's totally subject to my personal time, so it might be a while off.

One area which really could do with some more documentation is Music Services (more recently this includes radio as the radio service is provided by TuneIn). The trouble is many music services have their own API which Sonos hooks into to use them. This API is the property of the music service and,even if they wanted to, Sonos could get into contractual problems with their partner if they publish it. I'll try to illustrate some of these as I go along, but bear in mind I don't have access to all of the music services so I can't cover all of them.

These generally work in roughly the same way:

  1. use music service API to log in and browse music service
  2. Use UPnP to push selected tracks to Sonos queue (as normal)
More recently Sonos have announced a standard integration API for music services. I think this is a smart move as most music services have yet to develop their own streaming API. By providing a free, open API standard which makes it easy for services to build a streaming interface to their systems, and therefore to reach Sonos users, this should escalate the development process and may lead to a more standardised streaming service interface across the industry.
In theory this should make control app development easier, as the interface to control external music service access should be more standard.

Integrating with Sonos

As an active participant in the Sonos Forums, one of the subjects I come across fairly often is the subject of integration.

I should mention that IT integration, in general, is a subject I'm pretty familiar with: one of the major roles I have performed over the years is as a System Architect overseeing the design of operational systems for large telecommunications companies. Such systems usually comprise a hotch-potch of systems operating to different standards, to different data models, driving different operational processes. There is always a mixture of legacy and new. In fact the normal driver for the work I do is the introduction of a new system, new piece of technology, new process, etc. and the need to slot that as seamlessly as possible into the existing mess.

I will also mention that, as part of this work I often have to "roll my sleeves up" and actually get down and dirty with the integration. I am a competent and experienced developer in a range of development languages, including Java/J2EE, PHP and Perl, and have developed integration adapters for a range of systems, APIs, and protocols including XML, SOAP, JMS, HTTP, CORBA, and TMF standards (TMF814, TMF854).

Boasting aside, the point is, I understand this stuff pretty well!

So, being a long time Sonos user, I get pretty interested when the subject of integrating Sonos is discussed. Part of this is because, most of the time, it's spoken about with apparent authority by people who haven't got the first clue about the subject. Many of these are people who think they understand computers because they know how to use one competently. They think that, because they wrote a spreadsheet formula or a DOS batch script once, they are qualified to to authoritatively about professional software development. Worst still some believe such limited experience qualifies them to dismiss the views of highly experienced software professionals.

There are also a lot of industry observers and vested interests who like to put two and two together to create something much larger than 4.

The result is: a lot of bullshit is written about the ability to integrate Sonos with other systems.

I should point out that I do not, nor ever have, worked for Sonos. Nor do I have any financial or other vested interest in the company other than being a happy customer. There are people who try to suggest otherwise: they are bullies and idiots.

What i will attempt to do in this, my personal blog, is to dispel some of these myths. I plan to do this partly by using actual code showing that it is possible to integrate with Sonos using the UPnP API which it largely conforms to and uses.

Note that, at least to start with, the development platform I am using is C on Linux using the GUPnP Libraries which are readily available on a range of systems and languages, and are normally bundled with most Linux distros. It's important to realise that, despite a significant background in software development, I'm not normally a C coder. I would describe myself as adequate at best. Any code examples I post are to illustrate something as clearly as I can. Sometimes (and my lack of skill) this gets in the way of the examples being "good design". On that note I'm happy to take recommendations from others as to how to improve this code, as long as it's not at the expense of the educational value.

It's also important to realise that this is only one of a large number of permutations of hardware platform, OS, development language and library. There are literally dozens more. I can't cover all of them, but the principles I show here should be roughly applicable to other environments.

With that I will dispel a myth:

Myth 1: Sonos needs to release a SDK to support integration

This is a myth for two reasons:

  1. An "SDK" will target a specific environment, and there are two many variations to support even all of the "popular" ones. An SDK which is deigned for, say, Windows using C# and .NET libraries won't be at all useful to an iPhone iOS developer, or to a Mac user. In fact, many of the systems people want to integrate with (for example Russound or Control4) are not based on conventional PCs. Such an SDK would do nothing to support integration with those environments.
  2. There are already plenty of SDKs available. Any UPnP client library is, in fact, a Sonos support library, because the Sonos control protocol is compliant with UPnP. In fact, much of it is compliant with UPnP-AV which means code you develop for many Sonos functions will also control any other UPnP-AV media device. As I said, I'm using GUPnP, but I could easily have used libupnp or Platinum UPnP or Cyberlink and that's just some of the options I have in C. If I was using a different language I would have a different choice.
So, whilst in some circumstances I can certainly see some benefit in Sonos providing some additional development support, it's a nice to have, not a "need". This myth is spread by people who are either too lazy to check facts, ignorant of how software development works, or who just like slagging Sonos off. Sometimes it's all three!

Back to the subject, some of the code on this blog is based on code originally published in the Sonos Forums under the heading "Developer's Diary" (the subject line has now been changed to "Exploring the Sonos control API"). I have decided to focus my attention to posting in this blog for several reasons. Firstly, the Sonos Forums are largely made up of "end-users" whose only interest in development is the possibility they might get some cool free apps. There's nothing wrong with that, but it does mean that there were actually very few people who were genuinely interested in writing code of their own. I actually had someone quite aggressively querying why I was posting such information on the forums, and another couple who used it as an opportunity to make less than complimentary remarks.
Secondly, there wasn't enough space in a post to adequately cover one topic, so I found I was splitting articles up into multiple posts. Also the formatting options available are limited.
Finally, this is my blog, so I can post what I like. On the forums, as a volunteer moderator, I'm the target for all kinds of hate, abuse, and attempts to suppress my views. Forums are, by nature, more public. People join the Sonos forums for community support and discussion. In general they don't want to see fights and so I largely have to toe the line. As such, suggesting people are idiots (even if they plainly are) is likely to start a fight which is not conducive to a pleasant forum. On here, I can say what I like. If you don't like it, go somewhere else.

On that final note, if you're not happy about anything I post here, then here is the place to complain, not on the forums. This blog is deliberately separated from the forums and is nothing whatsoever to do with them. Attempts to use content on this blog to attack me on the forums will not be tolerated. As Wil Wheaton so beautifully put it: "Don't Be A Dick!"