Jump to content

Show Sonos song + album art and control volume with Shelly via HA. Shelly -> Ha -> Sonos


Recommended Posts

Posted (edited)

Hi everyone.

Sometimes the best way to learn is to do, so i made this Shelly script that uses 3 virtual components with Sonos information from HA.

I get the current song, album art and can control the volume with a slider.

The information path is Shelly -> HA -> Sonos.

I made a separate script for setting the volume (It can be 1 script, but i liked to have them separate)

 

We could implement more controls to be able to control Sonos from the Shelly app.

We could also get HA to control the components (so no polling is needed), but i wanted to do it like this to learn scripting.

 

I would love to be able to add the virtual component group to the dashboard, Shelly is this a thing that could be implemented?

 

App view of final result:

Shellyapp.thumb.png.af3c49ee49173ce979c26b71a45ffd4a.png

 

You need 3 virtual components (and put them in a group)

- 2 with text and label.

- 1  with number and slider.

Device:

Virtualcomponents.thumb.png.a48a2ab5ddd43ec5d15b0ccd50cabcdd.png

 

Code: The tabbing gets strange when posting the code, sorry about that.

 

Code for getting song, album art and current volume:

// CONFIG START
let CONFIG = {
    homeAssistantUrl: 'http://10.0.0.1:8123', 	// URL of your Home Assistant instance
    accessToken: 'YOUR HA TOKEN', 				// Your long-lived access token
    sonosEntityId: 'media_player.sonos', 		// Entity ID of your Sonos speaker
    checkInterval: 10000, 						// Check every 10 seconds
	debug: false,								// Show debug info
	debug_error: true,							// Show debug error info
	vd_textID: 200, 							// VD text id to hold the current song
	vd_textImageID: 202, 						// VD text id to hold the Album art
	volumeSliderID: 200,                        // VD of the virtual slider to hold the volume
	album_undefined_icon: 'https://www.shareicon.net/data/128x128/2015/08/12/83951_info_512x512.png'	//Change to what you like
};


// Debug log function
function log(log_info) {
	if (CONFIG.debug) { print(log_info) };
}; //Function log end

// Error log function
function log_e(log_error) {
	if (CONFIG.debug_error) { print("ERROR: " + log_error) };
}; //Function log error end

function GetSonosData() {
    print("Starting to fetch Sonos info from Home Assistant..."); // Debug message
    Timer.set(CONFIG.checkInterval, true, function () {

        Shelly.call(
            "http.request", {
                method: "GET",
                url: CONFIG.homeAssistantUrl + '/api/states/' + CONFIG.sonosEntityId, // Home Assistant API endpoint
                headers: {
                    "Authorization": "Bearer " + CONFIG.accessToken, // Bearer token for authentication
                    "Content-Type": "application/json"
                },
                timeout: 5 // Set a timeout for the request
            },
            function (res, error_code, error_message, ud) {
                if (error_code === 0) {
                    try {
                        let status = JSON.parse(res.body); // Parse the JSON response

	                    // Create the album art URL
	                    let albumArtImage = status.attributes.entity_picture;
	                    let albumArtUrl = CONFIG.homeAssistantUrl + albumArtImage;

                        // Get current track information
                        let currentTrack = status.attributes.media_title; 	// Get song
                        let currentArtist = status.attributes.media_artist; // Get artist
					
                        // Fetch the volume level
                        let volumeLevel = status.attributes.volume_level; // Float between 0 and 1
						

						// Update Album art  
						if (typeof albumArtImage !== "undefined") {
							
							// Display the album art
							Shelly.call("Text.Set", {
  	                          	id: CONFIG.vd_textImageID, 	// ID of the virtual text component
  	                          	value: albumArtUrl 			// Text to display (link to image)
  	                      	}); 					
							
                            // Print the current album art URL information
                            log("Current album art URL: " + albumArtUrl);

					  	} else {
                        	// Update the virtual text component to indicate no album art
                        	Shelly.call("Text.Set", {
                            	id: CONFIG.vd_textImageID,
                            	value: CONFIG.album_undefined_icon
                        	});
                        	log("No album art currently available, setting default image");
						};	// End Update album art IF
 
						  
						  
						// Update track info
                        if (currentTrack) {
                            // Create the text
                            let displayText = "Song: " + currentTrack + " by " + currentArtist;

                            // Update the virtual text component
                            Shelly.call("Text.Set", {
                                id: CONFIG.vd_textID, 		// ID of the virtual text component
                                value: displayText 			// Text to display
                            });

                            // Print the current track information
                            log("Current track info: " + displayText);
                           
                        } else {
                            // Update the virtual text component to indicate no track is playing
                            Shelly.call("Text.Set", {
                                id: CONFIG.vd_textID,
                                value: "No track is currently playing."
                            });
							
                            log("No track is currently playing.");
                        } // end of currentTrack IF
						
						
						
						// Update volume level  
						if (volumeLevel) {
	                        
							// Update the virtual slider with the current volume
	                        Shelly.call("Number.Set", {
	                            id: CONFIG.volumeSliderID, // ID of the virtual slider component
	                            value: volumeLevel * 100 // Convert to percentage (0-100)
	                        });			
							
                            // Print the current album art URL information
                            log("Current album art URL:" + albumArtUrl);
							
					  	} else {
							// Update the virtual slider with no volume
	                        Shelly.call("Number.Set", {
	                            id: CONFIG.volumeSliderID, // ID of the virtual slider component
	                            value: 0 // Convert to percentage (0-100)
	                        });
                        	log("No volume value received.");
						};	// End Update volume level I
						
						
						// Catch potential error
                    } 	catch (e) {											// Check JSON reply for errors
                        	log_e("Error parsing JSON: " + e.message);		// Log for error debugging 
                        	log_e("Response body: " + res.body); 			// Log for error debugging
                    	}
                } else {
                    	log_e("", "Error fetching Sonos info: " + error_message + " (Code: " + error_code + ")");
				}	// End of if error_code 0
				
            },		// End of response func 
       null);		// End of Shelly.call http request
    }, null); 		// End of Timer.set
};					// End of CheckSonosCurrentSong function

GetSonosData();

 

Code for setting the volume:

// CONFIG START
let CONFIG = {
    homeAssistantUrl: 'http://10.0.0.1:8123', 	// URL of your Home Assistant instance
    accessToken: 'YOUR HA TOKEN', 				// Your long-lived access token
    sonosEntityId: 'media_player.sonos', 		// Entity ID of your Sonos speaker
	debug: false,								// Show debug info
	debug_error: true,							// Show debug error info
    volumeSliderID: "number:200" 				// ID of the virtual slider to hold the volume
};


// Debug log function
function log(log_info) {
	if (CONFIG.debug) { print(log_info) };
}; //Function log end

// Error log function
function log_e(log_error) {
	if (CONFIG.debug_error) { print("ERROR: " + log_error) };
}; //Function log error end


// Function to set the volume in Home Assistant
function SetSonosVolume(volume) {
    log("Setting Sonos volume to: " + volume); // Debug message
    Shelly.call(
        "http.request", {
            method: "POST",
            url: CONFIG.homeAssistantUrl + '/api/services/media_player/volume_set', // Home Assistant API endpoint to set volume
            headers: {
                "Authorization": "Bearer " + CONFIG.accessToken, // Bearer token for authentication
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                entity_id: CONFIG.sonosEntityId, 	// Entity ID of the Sonos speaker
                volume_level: volume / 100 			// Convert percentage (0-100) to float (0-1)
            }),
            timeout: 5 								// Set a timeout for the request
        },
        function (res, error_code, error_message, ud) {
            if (error_code === 0) {
                log("Volume set successfully.");
            } else {
                log_e("Error setting Sonos volume: " + error_message + " (Code: " + error_code + ")");
            }
        },				// End volume response check
        null
    );					// End Shelly.call
};						// End SetSonosVolume function


function onEvent(event_data) {
	log("Event data: " + JSON.stringify(event_data));	// Shows event data
	
	let component = event_data.component;			// Get event data name 
  	let componet_ID = event_data.delta.id;			// Get event data ID 			
  	let componet_Value = event_data.delta.value;	// Get event data Value 
  	let info = event_data.info;						// Get event data info 
	
	if (component === CONFIG.volumeSliderID) { 		// Check for inpout from slider with type and slider ID
		log("VD information ID: " + componet_ID + " Volume: " + componet_Value)
    
		if (event_data.delta.value === 0) {
      		log("Volume is off");
	  		SetSonosVolume(event_data.delta.value);
    	}
    	else if (event_data.delta.value >= 0) {
      		log("Volume is on");
      		SetSonosVolume(event_data.delta.value)
    	}
  	}; 				// End IF component

  if (info) { 		//Check for false info
	  log_e("Error in received event data")
	  log_e(JSON.stringify(event_data));
    return;
  };				// End IF check for bad data
  
};					// End of onEvent function


print("Started Statushandler for Sonos VD Volume Slider");

// Create status hadler 
Shelly.addStatusHandler(onEvent);

 

Edited by Martin_

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...