[Nearing Release] Sonos Advanced Controller

I pushed a rather large update a bit ago that should bring in current state using the local API. You’ll need to open the app and click on the “test” button to migrate the device DNIs over to a new naming scheme that allows local updates of state. You might need to click through to the “create players page”, make sure it’s populated, then click out of it and then click “test”.

I tried to put in some migration code for anyone using it before this change, but you might need to click on the “subscribe to events” button to things going.

All the control uses the Sonos cloud API still, but I’ll move most of that over to local with the next update.

6 Likes

Hi @daniel.wicks!

First off, thanks a bunch for the changes you've made. I'm still navigating my way through this, so bear with me! :sweat_smile: I've been working with HPM for app installations and updates but have not used Libraries or Bundles until now. I'm diving into the Sonos Cloud Controller. I'm stuck at not finding the "test" button. I've got the Sonos Virtual Player Devices selected, made sure they're showing up, hit next, but alas, no "test" button in sight. Any guidance on that? And, while I'm at it, where can I locate the elusive "subscribe to events" button?

Thanks a ton in advance! :rocket:

The easiest way to update a bundle is via the "Bundles" menu option on the left side of Hubitat. From there, select "Import ZIP" in the top right. Click on "Download from a URL" in the middle of the modal window that appears. Paste in this URL (which will always be the latest ZIP file for this bundle). Click on "Import". It'll take around a minute probably, maybe more, as it's going to update a number of things.

After that you should have the latest version. You can check by opening any of the "Apps code", "Drivers code" or "Libraries code" areas and seeing if the timestamp on the relevant files has been updated.

I've just pushed a new update that hides the "Test" button since it's only really for my use anyway. I put some code in the "configure()" method on the device driver, which gets called with either "initialize()" or "configure()" on the device page. So just clicking through your "Sonos Cloud Player" devices and clicking on "configure()" should be all you need to get the "current states" stuff working on your install.

For anyone else coming along after this message, everything "current states" should just work from initial install for you.

#1 is already handled via events. So the way these speakers work, you can send a "SUBSCRIBE" http request to them and every time something changes, the player will send a "NOTIFY" request with new data. It's pretty robust. I tossed "Refresh" in anyway, which just causes the player device (in hubitat) to submit new "SUBSCRIBE" requests to the players, which causes the player to send all the latest info.

#2 and #3 are harder, since I think they'll need entirely separate device drivers. Hubitat doesn't support dynamically adding/removing capabilities from devices. So if I put "Battery" and "PowerSource", I'd need to have that in all devices using that driver, including most of them that don't have batteries.

For instance, on a non-Roam device, I just get this response (from Postman) at the URL you posted:

<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="/xml/review.xsl"?>
<ZPSupportInfo></ZPSupportInfo>

If you can post the XML a Roam sends back, I can see about adding it, depending on whether or not it breaks anything. If it does, I can just toss everything into a library and make two driver stubs with only the 'metadata' sections different for them (and anything needed to implement those two capabilities). This isn't a big deal to do, I've got some of my other drivers set up this way so I'm not duplicating code between drivers that are otherwise 99% the same.

I don't have a Roam (yet), so without the XML response, it'll be hard to put that in. That said, I've been wanting to replace a crappy BT speaker I have in the basement (which goes with me to the yard for yard work too) with a Roam... so I just ordered one. If you snag the XML, I'll probably get the battery stuff in sooner, but otherwise I'll add it when my Roam arrives.

@daniel.winks

You have made excellent progress on this app & drivers. It is exciting to see these extended controls and TTS for Sonos natively in HE without disturbing the playlist queue.

I have returned the Sonos Roam I recently purchased after some period of evaluation. I was disappointed in the Roam's lack of ability to consistently stay in a Sonos group after some period of idle time. Also adding Alexa/Google to the Roam for voice services seemed to make it perform slowly and TTL group announcements were not in sync with my other Alex devices causing an echo. The sound quality, size, and water resistance of the Roam are nice, but my Sonos Move, a much improved model IMHO (which $$$ costs more), has a much better all around sound output and longer battery life for my use/tastes.

I am disappointed in Sonos 's native 'Hey Sonos' voice control which is missing a critical integration for the Spotify music service. I am hoping that the next version of 'Hey Sonos' voice control and Roam model's performance are improved so I can add it to my existing 15 Sonos S2 devices. For the Roam, as a stand alone, small footprint, lower cost mobile speaker, it is just fine, just not for my intended use as of it today.

As far as 'Battery' and 'PowerSource' capabilities, these HE capabilities are certainly not critical but a 'nice to have' IMHO. I also wish that HE would allow dynamic driver capabilities down the road, so the need for separate static device drivers could be eliminated based on each models features. I don't see this happening as the HE development community has been asking for this for years.

I have extra/unused capabilities in several of my custom HE drivers for a variety of network attached weather stations and spa devices that do not get values, hence do not show up in the device page, unless I post a sendEvent. So, again, no need to get complicated and have separate Sonos device drivers depending on the model of the Sonos device if you decide to add.

Here is the XML output that is returned for both a Sonos model at 10.0.0.32 that has a battery, ie Move or Roam and a non battery Sonos device at 10.0.0.39. The XML for a non battery Sonos is missing these same XML 'Data' tags, hence no battery/powersource attributes and no need to post sendEvents.

http://10.0.0.32:1400/status/batterystatus

<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="/xml/review.xsl"?><ZPSupportInfo><LocalBatteryStatus>
<Data name="Health">GREEN</Data>
<Data name="Level">100</Data>
<Data name="Temperature">NORMAL</Data>
<Data name="PowerSource">SONOS_CHARGING_RING</Data>
</LocalBatteryStatus><!-- SDT: 0 ms --></ZPSupportInfo>

and here is the same XML returned for a Sonos Play5 (no battery):

<?xml version="1.0" ?>
<?xml-stylesheet type="text/xsl" href="/xml/review.xsl"?><ZPSupportInfo></ZPSupportInfo>

One could basically parse the returned XML text and iterate over each Zoneplayer's XML output from this example URI:

http://10.0.0.32:1400/support/review

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="/xml/review.xsl"?><ZPNetworkInfo type = 'User'><ZPSupportInfo><LocalBatteryStatus>
<Data name="Health">GREEN</Data>
<Data name="Level">100</Data>
<Data name="Temperature">NORMAL</Data>
<Data name="PowerSource">SONOS_CHARGING_RING</Data>
</LocalBatteryStatus><!-- SDT: 0 ms --><EnetPorts></EnetPorts><!-- SDT: 0 ms --><ZPInfo><ZoneName>Move</ZoneName><ZoneIcon></ZoneIcon><Configuration>1</Configuration><LocalUID>RINCON_48A6B8E2981E01400</LocalUID><SerialNumber>48-A6-B8-E2-98-1E:E</SerialNumber><SoftwareVersion>76.2-47270</SoftwareVersion><BuildType>release</BuildType><SWGen>2</SWGen><SoftwareDate>2023-11-27 10:33:55.547386</SoftwareDate><SoftwareScm>336e80a952</SoftwareScm><HHSwgenState>allow:2</HHSwgenState><MinCompatibleVersion>75.0-00000</MinCompatibleVersion><LegacyCompatibleVersion>75.0-00000</LegacyCompatibleVersion><HardwareVersion>1.25.1.8-1.2</HardwareVersion><DspVersion>0.25.3</DspVersion><SeriesID>A100</SeriesID><MfgLocation>0</MfgLocation><DateCode>0</DateCode><HwFlags>0x30</HwFlags><HwFeatures>0x0</HwFeatures><Variant>2</Variant><GeneralFlags>0x0</GeneralFlags><IPAddress>10.0.0.32</IPAddress><MACAddress>48:A6:B8:E2:98:1E</MACAddress><Copyright>© 2003-2023, Sonos, Inc. All rights reserved.</Copyright><ExtraInfo></ExtraInfo><HTAudioInCode>0</HTAudioInCode><IdxTrk></IdxTrk><MDP2Ver>5</MDP2Ver><MDP3Ver>2</MDP3Ver><RelBuild>1</RelBuild><AllowlistBuild>0x0</AllowlistBuild><ProdUnit>1</ProdUnit><FuseCfg>OK</FuseCfg><RevokeFuse>0x1</RevokeFuse><AuthFlags>0x0</AuthFlags><SwFeatures>0x0</SwFeatures><HouseholdControlID>Sonos_PzcOS2msIrUwSEiKo4XYdGwSlB.GGyDU-1b9cNNxrAdmVvF</HouseholdControlID><LocationId>lc_ee88e7f4215241cda347eb79aff78391</LocationId></ZPInfo><!-- SDT: 4 ms --><File name='/VERSION'>76.2-47270
</File><!-- SDT: 0 ms --><File name='/proc/ath_rincon/status'>Debug info for INFRA mode at 18851596
2 Likes

Hi @daniel.winks!

First off, a big thanks for walking me through the update process. I've got some Current States now, thanks to your additions!

Whenever you have a moment (I totally get how busy life can be), could you possibly add the following to the mix:

  • groupMute
  • groupVolume
  • groupRole
  • grouped
  • audioTrackData

I used these in my WebCore pistons back in my Samsung days before the changes. Hubitat lacks Group Current States, and I'm hoping your implementation can bridge that gap. :blush:

Now, on a bit of a snag. Some functionalities aren't playing nice for me. For instance:

  • Removing a player from the group and stopping it still leaves the Current State "status" reporting "playing."
  • Muting a selected device in a group mutes most players, but not all.
  • Trying to adjust a specific player's volume (other than the group coordinator) doesn't yield any change.
  • Adjusting the Group Coordinator’s volume sets all players' volumes to the same level.

Hubitat's implementation handles some of these correctly, but the Current State reporting seems off. Any insights into why this might be happening?

Thanks a bunch for your time and expertise! :rocket:

I don't see an "audioTrackData" on the built-in one. There's "trackData" which is JSON and not very friendly. I had a typo on the "sendEvent" for it. Just pushed a new build up to GitHub with that fix, so it should mimic what the built-in driver does, after you turn on the optional toggle to enable it. That said, it's JSON and annoying to deal with, and I've broken out most of the useful stuff from it as their own sensors, such as currentArtistName, currentAlbumName, currentTrackName, etc.

As for the bugs, I'll work through those here and see what's up with them. I'm working on moving most of the things like "mute", "volume change", etc, over to using the local (undocumented) API for faster response and better reliability. Hopefully a few of these issues just resolve themselves when I do that. I'll also add a few more buttons for group-specific things, like changing group volume rather than player volume, etc. I'll likely just add these to the "group device" rather than adding yet more buttons to the players.

First tho I think I'll add "groupRole" and "grouped", as I already have that data coming in to the driver, so adding some "sendEvent" for it is easy enough. In Sonos, everything is a group. Anytime something is playing, it's a 1-device group. So a "group" as most people would think of it is really a group with 2 or more players. It's silly, but that's how they do it. If you "completely ungroup" a player, it's in a 1-player group with itself as the coordinator and the only member. Easy enough to see if there's any other players in the group with it. And also easy to see if the group a player is in has itself as the coordinator or not, for the "groupRole".

But on that note, I 'm not really sure how Sonos handles "groupMute" and "groupVolume". I know the official Sonos app does this. I can click on the speaker and it'll mute the group, and if I move the volume up and down, it moves the group volume. I'm guessing, based on what I've seen from how things work, it's just a veneer/hack, and it's really just issuing a bunch of individual commands to the members of the group. "ZoneGroupTopology" doesn't have anything regarding volume or muted state, so I'll need to implement it like Sonos does, again likely on the "group device" rather than the players.

Hey @daniel.winks

Once again, thank you for your effort. I look forward to the next release to see the fixes and improvements.

Playing around with a pair of my speakers in a group just now, the built-in Sonos driver shows the correct info for the "coordinator" speaker, but the "follower" one doesn't get anything. Looks like the XML spit out from the "follower" one is pretty blank, so I'll have to figure out a clever way to have the device driver detect that it's a follower and just pull in the data from the coordinator speaker instead of its own.

For the favorites alone, I Love this!!! Thankyou!

However, I've been playing with this, and am struggling a bit. Groups don't seem to work like I anticipate.

I created a Group with a coordinator (TVR Sonos), and a Sonos Left Right pair as a follower (FMR SYMFONISK Pair).

I then created a rule using a button to test.

The rule, as originally written:

loadFavorite('3') on Sonos Cloud - TVR Sonos
play() on Sonos Cloud - TVR Sonos
Sonos Cloud - FPR Symfonisk Pair: Stop
--> delayed: 2:00:00(cancelable

I anticipated that by commanding the cordinator, my Favorite would play on the group. Instead, only the coordinator plays, but the Sonos App believes that it is playing to all three speakers.

On a varation yesterday, music came from the Left speaker of a Stereo Pair, but nothing from the coordinator or the Right speaker!

I must be missing a step or method here...

S

Pushed another update out with some new features and bug fixes.

Biggest new feature is the addition of "current state" for Artist, Album, and track info on follower players in a group. Previously only the coordinator would display track info, since the player themselves don't report it unless they're the coordinator. Long and short of why is that the coordinator "knows" what it's playing, and the follower players aren't "playing the same thing", they're instead receiving a time-sync'ed bitstream of audio and that's all they know. They know they're "playing", but nothing else.

So with this update, I put in some changes so when a coordinator plays in a group, it sends updates out to any followers so they all have the same "currently playing" states displayed. If you're using the built-in Sonos drivers, they don't have track info for follower devices, so this is another improvement over what the built-in devices provide.

Made a few more changes to group controls (on the devices), for things like mute, volume, etc.

1 Like

Hmm, that's definitely some odd behavior.

I've got a similar automation that doesn't seem to have any issues:

Set Volume on Sonos Cloud - Kids' Room to 10
loadFavorite('10') on Sonos Cloud - Kids' Room
enableCrossfade() on Sonos Cloud - Kids' Room
repeatAll() on Sonos Cloud - Kids' Room

The "loadFavorite()" should result in the favorite starting play on its own, so there shouldn't need to be anything beyond that to play it.

I did catch a few more issues with some of the API POST commands that I cleared up in the latest version on GitHub, so maybe that'll fix the issue you're running into.

One thing to note is that you'll want to click on "configure()" on all of the player devices to make sure they've got all their subscriptions running, and all of the newer-added attributes filled with current values, otherwise things might get odd.

Also note that, due to how Sonos groups work, if you stop a 'follower' speaker, it stops everything. So if you want your rule to play on the TVR + FMR, then in 2 hours stop on the FMR only, you'll want to instead call either "removePlayersFromCoordinator()" on the group device, or call "ungroup()" on the FMR pair. Ungrouping a follower causes it to stop, which is the same behaviors you get when removing speakers from a group via the official Sonos app on your phone or computer.

I don't have anything in a stereo pair, and I've read there's some caveats to dealing with them on the API documentation. I've got another Ikea speaker on order for my office, since I've been wanting to have stereo in here anyway, as it's my primary music listening location. I should have it by this weekend, so I'll be able to test that out in a stereo pair group with my Arc as coordinator this weekend and see if I can replicate what you're seeing.

1 Like

Changing the rule as you indicated, & performing a configure on all Cloud Player devices after updating to the latest bundle seems to have worked. My rule is below:

I also have one set up on a time trigger. Hopefully it works too. The rule above uses a Play:5 as the coordinator, and a Symfonisk Stereo pair as followers.

The other rule uses a Beam as the coord, and a stereo pair of Symfonisk frames. Similar, but just a bit different. The Frame pair was the setup that was acting most strangely, so itll be interesting to see what happens!

Thank you. This integration is brilliant, even in beta!

S

1 Like

Hi @daniel.winks!

Big thanks for the recent updates! I gave the four reported issues a test spin, and happy to report everything is working like a charm now. Kudos! :tada:

I noticed you've also added all the Current States I outlined earlier – much appreciated!

Quick question: Any plans to add this app to the Hubitat Package Manager once it's out of Beta? Would be super convenient!

Thanks again for all your hard work! :rocket:

Yeah, I'm just not sure what all is involved in that. I want to do a few more changes first. Namely, I want to sort out the local control stuff, and move everything that can be done locally to being done locally, including the onboarding of the speakers.

That way the app would have an onboarding that works just like the built-in one, and setting up a developer account, getting the client key/secret, etc, would be entirely optional and only needed for enabling the cloud-dependent non-interrupting TTS.

It'd be really nice if the built-in bundles stuff saved the URL you imported a bundle from, like it does when you import a driver or app via URL. That would make updating bundles much easier, to the point that the built-in bundles would be a solid enough to not need HPM.

I see that there is a workaround for audio clip endpoint that is local only. It would be AWESOME if we ca achieve everything without the need for cloud API.

3 Likes

New bundle on GitHub.

Changes:

  • Moved a lot of the XML processing into the parent app, which results in much better handling of "update the follower players with the coordinator player's info", as well as allowing for quicker short-circuiting of processing, reducing CPU usage a bit.

  • Fixes to event subscription/resubscription. Should properly resubscribe every 10 minutes, to keep speakers in-sync with current state properly.

  • Other fixes and improvements.

Hi Daniel,

Thanks to @DGBQ pointing me this way toward your Driver and App - I've gone ahead and have it all running on HE with AudioClip working.

I've noticed that for some reason there is now a 4-5 second delay between my WebCoRE piston calling the audioClip and hearing it - not sure if this is down to the Hubitat Hub's reduced CPU versus the server running the Node.JS plugin, or something in the driver.

I've still got some work to do to get my buttons working properly in the Bathroom and En-suite using this device code, at the moment when I call my Sonos Favourite it doesn't seem to play radio stations.

Thanks for this - very useful and much better than the HE Built-in SONOS App :+1:

1 Like

Odd, I'm using mine for my doorbell, which is a ZigBee button. From the time the button is pressed until when the (stored locally on my hubitat) MP3 is played is no more than 1 second, probably around half a second. It's not instant, but it's close to it. There's some delay, but it does have to send things to the Sonos Cloud and back down again, and then the speakers themselves take a bit of time to play too. Definitely not 4-5 seconds.

There's very very little done on Hubitat to make the TTS call. It's just the child device calling a parent method (which should take close to 0 time), then the parent method. The parent method just does a few lines of stuff, getting the playerId from the child device, building a 3 or 4 element Map object with a couple of values, then calling the http endpoint on Sonos. It's definitely not doing anything remotely heavy for this.

Pretty much the only sources of any appreciable delay would be WebCoRE, the Sonos Cloud, and the speaker responding to whatever Sonos Cloud sends it. Seeing as the only difference between what you're doing and what I do is you're using WebCoRE and I'm not, then perhaps that's causing a delay. I'm using Rule Machine for all of my stuff, and it's fast.

Do you see this same delay if you just do "play track" with an "http://..." address of an MP3 from one of the speaker devices themselves?

Hey @daniel.winks

Did I miss something? Didn't you have some track data on each device before this upgrade in Current State? Or am I mixing that up with the Hubitat app? I thought for sure I saw it and was about to add it to my Webcore pistons.