[Nearing Release] Sonos Advanced Controller

I do, there's an optional toggle for "Disable Track Data Events". I have it off by default since it's a JSON object and a pain to work with. Everything that's exposed by turning that toggle on is also just exposed as straight one-item string attributes too. So if you want to do something with track number, artist name, etc, you can do that just using the attributes for those, rather than having to parse them out of the trackData blob.

But if you turn that toggle on, it gives what I can best determine the built-in one does.

I'd personally recommend using the individual states for whatever you're wanting, they're easier to deal with, IMO.

I put in quite a bit of changes/fixes/enhancements on "current state" stuff today, so I'd also recommend grabbing the latest bundle if you haven't. It has the current state stuff much improved, especially for 'follower' speakers.

Edit: what in the trackData are you utilizing? I can make sure if there's anything missing from within it that needs added as a plain attribute I can get it added.

Hi @daniel.winks,

A massive thanks for the recent update – your hard work is genuinely appreciated!

I've been playing around with the "disable artist, album, and track events" toggle, and it's a fantastic addition. However, I've noticed a bit of instability. While it works some of the time, there are moments when it seems a bit temperamental. Currently, I'm struggling to get it to display artist, album, and track events reliably – it's stuck On but not displaying any of the related Current states. I have tried turning it On or Off and saving the preferences in between with no luck.

I've also run into a minor glitch in my testing. After using the "disable artist, album, and track events" toggle, it can occasionally locks up my Sonos system. For example, when trying to change the volume or switch to a new favorite in the device, it just won't respond using the device. The workaround involves ungrouping all players in the Sonos app, then using the Cloud Group device to join the player back to the coordinator. That usually gets things back into gear, and I can resume using the integration for actions like Stop, Play, changing the volume, selecting a favorite, etc.

Now, onto my quest: I'm on the lookout for a way to capture and display the current favorite being used. In Webcore (in the past), I've set up a piston to check the specific favorite playing at a certain time and switch if needed (mostly radio stations). In SmartThings and Hubitat's app, I used something like audioTrackData to identify if a particular favorite was on by the radio’s displayed station name. I hoped to use "currentAlbumName" to cover this when you can fix it, or is there another way to get this data in the Current States? Additionally, I'd love to display the Favorite in a Dashboard tile. I trust this is as clear as mud.

Any thoughts on achieving this without a major overhaul? I'm loving the current ability to Group and Pick Favorites, just hoping to add a bit more functionality to the setup where possible.

Thanks a bunch for your continued support! :blush:

Daniel,

I just updated the bundles, and hit "configure" on all the Cloud Players, and my logs picked some interesting entries...

Summary

512024-01-02 06:23:05.472 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:23:05.092 PMerrorjava.lang.NullPointerException: Cannot invoke method tokenize() on null object on line 827 (method processAVTransportMessages)

app:512024-01-02 06:23:01.140 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:23:00.743 PMerrorjava.lang.NullPointerException: Cannot invoke method tokenize() on null object on line 827 (method processAVTransportMessages)

app:512024-01-02 06:23:00.421 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:22:59.637 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:22:59.236 PMdebugSonos Cloud Controller: Just removed from group!

app:512024-01-02 06:22:55.981 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:22:55.109 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:22:54.676 PMdebugSonos Cloud Controller: Just removed from group!

app:512024-01-02 06:22:49.007 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:22:48.524 PMerrorjava.lang.NullPointerException: Cannot invoke method tokenize() on null object on line 827 (method processAVTransportMessages)

app:512024-01-02 06:22:43.920 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:22:43.631 PMerrorjava.lang.NullPointerException: Cannot invoke method tokenize() on null object on line 827 (method processAVTransportMessages)

app:512024-01-02 06:22:43.351 PMdebugSonos Cloud Controller: Current group name: Loft Current coordinator: Loft Current group member count: 1

app:512024-01-02 06:22:43.349 PMdebugSonos Cloud Controller: Current Group Member IDs: [Loft]

app:512024-01-02 06:22:42.937 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 921 (method processZoneGroupTopologyMessages)

app:512024-01-02 06:22:42.276 PMdebugSonos Cloud Controller: Just removed from group!

app:512024-01-02 06:22:38.347 PMdebugSonos Cloud Controller: Current group name: Loft Current coordinator:

I dunno if its important, but thought it was worth saving, just in case.

S.

I may have your quest at an end:

Latest version on GitHub stores a Map of favorites (updated every 3 hours), and upon each "AVTransport" it checks what's on 'enqueuedUri' and if it matches a favorite, it displays that favorite. If there a non-favorite playing, it has 'null'.

To get this working, beyond updating to the latest bundle, you need to open the app (just to the first page) and click on "Done" at the bottom. This invokes the "configure()" method on the app, which is where I added the call to appGetFavorites() and a scheduled refresh for that method every 3 hours. Or you can click on "getFavorites()" from any player device.

Both will cause the app to pull in a cached Map and schedule the update for every 3 hours. getFavorites() from a player device will cause the app to update its cached copy and start the 3 hour timer over, too, in cause you need to update it immediately for any reason. Not sure if I should make that refresh interval configurable or not.

As for the toggle issue, I noticed some goofiness myself earlier when toggling that on/off. I changed how it clears those attribute states out, not sure if that helps or not, but I think it may. I'll look into it. a bit more later.

Please update to the latest bundle on GH and let me know if this happens again. I may have got that fixed.

1 Like

New bundle on GitHub.

Changes:

  • Added local control for setting player volume and mute/unmute. Will add more local control in the near future. Let me know if the player volume has any issues.

  • Added "currently playing favorite" state. If you've got a favorite playing, it should show you the name and album art for the favorite. You need to click on "getFavorites()" from any player device once to build the initial cache needed for matching. The matching cache updates every 3 hours, and can be updated immediately by calling "getFavorites()" again from any player. Let me know if there's a need to adjust the update interval or change how an immediate refresh can be performed.

Will do.

...it's different, seems a bit better.

Summary

512024-01-02 09:05:00.320 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 990 (method processZoneGroupTopologyMessages)

dev:1112024-01-02 09:04:59.723 PMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_703.setCurrentArtistAlbumTrack() is applicable for argument types: (null, null, null, java.lang.String) values: [null, null, null, 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

dev:1142024-01-02 09:04:59.629 PMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_703.setCurrentArtistAlbumTrack() is applicable for argument types: (null, null, null, java.lang.String) values: [null, null, null, 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

app:512024-01-02 09:04:38.408 PMerrorjava.lang.NullPointerException: Cannot invoke method keySet() on null object on line 905 (method processAVTransportMessages)

app:512024-01-02 09:04:37.329 PMerrorjava.lang.NullPointerException: Cannot invoke method updateDataValue() on null object on line 990 (method processZoneGroupTopologyMessages)

app:512024-01-02 09:04:32.444 PMdebugSonos Cloud Controller: Current group name: Loft Current coordinator: Loft Current group member count: 1

app:512024-01-02 09:04:32.442 PMdebugSonos Cloud Controller: Current Group Member IDs: [Loft]

dev:1102024-01-02 09:04:32.165 PMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_703.setCurrentArtistAlbumTrack() is applicable for argument types: (null, null, null, java.lang.String) values: [null, null, null, 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

dev:102024-01-02 09:03:20.775 PMinfoBMT Bath Humidity Sensor 1 on Hub-3: battery is 100%

dev:102024-01-02 09:03:07.962 PMerrorjava.lang.NumberFormatException: null on line 197 (method parse)

S.

1 Like

**Edit: Found the Issue - My ISP uses 1.1.1.1 as it's default DNS service (CloudFlare) which adds latency calling the SONOS API, changed this to OpenDNS and Google and all works immediately now

Hi Daniel,

I've just done this video comparing the 2 actions - the first is using the 'Play Track and Restore' via the device page in HE using the local URL to the audio clip, and the second is using the Node AudioClip API HTTP call to play the same clip:

As you can see the action from the device page is around 4-5 seconds delayed with the Node HTTP call being instant.

Let me know if there's anything else you want me to try?

Best wishes,
Guy

That's really odd that it would cause latency issues. CloudFlare's DNS is really good, and their 1.1.1.1 service doesn't do any sort of filtering. My Hubitat has the example DNS overrides set (8.8.8.8, 8.8.4.4, 1.1.1.1, 1.0.0.1, 9.9.9.9), because my LAN has a rather restrictive adblocking DNS on it, and I don't want to deal with issues arising from having DNS queries on my Hubitat getting blocked. Assuming Hubitat doesn't use these round-robin and is fail-over/fallback instead, then unless both of Google's DNS are down I'd never have used CloudFlare's.

I'll be adding local non-interrupting TTS soon anyway, that won't need any DNS lookups. Apparently both the WSS endpoint and the REST endpoint on (S2 only) Sonos speakers accepts pretty much the exact same requests that their Cloud API does. It is S2 only, however, so I'll need to make sure that S1 speakers aren't using it. I don't have any S1 speakers, but my S2 ones all have "swGen: 2" on their Data section (which is pulled from the speaker when being set up). So if you (or anyone else here) has some S1 speakers, let me know if that says "swGen: 1" there, so I can use that to if/else the logic on which TTS to use.

I've already moved the "player set level" over to using the SOAP/UPnP local controls and will move over everything else that can be controlled locally to that now that I have it figured out. That should be S1 and S2 compatible, but it doesn't have the non-interrupting TTS, as that's an S2 only feature apparently (S2 only, locally, that is).

3 Likes

Hey @daniel.winks,

Love the look of that currentFavorite feature! Quick question: does it show the radio station when it's the selected Favorite?

I ran into a little hiccup after applying your latest update. The currentFavorite isn't displaying, and Get Favorites isn't working anymore. I followed your instructions to open the app to the first page and click on "Done" at the bottom, but no luck.

This is the Current States for one of my players as an example:

Current States

  • bass : 0
  • currentAlbumName : SB00009
  • currentCrossfadeMode : off
  • currentRepeatAllMode : off
  • currentRepeatOneMode : off
  • currentShuffleMode : off
  • currentTrackDuration : 0:00:00
  • currentTrackNumber : 1
  • groupCoordinatorName : Kitchen Player
  • groupMemberCount : 2
  • groupMemberNames : [Kitchen Player, Office Player]
  • groupName : Kitchen Player + 1
  • isGroupCoordinator : on
  • isGrouped : on
  • level : 25
  • loudness : off
  • mute : unmuted
  • status : playing
  • transportStatus : playing
  • treble : 0
  • volume : 25
  • currentArtistName : null
  • currentTrackName : null

Another interesting note is that

  • groupName : Kitchen Player + 1
    Is not correct I have 8 total players in the group.
    2024-01-03 07_46_53-Sonos

Also looking at the Bathroom for example this is the Current States

Current States

  • bass : 0
  • currentAlbumName : SB00009
  • currentCrossfadeMode : off
  • currentRepeatAllMode : off
  • currentRepeatOneMode : off
  • currentShuffleMode : off
  • currentTrackDuration : 0:00:00
  • currentTrackNumber : 1
  • groupCoordinatorName : Bathroom Player
  • groupMemberCount : 1
  • groupMemberNames : [Bathroom Player]
  • groupName : Bathroom Player
  • isGroupCoordinator : on
  • isGrouped : off
  • level : 32
  • loudness : off
  • mute : unmuted
  • status : playing
  • transportStatus : playing
  • treble : 0
  • volume : 32

Looking at the Laundry Room this is the Current States

Current States

  • bass : 0
  • currentAlbumName : ppm-jazz24mp3-ibc1
  • currentCrossfadeMode : off
  • currentRepeatAllMode : off
  • currentRepeatOneMode : off
  • currentShuffleMode : off
  • currentTrackDuration : 0:00:00
  • currentTrackNumber : 1
  • groupCoordinatorName : Laundry Player
  • groupMemberCount : 1
  • groupMemberNames : [Laundry Player]
  • groupName : Laundry Player
  • isGroupCoordinator : on
  • isGrouped : off
  • level : 26
  • loudness : off
  • mute : unmuted
  • status : playing
  • transportStatus : playing
  • treble : 0
  • volume : 26

Any suggestions on how to troubleshoot and fix this?

You could give a "configure()" on each player a try. I'm still working on getting the subscriptions to work 100%... seems like they fail to resubscribe every so often. Without the subscriptions working, the players don't get updates as they should.

Are there any errors in the logs when you click on getFavorites?

Hi @daniel.winks

Each time I update I have selected on each device Refresh, Configure, Subscribe to Events.

Here's part of the Laundry Player log

dev:4542024-01-03 10:28:18.563 AMwarnmethod parse of child device Sonos Cloud - Laundry Player ran for 120,544ms

dev:4542024-01-03 10:24:09.397 AMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_846.setCurrentArtistAlbumTrack() is applicable for argument types: (java.lang.String, java.lang.String, java.lang.String, java.lang.String) values: [, , , 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

dev:4542024-01-03 10:04:09.263 AMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_846.setCurrentArtistAlbumTrack() is applicable for argument types: (java.lang.String, java.lang.String, java.lang.String, java.lang.String) values: [, , , 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

dev:4542024-01-03 09:44:09.418 AMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_846.setCurrentArtistAlbumTrack() is applicable for argument types: (java.lang.String, java.lang.String, java.lang.String, java.lang.String) values: [, , , 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

dev:4542024-01-03 09:24:09.471 AMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_846.setCurrentArtistAlbumTrack() is applicable for argument types: (java.lang.String, java.lang.String, java.lang.String, java.lang.String) values: [, , , 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

dev:4542024-01-03 09:04:09.288 AMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_846.setCurrentArtistAlbumTrack() is applicable for argument types: (java.lang.String, java.lang.String, java.lang.String, java.lang.String) values: [, , , 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

dev:4542024-01-03 08:44:09.419 AMerrorgroovy.lang.MissingMethodException: No signature of method: user_driver_dwinks_Sonos_Cloud_Player_846.setCurrentArtistAlbumTrack() is applicable for argument types: (java.lang.String, java.lang.String, java.lang.String, java.lang.String) values: [, , , 0]
Possible solutions: setCurrentArtistAlbumTrack(java.lang.String, java.lang.String, java.lang.String, java.lang.Integer) on line 943 (method setCurrentArtistAlbumTrack)

And just a few minutes ago I tried Remove Players from Coordinators. It took a noticeable amount of time to separate them. Then I tried to joing them back but it did not work.
Here are some Logs

dev:4622024-01-03 10:28:56.515 AMwarnmethod joinPlayersToCoordinator of child device Sonos Group: Hold House Group ran for 120,420ms

dev:4622024-01-02 10:04:39.450 PMwarnmethod joinPlayersToCoordinator of child device Sonos Group: Hold House Group ran for 120,546ms

dev:4622024-01-02 10:02:37.809 PMwarnmethod joinPlayersToCoordinator of child device Sonos Group: Hold House Group ran for 120,538ms

dev:4622024-01-02 10:02:06.237 PMwarnmethod joinPlayersToCoordinator of child device Sonos Group: Hold House Group ran for 120,411ms

Thanks, found a copy/paste error... left something as a String when it needed to be an Integer. Fixed that.

As for the "ran for 120,000ms" thing... that's super odd. There's nothing that does any "work" in those method calls. It's just a bit of "getDataValue()" which should be next to instant, then it takes the couple bits of data from the "getData" stuff and uses it to put together the web request, which then calls asyncHttpGet(). It just doesn't make any sense whatsoever that any part of the dozen or so lines of code involved here would take more than a few milliseconds. Unless asyncHttpGet can somehow take 100+ seconds on its own, I don't know what could be causing any sort of hangup there.

Well, I left the above text here, while I tried a few things myself. Looks like I left "singleThreaded: true" on the app and driver code (which I had tossed in to troubleshoot something else, and isn't actually needed). I removed it on mine and no longer am I seeing 120 second delays on any of the 4 commands on the group devices. I definitely was able to replicate your issue on them tho. I even got a bunch of "java.lang.RuntimeException: Failed to acquire semaphore for method componentJoinPlayersToCoordinator within 120 seconds" messages, which lead me to realize it was likely a threading issue.

I'll focus on getting the join/group/leave commands to use the local endpoint next. It's WAY faster with them set to singleThreaded: false, but still a bit slower than if it were local.

New bundle on GH with multithreading allowed. See if that fixes it for you too.

1 Like

Yeah, I've noted a few hiccups with the favorites too. It's just doing some matching in the background with the data Sonos gives me on the "getFavorites" API and what the "EnqueuedTransportURI" has.

What sort of radio station is this? I'm only using my Sonos with Apple Music, so I only get those EnqueuedTransportURI to play with on the match-up logic. I could add something from whatever radio you're using and see if there's anything else that the code needs to better handle those too.

I'll re-work the favorites a bit here, as I'm seeing that mine doesn't seem to always properly detect when I'm no longer playing a favorite and keeps the last one I played on the states UI.

Hi @daniel.winks

I have not yet updated to see how things are going, but I wanted to answer your question.
I used a variety of providers.

  • TuneIn
  • myTuner
  • Apple Music
  • Sonos radio

Here's a view from my Sonos app.
BTW, the station currently playing or displaying is a result of using Hubitats integration via Webcore “Set Track to,” which is "x-rincon-mp3radio://https://jazzfm91.streamb.live/SB00009."

When I use Sonos Cloud currently, it doesn’t work for loading favourites.

Yeah, that's a quite different format than what I see when I'm loading/playing my favorites (which are all off Apple Music). They're all similar to this: "x-rincon-cpcontainer:1004206calbum%3a434113426?sid=204&flags=8300&sn=9"

I'll have to load up a favorite or of the others you list here and see what they spit out from the API calls and update the 'matching' logic to handle that too.

This is how it looks if I directly pick the station in the Sonos App.

So I just added TuneIn and myTuner, and aded that "jazz24" station on my favorites. It works like any other with both displaying that it's playing and loading it.

So the "loadFavorite", it doesn't work with stuff like " x-rincon-mp3radio://https://jazzfm91.streamb.live/SB00009". Instead it uses the "favorite Id" that Sonos gives each favorite. So, in my case, it used "20" for this newly added favorite. When you click on "getFavorites" it lists the number, name, and album art for them. It's that number that you use to load on, not the x-rincon stuff.

Now I do see that it doesn't seem to update the "currentAlbumName" properly for this, which I'll have to look into:

But loading this favorite seems to work on my HE.

Hi @daniel.winks,

Great news! After applying the latest update, I'm happy to report that grouping and ungrouping are back in action with no noticeable delays in the logs.

I unintentionally missed sharing the outcome regarding Current States when selecting a favorite using the Sonos Apps in my previous post. Take a look at the results below – there seem to be quite a few null values, as anticipated.

Current States:

  • bass: 0
  • currentAlbumName: null
  • currentCrossfadeMode: off
  • currentRepeatAllMode: off
  • currentRepeatOneMode: off
  • currentShuffleMode: off
  • currentTrackDuration: null
  • currentTrackNumber: 0
  • groupCoordinatorName: Laundry Player
  • groupMemberCount: 1
  • groupMemberNames: [Laundry Player]
  • groupName: null
  • isGroupCoordinator: on
  • isGrouped: off
  • level: 20
  • loudness: off
  • mute: unmuted
  • status: playing
  • transportStatus: playing
  • treble: 0
  • volume: 20
  • currentArtistName: null
  • currentTrackName: null
  • nextArtistName: null
  • nextAlbumName: null
  • nextTrackName: null

Just for kicks, I experimented with the 'Get Favorites' and 'Load a Favorite' functions, but nothing seemed to happen. As per my understanding from the post I'm responding to, this outcome aligns with expectations.

Let me know if there's anything specific you'd like to explore or if you have any questions about these findings! :blush: