Using UPnP enabled devices with Talend - Control Sonos Speakers

In the last tutorial published on here, we discussed using Talend with a UPnP device by Belkin. The tutorial looked at how to discover UPnP devices and how to use the device's UPnP description xml to work out how to use the actions available. The tutorial can be found here and will be useful to keep in mind before looking at this approach.

Before I start with this tutorial, I would like to ask if any questions could be asked at the bottom of the page. I have had many emails from people and I am more than happy to answer questions. It is very pleasing to see that this site is of use to people. However, a lot of the question that I get via email are very similar and would probably benefit a lot of people. If you have any questions I would really appreciate it if you would ask them in the comments section below. This way everyone can benefit and the tutorials can grow with the questions. It will also help me to decide upon further tutorials to write in the future.

 

UPnP usage discovery using WireShark

In the last tutorial on UPnP devices we used a bit of Java to discover the UPnP device and its description xml. The path to the description xml can be used in a different way to discover how a UPnP device works. In this tutorial we will be looking at controlling a Sonos speaker system. Using the discovery Java in this tutorial, we can discover the Sonos speaker system by searching the Java output for the word "sonos". There will be several messages that correspond to this, we just need one. An example from my system is below....

NOTIFY * HTTP/1.1
HOST: 239.255.255.250:1900
CACHE-CONTROL: max-age = 1800
LOCATION: http://192.168.1.87:1400/xml/device_description.xml
NT: upnp:rootdevice
NTS: ssdp:alive
SERVER: Linux UPnP/1.0 Sonos/28.1-83251 (ZPS9)
USN: uuid:RINCON_S3D56432F49F01400::upnp:rootdevice
X-RINCON-BOOTSEQ: 21
X-RINCON-HOUSEHOLD: Sonos_YvaSDF89a3hwugzZ7KCIZjjpDs

We can make use of the port that is being used (1400) along with the ip (192.168.1.87) with an application called WireShark, along with the Sonos application for Windows (assuming you are using Windows) to see exactly how the UPnP actions are being used with the Sonos speakers. First, install the WireShark from the link above and install the Sonos application on your Windows machine. If you are not using Windows, then you will need to use the Sonos app on your phone or tablet and configure WireShark to use your network card in monitor mode. This is a little more complicated and is not covered here. 

Once all applications are installed, we can configure WireShark. 

 

1) Load and configure WireShark

Open WireShark and click on the "List available capture interfaces" button circled in green underneath the "File" menu. This will reveal a window with a list of capture interfaces attached the machine. Below we can see that popup with the tickbox of the interface that we will be using circled in green. This was identified by the presence of packets (also circled in green). On your machine, this may be a little more complicated if you have more than one active interface. Some trial and error may be necessary.  

Once the popup window has been configured (only tick the one interface that will be being used to capture packets), click on the "Start" button. An example of what your popup window will look like is below.

Once WireShark starts collecting packets, we need to filter those packets. A simple filter is shown below. Type "tcp.port==1400 and xml" into the "Filter" input and click "Enter". This will start filtering packets so that only packets containing XML data on port 1400 will be captured. This should be enough to identify Sonos packets coming from the machine that WireShark is running on.

 

2) Load the Sonos app and start sending packets

In order to figure out how the UPnP actions provided by your Sonos system work, we need to start sending packets via the Windows application (or phone/tablet applications). The first thing to do for this tutorial is to clear any songs currently in the queue. If there are no songs in the queue, add some first and then click on the "Clear Queue" button (circled in green below). This will send a packet for the "RemoveAllTracks" action.

Once the queue is cleared, we need to search for and add a song. In this tutorial I have chosen "Thriller" from Spotify's collection of songs. In order to do this I searched for "Thriller" using the search box, then clicked on the down arrow related to the song (circled in green) and selected "Add to Queue". This calls a packet for the "AddURIToQueue" action.

The last step is to play the song. To do this, click on the down arrow for the song in the queue (circled in green below) and select "Play Track". This calls a packet for the "Play" action.

 

3) Find the packets in WireShark and see how the UPnP Actions are called

Bring the WireShark screen to the forefront and you will notice lots of packets have been captured. These packets can be read by clicking on them and then expanding the "eXtensible Markup Language" section in the "Packet Details" window.

The first packet we are looking for is the "RemoveAllTracks" packet. This can be seen below. The packet is highlighted by the red box and the SOAP message is highlighted by the green box. This demonstrates the format and the parameter values needed to remove all tracks from the queue. 

If we expand the Hypertext Transfer Protocol section we are presented with what we see below in the screenshot. The section in the red square box shows us what we will need to use for the "SOAP Action" input box in the tSOAP component, the URL in the blue box shows us what to use as the "Endpoint" and the section in the green box shows us the SOAP message for the "AddURItoQueue" action. These sections give us enough information to use these actions with the Talend tSOAP component. The values used by the SOAP message may need to change when we use them later.

It is useful to be able to copy the text from the "Packet Details" window. To do this, right click on the section of the message that you want. In the screenshot below, the "eXtensible Markup Language" section has been right clicked. Go down to "Copy" in the menu that results. Select "Bytes" from the sub menu. Then click on "Printable Text Only", as circled in green below. This will copy the whole of the "eXtensible Markup Language" section to your clipboard as plain text.

 

4) Messages and settings for each Action

In this tutorial we are going to be using 3 UPnP actions; RemoveAllTracks, AddURIToQueue and Play. The packets for these actions caught by WireShark can bee seen below.

 

RemoveAllTracks

This action is used to remove all tracks from the Sonos queue. There are two parameters, QueueID and UpdateID. In this tutorial they are set to 0. Experimentation with WireShark and the Sonos application may reveal other values.

POST /MediaRenderer/Queue/Control HTTP/1.1
CONNECTION: close
ACCEPT-ENCODING: gzip
HOST: 192.168.1.87:1400
USER-AGENT: Linux UPnP/1.0 Sonos/28.1-83040 (WDCR:Microsoft Windows NT 6.1.7601 Service Pack 1)
CONTENT-LENGTH: 283
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-sonos-com:service:Queue:1#RemoveAllTracks"

 

POST /MediaRenderer/Queue/Control 
 

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

<s:Body>
<u:RemoveAllTracks xmlns:u="urn:schemas-sonos-com:service:Queue:1">
<QueueID>0</QueueID>
<UpdateID>0</UpdateID>
</u:RemoveAllTracks>
</s:Body>
</s:Envelope>

 

AddURIToQueue

This action is used to add a song to the Sonos queue. The song chosen for this example was "Thriller" by Michael Jackson. 

POST /MediaRenderer/AVTransport/Control HTTP/1.1

CONNECTION: close
ACCEPT-ENCODING: gzip
HOST: 192.168.1.87:1400
USER-AGENT: Linux UPnP/1.0 Sonos/28.1-83040 (WDCR:Microsoft Windows NT 6.1.7601 Service Pack 1)
CONTENT-LENGTH: 1218
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"

POST /MediaRenderer/AVTransport/Control 

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddURIToQueue xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<EnqueuedURI>x-sonos-spotify:spotify%3atrack%3a1D9KEXIrlmPUkMTdYzqgX4?sid=9&amp;flags=32&amp;sn=2</EnqueuedURI>
<EnqueuedURIMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot;
xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:
metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;&lt;item id=&quot;00030020spotify%3atrack%3a1D9KEXIrlmPUkMTdYzqgX4&quot; parentID=&quot;00020000track:thriller&quot; restricted=&quot;true&quot;&gt;&lt;dc:title&gt;Thriller&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.
audioItem.musicTrack&lt;/upnp:class&gt;&lt;desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;&gt;SA_RINCON2311_X_#Svc2311-0-Token&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</EnqueuedURIMetaData>

<DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued>
<EnqueueAsNext>0</EnqueueAsNext>

</u:AddURIToQueue>
</s:Body>
</s:Envelope>

 

Parameters

The parameters names for this action are listed below. They are coloured to correspond to the values that are set in the SOAP message.

InstanceID
EnqueueURI
EnqueueURIMetaData
DesiredFirstTrackNumberEnqueued
EnqueueAsNext

So far I have noticed that InstanceID is always 0. This may change and on your system it may be different.
The EnqueueURI parameter is the URI of the song to be enqueued from Spotify. 
The EnqueueURIMetaData parameter is used to supply meta data (information about the song).
The DesiredFirstTrackNumberEnqueued parameter is the position in the queue at which the song should be positioned.
The EnqueueAsNext parameter is a boolean. If it is 1 (true), the song is added to the end of the queue. If the value is 0 (false), the song is added starting at the position defined by the parameter "DesiredFirstTrackNumberEnqueued".

 

Obviously, we are not going to want to schedule the same song again and again using a Talend Job. We will want to parameterise the SOAP message for this action so that we can add whatever song we fancy from Spotify. To learn how this can be done, I added another song using the Sonos application and captured the message below. From the message above and the message below, we will extract the parts that are the same and the parts that change. The parts that change will be parameterised when they are used. The parameters below are coloured in the same way as in the message above.

POST /MediaRenderer/AVTransport/Control HTTP/1.1

CONNECTION: close
ACCEPT-ENCODING: gzip
HOST: 192.168.1.87:1400
USER-AGENT: Linux UPnP/1.0 Sonos/28.1-83040 (WDCR:Microsoft Windows NT 6.1.7601 Service Pack 1)
CONTENT-LENGTH: 1218
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"

POST /MediaRenderer/AVTransport/Control 

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddURIToQueue xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<EnqueuedURI>x-sonos-spotify:spotify%3atrack%3a2Sww4Q4ym4X8HzQwe8uxNp?sid=9&amp;flags=32&amp;sn=2</EnqueuedURI><EnqueuedURIMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;&lt;item id=&quot;00030020spotify%3atrack%3a2Sww4Q4ym4X8HzQwe8uxNp&quot; parentID=&quot;00020000track:thriller&quot; restricted=&quot;true&quot;&gt;&lt;dc:title&gt;Thriller - Pacha 40th Anniversary Remix&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;&gt;SA_RINCON2311_X_#Svc2311-0-Token&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued>
<EnqueueAsNext>0</EnqueueAsNext>

</u:AddURIToQueue>
</s:Body>
</s:Envelope>
 

 

From comparing the two SOAP messages above, I have compiled the below parameterised version. Sections of the code that are simply not required have been shown with a strike through the text. The colour scheme identifying the parameters remains. This will be how this action is used by Talend....

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddURIToQueue xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<EnqueuedURI>x-sonos-spotify:spotify%3atrack%3a{SPOTIFY SONG ID}?sid=9&amp;flags=32&amp;sn=2</EnqueuedURI><EnqueuedURIMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;&lt;item id=&quot;00030020spotify%3atrack%3a{SPOTIFY SONG ID}&quot; parentID=&quot;00020000track:thriller&quot; restricted=&quot;true&quot;&gt;&lt;dc:title&gt;Thriller - Pacha 40th Anniversary Remix&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;&gt;SA_RINCON2311_X_#Svc2311-0-Token&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued>
<EnqueueAsNext>0</EnqueueAsNext>

</u:AddURIToQueue>
</s:Body>
</s:Envelope>

In the code above, the sections replaced by "{SPOTIFY SONG ID}" correspond to the Spotify song ID ("2Sww4Q4ym4X8HzQwe8uxNp" in this example), which can be changed depending on the song that is required.

 

Play

This action is used to play the songs in the Sonos queue. 

The InstanceID parameter, as with the AddURIToQueue action, always appears to be 0. 
The Speed parameter should be set to 1 to represent the actual recorded speed of the song. I have played with this and it does not appear to allow you to do things like play a song twice as fast with Spotify. It may be able to do this with songs from other service providers. However, in this example we will keep it set to 1.

POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
ACCEPT-ENCODING: gzip
HOST: 192.168.1.87:1400
USER-AGENT: Linux UPnP/1.0 Sonos/28.1-83040 (WDCR:Microsoft Windows NT 6.1.7601 Service Pack 1)
CONTENT-LENGTH: 266
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Play"

 

POST /MediaRenderer/AVTransport/Control

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<Speed>1</Speed>
</u:Play>
</s:Body>
</s:Envelope>

Now we have a basic understanding of the UPnP actions that we need to use and the parameters that are used, we can now use Talend to interact with these.

 

The "Sonos Add Song To Empty Queue" Job

This section deals with how to use the information we have gathered above in a Talend Job to clear the Sonos queue, add a new song and then play it. Obviously this is not going to be very useful on its own, but this is a basic example of how to use the UPnP actions. Using this I would expect you to be able to build useful Jobs or maybe even some ESB routes to control your Sonos system driven by events. 

Below we can see a screenshot of this Job. This is a very simple Job that can easily be used as a service or as part of a more complex Job. 

Context Variables

In this example, there are three context variables that are used. These can be seen below....

Change the "ip" and "port" values to be the values that are required for your system. We saw these here.

The "songid" can be found by using the Spotify web application (https://play.spotify.com/). Search for a song and when you find the one you want, right click on the link and select "Copy Spotify URL". Shown below circled in red.....

The URL will look something like this....

https://play.spotify.com/track/6pBM20jZldvNlbNcnwAX0y

The "songid" is the alphanumeric id at the end of the URL. In the case above it is "6pBM20jZldvNlbNcnwAX0y".

 

1) "Clear Queue" (tSOAP)

This component is used to call the "RemoveAllTracks" UPnP action. It configuration can be seen below....

The "Endpoint" value uses the "ip" and "port" context variables. The rest is made up of the POST URI found here (/MediaRenderer/Queue/Control).

The "SOAP Action" value is the same as the "SOAPACTION" parameter found here ("urn:schemas-sonos-com:service:Queue:1#RemoveAllTracks").

The "SOAP Message" box contains the message that describes the Action and supplies any required variables. In this case there are no variables. The \'s are there to escape the quotes which are needed. The actual value you need for this example is was taken from here. It is shown in the box below....

"<?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:RemoveAllTracks xmlns:u=\"urn:schemas-sonos-com:service:Queue:1\">
<QueueID>0</QueueID>
<UpdateID>0</UpdateID>
</u:RemoveAllTracks>
</s:Body>
</s:Envelope>"

 

2,4,6) "tLogRow_1, tLogRow_2, tLogRow_3" (tLogRow)

These components are simply connected to show the state of the data as it passes through the Job. There is no configuration required.

 

3) "Enqueue Song" (tSOAP)

This component is used to call the "AddURIToQueue" UPnP action. This is used to add a song to the Sonos queue. The screenshot below shows its configuration.....

The "Endpoint" value uses the "ip" and "port" context variables. The rest is made up of the POST URI found here (/MediaRenderer/AVTransport/Control).

The "SOAP Action" value is the same as the "SOAPACTION" parameter found here ("urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue").

The "SOAP Message" box contains the message that describes the Action and supplies any required parameters. In this case there are 5 parameters (InstanceID, EnqueueURI, EnqueueURIMetaData, DesiredFirstTrackNumberEnqueued and EnqueueAsNext), but only 2 of those will not be entirely hardcoded. We will use the "songid" context variable to enable the song being enqueued to be a little more dynamic. The EnqueueURI and EnqueueURIMetaData parameters are highlighted below to show how they have been configured. 

"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">
<s:Body>
<u:AddURIToQueue xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">
<InstanceID>0</InstanceID>
<EnqueuedURI>x-sonos-spotify:spotify%3atrack%3a"+context.songid+"?sid=9&amp;flags=32&amp;sn=2</EnqueuedURI>
<EnqueuedURIMetaData>&lt;DIDL-Lite xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot; xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot; xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;&lt;item id=&quot;00030020spotify%3atrack%3a"+context.songid+"&quot; parentID=&quot;00020000track:thriller&quot; restricted=&quot;true&quot;&gt;&lt;dc:title&gt;Thriller - Pacha 40th Anniversary Remix&lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack&lt;/upnp:class&gt;&lt;desc id=&quot;cdudn&quot; nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;&gt;SA_RINCON2311_X_#Svc2311-0-Token&lt;/desc&gt;&lt;/item&gt;&lt;/DIDL-Lite&gt;</EnqueuedURIMetaData>
<DesiredFirstTrackNumberEnqueued>0</DesiredFirstTrackNumberEnqueued>
<EnqueueAsNext>0</EnqueueAsNext>
</u:AddURIToQueue>
</s:Body>
</s:Envelope>"

 

 

4) "Play Queue" (tSOAP)

This component is used to call the "Play" UPnP action. This is used to start the Sonos queue playing. The screenshot below shows its configuration.....

The "Endpoint" value uses the "ip" and "port" context variables. The rest is made up of the POST URI found here (/MediaRenderer/AVTransport/Control).

The "SOAP Action" value is the same as the "SOAPACTION" parameter found here ("urn:schemas-upnp-org:service:AVTransport:1#Play").

The "SOAP Message" box contains the message that describes the Action and supplies any required parameters. In this case there are 2 parameters (InstanceID and Speed) and both of them are hardcoded.  The \'s are there to escape the quotes which are needed. The actual value you need for this example is was taken from here. It is shown in the box below....

"<?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:Play xmlns:u=\"urn:schemas-upnp-org:service:AVTransport:1\">
<InstanceID>0</InstanceID>
<Speed>1</Speed>    
</u:Play>
</s:Body>
</s:Envelope>"

 

 

Running the Job

To run this Job simply click on the "Run" button on the "Run" tab. If it has been configured correctly, you will see XML similar to below showing in the Run Window....and a song should start playing....

Starting job SonosAddSongToEmptyQueue at 22:24 29/01/2015.

[statistics] connecting to socket on port 3458
[statistics] connected
|<u:RemoveAllTracksResponse xmlns:u="urn:schemas-sonos-com:service:Queue:1"><NewUpdateID>9</NewUpdateID></u:RemoveAllTracksResponse>|
|<u:AddURIToQueueResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><FirstTrackNumberEnqueued>1</FirstTrackNumberEnqueued><NumTracksAdded>1</NumTracksAdded><NewQueueLength>1</NewQueueLength></u:AddURIToQueueResponse>|
|<u:PlayResponse xmlns:u="urn:schemas-upnp-org:service:AVTransport:1" />|
[statistics] disconnected
Job SonosAddSongToEmptyQueue ended at 22:24 29/01/2015. [exit code=0]

The shows that all three actions were called (RemoveAllTracks, AddURIToQueue and Play) and the responses they sent back.

A copy of the completed tutorial can be found here. It was built using Talend 5.5.1 but can be imported into subsequent versions. It cannot be imported into earlier versions, so you will either need to upgrade or recreate it following the tutorial. You will need to set the Context variables according to your system before running it.

Talend Version: 
Type of content: