Bose soundtouch create_zone

I was just thinking that you could use the hardware version to populate the supported_features

@jasebob Ahhh, understood. I already have checks in the integration code for device-specific features. Each SoundTouch device will return /supportedURLs, which basically indicates what the device is (and is not) capable of.

For example, the ST-10 does not support audio mode capabilities, tone controls, level controls, etc, while the ST-300 does. The ST-300 /supportedURLs will return extra information, which indicates it supports these features. Like so:

  <URL location="/audiodspcontrols" />
  <URL location="/audioproductlevelcontrols" />
  <URL location="/audioproducttonecontrols" />
  <URL location="/audiospeakerattributeandsetting" />
  <URL location="/productcechdmicontrol" />
  <URL location="/producthdmiassignmentcontrols" />

The SoundTouchPlus integration retrieves and caches this list of features when it is initialized, so it’s just a matter of querying the supported url’s cache to check if a feature is enabled or not for a device. Here’s the code for the select_sound_mode processing to give you an example (ST-10 does not support this, ST-300 does):

  _logsi.LogVerbose(STAppMessages.MSG_PLAYER_COMMAND + " - SoundMode='%s'", "Select Sound Mode", self.name, self.entity_id, sound_mode)

  # does device support audio dsp controls?
  if SoundTouchNodes.audiodspcontrols.Path in self._client.ConfigurationCache:
      
      # is sound_mode an audio mode name?  if so, then we need the value.
      audioDspAudioMode:str = AudioDspAudioModes.GetValueByName(sound_mode)
      if audioDspAudioMode is not None:
          sound_mode = audioDspAudioMode

      # does sound mode list contain the specified sound_mode?
      # if so, then change the sound mode; otherwise log an error message.
      config:AudioDspControls = self._client.ConfigurationCache[SoundTouchNodes.audiodspcontrols.Path]
      if sound_mode in config.SupportedAudioModes:
          cfgUpdate:AudioDspControls = AudioDspControls(audioMode=sound_mode)
          self._client.SetAudioDspControls(cfgUpdate)
      else:
          _logsi.LogError("'%s': Specified sound_mode value '%s' is not a supported audio mode; check the sound_mode_list state value for valid audio modes" % (self.name, sound_mode))
  else:
      _logsi.LogWarning("'%s': Device does not support AudioDspControls; cannot change the sound mode" % (self.name))

In summary, I don’t have to have specific checks for device types in my code - the device itself tells me if it supports a feature or not based on the /supportedURLs response.

If a device does not support a feature, then I will issue a warning log message so the user can see something in the HA logs that indicate the feature is not supported. I also try to document which devices support what features in the wiki, but it’s tough to do since I only have the ST-10 and ST-300 device types.

I really wish Bose would have published more detailed information about their API’s and such, but they closed the vault and threw away the key when they shut down their developer program in 2021-ish. Unfortunately for me, I was not a member of the program so I never got to see their docs back in the day.

Anyway, hope that helps you understand more of how things work under the covers so to speak.

I think that may be due to your ST-10 being a first generation device. I believe all of my ST-10’s are of the latest generation (not sure though, as it’s just a serial number), and they can play https url’s and TTS messages just fine. I thought I read somewhere in a forum that there is a time limit on earlier generation devices that limit the time (or size maybe) of the url being played.

btw, play_info is used to play TTS messages, as they are simply a call to the google translate service, which returns a response of the MP3 file that is generated for the text message.

Hi Todd

For the toggle function, I have a script solution for now which is working well. See below.

soundtouch_toggle_join:
  alias: SoundTouch Toggle Join
  fields:
    master:
      description: Master of the group
      example: media_player.soundtouch_kitchen
    slave:
      description: Slave device to be joined to the Master group
      example: media_player.soundtouch_dining
  sequence:
    - if:
        - condition: state
          entity_id: "{{ slave }}"
          state: "off"
      then:
        - service: media_player.join
          target:
            entity_id: "{{ master }}"
          data:
            group_members:
              - "{{ slave }}"
      else:
        - service: media_player.unjoin
          target:
            entity_id: "{{ slave }}"
          data: {}
  mode: single

@jasebob I added the Toggle Zone Member service that you suggested with release v1.0.25; I just pushed it out a couple of minutes ago.

The service toggles the given zone member to or from the master device’s zone. A new zone will be created automatically if needed.

Note that you have to issue the call to the media_player that is the MASTER of the zone.

Example - The following will toggle the bose_st10_2 zone member in the master zone controlled by bose_st10_1:

service: soundtouchplus.zone_toggle_member
data:
  entity_id_master: media_player.bose_st10_1
  entity_id_member: media_player.bose_st10_2

Let me know how it works for you.

OK Cool and thank you.
I’ve gone away for a couple of days, so I will try it over the weekend.

Perhaps this should really be a media_player function, calling join / unjoin. I wonder what would happen if you built a pull request to the base class? :slight_smile:

Hi Todd

I’ve upgraded to 1.0.26 and confirmed ZoneMemberToggle works well. Thanks!

I mostly have the preset and recent list stuff working too. All except for the recent list. The recent list service call is not returning the album art as a containerArt attribute. Its just missing in the retuned data. This does appear in the dictionary for Get Preset List and works well.

Here are the first two items I get back from Get Recent List.

recents:
  recent:
    - "@deviceID": F45EAB5367D7
      "@id": "2453655922"
      "@createdOn": "1703974731"
      "@SourceTitle": Spotify (**********@gmail.com)
      ContentItem:
        "@source": SPOTIFY
        "@type": tracklisturl
        "@location": >-
          /playback/container/c3BvdGlmeTpwbGF5bGlzdDozN2k5ZFFaRjFFMzdNY1JyaTNjMWQy
        "@sourceAccount": *****************************
        "@isPresetable": "true"
        itemName: Daily Mix 1
    - "@deviceID": F45EAB5367D7
      "@id": "2481184716"
      "@createdOn": "1703974711"
      "@SourceTitle": Spotify (*****************@gmail.com)
      ContentItem:
        "@source": SPOTIFY
        "@type": tracklisturl
        "@location": >-
          /playback/container/c3BvdGlmeTphcnRpc3Q6NmVVS1pYYUtrY3ZpSDBLdTl3Mm4zVg==
        "@sourceAccount": ********************************
        "@isPresetable": "true"
        itemName: Ed Sheeran

Do you use your ST10s as a stereo pair? How do you integrate them?
At the moment I only added one device to soundtouchplus and that seems to work just fine - except for the reboot service call. This only re-boots one of the two units.
I am wondering if you should automatically reboot the stereo pair, or tell people to add both devices?

@jasebob - I have not been able to test with Spotify, as I only have a free account; you have to have the premium level in order to add the Spotify music service to the device. With that said, forgive my 20 questions …

  1. Are all of the recent items that do not contain the cover art from the SPOTIFY source?
  2. Can you issue the following two commands from a desktop browser - make sure you are playing a track from the Spotify source on your device when you do. Change the IP address to your SoundTouch device IP address:
    http://192.168.1.131:8090/nowPlaying
    http://192.168.1.131:8090/recents
    Can you send me the output of those in a private message? I’m looking for anything in the results that looks like a url to a cover art image. All of the sources I have been able to test with return the artwork url in the containerArt node of the contentItem parent node of the recent list. That may not be the case for Spotify, but I am not sure as I have never been able to test with it.

I have used my ST-10’s as a stereo pair, but have them both added to HA as separate devices. If memory serves, the first device controls both devices though when SoundTouch WebService API calls are issued. Note that will not apply to the reboot function though, as the reboot is performed via a call to the SSH service on the device (not SoundTouch WebServices API). You would need to manually issue a call to the reboot service for the second device in order to reboot both devices.

I know when I have mine as a stereo pair, it automatically changes the device name that initiated the pair request (e.g. the main device) to something like ST-10-1 + ST-10-2, instead of keeping the ST-10-1 name. The main device seems to send commands to the paired device to keep them in sync after that, but again only SoundTouch WebService API commands (not SSH server commands).

Hi Todd

Yes, everything is Spotify for me and I have a premium family account. My preset selections are also all Spotify and they are working.

So
/nowPlaying returns a containerArt tag as part of the ContentItem and it has a nice URL.
/presets does the same, with a containerArt returned in the XML
/recents does not. The contentItem only has a itemName tag. The XML is actually quite short and the only thing remotely like a path is the location field of the ContentItem which is the referring to the Spotify path to play the track.

What do you get in the dreaded Bose Phone App?
When I go to recently played in there all the items have a generic Spotify icon.
Whereas when I click on the presets each item has a piece of album art.

Perhaps I can also help you figure out the station list for Spotify. I can’t get this service call to work

@jasebob Ya, same here … in the SoundTouch App, all of my recently played items are set to generic icons for ALL sources (Pandora, TuneIn, etc). I get the same results when I generate the list from the device using the http://192.168.1.131:8090/recents call as well. Presets are still fine though, just like yours.

This tells me that the Bose SoundTouch device is removing the cover art from the recent list items! I have no idea why it does that - maybe to conserve space as some image url’s can be quite long? Maybe it’s due to the fact that most image art url’s are from a CDN source, and are temporary in nature; e.g the device verifies the image url’s before returning them in the recents list, and they may no longer exist since they are on a CDN source? Maybe they remove them due to copyright / digital licensing rights? Not sure on that one.

Here’s my recent list now (it was fully populated with artwork yesterday) - the Zach Williams cover art is due to me playing that Pandora station today.

As it happens, I did receive a free 90-day trial offer from Spotify for a premium account a few days ago. I think I will get signed up for that, and try to figure out the station list navigation at least.

Wish I had better news on the recents list.

I just played something off of Tunein. Bingo! There is the containerArt which missing from a Spotify entry. So its definitely related to Spotify.

<recents>
<recent deviceID="000C8AC06D11" utcTime="1703995767">
<contentItem source="TUNEIN" type="stationurl" location="/v1/playback/station/s34804" sourceAccount="" isPresetable="true">
<itemName>KQED-FM</itemName>
<containerArt>http://cdn-profiles.tunein.com/s34804/images/logog.png?t=637231950230000000</containerArt>
</contentItem>
</recent>

So the really nasty solution is to Cache the ContainerArt and match it to the location string. But that needs non-volatile storage for the integration and would be a lot of work.

I just changed the recent selections from a picture-entity to a button card.
Its not as pretty but is very functional and gives access to all the recently played list.

So, in your example code replace

        'type': 'picture-entity',   
        'entity': 'input_boolean.always_enabled_helper',
        'image': itmContainerArt,   
        'show_state': false,   
        'show_name': true,  
        'name': itmItemName,   

with

        'type': 'button',
        'entity': 'input_boolean.always_enabled_helper',
        'show_state': false, 
        'show_icon': false,
        'show_name': true,
        'name': itmItemName,

This could be made prettier with the custom-button card so that the buttons are rectangular and better suited to a line of text.

@jasebob Nice - I added the button example to the wiki.

I was able to sign up for the free 90-day trial of Spotify Premium-level membership, so will keep you posted on what I find.

I updated the Play Content Item service examples with the various Spotify types (album, artist, etc), if you want to have a look at that.

@jasebob Regarding the Bose Developer Portal from back in the day … you didn’t happen to see (or pull) anything from there besides the Bose SoundTouch API Reference.pdf that you posted a few weeks ago did you?

I know it’s a long shot, but I am specifically looking for anything related to navigate or search services. These are the 2 services that I currently use to pull information from other Music Services that I have researched thus far: Pandora, Local Music, and NAS Server Music.

I am hoping that Spotify is accessed the same way, but I cannot figure out the XML request to query Spotify. I am wondering if they scrapped support for those when they started supporting the “Spotify Connect” methodology.

Any thoughts?

Hi Todd

Happy new year!
No, I didn’t grab anything else.
Perhaps this is what you are thinking, but it would be nice to pull down playlists and favorite lists from music services and then arrange them in the same data structures to display them as selection pages.
The Bose phone app has a nasty Spotify interface and it still works today. It can search and find songs / artists / albums. It might be a good app to trap the traffic from.

I have the SoundTouch App installed on Windows 11, and am using Wireshark to trap / monitor the traffic but no luck. It appears that the ST App is not using the device to search for content, but just to play it once it resolves as a ContentItem. Or maybe it’s setting itself up as a DLNA server, and the device is using that to stream. Not sure on that one. As a side note, I did find a helpful tool in the install directory called SystemLogTool.exe; it performs a bunch of different tests on the device, and Wireshark does capture that activity. I found a couple of new API calls that can be made to retrieve introspect and pdo type of data.

That was exactly what I was thinking when I created the Get Music Station List service for the integration, which allows you to retrieve your Pandora stations. I was thinking it would work for other music services as well (Spotify, Deezer, iHeart, etc), but it’s looking like it may not.

My underlying bousesoundtouchapi also has the ability to search Music Library on NAS and Music Library on Computer libraries, but I have not implemented that as a service in the integration as of yet. The Get Music Station List service utilizes the /navigate SoundTouch WebServices API service to retrieve the Pandora favorites, and the /search SoundTouch WebServices API service to search Pandora for content.

I was hoping to get the Spotify stuff working before my 90-day free trial ends. Fingers crossed …

Another interesting one for Spotify is the Daily Mix.
Spotify generates this for you based on your listening and it changes daily. But if you set it as a toundtouch preset it seems to fix it at that particular daily mix. I have not investigated much further, but it would be good to set a Daily Mix and have it keep updating.

@jasebob I am a month into the Spotify free premium trial, and have learned a thing or two to say the least! :smiley: I created a new Spotify Web API Client for python package that interfaces with the Spotify Web API to retrieve data, manage Spotify Connect player control, and other things. It supports all Spotify Web API endpoints, with the exception of Track Audio Analysis. More information and capabilities can be found on the Spotify Web API Python Client documentation site.

I am at the point now where I want to start soliciting ideas for how to integrate it with the SoundTouchPlus integration.

Here’s a list I have so far:

  • retrieve “Daily Mix” playlist uri for a user (@jasebob suggestion).
  • retrieve list of user favorites (album, artist, playlist, tracks, episodes, shows).
  • retrieve covert art image url for currently playing spotify uri content.

Are there any other features that you would like to see integrated for Spotify into the SoundTouchPlus integration?

Hi Todd

Sorry, it was a while before replying.
This looks quite interesting. How to integrate does seem to be a tricky question.

Could you integrate it as a Spotify Media Player targeting these features from your code

  • Player: Get Playback State, Get Currently Playing Track, Start/Resume/Pause/Skip/Seek/Repeat/Shuffle/Volume Playback,

Add some service calls to set the playback device etc.
Then leave most other things as service calls that a user can call, following the same concept you used in soundtouchplus?

A simpler concept would be to integrate it as a sensor with those services available.

@jasebob I was initially looking at adding it into the SoundTouchPlus integration, but have decided against that. If I make it a stand-alone integration, then anyone using Spotify could benefit from it (not just Bose device owners).

I like the idea of a simple sensor concept, but also want to be able to create multiple instances to support more than one Spotify account. I also want to allow Spotify free account functionality, as well as support premium only functions. As it stands, the premium membership is required for a lot of the player functions that you mentioned.

I had thought about integrating it with the existing Spotify media player integration, but was not sure the original author would be open to the kind of changes it would take to make it work.

Still mulling things over. Will keep you posted.