Thursday 7 August 2014

Sonos UPnP Development - Accessing the AV Transport service

This is an iteration of the code I produced in my post UPnP Discovery with Sonos - Event Driven. Now we have discovered the devices on the network, we should try to do something useful with them. In this post I show how to pull some interesting information from them.

More specifically, I am going to reference the AVTransport service which is part of the MediaRender specification. This introduces some other another important concepts in UPnP: Services, Events and Actions.

Services

Functionality in UPnP devices is primarily exposed via services. Services are advertised on the network and can be discovered. The advertisement for the service includes the technical document which explains its capabilities. This document may be viewed using UPnP Device Spy, or in the case of standards based services like AV the specifications are also published at upnp.org.

Services expose two types of capability: actions and events.

Actions

If you want to perform some function on the device, you call an appropriate action. For a media player device, for example, one action might be to start playing the current track. We will look at actions in a later article.

Events

When a device does something that the rest of the world might want to know about, it can trigger an event. The events that a device may trigger are part of it's device specification document which can be viewed using a UPnP Development toolkit like Device Spy, or in the case of AVTRansport by looking at the UPnP AV specifications at upnp.org.

Anything else on the network that is interested can register it's interest in knowing about that event by subscribing to it. It may also unsubscribe if it is no longer interested. If it is subscribed to an event, it will receive an event every time the target device does the thing which causes the event.

For instance, n the case of a MediaRenderer object there is a service called AVTransport which represents the status and capability of the renderer transport.

In this article we are going to look at this and used the event called "last_changed" which is triggered whenever the Transport state changes.

The code below is based on the version in my last article with some modifications:






The first thing to notice is I have removed a bunch of code:

device_info as been removed and the code to print the hashtable out in main_loop_timeout has also gone. This code was really only there in the previous version so that you could see what was going on.

I have also removed the "Device added:" and "Device removed:" print statements from device_proxy_available_cb and device_proxy_unavailable_cb so none of the console messages that were in the last version are there any more.

The major additions here are as follows:

Firstly we have added a new callback function on_last_change (lines 30 to 75). This function is passed the UDN of the device as well as some data. The data is an XML document describing the transport state.

A couple of things are important here. Firstly we used the UDN (Unique Device Name) of the device as the key when we stored it in the GHashTable. This means we can use the UDN to quickly find the renderer object.

Secondly, the XML doucment needs to be parsed to pull useful information from it. Luckily GUPnP provide a set of libraries (gupnp-av) to help with this.

So, this function parses the "TransportState" information from the XML user data, looks up the renderer from our GHashTable using the UDN, and prints out the device and state information.

We then need to subscribe to the AVTransport service. We do this in the device_proxy_available_cb. Whenever this is called it represents a new renderer device being found. We do the following:

1. look up the AVTransport service (line 92)
2. register our interest in the "LastChange" event, and point this at our on_last_change callback function (line 97-101)
3. and turn on the subscription (line 102)

From then on, any time the renderer transport state changes, it will fire a "LastChanged" event at us which will run our little function.

A few other, minor changes are needed to support this:
  • I have #included the gupnp-av headers (line 8)
  • I have created a parser object and initialised it (lines 16 and 135)
  • I have also moved the timeout value to a #define called "RUN_TIME" (lines 12 and 162) so it is easier to change and set it to 10 seconds
If you run this you should get something like this:

If you clear the queue you will get:


As you can see this starts to offer some interesting possibilities. You could build a GUI around this which not only listed all of the zones, but also listed whether they were currently playing or paused, all in real time.

You could also use this (with the appropriate hardware and drivers) to send on/off messages to an amplifier on a specific zone via Infrared (lirc) or a 12V trigger (parallel port?)

No comments:

Post a Comment