[APP][Pro] Sonos (LocalAPI)

This does indeed look great. I’m currently experimenting with implementing a widget showing Sonos favorites to recreate this functionality. Still in very early stage so this will take some time.

Is it also possible getting songs straight from Spotify? Would be nice being able to play songs even if you haven’t favorited them.

Are you able to test v1.11.0?

The new flowcards work!

Am I correct that currently there are no boolean tags available that indicate the state of both auto-play setings? I could not find them in the list of available tags.

Unfortunately I have not an iPhone.

([APP][Pro] Sonos (LocalAPI) - #176 by John_William_Brekke)

@xiutit : do you see any chance to have a trigger for having different flows if a stream is coming form network/tv or via a bluetooth source

Can you try v1.11.1? Added the tags :slight_smile:

I will look into this and see if it’s possible.

Yes, found them! Thank you :+1:.

Hi @xiutit !

Thanks again for the awesome updates, I’ve tested most of it by now and everything seems to work as intended.

The thing I’m still missing though is a way to pause from any speaker, whenever a group of speakers is playing. This works on the physical devices themselves with the pause button, and this also worked in the older cloud based Homey Sonos app (pausing any speaker that was playing would put the entire group that the speaker was in on pause).

Now, when I try to pause from a different device that is not the “group master” so to speak, I get this error:

Even more problematic: I have a separate office that has it’s own Sonos speaker that groups with the rest of the house whenever you enter it, or that starts playing music individually if nothing else is playing in the house. The tiny dashboard in that office shows the dedicated office speaker, but whenever this speaker is grouped and I try to pause the music from the dashboard there is no reaction: I suppose the same error is thrown but the dashboard does not seem to give the same error feedback as it does in the app, so now the behavior is very confusing. The only thing that happens is the pause icon becomes a play icon (and the music in the office keeps playing):

So when I’m in the office and the Sonos there is grouped with the rest of the house my only option to pause from the office is to hit the pause button on the Sonos amp that is hidden away in a closet :sweat_smile:.

I hope I’m explaining this right, it’s a bit obscure maybe.

Also not sure if it’s fixable, but if it is it would add a bit more user friendliness for people using separate dashboards per room with the ability to start/stop either the group or the individual speaker using the same pause button/action.

Let me know what you think!

Inspired by the question above, you will find a Homey script below to determine the sound source of the Sonos speaker. Unfortunately, I cannot test this for Bluetooth.
I tested this on my Sonos Playbar; other speakers might react differently.

Perhaps this could be an input for @xiutit

/**
 * Sonos Direct API Reader
 * Retrieves the current playback source from a Sonos speaker via UPnP/SOAP.
 */

// Configuration: The IP address of the target Sonos speaker
const SPEAKER_IP = '192.168.178.36';

async function getSonosSource() {
    // Construct the endpoint URL for the AVTransport service
    const endpointUrl = 'http://' + SPEAKER_IP + ':1400/MediaRenderer/AVTransport/Control';
    
    // Define the SOAP XML body to request position and track information
    // Using string concatenation (+) as per requested coding style
    const soapBody = 
        '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' +
            '<s:Body>' +
                '<u:GetPositionInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">' +
                    '<InstanceID>0</InstanceID>' +
                '</u:GetPositionInfo>' +
            '</s:Body>' +
        '</s:Envelope>';

    try {
        // Execute the POST request to the speaker
        const response = await fetch(endpointUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'text/xml; charset="utf-8"',
                'SOAPACTION': '"urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo"'
            },
            body: soapBody
        });

        // Parse the XML response text
        const responseXml = await response.text();
        
        // Extract the TrackURI using a Regular Expression
        const uriMatch = responseXml.match(/<TrackURI>(.*?)<\/TrackURI>/);
        let trackUri = uriMatch ? uriMatch[1] : "";
        
        // Decode the URI to make it human-readable for checking
        trackUri = decodeURIComponent(trackUri);

        // Default value if no specific source is identified
        let detectedSource = "Other / Unknown";

        // --- Logic: Source Identification ---

        // 1. Check for Spotify or Sonos Virtual Line-In
        if (trackUri.includes("spotify") || trackUri.includes("x-sonos-vli")) {
            detectedSource = "Spotify";
        } 
        
        // 2. Check for Radio streams (TuneIn or direct MP3 streams)
        else if (trackUri.includes("Tunein") || trackUri.includes("x-rincon-mp3radio")) {
            detectedSource = "Radio";
        }

        // 3. Check for TV input (Home Theater Audio Stream)
        else if (trackUri.includes("x-sonos-htastream")) {
            detectedSource = "TV";
        } 

        // 4. Check if the speaker is idle or in standby (Queue is empty or URI is blank)
        else if (trackUri === "" || trackUri.includes("x-rincon-queue")) {
            detectedSource = "No media / Standby";
        }

        // Log results for debugging purposes
        console.log("Current URI: " + trackUri);
        console.log("Detected Source: " + detectedSource);
        
        return detectedSource;

    } catch (error) {
        // Handle network errors or unreachable speakers
        console.error("Error: " + error.message);
        return "Error: Speaker unreachable";
    }
}

// Execute the function and return the result to Homey
return await getSonosSource();

Are you able to test v1.12.0 (test)? I have added a tag and a trigger card. I’m currently not at home so I could only test switching from TV to Music and back, but not Bluetooth. Let me know if this works.

I changed the pause behavior in v1.12.0 (test). Now pausing any speaker should pause the group as expected.

You’re amazing!


I’ve recently started using this app. Very happy with it. It works well. I have a SONOS Playbar. When I listen to the radio, I notice that under “Track” it shows the artist info, and under “Artist” it shows the track info. Is this a problem specific to the SONOS Playbar?

Case: I use a Homey script to retrieve all my favorite TuneIn stations and Spotify playlists via the local Sonos API. The result is a list of names, image URLs, and URIs.

It seems impossible to me to play music based on a URI.

I would like to use a Sonos card to play the TuneIn or Spotify URI. Is it technically possible to send the URI in a variable to a Sonos card to play the selected music?

I want to avoid having to create dozens of conditions in a flow to control a Sonos favorites card based on a playlist/TuneIn name, aside from maintenance such as changing favorites.

EDIT - better not this (only half of the favorites, the other half are favorite Spotify playlists):

Switching from TuneIn to Spotify (or back) doesn’t activate one of the cards below on my Sonos playbar. Or is my test wrong?

Question: should the Bluetooth show when connected to Bluetooth and playing music started or the moment it goes into Bluetooth mode?

Edit: it does say Bluetooth, but not before the music plays?

How did you even find this?

A little bit from me and from AI. Gemini AI can do quite nice things with Homey script.

Ooo, thanks!

Can you try the Play URI flowcard in v1.12.1 in test?

Can you add “play song from …” Card?

It works great and fast. Thank you.