Example: SONOS HiFi system control from Logic Machine

In LogicMachine with Application Store supported you can use SONOS control app installed from store.
Another way is to control SONOS via scripting. Below are both examples shown.

Sonos control app

image014

Sonos LogicMachine gateway

Sonos LogicMachine gateway

Sonos LogicMachine gateway

Sonos LogicMachine gateway

Sonos control function

Add the following function into Common functions

Source code    
  1. function upnpavcmd(host, port, cmd, param)
  2. local client, soap, reqs, service, res, err
  3.  
  4. require('socket')
  5. client = socket.tcp()
  6. client:settimeout(3)
  7.  
  8. -- try connecting to upnp endpoint
  9. res, err = client:connect(host, port)
  10. if not res then
  11. return nil, err
  12. end
  13.  
  14. -- guess service name based on command
  15. if cmd == 'SetVolume' or cmd == 'GetVolume' or cmd == 'SetMute' or cmd == 'GetMute' then
  16. service = 'RenderingControl'
  17. else
  18. service = 'AVTransport'
  19. end
  20.  
  21. -- soap envelope
  22. soap = '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">' ..
  23. '<s:Body>' ..
  24. '<u:' .. cmd .. ' xmlns:u="urn:schemas-upnp-org:service:' .. service .. ':1">' ..
  25. '<InstanceID>0</InstanceID>' ..
  26. (param or '') ..
  27. '</u:' .. cmd .. '>' ..
  28. '</s:Body>' ..
  29. '</s:Envelope>'
  30.  
  31. -- http request
  32. reqs = 'POST /MediaRenderer/' .. service .. '/Control HTTP/1.1\r\n' ..
  33. 'CONNECTION: close\r\n' ..
  34. 'HOST: ' .. host .. ':' .. port .. '\r\n' ..
  35. 'CONTENT-LENGTH: ' .. soap:len() .. '\r\n' ..
  36. 'CONTENT-TYPE: text/xml; charset="utf-8"\r\n' ..
  37. 'SOAPACTION: "urn:schemas-upnp-org:service:' .. service .. ':1#' .. cmd .. '"\r\n' ..
  38. '\r\n' .. soap
  39.  
  40. -- send http request
  41. res, err = client:send(reqs)
  42. if not res then
  43. return nil, err
  44. end
  45.  
  46. -- get reply and close connection
  47. res, err = client:receive('*a')
  48. client:close()
  49.  
  50. return res, err
  51. end

Event-based script which sends volume and other commands to Sonos on specific KNX group address change

Source code    
  1. -- ip, port, command, optional param
  2. upnpavcmd('192.168.1.52', 1400, 'SetVolume', '<Channel>Master</Channel><DesiredVolume>20</DesiredVolume>')
  3. upnpavcmd('192.168.1.52', 1400, 'Pause')
  4. upnpavcmd('192.168.1.52', 1400, 'Play', '<Speed>1</Speed>')
  5. upnpavcmd('192.168.1.52', 1400, 'Next')
  6. upnpavcmd('192.168.1.52', 1400, 'Previous')
  7. upnpavcmd('192.168.1.52', 1400, 'SetMute', '<Channel>Master</Channel><DesiredMute>0</DesiredMute>')
  8. upnpavcmd('192.168.1.52', 1400, 'SetMute', '<Channel>Master</Channel><DesiredMute>1</DesiredMute>')

Scheduled script for searching Sonos players

Created by Joep van den Aker from KNX groep B.V

Source code    
  1. require("socket")
  2. require('socket.http')
  3.  
  4. local udp = socket.udp()
  5.  
  6. udp:settimeout(5)
  7.  
  8. if udp == nil then
  9. return
  10. else
  11. local result, message
  12. local datagram = "M-SEARCH * HTTP/1.1\r\n"
  13. .. "HOST: 239.255.255.250:1900\r\n"
  14. .. "ST: urn:schemas-upnp-org:device:ZonePlayer:1\r\n"
  15. .. "\r\n"
  16. result, message = udp:sendto(datagram, "239.255.255.250", 1900)
  17.  
  18. for i = 1, 254 do
  19. datagram, message = udp:receivefrom()
  20. if datagram == nil then
  21. break
  22. else
  23. ip = datagram:match("LOCATION: http://(.-):1400/xml/device_description.xml")
  24. if ip then -- Fix for other UPNP devices like Philips Hue.
  25. data = socket.http.request("http://" .. ip .. ":1400/status/zp")
  26. zonename = data:match([[(.+).+%((%a+)%)]]) -- Fix for Sonos stereo pair
  27. if zonename == nil then
  28. zonename = data:match([[(.+)]])
  29. end
  30. pair = data:match([[.+%((%a+)%)]])
  31. if pair == "R" then
  32. log("Zoneplayer is part of a stereo pair and can't be used as a seperate zone " .. ip)
  33. elseif pair == "L" then
  34. log(zonename:gsub("%s", "-") .. " " .. ip)
  35. storage.set(zonename:gsub("%s", "-"), ip) -- Create storage with zonename and ip address. This can be used for the Sonos scripts (stereo pair)
  36. elseif pair == nil then
  37. log(zonename:gsub("%s", "-") .. " " .. ip)
  38. storage.set(zonename:gsub("%s", "-"), ip) -- Create storage with zonename and ip address. This can be used for the Sonos scripts (seperate player)
  39. end
  40. else
  41. log("No Sonos device: " .. datagram)
  42. break
  43. end
  44. end
  45. end
  46. end
  47.  
  48. udp:close()

Questions/Answers

Q: I am looking for a way to “dim” volume up and down.
A: It’s not possible to set relative volume directly. You have to get the current volume, parse the response and send set volume command afterwards:

Source code    
  1. res = upnpavcmd('192.168.1.52', 1400, 'GetVolume', '<Channel>Master</Channel>')

Q: Is it possible to change to a specified Spotify playlist or to change radio channel.
A: Try this for playing radio:

Source code    
  1. upnpavcmd('192.168.1.52', 1400, 'SetAVTransportURI', '<CurrentURI>x-rincon-mp3radio://www.abc-lounge.com/listen.m3u</CurrentURI><CurrentURIMetaData/>')