6

I’m trying to implement a SMAPI service, but am running into some issues.

I’ve built a “fake” service which gives hard-coded responses for these endpoints:

  • getMediaURI
  • getLastUpdate
  • getMediaMetadata
  • getMetadata
  • getExtendedMetadata
  • getSessionId

I've followed the instructions for adding a custom service, keeping all capabilities unchecked.

I see my new test service listed on my Sonos iPhone app, but when I select it, I get a screen saying "Unable to browse music".

I'm not really sure how to debug what's happening, so feel like I'm stabbing in the dark. I don't want to make changes speculatively that may not be anything to do with the problem - that seems like a recipe for tons of wasted effort.

Is there any way of seeing the errors the Sonos Controller app (client) is having? I've checked out the http://[device ip]:1400/support/aggregate logs, but don't see any mention of my service's domain name, or see anything that seems relevant.

I know that (at least some) requests are hitting my service, specifically call to getLastUpdate and getMetadata. Here's the output of the logs:

2016-02-23T18:55:24.316373+00:00 app[web.1]: Started POST "/soap/action" for 213.86.218.202 at 2016-02-23 18:55:24 +0000
2016-02-23T18:55:24.316485+00:00 app[web.1]: Started POST "/soap/action" for 213.86.218.202 at 2016-02-23 18:55:24 +0000
2016-02-23T18:55:24.431603+00:00 app[web.1]: Processing by SoapController#get_last_update as HTML
2016-02-23T18:55:24.431611+00:00 app[web.1]: Processing by SoapController#get_last_update as HTML
2016-02-23T18:55:24.438452+00:00 app[web.1]: params.inspect: {}
2016-02-23T18:55:24.438458+00:00 app[web.1]: params.inspect: {}
2016-02-23T18:55:24.447855+00:00 app[web.1]:   Rendered vendor/bundle/ruby/2.2.0/gems/wash_out-0.10.0/app/views/wash_out/document/response.builder (7.6ms)
2016-02-23T18:55:24.447907+00:00 app[web.1]:   Rendered vendor/bundle/ruby/2.2.0/gems/wash_out-0.10.0/app/views/wash_out/document/response.builder (7.6ms)
2016-02-23T18:55:24.448279+00:00 app[web.1]: Completed 200 OK in 17ms (Views: 9.5ms | ActiveRecord: 0.0ms)
2016-02-23T18:55:24.457801+00:00 app[web.1]: Started POST "/soap/action" for 213.86.218.202 at 2016-02-23 18:55:24 +0000
2016-02-23T18:55:24.448327+00:00 app[web.1]: Completed 200 OK in 17ms (Views: 9.5ms | ActiveRecord: 0.0ms)
2016-02-23T18:55:24.464365+00:00 app[web.1]: Processing by SoapController#get_metadata as HTML
2016-02-23T18:55:24.457861+00:00 app[web.1]: Started POST "/soap/action" for 213.86.218.202 at 2016-02-23 18:55:24 +0000
2016-02-23T18:55:24.464428+00:00 app[web.1]: Processing by SoapController#get_metadata as HTML
2016-02-23T18:55:24.465101+00:00 app[web.1]: params.inspect: {"id"=>"root", "index"=>0, "count"=>100}
2016-02-23T18:55:24.465154+00:00 app[web.1]: params.inspect: {"id"=>"root", "index"=>0, "count"=>100}
2016-02-23T18:55:24.472056+00:00 app[web.1]:   Rendered vendor/bundle/ruby/2.2.0/gems/wash_out-0.10.0/app/views/wash_out/document/response.builder (3.4ms)
2016-02-23T18:55:24.472049+00:00 app[web.1]:   Rendered vendor/bundle/ruby/2.2.0/gems/wash_out-0.10.0/app/views/wash_out/document/response.builder (3.4ms)
2016-02-23T18:55:24.472248+00:00 app[web.1]: Completed 200 OK in 8ms (Views: 6.9ms | ActiveRecord: 0.0ms)
2016-02-23T18:55:24.472251+00:00 app[web.1]: Completed 200 OK in 8ms (Views: 6.9ms | ActiveRecord: 0.0ms)

Any help would be hugely appreciated!

UPDATE Here's the output from making requests against my fake service with SoapUI:

  1. getLastUpdate

    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.sonos.com/Services/1.1">
       <soap:Body>
          <tns:getLastUpdateResponse>
             <getLastUpdateResult>
                <catalog>Catalog last updated 2016-02-18 11:52:30</catalog>
                <favorites>User: Rob last updated favorites 2016-02-18 12:01:00</favorites>
                <pollInterval>30</pollInterval>
             </getLastUpdateResult>
          </tns:getLastUpdateResponse>
       </soap:Body>
    </soap:Envelope>
    
  2. getSessionId

    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.sonos.com/Services/1.1">
       <soap:Body>
          <tns:getSessionIdResponse>
             <getSessionIdResult>user0001</getSessionIdResult>
          </tns:getSessionIdResponse>
       </soap:Body>
    </soap:Envelope>
    
  3. getMetadata

    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.sonos.com/Services/1.1">
       <soap:Body>
          <tns:getMetadataResponse>
             <getMetadataResult>
                <index>0</index>
                <count>2</count>
                <total>2</total>
                <mediaMetadata>
                   <id>some_id_1</id>
                   <title>Title 1</title>
                   <mimeType>audio/mp4</mimeType>
                   <itemType>track</itemType>
                   <displayType>List</displayType>
                   <summary>Summary text 1. Summary text 1. Summary text 1.</summary>
                   <trackMetadata>
                      <artistId/>
                      <artist/>
                      <composerId/>
                      <composer/>
                      <albumId/>
                      <album/>
                      <albumArtURI/>
                      <albumArtistId/>
                      <albumArtist/>
                      <genreId/>
                      <genre/>
                      <duration/>
                      <canPlay/>
                      <canSkip/>
                      <canAddToFavorites/>
                      <rating/>
                      <trackNumber/>
                   </trackMetadata>
                   <streamMetadata></streamMetadata>
                </mediaMetadata>
                <mediaMetadata>
                   <id>some_id_2</id>
                   <title>Title 2</title>
                   <mimeType>audio/mp4</mimeType>
                   <itemType>track</itemType>
                   <displayType>List</displayType>
                   <summary>Summary text 2. Summary text 2. Summary text 2.</summary>
                   <trackMetadata>
                      <artistId/>
                      <artist/>
                      <composerId/>
                      <composer/>
                      <albumId/>
                      <album/>
                      <albumArtURI/>
                      <albumArtistId/>
                      <albumArtist/>
                      <genreId/>
                      <genre/>
                      <duration/>
                      <canPlay/>
                      <canSkip/>
                      <canAddToFavorites/>
                      <rating/>
                      <trackNumber/>
                   </trackMetadata>
                   <streamMetadata></streamMetadata>
                </mediaMetadata>
             </getMetadataResult>
          </tns:getMetadataResponse>
       </soap:Body>
    </soap:Envelope>
    
  4. getExtendedMetadata

    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.sonos.com/Services/1.1">
       <soap:Body>
          <tns:getExtendedMetadataResponse>
             <getExtendedMetadataResult>
                <mediaMetadata>
                   <id>some_id_1</id>
                   <title>Title 1</title>
                   <mimeType>audio/mp4</mimeType>
                   <itemType>track</itemType>
                   <displayType>List</displayType>
                   <summary>Summary text 1. Summary text 1. Summary text 1.</summary>
                   <trackMetadata>
                      <artistId/>
                      <artist/>
                      <composerId/>
                      <composer/>
                      <albumId/>
                      <album/>
                      <albumArtURI/>
                      <albumArtistId/>
                      <albumArtist/>
                      <genreId/>
                      <genre/>
                      <duration/>
                      <canPlay/>
                      <canSkip/>
                      <canAddToFavorites/>
                      <rating/>
                      <trackNumber/>
                   </trackMetadata>
                   <streamMetadata></streamMetadata>
                </mediaMetadata>
             </getExtendedMetadataResult>
          </tns:getExtendedMetadataResponse>
       </soap:Body>
    </soap:Envelope>
    
  5. getMediaMetadata

    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.sonos.com/Services/1.1">
       <soap:Body>
          <tns:getMediaMetadataResponse>
             <getMediaMetadataResult>
                <id/>
                <title>Fake Track Title</title>
                <mimeType>audio/mp4</mimeType>
                <itemType>track</itemType>
                <displayType/>
                <summary/>
                <trackMetadata>
                   <artistId>artist:12345</artistId>
                   <artist>Bach</artist>
                   <composerId/>
                   <composer/>
                   <albumId/>
                   <album/>
                   <albumArtURI>https://sonos.therocketfuel.com/images/istockphoto_1945235_satisfaction.jpg</albumArtURI>
                   <albumArtistId/>
                   <albumArtist/>
                   <genreId/>
                   <genre>Classical</genre>
                   <duration>459</duration>
                   <canPlay>true</canPlay>
                   <canSkip>true</canSkip>
                   <canAddToFavorites>false</canAddToFavorites>
                   <rating/>
                   <trackNumber/>
                </trackMetadata>
                <streamMetadata></streamMetadata>
             </getMediaMetadataResult>
          </tns:getMediaMetadataResponse>
       </soap:Body>
    </soap:Envelope>
    
  6. getMediaURI

    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://www.sonos.com/Services/1.1">
       <soap:Body>
          <tns:getMediaURIResponse>
             <getMediaURIResult>https://sonos.therocketfuel.com/audio/960bpm.m4a</getMediaURIResult>
          </tns:getMediaURIResponse>
       </soap:Body>
    </soap:Envelope>
    
Rob Isenberg
  • 161
  • 5

2 Answers2

1

Have you tried using SoapUI, adding your endpoint and hitting it directly to see what you are getting back?

1

The solution

I finally figured it out, and essentially my service was returning valid XML, but that wasn't correct for the WSDL schema.

I debugged this by using SoapUI as follows:

  1. Create a new SOAP project

    • Project name: whatever you like
    • Initial WSDL: points to where your service serves up the WSDL
    • Check both "Create Requests" and "Create TestSuite"
  2. Ctrl-click (right-click) on the TestSteps folder and click to "Add Step", then select "SOAP Request"

  3. Give it any name you like (I used e.g. actionName_validate)

  4. Choose the SOAP operation/action you want to validate

  5. Make sure you tick the checkbox for "Add Schema Assertion (adds validation that response complies with its schema)" and click OK

  6. Double-click your newly created test step from the list on the left and run it (click the green play button at the top left)

  7. If your service's response complies with the WSDL, you'll see a green circle at the bottom left where it says "Assertions". If not, it will be red and it will give you a complete list of everything that's wrong with your response.

  8. Fix any problems with your schema one by one until it's green

Extra details if you're using Ruby

Also, here's some more details that may help someone in this situation, specifically if you're building your service in Ruby...

The tooling around SOAP services in Ruby is not as good as in Java (or even PHP). I was using the wash_out gem which, whilst good for a completely new SOAP service, isn't expressive enough to allow you to define type mappings etc that match the official Sonos WSDL. I also couldn't manage to get it to generate the correct XML responses (it missed off a key xmlns attribute needed, or wouldn't namespace all the elements.)

I got round this by manually serving up the XML responses with templates as this was the simplest approach at first. You can use .xml.erb templates or alternatively use xml builder syntax with .xml.builder templates. Really though, you can generate the XML any way you want e.g. to_xml or some other presenter type library.

For the WSDL, I switched to using the official Sonos SMAPI WSDL, and just changing the service address right at the bottom to point to where this modified WSDL is served up by my service.

Rob Isenberg
  • 161
  • 5