[Nearing Release] Sonos Advanced Controller

Is there any way to change the line level for the Hubitat TTS voice? I have only a Sonos Port so I am at the mercy of line level, both in and out. The SAC's volume range is apparently maxed at 90. Any value over 90 fails to operate at all. Anyway, I cannot get the TTS voice anywhere near the volume of my AirPlay to Sonos music.

There's nothing that limits things at 90 in the code. 100 is the limit. Both in my code and the 'loadAudioClip' API. However that API also says "There are internal upper and lower limits for the audio clip volume level in order to prevent the audio clip from being too loud or inaudible.":

I'm guessing it's detecting anything above 90 as clipping. I tested this on my Office stereo pair and it indeed ignores anything over 90. Seeing as this seems to be a limitation of the Sonos API, my hands are tied on being able to do anything about it.

Yes, I figured this had to do with protection. This morning, after reading some more on the Sonos Port configuration, I thought maybe the Line-In level might also include streamed content since the Port has within its list of Source Levels 1-10, named input types such as A/V Component, AirPlay, Mac Computer, & Portable Player, PC. However, this is not the case.

The only thing affected by changing the Line-In Source Level is what is connected to the Line-In RCA jacks. These labels must be there just as suggestions for the user to judge what level to select for their input device. I also tried Fixed and Pass-Through Line-Out, as well as Autoplay with Autoplay volume at 100%. None of these settings increased the volume beyond Line-Out Variable at 100%.

Hence, in my configuration the only way to increase the volume (beyond your programming) for the TTS voice would be to somehow affect the HE's line-out level.

This all would probably not be as much of an issue if I were not feeding the sound through my Harmon Kardon PA4000, as it has its own internal anti-clipping protection. I have everything turned wide open and it's impossible to even startle the dog.

...alright, going to give this a try:

But there will still be the issue of TTS being significantly quieter than my AirPlay. Interrupting music will be problematic unless I were to feed music to a device connected to the Sonos Port Line-In, where the gain can be adjusted in the Sonos app. So two Sonos Ports in succession – uh, no. I was fortunate enough to have this Port gifted to me from my son-in-law who didn't end up using it in his new house.

Maybe someone else will have a suggestion...

This honestly looks quite appealing for an inexpensive way to develop sound for HE, but I don't personally have the time to work on it until later this year. Link to developer API documentation is third URL below:

https://developer.arylic.com/httpapi/?gad_source=1#http-api

Inexpensive? Hardly. $50 for the board. Plus some sort of speaker, let's say $50 there too. That's $100 and Ikea Sonos are $120 normally, and I think they're on sale occasionally for $100.

Sonos Advanced is a PAIN to write, mostly because it's a hodgepodge of various undocumented APIs. But setting up a Sonos speaker is a million times easier than setting up that Acrylic board + some sort of case + a bunch of wires all over + a speaker or two connected to it.

Hopefully Sonos releases a proper local API with documentation "soon", like they've been promising for years now... but it's been years and nothing, so I don't expect it'll ever happen.

Edit: Compared to a Sonos Port it's way cheaper. Sonos Port is horribly, almost criminally, overpriced. Sonos must figure anyone wanting to play vinyl on their whole house Sonos system must have money or something. $450 for nothing more than an ADC that spits out a stream for Sonos to play? Absurd. You don't even need that Acrylic board for this. A $5 Pi Zero W + a $5 ADC hat + SnapCast can send a record player out on AirPlay for $10 total + time.

Now... I highly value my time. Probably enough to actually just pay the $450 and 'be done with it' rather than DIY stuff, but I have no records and don't ever plan on having any so it's a moot point. I can just pay a few dollars a month for Apple Music and have 100 million songs at my fingertips in far better quality than vinyl.

6 Likes

Okay. I'm obviously in my own head on this because I'm thinking pre-amp, no speaker, a few wires, no guessing at the API. Did you look at the fantastic documentation? I consider my time quite expensive, as I probably have no more than 25 years left. I think the case was $8 and $25 for the better DAC. So, for my use case, where I already have whole-house audio, it seems quite inexpensive compared to a Sonos Port...

Yeah, but you'd be doing what, using it to AirPlay to Sonos speakers? I find AirPlay to be FAR less reliable than playing things natively through the Sonos app, so I avoid AirPlay when possible.

My thought is to avoid Sonos entirely. I chose to start with this because it seemed the least buggy and easiest to implement of the various options. Sure didn't expect volume to be what snagged me, but I did expect the snag.

OK, it's been a bit, but there's a new version up on HPM that (fingers crossed) will resolve the "could not obtain semaphore" issue that's been causing trouble.

New version invokes the parent app WAY less. Now when there's a group playing, and the 'coordinator' device needs to update the state on 'follower' devices it directly obtains a handle to the DeviceWrapper for the follower and instructs it to update its state. The state for each group is stored in a ConcurrentMap shared across all player devices, so the coordinator needs only tell the follower "update yourself" and doesn't need to send it any data.

It took me a bit to figure out how to even do this, and requires a bit of @Field usage wizardry. A child driver can call getParent() and get a handle to the parent app. The parent app can call getChildDevice() to get any of its children. What does NOT exist is a getSiblingDevice() method at the driver level... so yeah, feel free to take a look at my rinconRegistry in the code to see how you can have one driver get a DeviceWrapper for another.

While I was at it I also moved all the 'favorites matching' stuff down at the driver level as well, and store it on a shared @Field Map.

Also put more of the code within @CompileStatic annotations, which runs dramatically faster, and puts a lot more type-safety in place (which led me to find that I was missing some () on a couple of .size() calls, so finding that bug while also speeding things up, double win.

But yeah, a lot of brain-bending "how the f can I even accomplish this within the limitations of Hubitat" changes here, but functionally Sonos Advanced is the same. No new features here, just (hopefully) some bug fixes.

Let me know if that dang 'can not obtain semaphore' issue continues on version 0.5.0+. I'm really hoping this straightens that up. I can't for the life of me see anything on the old code that should be causing it in the first place, so I can't say for sure this will fix it.

Edit: 0.5.1 up with a fix for some typo issues that caused loadFavorite() to not work properly.

9 Likes

I went the Arylic / Up2Stream route at our old house. The house had wired interior and exterior speakers so I did not want to pursue a Sonos-type solution. Without that physical infrastructure, I doubt that I would do it again.

It looks like I may have deleted my personal API notes. Not sure I left any lessons learned in various communities (SmartThings, Hubitat, webCoRE). That would have been the 2020-2021 timeframe, so memory has faded.

Looks like Sonos is getting aggressive in their most recent Limited-time offers. 30% off any new speaker allows me to get the companion Era 300 I have been waiting for (since it only pairs with itself) and with @daniel.winks SAC app, it's a no brainer!

This week only, your 15% upgrade credit is worth 30% off through the Sonos Upgrade Program.* *Valid through Feb 29, 2024. Terms and conditions apply.

I've loosely followed this thread since the beginning, but haven't yet had the chance to jump in. I have a little time this weekend to devote to HA activities, so...

I do run the native Sonos app/integration and have all my Sonos devices as HE devices.

Given that, is it as simple as installing this app, then deleting the HE native app integration? Do I need to do anything with my existing Sonos devices in HE, or will they be 'taken over' automatically? Any other considerations I need to be aware of? Thanks!

1 Like

Yes Sir. In the beginning their was a little more to it, but @daniel.winks has honed this to razor sharp great replacement app for the native HE Sonos integration, which was bare bones.

Just install this app from Hubitat Package Manager, perform the app's Sonos device discovery, add the components it finds, great a few groups and you can test out the difference and remove the native HE Sonos integration when you are confident.

We use the track descriptions for some fun Name that Tune, or Text to Speech on the Sonos devices without crushing the current playlists or volume.

2 Likes

Sonos Advanced does everything the built-in app provides, so there's no reason to run it. In fact, it'll probably cause issues if you run both at the same time.

Just like the built-in app, my app also creates all of the 'devices' in Hubitat as child devices. There's no way to 'take over' other devices, and when you remove the built-in app, it'll delete the devices it created. And since everything here we're talking about are all child devices, the 'replace device' tool in Hubitat settings won't work either.

The migration path is, with the least risk, to disable the built-in app and all the devices it created. This will keep them from running and causing issues, but won't actually delete them. Then install Sonos Advanced and have it create all its devices. Then (manually) replace any references you have to devices from the built-in app with new references to the device created by Sonos Advanced. So if you previously had "Bedroom Sonos" from the built-in one in a Rule, you'll need to manually edit that rule and change the device from "Bedroom Sonos" to "Bedroom Sonos Advanced" or whatever.

It's a pain but the only way. If I could 'adopt' the devices from the built-in app and then when it's removed, since all of its children would have been adopted they could remain. But that's just not a feature Hubitat has presently or likely to ever have, since the cases for using it are really pretty rare.

6 Likes

@daniel.winks - I upgraded to the latest builds through HPM.
When I try to peform a "speak" on one of my groups, it fails with this error:

Any idea?

That seems to be somehow getting a stack overflow just checking if a setting is true or false... really a silly place to get a stack overflow error. It might be Groovy trying to be "smart" and treating a getNameOfThing() method as somehow being the same object as nameOfThing. I renamed a few things so there's no long a method named similarly to a property, and put a bit more initialization on it to make sure it's never null.

Let me know if you still run into this on 0.5.2. Be sure to click on initialize() on things if you run into errors, just to make sure it's not a "only happens right after updating the driver code" type issue (which is really hard to work around in a way that doesn't cause needless extra CPU usage the rest of the time that's not "right after updating driver code".

0.5.2 on HPM.

I put in some improvements on the logic for subscribing/resubscribing. You'll notice a few more bits of data in the data section on the player drivers. Sonos Advanced will now do a bit better job making sure a subscription is valid before trying to resubscribe.

Additionally, failed resubscriptions will only raise a 'trace level' message and then just attempt a normal subscription 5 seconds later, rather than raising a warning message and waiting 60. The usual cause for resubscriptions failing are either they're expired or somehow Sonos Advanced is trying to use a subscription ID that's otherwise no longer valid. The proper way to handle that is to just delete the SID and do a fresh subscription, and that's what it does now. It's just normal behavior, and the official Sonos app on desktop and mobile acts the same way. If you spy on it with Wireshark long enough you'll see it attempt a resubscription every now and again almost immediately followed by a regular subscription. The same 60 second timer applies for when a regular subscription attempt happens, to keep things from bogging down the hub like crazy if you have a speaker that's offline for some reason.

Also added a checkin every 900 seconds. Every 900 seconds each player device will check to see if it's been 900 seconds since the last message was received on the various 'subscription channels'. If there's been nothing in 15 minutes then it'll do a resubscription on whichever topic hasn't had any messages. If the speaker is being used, this adds next to no additional hub load. On idle speakers it will add a very small extra amount of CPU use.

I put a few more optimizations in on the various parts of the code that uses the most CPU use with this update, so the overall usage is probably about the same or possibly lower than before.

That's pretty much it on this update.

2 Likes

Thank you!
Seeing different errors now. Rebooted the hub after updating to ensure all my devices have a fresh initialize.

getting this error now when trying the Play text on the group;

@daniel.winks - Interestingly enough, the "Play High Priority TTS" works fine and no errors.
The errors above are only on the normal "Speak" command.

1 Like

I saw a few of these errors too, but only once so far