Sonos - Control the device and get status

Post Reply
vrmp
Posts: 18
Joined: Friday 21 July 2017 23:51
Target OS: Raspberry Pi
Domoticz version: 3.8100
Location: Netherlands
Contact:

Sonos - Control the device and get status

Post by vrmp » Monday 24 July 2017 16:38

Since I'm experiencing problems with the new python plugin system, I decided to recreate my used plugins to LUA code.
For Sonos this is almost done. I created two functions (for now) to control the player and volume of the device.
I created some functions to get to status of the device too, but this is not completely finished at the moment.
The status of the device and volume levels can be fetched without a problem, but I'm not happy yet of the output of what is playing.
This does work, but the way it is shown on the virtual device is not yet perfect. I will share this part when it is finished.

The function to control the player:

Code: Select all

function SonosControl(Device_Sonos_IP,command) -- Play, Stop, Pause, Next, Previous
    if (command == "Play") or (command == "Stop") or (command == "Pause") or (command == "Next") or (command == "Previous")
    then
    	Device_Sonos_Port = "1400"
    	Device_Sonos_url = "/MediaRenderer/AVTransport/Control"
    	Device_Sonos_Method = "urn:schemas-upnp-org:service:AVTransport:1#" .. command
    	url = ('curl --header \'SOAPACTION: ' .. Device_Sonos_Method .. '\' --data \'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:' .. command .. ' xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID><Speed>1</Speed></u:' .. command .. '></s:Body></s:Envelope>\' http://' .. Device_Sonos_IP .. ':' .. Device_Sonos_Port .. '' .. Device_Sonos_url .. '')
    	io.popen(url .. ' &')
    end
end
How to use it:

Code: Select all

SonosControl("192.168.0.2","Play") -- Starts playing on Sonos device with IP address 192.168.0.2
SonosControl("192.168.0.2","Pause") -- Pauses on Sonos device with IP address 192.168.0.2
SonosControl("192.168.0.2","Stop") -- Sops playing on Sonos device with IP address 192.168.0.2
SonosControl("192.168.0.2","Next") -- Next song on Sonos device with IP address 192.168.0.2
SonosControl("192.168.0.2","Previous") -- Previous on Sonos device with IP address 192.168.0.2
The function to control volume and mute:

Code: Select all

function SonosVolume(Device_Sonos_IP,command,value) -- Volume -> 0-100, Mute -> On/Off
    if ((command == "Volume") or (command == "Mute")) and 
        ((value == "Off") or (value == "On") or ((tonumber(value) >= 0) and (tonumber(value) <= 100)))
	then
	    if (value == "Off") then value = 0
	    elseif (value == "On") then value = 1
	    end

    	Device_Sonos_Port = "1400"
    	Device_Sonos_url = "/MediaRenderer/RenderingControl/Control"
    	Device_Sonos_Method = "urn:schemas-upnp-org:service:RenderingControl:1#Set".. command
    	url = ('curl --header \'SOAPACTION: ' .. Device_Sonos_Method .. '\' --data \'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:Set' .. command ..' xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1"><InstanceID>0</InstanceID><Channel>Master</Channel><Desired' .. command ..'>' .. value .. '</Desired' .. command ..'></u:Set' .. command ..'></s:Body></s:Envelope>\' http://' .. Device_Sonos_IP .. ':' .. Device_Sonos_Port .. '' .. Device_Sonos_url .. '')
    	io.popen(url .. ' &')
	end
end
How to use it:

Code: Select all

SonosVolume("192.168.0.2","Volume", 15) -- Sets volume to 15% for Sonos device with IP address 192.168.0.2
SonosVolume("192.168.0.2","Mute", "On") -- Mutes Sonos device with IP address 192.168.0.2
SonosVolume("192.168.0.2","Mute", "Off") -- Unmutes Sonos device with IP address 192.168.0.2
Function to get status:

Code: Select all

function SonosGetStatus(Device_Sonos_IP) 
    Device_Sonos_Port = "1400"
    Device_Sonos_url = "/MediaRenderer/AVTransport/Control"
    Device_Sonos_Method = "urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"

	-- config ---------------------------------------------------------
    url = ('curl --header \'SOAPACTION: ' .. Device_Sonos_Method .. '\' --data \'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetTransportInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1"><InstanceID>0</InstanceID></u:GetTransportInfo></s:Body></s:Envelope>\' http://' .. Device_Sonos_IP .. ':' .. Device_Sonos_Port .. '' .. Device_Sonos_url .. '')
	local f = io.popen(url)
	line = f:read()
	i, j = string.find(line, '<CurrentTransportState>')
	ValueStart = j+1
	i, j = string.find(line, '</CurrentTransportState>')
	ValueStop = i-1	
	SonosResult = string.sub(line, ValueStart, ValueStop)
	
	return SonosResult
end
How to use it:

Code: Select all

print(SonosGetStatus("192.168.0.2")  -- Gets player status from Sonos device with IP address 192.168.0.2
Function to get volume/mute status:

Code: Select all

function SonosGetVolume(Device_Sonos_IP,command) --  Volume, Mute
    Device_Sonos_Port = "1400"
    Device_Sonos_url = "/MediaRenderer/RenderingControl/Control"
    Device_Sonos_Method = "urn:schemas-upnp-org:service:RenderingControl:1#Get" .. command

	-- config ---------------------------------------------------------
    url = ('curl --header \'SOAPACTION: ' .. Device_Sonos_Method .. '\' --data \'<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:Get'.. command .. ' xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1"><InstanceID>0</InstanceID><Channel>Master</Channel></u:Get'.. command .. '></s:Body></s:Envelope>\' http://' .. Device_Sonos_IP .. ':' .. Device_Sonos_Port .. '' .. Device_Sonos_url .. '')
	local f = io.popen(url)
	line = f:read()
	i, j = string.find(line, '<Current' .. command .. '>')
	ValueStart = j+1
	i, j = string.find(line, '</Current' .. command .. '>')
	ValueStop = i-1	
	SonosResult = string.sub(line, ValueStart, ValueStop)
	if (command == "Mute") and (SonosResult == "0") then SonosResult = "Off"
	elseif (command == "Mute") and (SonosResult == "1") then SonosResult = "On"
	end
	
	return SonosResult
end
How to use it:

Code: Select all

print(SonosGetVolume("192.168.0.2","Volume")  -- Gets volume status from Sonos device with IP address 192.168.0.2
print(SonosGetVolume("192.168.0.2","Mute")  -- Gets mutestatus from Sonos device with IP address 192.168.0.2
I'm using this at the moment attached to virtual devices to control the player, it's volume and to see what is playing.
The function to get information are stored in a timed LUA script, which works fine.
Updates are done every minute and for me that is fast enough.

Update with title information:

Code: Select all

function SonosGetTitle(Device_Sonos_IP)
    Device_Sonos_Port = "1400"
    Device_Sonos_url = "/MediaRenderer/AVTransport/Control"
    Device_Sonos_Method = "uurn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo"

	-- config ---------------------------------------------------------
    url = ('curl --header \'SOAPACTION: ' .. Device_Sonos_Method .. '\' --data \'<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><Channel>Master</Channel></u:GetPositionInfo></s:Body></s:Envelope>\' http://' .. Device_Sonos_IP .. ':' .. Device_Sonos_Port .. '' .. Device_Sonos_url .. '')
	local f = io.popen(url)
	line = f:read()
	if string.find(line, "spdif") and not string.find(line, "streamContent") 
	then
		-- Result
		SonosResult = "Playing from external source"		
	elseif string.find(line, "dc:creator") 
	then
		-- Title
		i, j = string.find(line, 'dc:title&gt;')
		ValueStart = j+1
		i, j = string.find(line, '&lt;/dc:title')
		ValueStop = i-1	
		SonosTitle = string.sub(line, ValueStart, ValueStop)
		-- Artist
		i, j = string.find(line, 'dc:creator&gt;')
		ValueStart = j+1
		i, j = string.find(line, '&lt;/dc:creator')
		ValueStop = i-1	
		SonosArtist = string.sub(line, ValueStart, ValueStop)
		-- Result
		SonosResult = SonosTitle .. ' - ' .. SonosArtist
	else
		--Radio/streaming title
		i, j = string.find(line, 'streamContent&gt;')
		ValueStart = j+1
		i, j = string.find(line, '&lt;/r:streamContent')
		ValueStop = i-1	
		SonosTitle = string.sub(line, ValueStart, ValueStop)
		
		-- Radio Station
		i, j = string.find(line, 'dc:title&gt;')
		ValueStart = j+1
		i, j = string.find(line, '&lt;/dc:title')
		ValueStop = i-1	
		SonosRadio = string.sub(line, ValueStart, ValueStop)
        SonosRadioFirst, SonosRadioLast = SonosRadio:match("([^.]+).([^.]+)")
        SonosResult = SonosTitle .. ' @ ' .. SonosRadioFirst
	end
	
	--Adding music source
	if string.find(line, "mp3radio") 
	then
	    SonosResult = SonosResult .. " (Radio)"
	elseif string.find(line, "spotify") 
	then
	    SonosResult = SonosResult .. " (Spotify)"
    end

    --Formating string
	SonosResult = string.lower(SonosResult)	
	SonosResult = string.gsub(" "..SonosResult, "%W%l", string.upper):sub(2)
	SonosResult = string.gsub(SonosResult, "&Amp;Amp;", "&")
	SonosResult = string.gsub(SonosResult, "&Amp;", "&")
	SonosResult = string.gsub(SonosResult, "&Lt;", "<")
	SonosResult = string.gsub(SonosResult, "&Gt;", ">")

	return SonosResult
end
Last edited by vrmp on Saturday 26 August 2017 12:41, edited 3 times in total.

snuiter
Posts: 34
Joined: Saturday 17 June 2017 12:30
Target OS: Raspberry Pi
Domoticz version: beta
Contact:

Re: Sonos - Control the device and get status

Post by snuiter » Friday 25 August 2017 17:42

Hi vrmp,
I love your LUA scripts for the Sonos worked on it today and they work good. Last part I was adding and playing with is the SonosGetTitle script this is reporting an error, do you have that as well?

This is the error I get in the domoticz log:
2017-08-25 17:38:00.510 Error: EventSystem: in aa: ./scripts/lua/myfunctions.lua:47: attempt to perform arithmetic on global 'j' (a nil value)


This is the title part
45 -- Title
46 i, j = string.find(line, 'dc:title>')
47 ValueStart = j+1
i, j = string.find(line, '</dc:title')
ValueStop = i-1
SonosTitle = string.sub(line, ValueStart, ValueStop)


Are able to change volume and use the play/pause and prev/next functions but this one not.

Ay idea?

vrmp
Posts: 18
Joined: Friday 21 July 2017 23:51
Target OS: Raspberry Pi
Domoticz version: 3.8100
Location: Netherlands
Contact:

Re: Sonos - Control the device and get status

Post by vrmp » Friday 25 August 2017 18:11

Hi,

It should work, but perhaps you are playing from a different source then I did and tested it with. I only use radiostations and spotify.

Can you add behind (probably on Line 42 in your code)

line = f:read()

Print(Line)

And post the output?

snuiter
Posts: 34
Joined: Saturday 17 June 2017 12:30
Target OS: Raspberry Pi
Domoticz version: beta
Contact:

Re: Sonos - Control the device and get status

Post by snuiter » Friday 25 August 2017 20:16

Line 42 indeed. I do use radiostations and spotify only as well.

Hmm I do have an output in which I see title information, am I doing something wrong as the error is still there on all three scenarios below

This is the output:
LUA: 1NOT_IMPLEMENTEDNOT_IMPLEMENTEDx-sonos-htastream:RINCON_000E58B5B6A201400:spdifNOT_IMPLEMENTEDNOT_IMPLEMENTED21474836472147483647

switching off TV sound and back to radio:
LUA: 10:00:00<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="-1" parentID="-1" restricted="true"><res protocolInfo="x-rincon-mp3radio:*:*:*">x-rincon-mp3radio://http://playerservices.streamtheworld.co ... ontent>THE BOY NEXT DOOR FT. JODY BERNAL - LA COLEGIALA</r:streamContent><dc:title>RADIO538.mp3?tdtok=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImtpZCI6ImZTeXA4In0.eyJpc3MiOiJ0aXNydiIsInN1YiI6IjIxMDY0IiwiaWF0IjoxNTAzNjg0OTg4LCJ0ZC1yZWciOmZhbHNlfQ.wn6FM-V1VJwD7nEuwkGLH4agL01j2HygZvQ3JmQkbiw</dc:title><upnp:class>object.item</upnp:class></item></DIDL-Lite>x-rincon-mp3radio://http://playerservices.streamtheworld.co ... 2147483647


Playing a song via spotify

2017-08-25 20:21:00.571 LUA: 20:03:18<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="-1" parentID="-1" restricted="true"><res protocolInfo="sonos.com-spotify:*:audio/x-spotify:*" duration="0:03:18">x-sonos-spotify:spotify%3atrack%3a1UiVMKcTVb2BDaXLKQFbR8?sid=9&amp;flags=8224&amp;sn=3</res><r:streamContent></r:streamContent><upnp:albumArtURI>/getaa?s=1&amp;u=x-sonos-spotify%3aspotify%253atrack%253a1UiVMKcTVb2BDaXLKQFbR8%3fsid%3d9%26flags%3d8224%26sn%3d3</upnp:albumArtURI><dc:title>Manoeuvres</dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><dc:creator>Jonna Fraser</dc:creator><upnp:album>Manoeuvres</upnp:album><r:tags>1</r:tags></item></DIDL-Lite>x-sonos-spotify:spotify%3atrack%3a1UiVMKcTVb2BDaXLKQFbR8?sid=9&flags=8224&sn=30:00:47NOT_IMPLEMENTED21474836472147483647

vrmp
Posts: 18
Joined: Friday 21 July 2017 23:51
Target OS: Raspberry Pi
Domoticz version: 3.8100
Location: Netherlands
Contact:

Re: Sonos - Control the device and get status

Post by vrmp » Friday 25 August 2017 21:07

The spdif connection will create an error, but not in the line you report.
I can fix that for you, but it does not explain what is happening.
In the three outputs you gave only the Spotify one triggers the if statement containing that part of the code. Or well it should be.

Does function that gets the volume work?

Edit:
I did a check on my own code and it seems it doesn't match anymore :oops:

Code: Select all

function SonosGetTitle(Device_Sonos_IP)
    Device_Sonos_Port = "1400"
    Device_Sonos_url = "/MediaRenderer/AVTransport/Control"
    Device_Sonos_Method = "uurn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo"

	-- config ---------------------------------------------------------
    url = ('curl --header \'SOAPACTION: ' .. Device_Sonos_Method .. '\' --data \'<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><Channel>Master</Channel></u:GetPositionInfo></s:Body></s:Envelope>\' http://' .. Device_Sonos_IP .. ':' .. Device_Sonos_Port .. '' .. Device_Sonos_url .. '')
	local f = io.popen(url)
	line = f:read()
	if string.find(line, "dc:creator") 
	then
		-- Title
		i, j = string.find(line, 'dc:title&gt;')
		ValueStart = j+1
		i, j = string.find(line, '&lt;/dc:title')
		ValueStop = i-1	
		SonosTitle = string.sub(line, ValueStart, ValueStop)
		-- Artist
		i, j = string.find(line, 'dc:creator&gt;')
		ValueStart = j+1
		i, j = string.find(line, '&lt;/dc:creator')
		ValueStop = i-1	
		SonosArtist = string.sub(line, ValueStart, ValueStop)
		-- Result
		SonosResult = SonosTitle .. ' - ' .. SonosArtist
	else
		--Radio/streaming title
		i, j = string.find(line, 'streamContent&gt;')
		ValueStart = j+1
		i, j = string.find(line, '&lt;/r:streamContent')
		ValueStop = i-1	
		SonosTitle = string.sub(line, ValueStart, ValueStop)
		
		-- Radio Station
		i, j = string.find(line, 'dc:title&gt;')
		ValueStart = j+1
		i, j = string.find(line, '&lt;/dc:title')
		ValueStop = i-1	
		SonosRadio = string.sub(line, ValueStart, ValueStop)
        SonosRadioFirst, SonosRadioLast = SonosRadio:match("([^.]+).([^.]+)")
        SonosResult = SonosTitle .. ' @ ' .. SonosRadioFirst
	end
	
	--Adding music source
	if string.find(line, "mp3radio") 
	then
	    SonosResult = SonosResult .. " (Radio)"
	elseif string.find(line, "spotify") 
	then
	    SonosResult = SonosResult .. " (Spotify)"
    end

    --Formating string
	SonosResult = string.lower(SonosResult)	
	SonosResult = string.gsub(" "..SonosResult, "%W%l", string.upper):sub(2)
	SonosResult = string.gsub(SonosResult, "&Amp;Amp;", "&")
	SonosResult = string.gsub(SonosResult, "&Amp;", "&")
	SonosResult = string.gsub(SonosResult, "&Lt;", "<")
	SonosResult = string.gsub(SonosResult, "&Gt;", ">")


	return SonosResult
end
This should work for radiostations and spotify. If it does I will change the code a bit to filter out the spdif connection to your TV.

snuiter
Posts: 34
Joined: Saturday 17 June 2017 12:30
Target OS: Raspberry Pi
Domoticz version: beta
Contact:

Re: Sonos - Control the device and get status

Post by snuiter » Saturday 26 August 2017 11:52

It works thanks ! I get title information for both radio and spotify.

LUA: Manoeuvres - Jonna Fraser (Spotify)

vrmp
Posts: 18
Joined: Friday 21 July 2017 23:51
Target OS: Raspberry Pi
Domoticz version: 3.8100
Location: Netherlands
Contact:

Re: Sonos - Control the device and get status

Post by vrmp » Saturday 26 August 2017 12:41

Good to hear it works now!
I have updated my first post with the new function and added a check for your spdif connection to the TV.
It should update now with "Playing from external source" when used.

snuiter
Posts: 34
Joined: Saturday 17 June 2017 12:30
Target OS: Raspberry Pi
Domoticz version: beta
Contact:

Re: Sonos - Control the device and get status

Post by snuiter » Sunday 27 August 2017 18:55

Yep indeed working as well..
Schermafbeelding 2017-08-27 om 18.54.07.png
Schermafbeelding 2017-08-27 om 18.54.07.png (16.77 KiB) Viewed 379 times

Thanks !

Post Reply

Who is online

Users browsing this forum: No registered users and 2 guests