Index: /trunk/L_Sonos1.lua
===================================================================
--- /trunk/L_Sonos1.lua	(revision 20)
+++ /trunk/L_Sonos1.lua	(revision 30)
@@ -2,4 +2,5 @@
 
 local http = require("socket.http")
+
 -- 5 Second timeout
 http.TIMEOUT = 5
@@ -12,7 +13,26 @@
 </s:Envelope>]]
 
-UPNP_AVTRANSPORT_SID = 'urn:schemas-upnp-org:service:AVTransport:1'
-UPNP_RENDERING_CONTROL_SID = 'urn:schemas-upnp-org:service:RenderingControl:1'
-UPNP_DEVICE_PROPERTIES_SID = 'urn:schemas-upnp-org:service:DeviceProperties:1'
+UPNP_AVTRANSPORT_SERVICE = 'urn:schemas-upnp-org:service:AVTransport:1'
+UPNP_RENDERING_CONTROL_SERVICE = 'urn:schemas-upnp-org:service:RenderingControl:1'
+UPNP_DEVICE_PROPERTIES_SERVICE = 'urn:schemas-upnp-org:service:DeviceProperties:1'
+
+UPNP_AVTRANSPORT_SID = 'urn:upnp-org:serviceId:AVTransport'
+UPNP_RENDERING_CONTROL_SID = 'urn:upnp-org:serviceId:RenderingControl'
+UPNP_DEVICE_PROPERTIES_SID = 'urn:upnp-org:serviceId:DeviceProperties'
+
+local DIDL_FORMAT=[[<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="%s" parentID="%s" restricted="true">
+<res protocolInfo="x-file-cifs:*:audio/mpeg:*">%s</res>
+<r:streamContent>%s</r:streamContent>
+<upnp:albumArtURI>%s</upnp:albumArtURI>
+<dc:title>%s</dc:title>
+<upnp:class>%s</upnp:class>
+<dc:creator>%s</dc:creator>
+<upnp:album>%s</upnp:album>
+<upnp:originalTrackNumber>%s</upnp:originalTrackNumber>
+<r:albumArtist>%s</r:albumArtist>
+</item>
+</DIDL-Lite>
+]]
 
 local log = print
@@ -24,23 +44,45 @@
 end
 
-function decode(val)
+local function decode(val)
       return val:gsub("&amp;", "&")
                 :gsub("&lt;", "<")
                 :gsub("&gt;", ">")
                 :gsub("&quot;", '"')
-    end
+end
+
+local function encode(val)
+      return val:gsub("&", "&amp;")
+                :gsub("<", "&lt;")
+                :gsub(">", "&gt;")
+                :gsub('"', "&quot;")
+end
+
+function createDIDL(resURI, creator, album, title, artist, streamContent, albumArtURI, trackNumber, class, id, parentID)
+  return DIDL_FORMAT:format(encode(id or 1),
+                            encode(parentID or -1),
+                            encode(resURI or ""),
+                            encode(streamContent or ""),
+                            encode(albumArtURI or ""),
+                            encode(title or "No title"),
+                            encode(class or "object.item.audioItem.musicTrack"),
+                            encode(creator or ""),
+                            encode(album or ""),
+                            encode(trackNumber or 1),
+                            encode(artist or "No artist"))
+end
 
 -- event url, user-agent, callback, nt, TimeOut
-function UPnP_subscribe(ipAddress, ipPort)
-
+function UPnP_subscribe(ipAddress, ipPort, serverURL, callbackURL)
   --
   -- Execute the subscription
   --
   local status, statusMsg = http.request{
-    method = "SUBSCRIBE /MediaRenderer/RenderingControl/Event HTTP/1.1",
-	headers = {["HOST"] = ipAddress .. ":" .. ipPort,
-			   ["CALLBACK"] = "<http://192.168.1.15/cmh/event>",
-			   ["NT"] = "upnp:event",},
-	}
+    url = serverURL:format(ipAddress, ipPort),
+    method = "SUBSCRIBE",
+    headers = {["HOST"] = string.format("%s:%s", ipAddress, ipPort),
+               ["CALLBACK"] = string.format("<%s>", callbackURL),
+               ["NT"] = "upnp:event"}
+  }
+
   return status
 end  
@@ -58,5 +100,5 @@
                 :gsub('"', "&quot;")
     end
-	
+
     local result = ""
 
@@ -118,5 +160,5 @@
     -- Handle TIMEOUT
     --
-    return {}
+    return false, statusMsg or "An error occurred during the UPnP call"
   end
   
@@ -125,7 +167,6 @@
     -- Handle SUCCESS
     --
-	
-	if (data == nil or data == "") then
-      return ""
+    if (data == nil or data == "") then
+      return true, ""
     else
       local pattern = string.format('.*<s:Body><u:%sResponse xmlns:u="urn:(.*)">(.*)</u:%sResponse></s:Body></s:Envelope>.*', action, action)
@@ -134,5 +175,5 @@
       -- TODO: Handle Non-Scalar results
       -- luup.log("Before gmatch: " .. data)
-	  local sid, value
+      local sid, value
       for serviceId, result in data:gmatch(pattern) do
         -- luup.log(serviceId .. " --> " .. result)
@@ -141,13 +182,12 @@
 
       -- commented out and returns a simple string instead. Due to multiple tags (not nested though)
---      [[local dataTable = {}
---      for tagBegin, value, tagEnd in value:gmatch("<(.*)>(.*)</(.*)>") do
- --       print(tagBegin .. " -> " .. value .. " <- " .. tagEnd)
- --       dataTable[tagBegin] = value
- --     end
-
-  --     return dataTable]]
-	   return decode(value)
-	  
+      --      [[local dataTable = {}
+      --      for tagBegin, value, tagEnd in value:gmatch("<(.*)>(.*)</(.*)>") do
+      --        print(tagBegin .. " -> " .. value .. " <- " .. tagEnd)
+      --        dataTable[tagBegin] = value
+      --      end
+
+      --     return dataTable]]
+      return true, decode(value)
     end
   else
Index: /trunk/S_Sonos1.xml
===================================================================
--- /trunk/S_Sonos1.xml	(revision 20)
+++ /trunk/S_Sonos1.xml	(revision 30)
@@ -14,32 +14,47 @@
     <action>
       <name>SetVolume</name>
-		<argumentList>
-		  <argument>
-			<name>NewVolume</name>
-			<direction>in</direction>
-			<relatedStateVariable>ui4</relatedStateVariable>
-		  </argument>
-		</argumentList>
-	</action>
-	<action>
+      <argumentList>
+        <argument>
+          <name>NewVolume</name>
+          <direction>in</direction>
+          <relatedStateVariable>ui4</relatedStateVariable>
+          </argument>
+      </argumentList>
+    </action>
+    <action>
       <name>SetFileToPlay</name>
-		<argumentList>
-		  <argument>
-			<name>FileToPlay</name>
-			<direction>in</direction>
-			<relatedStateVariable>string</relatedStateVariable>
-		  </argument>
-		</argumentList>
-	</action>
-	<action>
+      <argumentList>
+        <argument>
+          <name>FileToPlay</name>
+          <direction>in</direction>
+          <relatedStateVariable>string</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
       <name>SetURIToPlay</name>
-		<argumentList>
-		  <argument>
-			<name>URIToPlay</name>
-			<direction>in</direction>
-			<relatedStateVariable>string</relatedStateVariable>
-		  </argument>
-		</argumentList>
-	</action>
+      <argumentList>
+        <argument>
+          <name>URIToPlay</name>
+          <direction>in</direction>
+          <relatedStateVariable>string</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Say</name>
+      <argumentList>
+        <argument>
+          <name>Text</name>
+          <direction>in</direction>
+          <relatedStateVariable>string</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Language</name>
+          <direction>in</direction>
+          <relatedStateVariable>string</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
   </actionList>
 </scpd>
Index: /trunk/D_Sonos1.xml
===================================================================
--- /trunk/D_Sonos1.xml	(revision 20)
+++ /trunk/D_Sonos1.xml	(revision 30)
@@ -24,4 +24,9 @@
 
       <service>
+        <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+      </service>
+
+      <service>
         <serviceType>urn:micasaverde-com:service:Volume:1</serviceType>
         <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
Index: /trunk/I_Sonos1.xml
===================================================================
--- /trunk/I_Sonos1.xml	(revision 20)
+++ /trunk/I_Sonos1.xml	(revision 30)
@@ -3,14 +3,35 @@
 
 <functions>
-  local isMuted = true
+  local url = require("socket.url")
+
+  local MSG_CLASS = "Sonos"
+
   local DEBUG_MODE = true
+  local taskHandle = -1
+
+  local TASK_ERROR = 2
+  local TASK_ERROR_PERM = -2
+  local TASK_SUCCESS = 4
+  local TASK_BUSY = 1
+
+  local PARENT_DEVICE
 
   local upnp
-  
+
+  local HADEVICE_SID = "urn:micasaverde-com:serviceId:HaDevice1"
+
   local ipPort = 1400 -- Sonos
   local UPNP_AVTRANSPORT_URL = 'http://%s:%s/MediaRenderer/AVTransport/Control'
   local UPNP_RENDERING_CONTROL_URL = 'http://%s:%s/MediaRenderer/RenderingControl/Control'
   local UPNP_DEVICE_PROPERTIES_URL = 'http://%s:%s/DeviceProperties/Control'
-  
+  local UPNP_RENDERING_EVENT_URL = 'http://%s:%s/MediaRenderer/RenderingControl/Event'
+
+  -- Derived from:
+  --    http://sonosIP:1400/xml/device_description.xml
+  --
+  local SONOS_AUDIOINPUT_URL = 'http://%s:%s/AudioIn/Control'
+  local SONOS_AUDIOINPUT_SERVICE = 'urn:schemas-upnp-org:service:AudioIn:1'
+  local SONOS_AUDIOINPUT_SID = 'urn:upnp-org:serviceId:AudioIn'
+ 
   -- local ipPort = 8888 -- Onkyo Receiver
   -- local UPNP_AVTRANSPORT_URL = 'http://%s:%s/upnp_control_2'
@@ -18,24 +39,165 @@
 
   local function log(stuff, level)
-    luup.log("Sonos: " .. stuff, (level or 50))
+    luup.log(string.format("%s: %s", MSG_CLASS, stuff), (level or 50))
   end
 
   local function debug(stuff)
     if (DEBUG_MODE) then
-      log("debug " .. stuff, 1)
+      log("debug " .. stuff, 35)
     end
+  end
+
+  local function task(text, mode)
+    luup.log("task " .. text)
+    if (mode == TASK_ERROR_PERM) then
+      taskHandle = luup.task(text, TASK_ERROR, MSG_CLASS, taskHandle)
+    else
+      taskHandle = luup.task(text, mode, MSG_CLASS, taskHandle)
+
+      -- Clear the previous error, since they're all transient
+      if (mode ~= TASK_SUCCESS) then
+        luup.call_delay("clearTask", 30, "", false)
+      end
+    end
+  end
+
+  --
+  -- Has to be "non-local" in order for MiOS to call it :(
+  --
+  function clearTask()
+    task("Clearing...", TASK_SUCCESS)
   end
 
   -- Low level xml element extracter. Borrowed from @cedriclocqueneux example.  Enhanced to handle xml == nil
   local function extractElement(tag, xml, default)
-    if xml == nil then 
-	  return(default) 
-	else
-	  local pattern = "&lt;"..tag..">(.*)&lt;/"..tag..">"
+    if (xml == nil) then
+      return(default)
+    else
+      local pattern = "&lt;"..tag..">(.*)&lt;/"..tag..">"
       local result = (string.match(xml, pattern) or default)
       return result
-	end
-  end
-  
+    end
+  end
+
+  local function setIfChanged(serviceId, name, value, deviceId)
+      local curValue = luup.variable_get(serviceId, name, deviceId)
+
+      if ((value ~= curValue) or (curValue == nil)) then
+          luup.variable_set(serviceId, name, value, deviceId)
+          return true
+      else
+          return false
+      end
+  end
+
+  function refreshCache()
+    debug("refreshCache: start")
+    local status, tmp
+    local changed = false
+    local ip = luup.devices[PARENT_DEVICE].ip
+
+    --
+    -- Loop every 15 seconds to refresh the cache
+    --
+    luup.call_delay("refreshCache", 15)
+
+    --
+    -- The next section reads various status on the Sonos Zone and populates variables on the device. For use by UI or Mute function, etc.
+    -- Is only executed upon initial load of module for now though
+    -- Get ZoneName
+    status, tmp = upnp.UPnP_request(ip, ipPort, UPNP_DEVICE_PROPERTIES_URL,
+      "GetZoneAttributes", upnp.UPNP_DEVICE_PROPERTIES_SERVICE, {CurrentZoneName="", CurrentIcon=""})
+
+    if (status ~= true) then
+        debug("refreshCache: Device offline? status=" .. tmp)
+        return
+    end
+
+    changed = changed or setIfChanged("urn:schemas-micasaverde-com:device:avmisc:1", "ZoneName", extractElement("CurrentZoneName", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_DEVICE_PROPERTIES_SID, "ZoneName", extractElement("CurrentZoneName", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_DEVICE_PROPERTIES_SID, "Icon", extractElement("CurrentIcon", tmp, ""), PARENT_DEVICE)
+
+    -- GetCurrentTransportState  (PLAYING, STOPPED, etc)
+    status, tmp = upnp.UPnP_request(ip, ipPort, UPNP_AVTRANSPORT_URL,
+      "GetTransportInfo", upnp.UPNP_AVTRANSPORT_SERVICE, {InstanceID="0", CurrentTransportState="", CurrentTransportStatus="", CurrentSpeed="1"})
+    changed = changed or setIfChanged("urn:schemas-micasaverde-com:device:avmisc:1", "CurrentTransportState", extractElement("CurrentTransportState", tmp, ""),  PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "TransportState", extractElement("CurrentTransportState", tmp, ""),  PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "TransportStatus", extractElement("CurrentTransportStatus", tmp, ""),  PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "TransportPlaySpeed", extractElement("CurrentSpeed", tmp, ""),  PARENT_DEVICE)
+
+    -- Get Playmode (NORMAL, REPEAT_ALL, SHUFFLE_NOREPEAT, SHUFFLE)
+    status, tmp = upnp.UPnP_request(ip, ipPort, UPNP_AVTRANSPORT_URL,
+      "GetTransportSettings", upnp.UPNP_AVTRANSPORT_SERVICE, {InstanceID="0", PlayMode="", ReqQualityMode=""})
+    changed = changed or setIfChanged("urn:schemas-micasaverde-com:device:avmisc:1", "PlayMode", extractElement("PlayMode", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "CurrentPlayMode", extractElement("PlayMode", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "CurrentRecordQualityMode", extractElement("RecQualityMode", tmp, ""), PARENT_DEVICE)
+
+    -- Get Current Transport Actions (a CSV of valid Transport Action/Transitions)
+    status, tmp = upnp.UPnP_request(ip, ipPort, UPNP_AVTRANSPORT_URL,
+      "GetCurrentTransportActions", upnp.UPNP_AVTRANSPORT_SERVICE, {InstanceID="0"})
+    changed = changed or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "CurrentTransportActions", extractElement("Actions", tmp, ""), PARENT_DEVICE)
+
+    -- Get Audio Line in settings
+    status, tmp = upnp.UPnP_request(ip, ipPort, SONOS_AUDIOINPUT_URL,
+      "GetAudioInputAttributes", SONOS_AUDIOINPUT_SERVICE, {InstanceID="0"})
+    changed = changed or setIfChanged(SONOS_AUDIOINPUT_SID, "AudioInputName", extractElement("CurrentName", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(SONOS_AUDIOINPUT_SID, "Icon", extractElement("CurrentIcon", tmp, ""), PARENT_DEVICE)
+
+    -- Get Media Information
+    status, tmp = upnp.UPnP_request(ip, ipPort, UPNP_AVTRANSPORT_URL,
+      "GetMediaInfo", upnp.UPNP_AVTRANSPORT_SERVICE, {InstanceID="0"})
+    changed = changed or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "NumberOfTracks", extractElement("NrTracks", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "CurrentMediaDuration", extractElement("MediaDuration", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "AVTransportURI", extractElement("CurrentURI", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "NextAVTransportURI", extractElement("NextURI", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "PlaybackStorageMedium", extractElement("PlayMedium", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "RecordStorageMedium", extractElement("RecordMedium", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "RecordMediumWriteStatus", extractElement("WriteStatus", tmp, ""), PARENT_DEVICE)
+
+    -- Get Current URI - song or radio station etc
+    status, tmp = upnp.UPnP_request(ip, ipPort, UPNP_AVTRANSPORT_URL,
+      "GetPositionInfo", upnp.UPNP_AVTRANSPORT_SERVICE, {InstanceID="0", Track="0", TrackMetaData="", TrackURI="", RelTime="", AbsTime="", RelCount="0", AbsCount="0"})
+    changed = changed or setIfChanged("urn:schemas-micasaverde-com:device:avmisc:1", "TrackURI", extractElement("TrackURI", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "CurrentTrack", extractElement("Track", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "CurrentTrackDuration", extractElement("TrackDuration", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "CurrentTrackURI", extractElement("TrackURI", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "RelativeTimePosition", extractElement("RelTime", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "AbsoluteTimePosition", extractElement("AbsTime", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "RelativeCounterPosition", extractElement("RelTime", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_AVTRANSPORT_SID, "AbsoluteCounterPosition", extractElement("AbsTime", tmp, ""), PARENT_DEVICE)
+
+    -- Get Mute status
+    status, tmp = upnp.UPnP_request(ip, ipPort, UPNP_RENDERING_CONTROL_URL,
+      "GetMute", upnp.UPNP_RENDERING_CONTROL_SERVICE, {InstanceID="0", Channel="Master", CurrentMute=""})
+    changed = changed or setIfChanged("urn:schemas-micasaverde-com:device:avmisc:1", "CurrentMute", extractElement("CurrentMute", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_RENDERING_CONTROL_SID, "Mute", extractElement("CurrentMute", tmp, ""), PARENT_DEVICE)
+
+    -- Get Volume
+    status, tmp = upnp.UPnP_request(ip, ipPort, UPNP_RENDERING_CONTROL_URL,
+      "GetVolume", upnp.UPNP_RENDERING_CONTROL_SERVICE, {InstanceID="0", Channel="Master", CurrentVolume=""})
+    changed = changed or setIfChanged("urn:schemas-micasaverde-com:device:avmisc:1", "CurrentVolume", extractElement("CurrentVolume", tmp, ""), PARENT_DEVICE)
+                      or setIfChanged(upnp.UPNP_RENDERING_CONTROL_SID, "Volume", extractElement("CurrentVolume", tmp, ""), PARENT_DEVICE)
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), PARENT_DEVICE)
+    end
+  end
+
+  function deferredStartup()
+    debug("deferredStartup: start")
+
+    local status, tmp
+
+    local ip = luup.devices[PARENT_DEVICE].ip
+
+    --
+    --  Attempt to subscribe to events
+    --
+    --ip, port, event url, user-agent, callback, nt, TimeOut
+    -- status = upnp.UPnP_subscribe(ip, ipPort, UPNP_RENDERING_EVENT_URL, "http://192.168.5.209:3480/cmh/event")
+
+    refreshCache()
+  end
+
   function sonosStartup(lul_device)
     if (package.path:find("/etc/cmh-ludl/?.lua;/etc/cmh-lu/?.lua", 1, true) == nil) then
@@ -44,61 +206,11 @@
     package.loaded.L_Sonos1 = nil
 
+    log("#" .. lul_device .. " starting up with id " .. luup.devices[lul_device].id)
+    PARENT_DEVICE = lul_device
     upnp = require("L_Sonos1")
     upnp.initialize(debug)
-	
-    --
-	--  Attempt to subscribe to events
-	--
-	--ip, port, event url, user-agent, callback, nt, TimeOut
-	-- local l_SubscribeInfo = upnp.UPnP_subscribe(luup.devices[lul_device].ip, ipPort)
-		
-	-- 
-	-- The next section reads various status on the Sonos Zone and populates variables on the device. For use by UI or Mute function, etc.
-	-- Is only executed upon initial load of module for now though
-	-- Get ZoneName
-	local l_DeviceInfo = upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_DEVICE_PROPERTIES_URL,
-						"GetZoneAttributes", upnp.UPNP_DEVICE_PROPERTIES_SID,
-						{CurrentZoneName="", CurrentIcon=""})
-	l_CurrentZoneName = extractElement("CurrentZoneName", l_DeviceInfo, "")	
-	luup.variable_set("urn:schemas-micasaverde-com:device:avmisc:1", "ZoneName", l_CurrentZoneName, lul_device)
-	
-	-- GetCurrentTransportState  (PLAYING, STOPPED, etc)
-	local l_GetCurrentTransportState = upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-						"GetTransportInfo", upnp.UPNP_AVTRANSPORT_SID,
-						{InstanceID="0", CurrentTransportState="", CurrentTransportStatus="", CurrentSpeed = "1"})
-	l_CurrentTransportState = extractElement("CurrentTransportState", l_GetCurrentTransportState, "")	
-	luup.variable_set("urn:schemas-micasaverde-com:device:avmisc:1", "CurrentTransportState", l_CurrentTransportState,  lul_device)
-		
-	-- Get Playmode (NORMAL, REPEAT_ALL, SHUFFLE_NOREPEAT, SHUFFLE)
-	local l_PlayMode = upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-						"GetTransportSettings", upnp.UPNP_AVTRANSPORT_SID,
-						{InstanceID="0", PlayMode="", ReqQualityMode=""})
-	l_PlayMode = extractElement("PlayMode", l_PlayMode, "")	
-	luup.variable_set("urn:schemas-micasaverde-com:device:avmisc:1", "PlayMode", l_PlayMode, lul_device)
-		
-	-- Get Current URI - song or radio station etc
-	local l_PositionInfo = upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-						"GetPositionInfo", upnp.UPNP_AVTRANSPORT_SID,
-						{InstanceID="0", Track="0", TrackMetaData="", TrackURI = "", RelTime = "", AbsTime = "", RelCount = "0", AbsCount = "0"})
-	l_TrackURI = extractElement("TrackURI", l_PositionInfo, "")	
-	luup.variable_set("urn:schemas-micasaverde-com:device:avmisc:1", "TrackURI", l_TrackURI, lul_device)
-
-    -- Get Mute status
-	local l_CurrentMute = upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
-						"GetMute", upnp.UPNP_RENDERING_CONTROL_SID,
-						{InstanceID="0", Channel="Master", CurrentMute=""})
-	l_CurrentMute = extractElement("CurrentMute", l_CurrentMute, "")	
-	luup.variable_set("urn:schemas-micasaverde-com:device:avmisc:1", "CurrentMute", l_CurrentMute, lul_device)
-	isMuted = l_CurrentMute
-	
-    -- Get Volume
-	local l_CurrentVolume = upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
-						"GetVolume", upnp.UPNP_RENDERING_CONTROL_SID,
-						{InstanceID="0", Channel="Master", CurrentVolume=""})
-	l_CurrentVolume = extractElement("CurrentVolume", l_CurrentVolume, "")	
-	luup.variable_set("urn:schemas-micasaverde-com:device:avmisc:1", "CurrentVolume", l_CurrentVolume, lul_device)
-	
-end
- 
+
+    luup.call_delay("deferredStartup", 1) 
+  end
 </functions>
 <startup>sonosStartup</startup>
@@ -109,6 +221,58 @@
     <run>
       upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-                   "Play", upnp.UPNP_AVTRANSPORT_SID,
-                   {InstanceID="0", Speed="1"})
+        "Play", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID="0", Speed="1"})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Play</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "Play", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0"), Speed=(lul_settings.Speed or "1")})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:Sonos1</serviceId>
+    <name>Say</name>
+    <run>
+      local url = string.format("x-rincon-mp3radio://translate.google.com/translate_tts?tl=%s&amp;q=%s",
+                                (lul_settings.Language or "en"),
+                                (url.escape(lul_settings.Text or "Please pass a text parameter"))),
+
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "SetPlayMode", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0"), NewPlayMode="SHUFFLE_NOREPEAT"})
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "StartAutoplay", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or '0'),
+         ProgramURI=url,
+         ProgramMetaData="",
+         Volume=(lul_settings.Volume or 50),
+         IncludeLinkedZones=1,
+         ResetVolumeAfter=1})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Record</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "Record", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0")})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Seek</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "Record", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0"), Unit=(lul_settings.Unit or ""), Target=(lul_settings.Target or "")})
     </run>
   </action>
@@ -119,8 +283,19 @@
     <run>
       upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-                   "Pause", upnp.UPNP_AVTRANSPORT_SID,
-                   {InstanceID="0"})
-    </run>
-  </action>
+        "Pause", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID="0"})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Pause</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "Pause", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0")})
+    </run>
+  </action>
+
 
   <action>
@@ -129,6 +304,16 @@
     <run>
       upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-                   "Stop", upnp.UPNP_AVTRANSPORT_SID,
-                   {InstanceID="0"})
+        "Stop", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID="0"})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Stop</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "Stop", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0")})
     </run>
   </action>
@@ -139,7 +324,26 @@
     <run>
       upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-                   "Next", upnp.UPNP_AVTRANSPORT_SID,
-                   {InstanceID="0"})
-
+        "Next", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID="0"})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Next</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "Next", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0")})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>NextSection</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "NextSection", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0")})
     </run>
   </action>
@@ -150,7 +354,57 @@
     <run>
       upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-                   "Previous", upnp.UPNP_AVTRANSPORT_SID,
-                   {InstanceID="0"})
-
+        "Previous", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID="0"})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Previous</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "Previous", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0")})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>PreviousSection</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "PreviousSection", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0")})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetPlayMode</name>
+    <run>
+      -- NORMAL, SHUFFLE, SHUFFLE_NOREPEAT, REPEAT_ONE, REPEAT_ALL, RANDOM, DIRECT_1, INTRO
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "SetPlayMode", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0"), NewPlayMode=(lul_settings.NewPlayMode)})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetRecordQualityMode</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "SetRecordQualityMode", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0"), NewRecordQualityMode=(lul_settings.NewRecordQualityMode)})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetMute</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
+        "SetMute", upnp.UPNP_RENDERING_CONTROL_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or "0"), Channel=(lul_settings.Channel or "Master"), DesiredMute=(tonumber(lul_settings.DesiredMute) or 1)})
     </run>
   </action>
@@ -161,6 +415,6 @@
     <run>
       upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
-                   "SetMute", upnp.UPNP_RENDERING_CONTROL_SID,
-                   {InstanceID="0", Channel="Master", DesiredMute=1})
+        "SetMute", upnp.UPNP_RENDERING_CONTROL_SERVICE,
+        {InstanceID="0", Channel="Master", DesiredMute=1})
     </run>
   </action>
@@ -169,18 +423,28 @@
     <serviceId>urn:micasaverde-com:serviceId:Sonos1</serviceId>
     <name>UnMute</name>
-		<run>
-      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
-                   "SetMute", upnp.UPNP_RENDERING_CONTROL_SID,
-                   {InstanceID="0", Channel="Master", DesiredMute=0})
-    </run>
-  </action>
-  
-   <action>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
+        "SetMute", upnp.UPNP_RENDERING_CONTROL_SERVICE,
+        {InstanceID="0", Channel="Master", DesiredMute=0})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetVolume</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
+        "SetVolume", upnp.UPNP_RENDERING_CONTROL_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or '0'), Channel=(lul.settings.Channel or 'Master'), DesiredVolume=(tonumber(lul_settings.DesiredVolume) or 0)})
+    </run>
+  </action>
+
+  <action>
     <serviceId>urn:micasaverde-com:serviceId:Sonos1</serviceId>
     <name>SetVolume</name>
     <run>
-	upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
-                   "SetVolume", upnp.UPNP_RENDERING_CONTROL_SID,
-				   {InstanceID='0', Channel='Master', DesiredVolume=tonumber(lul_settings.NewVolume)})
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
+        "SetVolume", upnp.UPNP_RENDERING_CONTROL_SERVICE,
+        {InstanceID='0', Channel='Master', DesiredVolume=tonumber(lul_settings.NewVolume)})
     </run>
   </action>
@@ -190,8 +454,42 @@
     <name>SetFileToPlay</name>
     <run>
-	-- kept for backward compatability to first version. SetURIToPlay should be used instead. Will only allow protocol for files.
-	upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-                   "SetAVTransportURI", upnp.UPNP_AVTRANSPORT_SID,
-				   {InstanceID='0', CurrentURI="x-file-cifs:" .. lul_settings.FileToPlay, CurrentURIMetaData = ""})
+      -- kept for backward compatability to first version. SetURIToPlay should be used instead. Will only allow protocol for files.
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "SetAVTransportURI", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID='0', CurrentURI="x-file-cifs:" .. lul_settings.FileToPlay, CurrentURIMetaData = ""})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetAVTransportURI</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "SetAVTransportURI", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or '0'), CurrentURI=(lul_settings.CurrentURI), CurrentURIMetaData=(lul_settings.CurrentURIMetaData or "")})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>AddURIToQueue</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "SetAVTransportURI", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or '0'),
+         EnqueuedURI=(lul_settings.EnqueuedURI or ""),
+         EnqueuedURIMetaData=(lul_settings.EnqueuedURIMetaData or ""),
+         DesiredFirstTrackNumberEnqueued=(lul_settings.DesiredFirstTrackNumberEnqueued or 1),
+         EnqueueAsNext=(lul_settings.EnqueueAsNext or true)})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>RemoveAllTracksFromQueue</name>
+    <run>
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "RemoveAllTracksFromQueue", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID=(lul_settings.InstanceID or '0'))})
     </run>
   </action>
@@ -201,21 +499,20 @@
     <name>SetURIToPlay</name>
     <run>
-	upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
-                   "SetAVTransportURI", upnp.UPNP_AVTRANSPORT_SID,
-				   {InstanceID='0', CurrentURI=lul_settings.URIToPlay, CurrentURIMetaData = ""})
-				   
-				    -- URI must include protocol as prefix.
-					-- x-file-cifs:
-					-- file:
-					-- x-rincon:
-					-- x-rincon-mp3radio:
-					-- x-rincon-playlist:
-					-- x-rincon-queue:
-					-- x-rincon-stream:
-                    -- example is DR Jazz Radio: x-rincon-mp3radio://live-icy.gss.dr.dk:8000/Channel22_HQ.mp3
-	
-	</run>
-  </action>
-  
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_AVTRANSPORT_URL,
+        "SetAVTransportURI", upnp.UPNP_AVTRANSPORT_SERVICE,
+        {InstanceID='0', CurrentURI=lul_settings.URIToPlay, CurrentURIMetaData = ""})
+
+      -- URI must include protocol as prefix.
+      -- x-file-cifs:
+      -- file:
+      -- x-rincon:
+      -- x-rincon-mp3radio:
+      -- x-rincon-playlist:
+      -- x-rincon-queue:
+      -- x-rincon-stream:
+      -- example is DR Jazz Radio: x-rincon-mp3radio://live-icy.gss.dr.dk:8000/Channel22_HQ.mp3
+    </run>
+  </action>
+
   <action>
     <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
@@ -223,9 +520,15 @@
     <run>
       -- Toggle Mute
-      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
-                   "SetMute", upnp.UPNP_RENDERING_CONTROL_SID,
-                   {InstanceID="0", Channel="Master", DesiredMute=isMuted})
-
-      isMuted = (isMuted == false)
+      local changed = false
+      local isMuted = luup.variable_get(upnp.UPNP_RENDERING_CONTROL_SID, "CurrentMute", lul_device)
+      upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
+        "SetMute", upnp.UPNP_RENDERING_CONTROL_SID, {InstanceID="0", Channel="Master", DesiredMute=isMuted})
+
+      changed = changed or setIfChanged("urn:schemas-micasaverde-com:device:avmisc:1", "CurrentMute", (1-isMuted), lul_device)
+                        or setIfChanged(upnp.UPNP_RENDERING_CONTROL_SERVICE, "CurrentMute", (1-isMuted), PARENT_DEVICE)
+
+      if (changed) then
+          luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), PARENT_DEVICE)
+      end
     </run>
   </action>
@@ -237,7 +540,6 @@
       -- Volume up
       upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
-                   "SetRelativeVolume", upnp.UPNP_RENDERING_CONTROL_SID,
-                   {InstanceID="0", Channel="Master", Adjustment=3})
-
+        "SetRelativeVolume", upnp.UPNP_RENDERING_CONTROL_SERVICE,
+        {InstanceID="0", Channel="Master", Adjustment=3})
     </run>
   </action>
@@ -249,9 +551,8 @@
       -- Volume down
       upnp.UPnP_request(luup.devices[lul_device].ip, ipPort, UPNP_RENDERING_CONTROL_URL,
-                   "SetRelativeVolume", upnp.UPNP_RENDERING_CONTROL_SID,
-                   {InstanceID="0", Channel="Master", Adjustment=-3})
+        "SetRelativeVolume", upnp.UPNP_RENDERING_CONTROL_SERVICE,
+        {InstanceID="0", Channel="Master", Adjustment=-3})
     </run>
   </action>
 </actionList>
 </implementation>
-
