Index: /tags/0.4/I_DLNAMediaController1.xml
===================================================================
--- /tags/0.4/I_DLNAMediaController1.xml	(revision 30)
+++ /tags/0.4/I_DLNAMediaController1.xml	(revision 30)
@@ -0,0 +1,2085 @@
+<?xml version="1.0"?>
+<implementation>
+
+<functions>
+  local socket = require("socket")
+  local url = require("socket.url")
+
+  local MSG_CLASS = "DLNA"
+
+  local DEBUG_MODE = false
+  local taskHandle = -1
+
+  local TASK_ERROR = 2
+  local TASK_ERROR_PERM = -2
+  local TASK_SUCCESS = 4
+  local TASK_BUSY = 1
+
+  local VERA_IP
+  local VERA_WEB_PORT
+
+  local UPNP_AVTRANSPORT_SERVICE = 'urn:schemas-upnp-org:service:AVTransport:1'
+  local UPNP_RENDERING_CONTROL_SERVICE = 'urn:schemas-upnp-org:service:RenderingControl:1'
+  local UPNP_CONNECTION_MANAGER_SERVICE = 'urn:schemas-upnp-org:service:ConnectionManager:1'
+  local UPNP_MR_CONTENT_DIRECTORY_SERVICE = 'urn:schemas-upnp-org:service:ContentDirectory:1'
+
+  local UPNP_AVTRANSPORT_SID = 'urn:upnp-org:serviceId:AVTransport'
+  local UPNP_RENDERING_CONTROL_SID = 'urn:upnp-org:serviceId:RenderingControl'
+  local UPNP_CONNECTION_MANAGER_SID = 'urn:upnp-org:serviceId:ConnectionManager'
+  local UPNP_MR_CONTENT_DIRECTORY_SID = 'urn:upnp-org:serviceId:ContentDirectory'
+
+  if (package.path:find("/etc/cmh-ludl/?.lua;/etc/cmh-lu/?.lua", 1, true) == nil) then
+      package.path = package.path..";/etc/cmh-ludl/?.lua;/etc/cmh-lu/?.lua"
+  end
+  package.loaded.L_DLNAUPnPUtility1 = nil
+
+  local upnp = require("L_DLNAUPnPUtility")
+
+  local ipDMR = nil
+  local portDMR
+  local DMRDescriptionURL
+  local iconURL
+  local uuidDMR
+
+  local ipDMS
+  local portDMS
+  local DMSDescriptionURL
+  local uuidDMS
+
+  local HADEVICE_SID = "urn:micasaverde-com:serviceId:HaDevice1"
+  local DLNA_DMC_SID = "urn:dlna-org:serviceId:DLNAMediaController1"
+  local DLNA_DMC_DEVICE_TYPE = "urn:dlna-org:device:DLNAMediaController:1"
+
+  local EventSubscriptions = {
+      { service = UPNP_AVTRANSPORT_SERVICE,
+        eventVariable = "LastChange",
+        actionName = "NotifyAVTransportChange",
+        id = "",
+        expiry = "" },
+      { service = UPNP_RENDERING_CONTROL_SERVICE,
+        eventVariable = "LastChange",
+        actionName = "NotifyRenderingChange",
+        id = "",
+        expiry = "" }
+  }
+
+  local PLUGIN_ICON = "http://%s:%d/cmh/skins/default/icons/DLNA.png"
+
+  local SAY_TMP_FILE = "/tmp/Say.%s.%s.mp3"
+  local SAY_OUTPUT_FILE = "/www/Say.%s.mp3"
+  local SAY_OUTPUT_URI = "http://%s:%d/Say.%s.mp3"
+  local DEFAULT_METADATA = '&lt;DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/">'
+                           .. '&lt;item id="DLNA" parentID="-1" restricted="1">'
+                           .. '&lt;dc:title>%s&lt;/dc:title>'
+                           .. '%s&lt;/item>&lt;/DIDL-Lite>'
+
+  local SAY_EXECUTE = "rm " .. SAY_TMP_FILE .. " ; wget --output-document " .. SAY_TMP_FILE ..[[ \
+ --quiet \
+ --header "Accept-Charset: utf-8;q=0.7,*;q=0.3" \
+ --header "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
+ --user-agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11" \
+ "http://translate.google.com/translate_tts?tl=%s&amp;q=%s"]]
+
+  local CONCAT_EXECUTE = "cat %s > " .. SAY_OUTPUT_FILE .. " ; rm %s"
+  local DELETE_EXECUTE = "rm " .. SAY_OUTPUT_FILE
+
+  local playbackCxt = nil
+  local sayPlayback = nil
+  local sayQueue = {}
+
+  local dataTable = {}
+
+  local variableSidTable = {
+        TransportState = UPNP_AVTRANSPORT_SID,
+        TransportStatus = UPNP_AVTRANSPORT_SID,
+        TransportPlaySpeed = UPNP_AVTRANSPORT_SID,
+        CurrentPlayMode = UPNP_AVTRANSPORT_SID,
+        CurrentTransportActions = UPNP_AVTRANSPORT_SID,
+        NumberOfTracks = UPNP_AVTRANSPORT_SID,
+        CurrentMediaDuration = UPNP_AVTRANSPORT_SID,
+        AVTransportURI = UPNP_AVTRANSPORT_SID,
+        AVTransportURIMetaData = UPNP_AVTRANSPORT_SID,
+        CurrentTrack = UPNP_AVTRANSPORT_SID,
+        CurrentTrackDuration = UPNP_AVTRANSPORT_SID,
+        CurrentTrackURI = UPNP_AVTRANSPORT_SID,
+        CurrentTrackMetaData = UPNP_AVTRANSPORT_SID,
+        CurrentStatus = UPNP_AVTRANSPORT_SID,
+        CurrentTitle = UPNP_AVTRANSPORT_SID,
+        CurrentArtist = UPNP_AVTRANSPORT_SID,
+        CurrentAlbum = UPNP_AVTRANSPORT_SID,
+        CurrentAlbumArt = UPNP_AVTRANSPORT_SID,
+        CurrentAlbumArt2 = UPNP_AVTRANSPORT_SID,
+        RelativeTimePosition = UPNP_AVTRANSPORT_SID,
+
+        Mute = UPNP_RENDERING_CONTROL_SID,
+        Volume = UPNP_RENDERING_CONTROL_SID,
+
+        SinkProtocolInfo = UPNP_CONNECTION_MANAGER_SID,
+
+        Online = DLNA_DMC_SID,
+        ModelName = DLNA_DMC_SID,
+
+        ProxyUsed = DLNA_DMC_SID
+  }
+
+  local function log(stuff, level)
+    luup.log(string.format("%s: %s", MSG_CLASS, stuff), (level or 50))
+  end
+
+  local function warning(stuff)
+    log("warning " .. stuff, 2)
+  end
+
+  local function error(stuff)
+    log("error " .. stuff, 1)
+  end
+
+  local function debug(stuff)
+    if (DEBUG_MODE) then
+      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
+
+  local function defaultValue(arr, val, default)
+    if (arr == nil or arr[val] == nil or arr[val] == "") then
+      return default
+    else
+      return arr[val]
+    end
+  end
+
+  local function setData(name, value, deviceId, default)
+      dataTable[name] = value
+
+      if (variableSidTable[name] == nil) then
+          return (default or false)
+      end
+
+      local curValue = luup.variable_get(variableSidTable[name], name, deviceId)
+
+      if ((value ~= curValue) or (curValue == nil)) then
+          luup.variable_set(variableSidTable[name], name, value, deviceId)
+          return true
+      else
+          return (default or false)
+      end
+  end
+
+  --
+  -- Put together a rudimentary status string for the Dashboard
+  --
+  local function getSimpleDIDLStatus(meta)
+      local title = ""
+      local artist = ""
+      local album = ""
+      local albumArt = ""
+      local duration = ""
+      local didl = nil
+      local didlTable = nil
+      local complement = ""
+      if (meta ~= nil and meta ~= "") then
+          didl, didlTable = upnp.parseDIDLItem(meta)
+
+          if (didlTable["upnp:class"] == "object.item") then
+              title = upnp.decode(didlTable["dc:title"] or title)
+              if (didlTable["upnp:albumArtURI"] ~= nil) then
+                  albumArt = upnp.decode(didlTable["upnp:albumArtURI"])
+              end
+              duration = didlTable["res@duration"] or duration
+          elseif ((didlTable["upnp:class"] == "object.item.audioItem.musicTrack")
+                  or (didlTable["upnp:class"] == "object.item.audioItem")) then
+              title = upnp.decode(didlTable["dc:title"] or title)
+              if (didlTable["dc:creator"] ~= "Unknown") then
+                  artist = upnp.decode(didlTable["dc:creator"] or artist)
+              end
+              if (didlTable["upnp:album"] ~= "Unknown") then
+                  album = upnp.decode(didlTable["upnp:album"] or album)
+              end
+              if (didlTable["upnp:albumArtURI"] ~= nil) then
+                  albumArt = upnp.decode(didlTable["upnp:albumArtURI"])
+              end
+              if (artist ~= "" or album ~= "") then
+                  complement = string.format(" (%s, %s)", artist, album)
+              end
+              duration = didlTable["res@duration"] or duration
+          elseif (didlTable["upnp:class"] == "object.item.audioItem.audioBroadcast") then
+              title = upnp.decode(didlTable["dc:title"] or title)
+          elseif (didlTable["upnp:class"] == "object.item.videoItem") then
+              title = upnp.decode(didlTable["dc:title"] or title)
+              if (didlTable["upnp:albumArtURI"] ~= nil) then
+                  albumArt = upnp.decode(didlTable["upnp:albumArtURI"])
+              end
+              duration = didlTable["res@duration"] or duration
+          end
+          duration = duration:match("(%d+:%d+:%d+).-") or ""
+      end
+      return complement, title, artist, album, albumArt, duration, didl, didlTable
+  end
+
+  local function deviceIsOnline(device)
+    local changed = setData("Online", "1", device, false)
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+    return changed
+  end
+
+  local function deviceIsOffline(device)
+    local changed = setData("Online", "0", device, false)
+    if (changed) then
+        iconURL = PLUGIN_ICON:format(VERA_IP, VERA_WEB_PORT)
+
+        changed = setData("TransportState", "STOPPED",  device, changed)
+        changed = setData("TransportStatus", "KO",  device, changed)
+        changed = setData("TransportPlaySpeed", "1",  device, changed)
+        changed = setData("CurrentPlayMode", "NORMAL", device, changed)
+        changed = setData("CurrentTransportActions", "", device, changed)
+        changed = setData("NumberOfTracks", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("CurrentMediaDuration", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("AVTransportURI", "", device, changed)
+        changed = setData("AVTransportURIMetaData", "", device, changed)
+        changed = setData("CurrentTrack", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("CurrentTrackDuration", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("CurrentTrackURI", "", device, changed)
+        changed = setData("CurrentTrackMetaData", "", device, changed)
+        changed = setData("CurrentStatus", "Offline", device, changed)
+        changed = setData("CurrentTitle", "", device, changed)
+        changed = setData("CurrentArtist", "", device, changed)
+        changed = setData("CurrentAlbum", "", device, changed)
+        changed = setData("CurrentAlbumArt", iconURL, device, changed)
+        changed = setData("CurrentAlbumArt2", iconURL, device, changed)
+        changed = setData("RelativeTimePosition", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("Volume", "0", device, changed)
+        changed = setData("Mute", "0", device, changed)
+        changed = setData("SinkProtocolInfo", "", device, changed)
+
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+
+        upnp.cancelProxySubscriptions(EventSubscriptions)
+    end
+  end
+
+  local function commsFailure(device, text)
+    debug("commsFailure: Device offline? status=" .. text)
+
+    deviceIsOffline(device)
+  end
+
+  local function extractDataFromMetaData(device, currentUri, currentUriMetaData, trackUri, trackUriMetaData)
+    local statusString, info, title, title2, artist, album, albumArt, duration
+    if (currentUriMetaData ~= trackUriMetaData) then
+        info, title = getSimpleDIDLStatus(currentUriMetaData)
+    end
+    info, title2, artist, album, albumArt, duration = getSimpleDIDLStatus(trackUriMetaData)
+    if (currentUriMetaData == trackUriMetaData) then
+        title = title2
+    end
+    if (title ~= "" and title ~= title2 and string.sub(title2, 1, 10) == "x-sonosapi") then
+        title2 = title
+    end
+    if (title == "") then
+        title = title2
+    end
+    statusString = ""
+    if (title ~= "") then
+        statusString = statusString .. title
+    end
+    if (title2 ~= "" and title2 ~= title) then
+        if (statusString ~= "") then
+            statusString = statusString .. ": "
+        end
+        statusString = statusString .. title2
+    end
+    if (currentUri == "") then
+        info = "No media"
+    end
+    if (info ~= "") then
+        if (statusString ~= "") then
+            statusString = statusString .. ": "
+        end
+        statusString = statusString .. info
+    end
+    if (albumArt ~= "") then
+        albumArt = url.absolute(string.format("http://%s:%s/", ipDMR, portDMR), albumArt)
+    else
+        albumArt = iconURL
+    end
+    return statusString, title2, artist, album, albumArt, duration
+  end
+
+  local function refreshNow(device, force)
+    debug("refreshNow: start")
+
+    if (upnp.proxyVersionAtLeast(1) and force == false) then
+        return
+    end
+
+    if (uuidDMR == nil or uuidDMR == "") then
+        return
+    end
+
+    local status, tmp
+    local changed = false
+    local statusString, title, artist, album, albumArt, duration
+    local currentUri, currentUriMetaData, trackUri, trackUriMetaData
+
+    local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+    if (AVTransport ~= nil) then
+        -- GetCurrentTransportState  (PLAYING, STOPPED, etc)
+        status, tmp = AVTransport.GetTransportInfo({InstanceID="0"})
+        if (status ~= true) then
+            commsFailure(device, tmp)
+            return
+        end
+        if (deviceIsOnline(device)) then
+            setup(device, true)
+        end
+        changed = setData("TransportState", upnp.extractElement("CurrentTransportState", tmp, ""), device, changed)
+        changed = setData("TransportStatus", upnp.extractElement("CurrentTransportStatus", tmp, ""), device, changed)
+        changed = setData("TransportPlaySpeed", upnp.extractElement("CurrentSpeed", tmp, ""), device, changed)
+
+        -- Get Playmode (NORMAL, REPEAT_ALL, SHUFFLE_NOREPEAT, SHUFFLE)
+        status, tmp = AVTransport.GetTransportSettings({InstanceID="0"})
+        changed = setData("CurrentPlayMode", upnp.extractElement("PlayMode", tmp, ""), device, changed)
+
+        -- Get Current Transport Actions (a CSV of valid Transport Action/Transitions)
+        status, tmp = AVTransport.GetCurrentTransportActions({InstanceID="0"})
+        changed = setData("CurrentTransportActions", upnp.extractElement("Actions", tmp, ""), device, changed)
+
+        -- Get Media Information
+        status, tmp = AVTransport.GetMediaInfo({InstanceID="0"})
+        currentUri = upnp.extractElement("CurrentURI", tmp, "")
+        currentUriMetaData = upnp.extractElement("CurrentURIMetaData", tmp, "")
+        changed = setData("NumberOfTracks", upnp.extractElement("NrTracks", tmp, "NOT_IMPLEMENTED"), device, changed)
+        changed = setData("CurrentMediaDuration", upnp.extractElement("MediaDuration", tmp, "NOT_IMPLEMENTED"), device, changed)
+        changed = setData("AVTransportURI", currentUri, device, changed)
+        changed = setData("AVTransportURIMetaData", currentUriMetaData, device, changed)
+
+        -- Get Current URI - song or radio station etc
+        status, tmp = AVTransport.GetPositionInfo({InstanceID="0"})
+        trackUri = upnp.extractElement("TrackURI", tmp, "")
+        trackUriMetaData = upnp.extractElement("TrackMetaData", tmp, "")
+        changed = setData("CurrentTrack", upnp.extractElement("Track", tmp, "NOT_IMPLEMENTED"), device, changed)
+        changed = setData("CurrentTrackDuration", upnp.extractElement("TrackDuration", tmp, "NOT_IMPLEMENTED"), device, changed)
+        changed = setData("CurrentTrackURI", trackUri, device, changed)
+        changed = setData("CurrentTrackMetaData", trackUriMetaData, device, changed)
+        changed = setData("RelativeTimePosition", upnp.extractElement("RelTime", tmp, "NOT_IMPLEMENTED"), device, changed)
+
+        statusString, title, artist, album, albumArt, duration =
+            extractDataFromMetaData(device, currentUri, currentUriMetaData, trackUri, trackUriMetaData)
+
+        changed = setData("CurrentStatus", statusString, device, changed)
+        changed = setData("CurrentTitle", title, device, changed)
+        changed = setData("CurrentArtist", artist, device, changed)
+        changed = setData("CurrentAlbum", album, device, changed)
+        changed = setData("CurrentAlbumArt", albumArt, device, changed)
+
+        if (albumArt == "" or not upnp.testURL(albumArt, nil)) then
+            albumArt = iconURL
+        end
+        changed = setData("CurrentAlbumArt2", albumArt, device, changed)
+    end
+
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+    if (Rendering ~= nil) then
+        -- Get Mute status
+        status, tmp = Rendering.GetMute({OrderedArgs={"InstanceID=0", "Channel=Master"}})
+        if (status ~= true) then
+            commsFailure(device, tmp)
+            return
+        end
+        if (deviceIsOnline(device)) then
+            setup(device, true)
+        end
+        changed = setData("Mute", upnp.extractElement("CurrentMute", tmp, ""), device, changed)
+
+        -- Get Volume
+        status, tmp = Rendering.GetVolume({OrderedArgs={"InstanceID=0", "Channel=Master"}})
+        changed = setData("Volume", upnp.extractElement("CurrentVolume", tmp, ""), device, changed)
+    end
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+  end
+
+  local function refreshVolumeNow(device)
+    debug("refreshVolumeNow: start")
+
+    if (upnp.proxyVersionAtLeast(1)) then
+        return
+    end
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+    if (Rendering == nil) then
+        return
+    end
+
+    local status, tmp, changed
+
+    -- Get Volume
+    status, tmp = Rendering.GetVolume({OrderedArgs={"InstanceID=0", "Channel=Master"}})
+
+    if (status ~= true) then
+        commsFailure(device, tmp)
+        return
+    end
+
+    changed = setData("Volume", upnp.extractElement("CurrentVolume", tmp, ""), device, false)
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+  end
+
+  local function refreshMuteNow(device)
+    debug("refreshMuteNow: start")
+
+    if (upnp.proxyVersionAtLeast(1)) then
+        return
+    end
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+    if (Rendering == nil) then
+        return
+    end
+
+    local status, tmp, changed
+
+    -- Get Mute status
+    status, tmp = Rendering.GetMute({OrderedArgs={"InstanceID=0", "Channel=Master"}})
+
+    if (status ~= true) then
+        commsFailure(device, tmp)
+        return
+    end
+
+    changed = setData("Mute", upnp.extractElement("CurrentMute", tmp, ""), device, false)
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+  end
+
+ local function savePlaybackContext(device)
+    debug("savePlaybackContext: start")
+
+    local cxt = {}
+
+    refreshNow(device, true)
+    cxt.TransportState = dataTable.TransportState
+    cxt.TransportPlaySpeed = dataTable.TransportPlaySpeed
+    cxt.CurrentPlayMode = dataTable.CurrentPlayMode
+    cxt.CurrentTransportActions = dataTable.CurrentTransportActions
+    cxt.AVTransportURI = dataTable.AVTransportURI
+    cxt.AVTransportURIMetaData = dataTable.AVTransportURIMetaData
+    cxt.CurrentTrackDuration = dataTable.CurrentTrackDuration
+    cxt.RelativeTimePosition = dataTable.RelativeTimePosition
+    cxt.Mute = dataTable.Mute
+    cxt.Volume = dataTable.Volume
+
+    return cxt
+  end
+
+  local function restorePlaybackContext(device, cxt)
+    debug("restorePlaybackContext: start")
+    local instanceId="0"
+    local channel="Master"
+
+    if (cxt == nil) then
+        log("Please save the context before restoring it !")
+        return
+    end
+
+    local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+
+    if (AVTransport ~= nil) then
+        AVTransport.Stop({InstanceID=instanceId})
+    end
+
+    if (Rendering ~= nil) then
+        local mute = cxt.Mute
+        if (mute == "true" or mute == "yes") then
+            mute = "1"
+        elseif (mute == "false" or mute == "no") then
+            mute = "0"
+        end
+        Rendering.SetMute(
+            {OrderedArgs={"InstanceID=" .. instanceId,
+                          "Channel=" .. channel,
+                          "DesiredMute=" .. mute}})
+
+        Rendering.SetVolume(
+            {OrderedArgs={"InstanceID=" .. instanceId,
+                          "Channel=" .. channel,
+                          "DesiredVolume=" .. cxt.Volume}})
+    end
+
+    if (AVTransport ~= nil) then
+        AVTransport.SetAVTransportURI(
+            {OrderedArgs={"InstanceID=" .. instanceId,
+                          "CurrentURI=" .. cxt.AVTransportURI,
+                          "CurrentURIMetaData=" .. cxt.AVTransportURIMetaData}})
+
+        if (cxt.AVTransportURI ~= "") then
+            if (cxt.TransportState == "PLAYING"
+                    or cxt.TransportState == "TRANSITIONING"
+                    or cxt.TransportState == "PAUSED_PLAYBACK") then
+                AVTransport.Play(
+                    {OrderedArgs={"InstanceID=" .. instanceId,
+                                  "Speed=" .. cxt.TransportPlaySpeed}})
+            end
+
+            if (cxt.CurrentTrackDuration ~= "NOT_IMPLEMENTED"
+                    and cxt.CurrentTrackDuration ~= "0:00:00"
+                    and cxt.CurrentTrackDuration ~= "00:00:00"
+                    and cxt.RelativeTimePosition ~= "NOT_IMPLEMENTED") then
+				-- Wait for 1s as it does not work without this delay for few DMR
+                luup.sleep(1000)
+                AVTransport.Seek(
+                    {OrderedArgs={"InstanceID=" .. instanceId,
+                                  "Unit=REL_TIME",
+                                  "Target=" .. cxt.RelativeTimePosition}})
+            end
+
+            -- Restore repeat, shuffle
+            AVTransport.SetPlayMode(
+                {OrderedArgs={"InstanceID=" .. instanceId,
+                              "NewPlayMode=" .. cxt.CurrentPlayMode}})
+
+            if (cxt.TransportState == "PLAYING"
+                    or cxt.TransportState == "TRANSITIONING") then
+                -- Nothing to do, Playback has already been started
+            elseif (cxt.TransportState == "PAUSED_PLAYBACK") then
+                AVTransport.Pause({InstanceID=instanceId})
+            else
+                AVTransport.Stop({InstanceID=instanceId})
+            end
+        end
+    end
+
+    refreshNow(device, false)
+  end
+
+  local function play(device, parameters, saveAndRestore)
+    local defaultLang = luup.variable_get(DLNA_DMC_SID, "DefaultLanguageTTS", device) or "en"
+    local instanceId = defaultValue(parameters, "InstanceID", "0")
+    local volume = defaultValue(parameters, "Volume", nil)
+    local text = defaultValue(parameters, "Text", "The monkey is on the branch, the mouse is under the table")
+    local language = defaultValue(parameters, "Language", defaultLang)
+    local uri = defaultValue(parameters, "URI", nil)
+    local uriMetaData = defaultValue(parameters, "URIMetadata", nil)
+    local protocol = defaultValue(parameters, "Protocol", nil)
+    local duration = defaultValue(parameters, "Duration", "0")
+    local channel = "Master"
+    local title = ""
+    local data = "&lt;upnp:class>object.item&lt;/upnp:class>"
+
+    if (uri == nil) then
+        -- TTS case
+
+        -- Cut the text in fragments of max 100 characters
+        local tableTextFragments = {}
+        local cutSize = 100	
+        local remaining = text
+        while (#remaining > cutSize) do
+            local pos = string.find(string.reverse(string.sub(remaining, 1, cutSize+1)), " ")
+            if (pos ~= nil) then
+                table.insert(tableTextFragments, string.sub(remaining, 1, cutSize+1-pos))
+                remaining = string.sub(remaining, cutSize+3-pos)
+            else
+                remaining = ""
+            end
+        end
+        if (#remaining > 0) then
+            table.insert(tableTextFragments, remaining)
+        end
+
+        -- Get the MP3 files from Google
+        local strFiles = ""
+        for i, v in ipairs(tableTextFragments)
+        do
+            strFiles = strFiles .. " " .. SAY_TMP_FILE:format(device, i)
+            os.execute(SAY_EXECUTE:format(device, i, device, i, language, url.escape(v)))
+        end
+        -- Concat the multiple MP3 files
+        os.execute(CONCAT_EXECUTE:format(strFiles, device, strFiles))
+
+        -- Get the file size to deduce its playback duration
+        local fh = io.open(SAY_OUTPUT_FILE:format(device), "a+")
+        local size = fh:seek("end")
+        fh:close()
+
+        -- Compute the play duration from the file size (32 kbps)
+        -- and add 1 second to be sure to not cut the end of the text
+        duration = math.ceil(size/4000) + 1
+
+        uri = SAY_OUTPUT_URI:format(VERA_IP, VERA_WEB_PORT, device)
+        title = "Google TTS"
+        protocol = "http-get:*:audio/mpeg:*"
+    elseif (uri == "") then
+        return
+    end
+
+    if (uriMetaData == nil) then
+        local subURL = uri:match("^http:.*/([^/]-)$")
+        if (title == "") then
+            title = subURL or "No title"
+        end
+        if (protocol ~= nil and protocol ~= "") then
+            data = '&lt;res protocolInfo="' .. protocol ..'">' .. uri
+                       .. '&lt;/res>&lt;upnp:class>object.item&lt;/upnp:class>'
+        end
+        uriMetaData = DEFAULT_METADATA:format(title, data)
+    end
+
+    if (saveAndRestore == true and sayPlayback == nil) then
+        sayPlayback = savePlaybackContext(device)
+    end
+
+    local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+
+    if (AVTransport ~= nil and uri ~= nil and uri ~= "") then
+        if (dataTable.TransportState ~= "NO_MEDIA_PRESENT") then
+            AVTransport.Stop({InstanceID=instanceId})
+        end
+
+        AVTransport.SetAVTransportURI(
+            {OrderedArgs={"InstanceID=" .. instanceId,
+                          "CurrentURI=" .. uri,
+                          "CurrentURIMetaData=" .. uriMetaData}})
+
+        if (volume ~= nil and Rendering ~= nil) then
+            Rendering.SetVolume(
+                {OrderedArgs={"InstanceID=" .. instanceId,
+                              "Channel=" .. channel,
+                              "DesiredVolume=" .. volume}})
+        end
+
+        local delay = tonumber(luup.variable_get(DLNA_DMC_SID, "DelayBeforePlayback", device) or "0")
+        if (delay ~= nil and delay > 0) then
+            luup.sleep(delay)
+        end
+
+        AVTransport.Play(
+           {OrderedArgs={"InstanceID=" .. instanceId,
+                         "Speed=1"}})
+    end
+
+    if (saveAndRestore == true) then
+        luup.call_delay("endSayAlert", duration, device)
+    end
+
+    refreshNow(device, false)
+  end
+
+  function endSayAlert(device)
+    device = tonumber(device)
+    table.remove(sayQueue, 1)
+    if (#sayQueue == 0) then
+        restorePlaybackContext(device, sayPlayback)
+        sayPlayback = nil
+        os.execute(DELETE_EXECUTE:format(device))
+    else
+        play(device, sayQueue[1], true)
+    end
+  end
+
+  function updateWithoutProxy(device)
+    if (upnp.proxyVersionAtLeast(1) == false) then
+        luup.call_delay("updateWithoutProxy", 15, device)
+    end
+    device = tonumber(device)
+
+    refreshNow(device, true)
+  end
+
+  function setup(device, init)
+    device = tonumber(device)
+
+    local changed = false
+
+    local newDevice = false
+    local newDescrURL = luup.variable_get(DLNA_DMC_SID, "DescriptionURL", device) or ""
+    local newIP, newPort = newDescrURL:match("http://([%d%.]-):(%d+)/.-")
+	if (newIP == nil or newPort == nil) then
+        luup.variable_set(DLNA_DMC_SID, "DescriptionURL", DMRDescriptionURL, device)
+    elseif (newDescrURL ~= DMRDescriptionURL) then
+        newDevice = true
+        upnp.resetServices(uuidDMR)
+        upnp.cancelProxySubscriptions(EventSubscriptions)
+        ipDMR = newIP
+        portDMR = newPort
+        DMRDescriptionURL = newDescrURL
+        uuidDMR = ""
+        changed = setData("ModelName", "", device, changed)
+        changed = setData("Online", "0", device, changed)
+    end
+
+    local descrXML = upnp.UPnP_getDeviceDescription(DMRDescriptionURL)
+    if (descrXML == nil) then
+        deviceIsOffline(device)
+        return
+    end
+
+    local newOnline = deviceIsOnline(device)
+
+    local values = upnp.getInfosFromDescription(
+                            descrXML,
+                            "urn:schemas-upnp-org:device:MediaRenderer:1",
+                            { "UDN", "modelName" })
+
+    uuidDMR = values.UDN:match("uuid:(.+)") or ""
+    changed = setData("ModelName", values.modelName or "", device, changed)
+
+    iconURL = upnp.getIconFromDescription(descrXML)
+    if (iconURL ~= nil) then
+        iconURL = url.absolute(string.format("http://%s:%s/", ipDMR, portDMR), iconURL)
+    else
+        iconURL = PLUGIN_ICON:format(VERA_IP, VERA_WEB_PORT)
+    end
+
+    if (upnp.aresServicesLoaded(uuidDMR) == false) then
+        upnp.addServices(uuidDMR, ipDMR, portDMR, descrXML,
+                         "urn:schemas-upnp-org:device:MediaRenderer:1",
+                         { UPNP_AVTRANSPORT_SERVICE,
+                           UPNP_RENDERING_CONTROL_SERVICE,
+                           UPNP_CONNECTION_MANAGER_SERVICE })
+    end
+
+    if (init or newDevice or newOnline) then
+        upnp.subscribeToEvents(device, ipDMR, portDMR, EventSubscriptions, DLNA_DMC_SID, uuidDMR)
+    end
+
+    if (upnp.proxyVersionAtLeast(1)) then
+        changed = setData("ProxyUsed", "proxy is used", device, changed)
+    else
+        changed = setData("ProxyUsed", "proxy is not used", device, changed)
+    end
+
+    if (init or newDevice or newOnline) then
+        local ConnectionManager = upnp.getService(uuidDMR, UPNP_CONNECTION_MANAGER_SERVICE)
+        if (ConnectionManager ~= nil) then
+            local status, tmp = ConnectionManager.GetProtocolInfo()
+            changed = setData("SinkProtocolInfo", upnp.extractElement("Sink", tmp, ""), device, changed)
+        end
+    end
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+
+    if (newDevice or newOnline) then
+        refreshNow(device, true)
+    end
+  end
+
+  local function setupDMS(descrURL)
+    if (descrURL ~= nil) then
+        local newIP, newPort = descrURL:match("http://([%d%.]-):(%d+)/.-")
+        if (newIP == nil or newPort == nil) then
+            return false, "Bad server description URL"
+        end
+
+        if (descrURL ~= DMSDescriptionURL) then
+            local descrXML = upnp.UPnP_getDeviceDescription(descrURL)
+            if (descrXML == nil) then
+                return false, "Server not available"
+            end
+
+            upnp.resetServices(uuidDMS)
+            ipDMS = newIP
+            portDMS = newPort
+            DMSDescriptionURL = descrURL
+            uuidDMS = ""
+
+            local values = upnp.getInfosFromDescription(
+                                descrXML,
+                                "urn:schemas-upnp-org:device:MediaServer:1",
+                                { "UDN" })
+
+            uuidDMS = values.UDN:match("uuid:(.+)") or ""
+
+            if (upnp.aresServicesLoaded(uuidDMS) == false) then
+                upnp.addServices(uuidDMS, ipDMS, portDMS, descrXML,
+                                 "urn:schemas-upnp-org:device:MediaServer:1",
+                                 { UPNP_MR_CONTENT_DIRECTORY_SERVICE })
+            end
+        end
+    end
+
+    local errorMsg = ""
+    local status = upnp.aresServicesLoaded(uuidDMS)
+    if (status == false) then
+        errorMsg = "Server access is not setup"
+    end
+    return status, errorMsg
+  end
+
+  local function browseDMS(device, descrURL, object)
+    local result = ""
+    luup.variable_set(DLNA_DMC_SID, "BrowseResult", "loading", device)
+    local status, errorMsg = setupDMS(descrURL)
+    if (status == true) then
+        errorMsg = "Done"
+        result = upnp.browseContent(uuidDMS, UPNP_MR_CONTENT_DIRECTORY_SERVICE, object, false)
+        if (result == "") then
+            errorMsg = "Server content not available"
+        end
+    end
+    luup.variable_set(DLNA_DMC_SID, "PlaybackResult", errorMsg, device)
+    luup.variable_set(DLNA_DMC_SID, "BrowseResult", result, device)
+  end
+
+  local function playDMS(device, descrURL, object, forceProtocol)
+    if (uuidDMR == "" or dataTable.SinkProtocolInfo == nil or dataTable.SinkProtocolInfo == "") then
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Renderer not available", device)
+        return
+    end
+
+    local status, errorMsg = setupDMS(descrURL)
+    if (status == false) then
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", errorMsg, device)
+        return
+    end
+
+    local item, DMRprotocol, protocol, uri
+
+    -- local item = nil
+    -- local elt, id, startItem, endItem, startDIDL, endDIDL
+    -- local directory = luup.variable_get(DLNA_DMC_SID, "BrowseResult", device) or ""
+    -- for elt, id in directory:gmatch('(&lt;item.-%sid="([^"]-)"[^>]->.-&lt;/item>)') do
+        -- if (upnp.decode(id) == object) then
+            -- startItem, item, endItem = elt:match("(&lt;item%s?[^>]->.-)(&lt;res%s?[^>]->.*&lt;/res>)(.-&lt;/item>)")
+            -- if (startItem ~= nil and endItem ~= nil) then
+                -- startDIDL = directory:match("(&lt;DIDL%-Lite%s?[^>]->)") .. startItem
+                -- endDIDL = endItem .. '&lt;/DIDL-Lite>'
+-- log("compare")
+-- local xml = upnp.browseContent(uuidDMS, UPNP_MR_CONTENT_DIRECTORY_SERVICE, object, true)
+-- local startDIDL2, item2, endDIDL2 = xml:match("(&lt;DIDL%-Lite%s?[^>]->&lt;item%s?[^>]->.-)(&lt;res%s?[^>]->.*&lt;/res>)(.-&lt;/item>&lt;/DIDL%-Lite>)")
+-- if (startDIDL ~= startDIDL2) then
+-- log("diff startDIDL")
+-- log("startDIDL " .. startDIDL)
+-- log("startDIDL2 " .. startDIDL2)
+-- end
+-- if (endDIDL ~= endDIDL2) then
+-- log("diff endDIDL")
+-- log("endDIDL " .. endDIDL)
+-- log("endDIDL2 " .. endDIDL2)
+-- end
+-- if (item ~= item2) then
+-- log("diff item")
+-- log("item " .. item)
+-- log("item2 " .. item2)
+-- end
+            -- end
+            -- break
+        -- end
+    -- end
+    -- if (item == nil) then
+        local xml = upnp.browseContent(uuidDMS, UPNP_MR_CONTENT_DIRECTORY_SERVICE, object, true)
+        if (xml == "") then
+            luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media not available", device)
+            return
+        end
+        local startDIDL, item, endDIDL = xml:match("(&lt;DIDL%-Lite%s?[^>]->&lt;item%s?[^>]->.-)(&lt;res%s?[^>]->.*&lt;/res>)(.-&lt;/item>&lt;/DIDL%-Lite>)")
+    -- end
+
+    local selectedProtocol = ""
+    local selectedUri = ""
+
+    if (item ~= nil) then
+        local DMRprotocols = {}
+        for DMRprotocol in dataTable.SinkProtocolInfo:gmatch("[^,]+") do
+            local protocol_p1, protocol_p2 = DMRprotocol:match("^([^:]-:[^:]-):([^:]-):.-$")
+            if (protocol_p1 ~= nil and protocol_p2 ~= nil) then
+                if (protocol_p2 ~= "*") then
+                    protocol_p2 = protocol_p2 .. "*"
+                end
+                DMRprotocol = protocol_p1 .. ":" .. protocol_p2
+                DMRprotocol = DMRprotocol:gsub("-", "%%-"):gsub("*", "[^:]-")
+                DMRprotocol = "^(" .. DMRprotocol .. ":.-)$"
+                DMRprotocols[DMRprotocol] = true
+            end
+        end
+
+--log("selected protocol " .. (forceProtocol or "nil"))
+        for protocol, uri in item:gmatch('&lt;res%s?.-%s?protocolInfo="(.-)"%s?[^>]->(.-)&lt;/res>') do
+--log("server protocol " .. protocol)
+            if (forceProtocol == nil or forceProtocol == ""
+                    or forceProtocol == "AUTO" or forceProtocol == protocol) then
+--log("server protocol considered")
+                for DMRprotocol, v in pairs(DMRprotocols) do
+                    if (protocol:match(DMRprotocol) ~= nil) then
+--log("Matching with DMR protocol " .. DMRprotocol)
+                        selectedProtocol = protocol
+                        selectedUri = uri
+                        break
+                    end
+                end
+                if (selectedProtocol ~= "") then
+                    break
+                end
+            -- else
+-- log("server protocol bypassed")
+            end
+        end
+    end
+
+    if (selectedUri ~= "" and selectedProtocol ~= ""
+            and startDIDL ~= nil and endDIDL ~= nil) then
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media OK", device)
+        local settings = {}
+        settings.URI = selectedUri
+        if (forceProtocol == nil or forceProtocol == "") then
+            settings.URIMetadata = startDIDL .. endDIDL
+        else
+            settings.URIMetadata = startDIDL .. '&lt;res protocolInfo="' .. selectedProtocol .. '">'
+                .. selectedUri .. '&lt;/res>' .. endDIDL
+        end
+        play(device, settings, false)
+    else
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media not compatible with renderer", device)
+        log("Media cannot be rendered by the selected renderer.", 2)
+    end
+  end
+
+  local function getCheckStateRate(device)
+    local rate = luup.variable_get(DLNA_DMC_SID, "CheckStateRate", device)
+	if (rate == nil or tonumber(rate) == nil) then
+        rate = "0"
+        luup.variable_set(DLNA_DMC_SID, "CheckStateRate", rate, device)
+    end
+    return tonumber(rate) * 60
+  end
+
+  function checkDeviceState(device)
+    device = tonumber(device)
+    local rate = getCheckStateRate(device)
+    if (rate > 0) then
+        luup.call_delay("checkDeviceState", rate, device)
+    	setup(device, false)
+    end
+  end
+
+  local function handleRenderingChange(device, event)
+      debug("handleRenderingChange for device " .. device .. " value " .. event)
+      local changed = false
+      local token, attributes, attr, value
+      local tmp = event:match("&lt;Event%s?[^>]->&lt;InstanceID%s?[^>]->(.+)&lt;/InstanceID>&lt;/Event>")
+      if (tmp ~= nil) then
+          for token, attributes in tmp:gmatch('&lt;([a-zA-Z0-9:]+)(%s?.-)/>') do
+              local attrTable = {}
+              for attr, value in attributes:gmatch('%s(.-)="(.-)"') do
+                  attrTable[attr] = value
+              end
+              if (attrTable.val ~= nil and
+                      (attrTable.Channel == "Master" or attrTable.channel == "Master" or
+                           (attrTable.Channel == nil and attrTable.channel == nil))) then
+                  changed = setData(token, upnp.decode(attrTable.val), device, changed)
+              end
+          end
+          for token, attributes in tmp:gmatch('&lt;([a-zA-Z0-9:]+)(%s?[^>]-)>&lt;/[^>]->') do
+              local attrTable = {}
+              for attr, value in attributes:gmatch('%s(.-)="(.-)"') do
+                  attrTable[attr] = value
+              end
+              if (attrTable.val ~= nil and
+                      (attrTable.Channel == "Master" or attrTable.channel == "Master" or
+                           (attrTable.Channel == nil and attrTable.channel == nil))) then
+                  changed = setData(token, upnp.decode(attrTable.val), device, changed)
+              end
+          end
+      end
+      if (changed) then
+          luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+      end
+  end
+
+  local function handleAVTransportChange(device, event)
+      debug("handleAVTransportChange for device " .. device .. " value " .. event)
+      local statusString, title, artist, album, albumArt, duration
+      local currentUri, currentUriMetaData, trackUri, trackUriMetaData, service, currentAlbumArt
+      local changed = false
+      local token, attributes, attr, value
+      local tmp = event:match("&lt;Event%s?[^>]->&lt;InstanceID%s?[^>]->(.+)&lt;/InstanceID>&lt;/Event>")
+      if (tmp ~= nil) then
+          local found = false
+          local found2 = false
+          for token, attributes in tmp:gmatch('&lt;([a-zA-Z0-9:]+)(%s?.-)/>') do
+              if (token == "RelativeTimePosition") then
+                  found = true
+              elseif (token == "CurrentTrackDuration") then
+                  found2 = true
+              end
+              local attrTable = {}
+              for attr, value in attributes:gmatch('%s(.-)="(.-)"') do
+                  attrTable[attr] = value
+              end
+              if (attrTable.val ~= nil) then
+                  changed = setData(token, upnp.decode(attrTable.val), device, changed)
+              end
+          end
+          for token, attributes in tmp:gmatch('&lt;([a-zA-Z0-9:]+)(%s?[^>]-)>&lt;/[^>]->') do
+              if (token == "RelativeTimePosition") then
+                  found = true
+              elseif (token == "CurrentTrackDuration") then
+                  found2 = true
+              end
+              local attrTable = {}
+              for attr, value in attributes:gmatch('%s(.-)="(.-)"') do
+                  attrTable[attr] = value
+              end
+              if (attrTable.val ~= nil) then
+                  changed = setData(token, upnp.decode(attrTable.val), device, changed)
+              end
+          end
+
+          currentUri = dataTable.AVTransportURI
+          currentUriMetaData = dataTable.AVTransportURIMetaData
+          trackUri = dataTable.CurrentTrackURI
+          trackUriMetaData = dataTable.CurrentTrackMetaData
+          currentAlbumArt = dataTable.CurrentAlbumArt
+          statusString, title, artist, album, albumArt, duration =
+              extractDataFromMetaData(device, currentUri, currentUriMetaData, trackUri, trackUriMetaData)
+          changed = setData("CurrentStatus", statusString, device, changed)
+          changed = setData("CurrentTitle", title, device, changed)
+          changed = setData("CurrentArtist", artist, device, changed)
+          changed = setData("CurrentAlbum", album, device, changed)
+          changed = setData("CurrentAlbumArt", albumArt, device, changed)
+          if (albumArt == currentAlbumArt) then
+              albumArt = dataTable.CurrentAlbumArt2
+          elseif (albumArt == "" or not upnp.testURL(albumArt, nil)) then
+              albumArt = iconURL
+          end
+          changed = setData("CurrentAlbumArt2", albumArt, device, changed)
+          -- As BubbleUPnP does not update the track duration, we got it from the meta data
+          if (dataTable.ModelName == "BubbleUPnP Media Renderer" and not found2 and duration ~= "") then
+              changed = setData("CurrentTrackDuration", duration, device, changed)
+          end
+
+          if (not found) then
+              local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+              if (AVTransport ~= nil) then
+                  local status, tmp2 = AVTransport.GetPositionInfo({InstanceID="0"})
+                  changed = setData("RelativeTimePosition", upnp.extractElement("RelTime", tmp2, "NOT_IMPLEMENTED"), device, changed)
+              end
+          end
+      end
+      if (changed) then
+          luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+      end
+  end
+
+  function renewSubscriptions(data)
+    local device, uuid = data:match("(%d+):(.*)")
+    if (device ~= nil and uuid ~= nil) then
+        device = tonumber(device)
+        log("Renewal of all event subscriptions for device " .. device)
+        if (uuid ~= uuidDMR) then
+            log("Renewal ignored for uuid " .. uuid)
+        elseif (upnp.subscribeToEvents(device, ipDMR, portDMR, EventSubscriptions, DLNA_DMC_SID, uuidDMR) == false) then
+            setup(device, true)
+        end
+    end
+  end
+
+  function cancelProxySubscription(sid)
+    upnp.cancelProxySubscription(sid)
+  end
+
+  function deferredDLNAStartup(device)
+    debug("deferredDLNAStartup: start " .. device)
+    device = tonumber(device)
+
+    -- Check that the proxy is running. 
+    for retries = 1, 3 do
+        local version = upnp.getProxyApiVersion()
+        if (version) then 
+            log("UPnP proxy event identified - API version " .. version)
+            break 
+        end 
+    end
+
+-- the next line has to be uncommented to force the old mode without the UPnP event proxy
+-- upnp.unuseProxy()
+
+    if (luup.devices[device].device_type == DLNA_DMC_DEVICE_TYPE) then
+        DMRDescriptionURL = luup.variable_get(DLNA_DMC_SID, "DescriptionURL", device) or ""
+        ipDMR, portDMR = DMRDescriptionURL:match("http://([%d%.]-):(%d+)/.-")
+    	if (ipDMR == nil or portDMR == nil) then
+            ipDMR = luup.attr_get("ip", device)
+            portDMR = 1367
+            DMRDescriptionURL = string.format("http://%s:%s/", ipDMR, portDMR)
+            luup.variable_set(DLNA_DMC_SID, "DescriptionURL", DMRDescriptionURL, device)
+        else
+            portDMR = tonumber(portDMR)
+        end
+
+        DMSDescriptionURL = ""
+
+        uuidDMR = ""
+        uuidDMS = ""
+
+        sayQueue = {}
+        dataTable = {}
+
+        local rate = getCheckStateRate(device)
+        if (rate > 0) then
+            luup.call_delay("checkDeviceState", rate, device)
+        end
+        setup(device, true)
+        updateWithoutProxy(device)
+    end
+  end
+
+  function DLNAStartup(lul_device)
+    log("#" .. lul_device .. " starting up with id " .. luup.devices[lul_device].id)
+
+    if (luup.variable_get(DLNA_DMC_SID, "DMRDiscoveryResult", lul_device) == nil
+            or luup.variable_get(DLNA_DMC_SID, "DMRDiscoveryResult", lul_device) == "scanning") then
+        luup.variable_set(DLNA_DMC_SID, "DMRDiscoveryResult", "", lul_device)
+    end
+    if (luup.variable_get(DLNA_DMC_SID, "DMSDiscoveryResult", lul_device) == nil
+            or luup.variable_get(DLNA_DMC_SID, "DMSDiscoveryResult", lul_device) == "scanning") then
+        luup.variable_set(DLNA_DMC_SID, "DMSDiscoveryResult", "", lul_device)
+    end
+    luup.variable_set(DLNA_DMC_SID, "BrowseResult", "", lul_device)
+    luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "", lul_device)
+
+    local routerIp = luup.variable_get(DLNA_DMC_SID, "RouterIp", lul_device)
+	if (routerIp == nil) then
+        routerIp = ""
+        luup.variable_set(DLNA_DMC_SID, "RouterIp", routerIp, lul_device)
+    end
+    local routerPort = luup.variable_get(DLNA_DMC_SID, "RouterPort", lul_device)
+	if (routerPort == nil or routerPort == "") then
+        routerPort = "80"
+        luup.variable_set(DLNA_DMC_SID, "RouterPort", routerPort, lul_device)
+    end
+    routerPort = tonumber(routerPort)
+
+    local delay = luup.variable_get(DLNA_DMC_SID, "DelayBeforePlayback", lul_device)
+	if (delay == nil or tonumber(delay) == nil) then
+        luup.variable_set(DLNA_DMC_SID, "DelayBeforePlayback", "0", lul_device)
+    end
+
+    local rate = luup.variable_get(DLNA_DMC_SID, "CheckStateRate", lul_device)
+	if (rate == nil or tonumber(rate) == nil) then
+        luup.variable_set(DLNA_DMC_SID, "CheckStateRate", "0", lul_device)
+    end
+
+    local lang = luup.variable_get(DLNA_DMC_SID, "DefaultLanguageTTS", lul_device)
+	if (lang == nil) then
+        luup.variable_set(DLNA_DMC_SID, "DefaultLanguageTTS", "en", lul_device)
+    end
+
+    if (routerIp == "") then
+        --
+        -- Acquire the IP Address of Vera itself, needed for the Say method later on.
+        -- Note: We're assuming Vera is connected via it's WAN Port to the DLNA Media Renderer
+        --
+        local stdout = io.popen("GetNetworkState.sh ip_wan")
+        VERA_IP = stdout:read("*a")
+        stdout:close()
+        debug("DLNAStartup: Vera IP Address=" .. VERA_IP)
+        VERA_WEB_PORT = 80
+    else
+        VERA_IP = routerIp
+        VERA_WEB_PORT = routerPort
+    end
+    upnp.initialize(VERA_IP, debug, warning, error)
+
+    iconURL = PLUGIN_ICON:format(VERA_IP, VERA_WEB_PORT)
+
+    luup.call_delay("deferredDLNAStartup", 1, lul_device)
+  end
+</functions>
+<startup>DLNAStartup</startup>
+<actionList>
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>Play</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local speed = defaultValue(lul_settings, "Speed", "1")
+
+      AVTransport.Play(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Speed=" .. speed}})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Play</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local speed = defaultValue(lul_settings, "Speed", "1")
+
+      AVTransport.Play(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Speed=" .. speed}})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>Play</name>
+    <run>
+      local duration = tonumber(defaultValue(lul_settings, "Duration", "0"))
+      if (duration > 0) then
+          table.insert(sayQueue, lul_settings)
+          if (#sayQueue == 1) then
+              play(lul_device, lul_settings, true)
+          end
+      else
+          play(lul_device, lul_settings, false)
+      end
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>Say</name>
+    <run>
+      table.insert(sayQueue, lul_settings)
+      if (#sayQueue == 1) then
+          play(lul_device, lul_settings, true)
+      end
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Seek</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local unit = defaultValue(lul_settings, "Unit", "")
+      local target = defaultValue(lul_settings, "Target", "")
+
+      AVTransport.Seek(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Unit=" .. unit,
+                       "Target=" .. target}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>Pause</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Pause({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Pause</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Pause({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>Stop</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Stop({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Stop</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Stop({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Record</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Record({InstanceID=instanceId})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>SkipDown</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Next({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Next</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Next({InstanceID=instanceId})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>SkipUp</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Previous({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Previous</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Previous({InstanceID=instanceId})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>GetPositionInfo</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      local status, tmp = AVTransport.GetPositionInfo({InstanceID=instanceId})
+      setData("RelativeTimePosition", upnp.extractElement("RelTime", tmp, "NOT_IMPLEMENTED"), lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetPlayMode</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local newPlayMode = defaultValue(lul_settings, "NewPlayMode", "NORMAL")
+
+      AVTransport.SetPlayMode(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "NewPlayMode=" .. newPlayMode}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetRecordQualityMode</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local newRecordQualityMode = defaultValue(lul_settings, "NewRecordQualityMode", "NOT_IMPLEMENTED")
+
+      AVTransport.SetRecordQualityMode(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "NewRecordQualityMode=" .. newRecordQualityMode}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetAVTransportURI</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local currentURI = defaultValue(lul_settings, "CurrentURI", "")
+      local currentURIMetaData = defaultValue(lul_settings, "CurrentURIMetaData", "")
+
+      AVTransport.SetAVTransportURI(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "CurrentURI=" .. currentURI,
+                       "CurrentURIMetaData=" .. currentURIMetaData}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetNextAVTransportURI</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local nextURI = defaultValue(lul_settings, "NextURI", "")
+      local nextURIMetaData = defaultValue(lul_settings, "NextURIMetaData", "")
+
+      AVTransport.SetNextAVTransportURI(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "NextURI=" .. nextURI,
+                       "NextURIMetaData=" .. nextURIMetaData}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
+    <name>Mute</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+     -- Toggle Mute
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local isMuted = luup.variable_get(UPNP_RENDERING_CONTROL_SID, "Mute", lul_device)
+      if (isMuted == "true" or isMuted == "yes") then
+          isMuted = "1"
+      elseif (isMuted == "false" or isMuted == "no") then
+          isMuted = "0"
+      end
+      local desiredMute = 1 - (tonumber(isMuted) or 0)
+
+      Rendering.SetMute(
+          {OrderedArgs={"InstanceID=" .. instanceId,
+                        "Channel=" .. channel,
+                        "DesiredMute=" .. desiredMute}})
+
+      refreshMuteNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
+    <name>Up</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      -- Volume up
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+
+      local desiredVolume = luup.variable_get(UPNP_RENDERING_CONTROL_SID, "Volume", lul_device) or "100"
+      desiredVolume = tonumber(desiredVolume) + 5
+      if (desiredVolume > 100) then
+          desiredVolume = 100
+      end
+
+      Rendering.SetVolume(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredVolume=" .. desiredVolume}})
+
+      refreshVolumeNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
+    <name>Down</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      -- Volume down
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+
+      local desiredVolume = luup.variable_get(UPNP_RENDERING_CONTROL_SID, "Volume", lul_device) or "100"
+      desiredVolume = tonumber(desiredVolume) - 5
+      if (desiredVolume &lt; 0) then
+          desiredVolume = 0
+      end
+
+      Rendering.SetVolume(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredVolume=" .. desiredVolume}})
+
+      refreshVolumeNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetMute</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local desiredMute = defaultValue(lul_settings, "DesiredMute", nil)
+
+      -- If parameter is nill, we consider the callback as a toggle
+      if (desiredMute == nil) then
+          local isMuted = luup.variable_get(UPNP_RENDERING_CONTROL_SID, "Mute", lul_device)
+          if (isMuted == "true" or isMuted == "yes") then
+              isMuted = "1"
+          elseif (isMuted == "false" or isMuted == "no") then
+              isMuted = "0"
+          end
+          desiredMute = 1 - (tonumber(isMuted) or 0)
+      end
+
+      Rendering.SetMute(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredMute=" .. desiredMute}})
+
+      refreshMuteNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetVolume</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local desiredVolume = tonumber(defaultValue(lul_settings, "DesiredVolume", "5"))
+
+      Rendering.SetVolume(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredVolume=" .. desiredVolume}})
+
+      refreshVolumeNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetVolumeDB</name>
+    <run>
+      local Rendering = upnp.getService(UUIDs[lul_device], UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local desiredVolume = defaultValue(lul_settings, "DesiredVolume", "0")
+
+      Rendering.SetVolumeDB(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredVolume=" .. desiredVolume}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SelectPreset</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local presetName = defaultValue(lul_settings, "PresetName", "FactoryDefaults")
+
+      Rendering.SelectPreset(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "PresetName=" .. presetName}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetLoudness</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local desiredLoudness = defaultValue(lul_settings, "DesiredLoudness", "5")
+
+      Rendering.SetLoudness(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredLoudness=" .. desiredLoudness}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetBrightness</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredBrightness = defaultValue(lul_settings, "DesiredBrightness", "0")
+
+      Rendering.SetBrightness(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredBrightness=" .. desiredBrightness}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetContrast</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredContrast = defaultValue(lul_settings, "DesiredContrast", "0")
+
+      Rendering.SetContrast(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredContrast=" .. desiredContrast}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetSharpness</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredSharpness = defaultValue(lul_settings, "DesiredSharpness", "0")
+
+      Rendering.SetSharpness(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredSharpness=" .. desiredSharpness}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetRedVideoGain</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredRedVideoGain = defaultValue(lul_settings, "DesiredRedVideoGain", "0")
+
+      Rendering.SetRedVideoGain(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredRedVideoGain=" .. desiredRedVideoGain}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetGreenVideoGain</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredGreenVideoGain = defaultValue(lul_settings, "DesiredGreenVideoGain", "0")
+
+      Rendering.SetGreenVideoGain(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredGreenVideoGain=" .. desiredGreenVideoGain}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetBlueVideoGain</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredBlueVideoGain = defaultValue(lul_settings, "DesiredBlueVideoGain", "0")
+
+      Rendering.SetBlueVideoGain(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredBlueVideoGain=" .. desiredBlueVideoGain}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetRedVideoBlackLevel</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredRedVideoBlackLevel = defaultValue(lul_settings, "DesiredRedVideoBlackLevel", "0")
+
+      Rendering.SetRedVideoBlackLevel(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredRedVideoBlackLevel=" .. desiredRedVideoBlackLevel}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetGreenVideoBlackLevel</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredGreenVideoBlackLevel = defaultValue(lul_settings, "DesiredGreenVideoBlackLevel", "0")
+
+      Rendering.SetGreenVideoBlackLevel(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredGreenVideoBlackLevel=" .. desiredGreenVideoBlackLevel}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetBlueVideoBlackLevel</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredBlueVideoBlackLevel = defaultValue(lul_settings, "DesiredBlueVideoBlackLevel", "0")
+
+      Rendering.SetBlueVideoBlackLevel(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredBlueVideoBlackLevel=" .. desiredBlueVideoBlackLevel}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetColorTemperature</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredColorTemperature = defaultValue(lul_settings, "DesiredColorTemperature", "0")
+
+      Rendering.SetColorTemperature(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredColorTemperature=" .. desiredColorTemperature}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetHorizontalKeystone</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredHorizontalKeystone = defaultValue(lul_settings, "DesiredHorizontalKeystone", "0")
+
+      Rendering.SetHorizontalKeystone(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredHorizontalKeystone=" .. desiredHorizontalKeystone}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetVerticalKeystone</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredVerticalKeystone = defaultValue(lul_settings, "DesiredVerticalKeystone", "0")
+
+      Rendering.SetVerticalKeystone(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredVerticalKeystone=" .. desiredVerticalKeystone}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+    <name>Poll</name>
+    <job>
+      refreshNow(lul_device, true)
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+    <name>ToggleState</name>
+    <job>
+      -- TODO: This needs to be implemented to make the Icon cause a toggle action
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>SavePlaybackContext</name>
+    <run>
+      playbackCxt = savePlaybackContext(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>RestorePlaybackContext</name>
+    <run>
+      restorePlaybackContext(lul_device, playbackCxt)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>StartDMRDiscovery</name>
+    <job>
+      luup.variable_set(DLNA_DMC_SID, "DMRDiscoveryResult", "scanning", lul_device)
+      local xml = upnp.scanUPnPDevices("urn:schemas-upnp-org:device:MediaRenderer:1", { "modelName", "friendlyName" })
+      luup.variable_set(DLNA_DMC_SID, "DMRDiscoveryResult", xml, lul_device)
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>SelectDMRDevice</name>
+    <run>
+      local newDescrURL = lul_settings.URL or ""
+      local newIP, newPort = newDescrURL:match("http://([%d%.]-):(%d+)/.-")
+	  if (newIP ~= nil and newPort ~= nil) then
+          luup.variable_set(DLNA_DMC_SID, "DescriptionURL", newDescrURL, lul_device)
+          setup(lul_device, false)
+      end
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>SearchAndSelectDMRDevice</name>
+    <job>
+      if (lul_settings.Name == nil or lul_settings.Name == "") then
+          return
+      end
+
+      local descrURL = upnp.searchUPnPDevices("urn:schemas-upnp-org:device:MediaRenderer:1",
+                                              lul_settings.Name,
+                                              lul_settings.IP)
+      if (descrURL ~= nil) then
+          local newIP, newPort = descrURL:match("http://([%d%.]-):(%d+)/.-")
+	      if (newIP ~= nil and newPort ~= nil) then
+              luup.variable_set(DLNA_DMC_SID, "DescriptionURL", descrURL, lul_device)
+              setup(lul_device, false)
+          end
+      end
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>StartDMSDiscovery</name>
+    <job>
+      luup.variable_set(DLNA_DMC_SID, "DMSDiscoveryResult", "scanning", lul_device)
+      luup.variable_set(DLNA_DMC_SID, "BrowseResult", "", lul_device)
+      local xml = upnp.scanUPnPDevices("urn:schemas-upnp-org:device:MediaServer:1", { "modelName", "friendlyName" })
+      luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Done", lul_device)
+      luup.variable_set(DLNA_DMC_SID, "DMSDiscoveryResult", xml, lul_device)
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>BrowseDMS</name>
+    <run>
+      browseDMS(lul_device, lul_settings.DescriptionURL, lul_settings.ObjectID or "0")
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>PlayDMSMedia</name>
+    <run>
+      playDMS(lul_device, lul_settings.DescriptionURL, lul_settings.ObjectID or "", lul_settings.Protocol or "")
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>NotifyRenderingChange</name>
+    <job>
+      if (upnp.isValidNotification("NotifyRenderingChange", lul_settings.sid, EventSubscriptions)) then
+          handleRenderingChange(lul_device, upnp.unformatXML(lul_settings.LastChange or ""))
+      end
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>NotifyAVTransportChange</name>
+    <job>
+      if (upnp.isValidNotification("NotifyAVTransportChange", lul_settings.sid, EventSubscriptions)) then
+          handleAVTransportChange(lul_device, upnp.unformatXML(lul_settings.LastChange or ""))
+      end
+    </job>
+  </action>
+</actionList>
+</implementation>
Index: /tags/0.4/D_DLNAMediaController1.json
===================================================================
--- /tags/0.4/D_DLNAMediaController1.json	(revision 30)
+++ /tags/0.4/D_DLNAMediaController1.json	(revision 30)
@@ -0,0 +1,395 @@
+[
+    {
+        "flashicon": "icons\/DLNA.png",
+        "imgIconBody": "",
+        "imgIconDimmable": "",
+        "imgIconTurnable": "",
+        "imgIconMin": "",
+        "imgIconMax": "",
+        "state_icons": [],
+        "halloIconsDir": "pics/hallo",
+        "inScene": "1",
+        "DisplayStatus": {},
+        "doc_url": {
+            "doc_language": 1,
+            "doc_manual": 1,
+            "doc_version": 1,
+            "doc_platform": 0,
+            "doc_page": "devices"
+        },
+        "Tabs": [
+            {
+                "Label": {
+                    "lang_tag": "tabname_control",
+                    "text": "Control"
+                },
+                "Position": "0",
+                "TabType": "flash",
+                "SceneGroup": [
+                    {
+                        "id": "1",
+                        "top": "0.25",
+                        "left": "0",
+                        "x": "2",
+                        "y": "1"
+                    },
+                    {
+                        "id": "2",
+                        "top": "1",
+                        "left": "0",
+                        "x": "2",
+                        "y": "3"
+                    }
+                ],
+                "ControlGroup": [
+                    {
+                        "id": "1",
+                        "type": "info", 
+                        "scenegroup": "1"
+                    },
+                    {
+                        "id": "2",
+                        "scenegroup": "2",
+                        "isSingle": "1"
+                    }
+                ],
+                "Control": [
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "0",
+                        "left": "0",
+                        "Label": {
+                            "lang_tag": "cmd_play",
+                            "text": "Play"
+                        },
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:AVTransport",
+                            "Variable": "TransportState",
+                            "Value": "PLAYING",
+                            "Top": 30,
+                            "Left": 30,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "Play", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "0",
+                        "left": "1",
+                        "Label": {
+                            "lang_tag": "cmd_pause",
+                            "text": "Pause"
+                        },
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:AVTransport",
+                            "Variable": "TransportState",
+                            "Value": "PAUSED_PLAYBACK",
+                            "Top": 30,
+                            "Left": 90,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "Pause", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "1",
+                        "left": "0",
+                        "Label": {
+                            "lang_tag": "cmd_stop",
+                            "text": "Stop"
+                        },
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:AVTransport",
+                            "Variable": "TransportState",
+                            "Value": "STOPPED",
+                            "Top": 30,
+                            "Left": 150,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "Stop", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "1",
+                        "left": "1",
+                        "Label": {
+                            "lang_tag": "cmd_mute",
+                            "text": "Mute"
+                        },
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:RenderingControl",
+                            "Variable": "Mute",
+                            "Value": "1",
+                            "Top": 30,
+                            "Left": 250,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:upnp-org:serviceId:RenderingControl", 
+                            "Action": "SetMute", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlType": "slider",
+                        "Display": {
+                            "ID": "Volume",
+                            "Service": "urn:upnp-org:serviceId:RenderingControl",
+                            "Variable": "Volume",
+                            "MinValue": "0",
+                            "MaxValue": "100",
+                            "Top": 30,
+                            "Left": 340,
+                            "Width": 200,
+                            "Height": 20
+                        },
+                        "Command": {
+                            "Service": "urn:upnp-org:serviceId:RenderingControl",
+                            "Action": "SetVolume",
+                            "Parameters": [
+                                {
+                                    "Name": "DesiredVolume",
+                                    "ID": "Volume"
+                                }
+                            ]
+                        }
+                    },
+                    {
+                        "ControlType": "variable",
+                        "text_align": "left",
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:RenderingControl",
+                            "Variable": "Volume",
+                            "Top": 35,
+                            "Left": 310,
+                            "Width": 30,
+                            "Height": 20
+                        }
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "2",
+                        "left": "0",
+                        "Label": {
+                            "lang_tag": "cmd_prev",
+                            "text": "Prev"
+                        },
+                        "Display": {
+                            "Top": 60,
+                            "Left": 30,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "SkipUp", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "2",
+                        "left": "1",
+                        "Label": {
+                            "lang_tag": "cmd_next",
+                            "text": "Next"
+                        },
+                        "Display": {
+                            "Top": 60,
+                            "Left": 90,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "SkipDown", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlType": "label",
+                        "text_align": "left",
+                        "Label": {
+                            "lang_tag": "playing",
+                            "text": "Playing..."
+                        },
+                        "Display": {
+                            "Top": 100,
+                            "Left": 30,
+                            "Width": 80,
+                            "Height": 20
+                        }
+                    },
+                    {
+                        "ControlGroup": "1",
+                        "ControlPair": "1",
+                        "ControlHeader": "1",
+                        "ControlType": "variable",
+                        "top": "0",
+                        "left": "0",
+                        "x": "2",
+                        "y": "1",
+                        "text_align": "left",
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:AVTransport",
+                            "Variable": "CurrentStatus",
+                            "Top": 100,
+                            "Left": 110,
+                            "Width": 300,
+                            "Height": 60
+                        }
+                    }
+                ]
+            },
+            {
+                "Label": {
+                    "lang_tag": "tabname_player",
+                    "text": "Player"
+                },
+                "Position": "1",
+                "TabType": "javascript",
+                "ScriptName": "J_DLNAMediaController1.js",
+                "Function": "DLNA_showPlayer"
+            },
+            {
+                "Label": {
+                    "lang_tag": "settings",
+                    "text": "Settings"
+                },
+                "Position": "2",
+                "TabType": "javascript",
+                "ScriptName": "J_DLNAMediaController1.js",
+                "Function": "DLNA_showSettings"
+            },
+            {
+                "Label": {
+                    "lang_tag": "help",
+                    "text": "Help"
+                },
+                "Position": "3",
+                "TabType": "javascript",
+                "ScriptName": "J_DLNAMediaController1.js",
+                "Function": "DLNA_showHelp"
+            },
+            {
+                "Label": {
+                    "lang_tag": "advanced",
+                    "text": "Advanced"
+                },
+                "Position": "4",
+                "TabType": "javascript",
+                "ScriptName": "shared.js",
+                "Function": "advanced_device"
+            },
+            {
+                "Label": {
+                    "lang_tag": "device_options",
+                    "text": "Device Options"
+                },
+                "Position": "5",
+                "TabType": "javascript",
+                "ScriptName": "shared.js",
+                "Function": "device_zwave_options"
+            }
+        ],
+        "DeviceType": "urn:dlna-org:device:DLNAMediaController:1",
+        "eventList": {
+            "event_1": {
+                "label": "Playing state",
+                "serviceId": "urn:upnp-org:serviceId:AVTransport",
+                "argumentList" : {
+                    "argument_1" : {
+                        "dataType": "string",
+                        "defaultValue": "PLAYING",
+                        "allowedValueList" : {
+                            "PLAYING" : "PLAYING",
+                            "STOPPED" : "STOPPED",
+                            "TRANSITIONING" : "TRANSITIONING",
+                            "PAUSED_PLAYBACK" : "PAUSED_PLAYBACK"
+                        },
+                        "name": "TransportState",
+                        "comparisson": "=",
+                        "prefix": "",
+                        "suffix": ""
+                    }
+                }
+            }
+        },
+        "eventList2": [
+            {
+                "id": 2,
+                "label": {
+                    "lang_tag": "playing_state",
+                    "text": "Playing state"
+                },
+                "serviceId": "urn:upnp-org:serviceId:AVTransport",
+                "argumentList": [
+                    {
+                        "id": 1,
+                        "dataType": "string",
+                        "defaultValue": "PLAYING",
+                        "allowedValueList": [
+                            {
+                                "Playing": "PLAYING",
+                                "HumanFriendlyText": {
+                                    "lang_tag": "is_playing",
+                                    "text": "_DEVICE_NAME_ is Playing"
+                                }
+                            },
+                            {
+                                "Stopped": "STOPPED",
+                                "HumanFriendlyText": {
+                                    "lang_tag": "is_stopped",
+                                    "text": "_DEVICE_NAME_ is Stopped"
+                                }
+                            },
+                            {
+                                "Transitioning": "TRANSITIONING",
+                                "HumanFriendlyText": {
+                                    "lang_tag": "is_transitioning",
+                                    "text": "_DEVICE_NAME_ is Transitioning"
+                                }
+                            },
+                            {
+                                "Paused": "PAUSED_PLAYBACK",
+                                "HumanFriendlyText": {
+                                    "lang_tag": "is_paused",
+                                    "text": "_DEVICE_NAME_ is Paused"
+                                }
+                            }
+                        ],
+                        "name": "TransportState",
+                        "comparisson": "=",
+                        "prefix": {
+                            "lang_tag": "",
+                            "text": ""
+                        },
+                        "suffix": {}
+                    }
+                ]
+            }
+        ]
+    }
+]
Index: /tags/0.4/S_AVTransport1.xml
===================================================================
--- /tags/0.4/S_AVTransport1.xml	(revision 30)
+++ /tags/0.4/S_AVTransport1.xml	(revision 30)
@@ -0,0 +1,1731 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <!-- $Id: S_SonosAVTransport1.xml 121 2013-10-01 19:33:24Z lolodomo $ -->
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_AlarmIncludeLinkedZones</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_AlarmState</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_AlarmVolume</name>
+      <dataType>ui2</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_CurrentAVTransportURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_EnqueueAsNext</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_GroupID</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_InstanceID</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ISO8601Time</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_LIST_URI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_LIST_URIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_MemberID</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_MemberList</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_NumTracks</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_NumTracksChange</name>
+      <dataType>i4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ObjectID</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Queue</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_RejoinGroup</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ResetVolumeAfter</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ResumePlayback</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SavedQueueTitle</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SeekMode</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>TRACK_NR</allowedValue>
+        <allowedValue>ABS_TIME</allowedValue>
+        <allowedValue>REL_TIME</allowedValue>
+        <allowedValue>ABS_COUNT</allowedValue>
+        <allowedValue>REL_COUNT</allowedValue>
+        <allowedValue>CHANNEL_FREQ</allowedValue>
+        <allowedValue>TAPE-INDEX</allowedValue>
+        <allowedValue>FRAME</allowedValue>
+        <!-- Sonos -->
+        <allowedValue>SECTION</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SeekTarget</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SleepTimerState</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SourceState</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_StreamRestartState</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_TrackList</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_TrackNumber</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_TransportSettings</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_URI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_URIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>AbsoluteCounterPosition</name>
+      <dataType>i4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>AbsoluteTimePosition</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AlarmIDRunning</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AlarmLoggedStartTime</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AlarmRunning</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>AVTransportURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>AVTransportURIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Vera plugin -->
+    <stateVariable sendEvents="no">
+      <name>CurrentAlbumArt</name>
+      <shortCode>currentalbumart</shortCode>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>CurrentCrossfadeMode</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentMediaDuration</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentPlayMode</name>
+      <dataType>string</dataType>
+      <defaultValue>NORMAL</defaultValue>
+      <allowedValueList>
+        <allowedValue>NORMAL</allowedValue>
+        <allowedValue>SHUFFLE</allowedValue>
+        <allowedValue>REPEAT_ONE</allowedValue>
+        <allowedValue>REPEAT_ALL</allowedValue>
+        <allowedValue>RANDOM</allowedValue>
+        <allowedValue>DIRECT_1</allowedValue>
+        <allowedValue>INTRO</allowedValue>
+        <!-- Sonos and XBMC -->
+        <allowedValue>SHUFFLE_NOREPEAT</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentRecordQualityMode</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>0:EP</allowedValue>
+        <allowedValue>1:LP</allowedValue>
+        <allowedValue>2:SP</allowedValue>
+        <allowedValue>0:BASIC</allowedValue>
+        <allowedValue>1:MEDIUM</allowedValue>
+        <allowedValue>2:HIGH</allowedValue>
+        <allowedValue>NOT_IMPLEMENTED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>CurrentSection</name>
+      <dataType>ui4</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <maximum>255</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <!-- Vera plugin -->
+    <stateVariable sendEvents="no">
+      <name>CurrentStatus</name>
+      <shortCode>currentstatus</shortCode>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTrack</name>
+      <dataType>ui4</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTrackDuration</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTrackMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTrackURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTransportActions</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>EnqueuedTransportURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>EnqueuedTransportURIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>LastChange</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>NextAVTransportURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>NextAVTransportURIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>NextTrackMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>NextTrackURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>NumberOfTracks</name>
+      <dataType>ui4</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PlaybackStorageMedium</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>UNKNOWN</allowedValue>
+        <allowedValue>DV</allowedValue>
+        <allowedValue>MINI-DV</allowedValue>
+        <allowedValue>VHS</allowedValue>
+        <allowedValue>W-VHS</allowedValue>
+        <allowedValue>S-VHS</allowedValue>
+        <allowedValue>D-VHS</allowedValue>
+        <allowedValue>VHSC</allowedValue>
+        <allowedValue>VIDEO8</allowedValue>
+        <allowedValue>HI8</allowedValue>
+        <allowedValue>CD-ROM</allowedValue>
+        <allowedValue>CD-DA</allowedValue>
+        <allowedValue>CD-R</allowedValue>
+        <allowedValue>CD-RW</allowedValue>
+        <allowedValue>VIDEO-CD</allowedValue>
+        <allowedValue>SACD</allowedValue>
+        <allowedValue>MD-AUDIO</allowedValue>
+        <allowedValue>MD-PICTURE</allowedValue>
+        <allowedValue>DVD-ROM</allowedValue>
+        <allowedValue>DVD-VIDEO</allowedValue>
+        <allowedValue>DVD-R</allowedValue>
+        <allowedValue>DVD+RW</allowedValue>
+        <allowedValue>DVD-RW</allowedValue>
+        <allowedValue>DVD-RAM</allowedValue>
+        <allowedValue>DVD-AUDIO</allowedValue>
+        <allowedValue>DAT</allowedValue>
+        <allowedValue>LD</allowedValue>
+        <allowedValue>HDD</allowedValue>
+        <allowedValue>MICRO_MV</allowedValue>
+        <allowedValue>NETWORK</allowedValue>
+        <allowedValue>NONE</allowedValue>
+        <allowedValue>NOT_IMPLEMENTED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PossiblePlaybackStorageMedia</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PossibleRecordQualityModes</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PossibleRecordStorageMedia</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>QueueUpdateID</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RecordMediumWriteStatus</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>WRITABLE</allowedValue>
+        <allowedValue>PROTECTED</allowedValue>
+        <allowedValue>NOT_WRITABLE</allowedValue>
+        <allowedValue>UNKNOWN</allowedValue>
+        <allowedValue>NOT_IMPLEMENTED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RecordStorageMedium</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>UNKNOWN</allowedValue>
+        <allowedValue>DV</allowedValue>
+        <allowedValue>MINI-DV</allowedValue>
+        <allowedValue>VHS</allowedValue>
+        <allowedValue>W-VHS</allowedValue>
+        <allowedValue>S-VHS</allowedValue>
+        <allowedValue>D-VHS</allowedValue>
+        <allowedValue>VHSC</allowedValue>
+        <allowedValue>VIDEO8</allowedValue>
+        <allowedValue>HI8</allowedValue>
+        <allowedValue>CD-ROM</allowedValue>
+        <allowedValue>CD-DA</allowedValue>
+        <allowedValue>CD-R</allowedValue>
+        <allowedValue>CD-RW</allowedValue>
+        <allowedValue>VIDEO-CD</allowedValue>
+        <allowedValue>SACD</allowedValue>
+        <allowedValue>MD-AUDIO</allowedValue>
+        <allowedValue>MD-PICTURE</allowedValue>
+        <allowedValue>DVD-ROM</allowedValue>
+        <allowedValue>DVD-VIDEO</allowedValue>
+        <allowedValue>DVD-R</allowedValue>
+        <allowedValue>DVD+RW</allowedValue>
+        <allowedValue>DVD-RW</allowedValue>
+        <allowedValue>DVD-RAM</allowedValue>
+        <allowedValue>DVD-AUDIO</allowedValue>
+        <allowedValue>DAT</allowedValue>
+        <allowedValue>LD</allowedValue>
+        <allowedValue>HDD</allowedValue>
+        <allowedValue>MICRO_MV</allowedValue>
+        <allowedValue>NETWORK</allowedValue>
+        <allowedValue>NONE</allowedValue>
+        <allowedValue>NOT_IMPLEMENTED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RelativeCounterPosition</name>
+      <dataType>i4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RelativeTimePosition</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>RestartPending</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SleepTimerGeneration</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SnoozeRunning</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>TransportErrorDescription</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>TransportErrorHttpCode</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>TransportErrorHttpHeaders</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>TransportErrorURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>TransportPlaySpeed</name>
+      <dataType>string</dataType>
+      <defaultValue>1</defaultValue>
+      <allowedValueList>
+        <allowedValue>1</allowedValue>
+        <!-- Sonny -->
+        <allowedValue>10</allowedValue>
+        <!-- Sonny -->
+        <allowedValue>-10</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>TransportState</name>
+      <shortCode>transportstate</shortCode>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>STOPPED</allowedValue>
+        <allowedValue>PAUSED_PLAYBACK</allowedValue>
+        <allowedValue>PLAYING</allowedValue>
+        <allowedValue>TRANSITIONING</allowedValue>
+        <allowedValue>PAUSED_RECORDING</allowedValue>
+        <allowedValue>RECORDING</allowedValue>
+        <allowedValue>NO_MEDIA_PRESENT</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>TransportStatus</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>OK</allowedValue>
+        <allowedValue>ERROR_OCCURRED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonny -->
+    <stateVariable sendEvents="no">
+      <name>X_DLNA_RelativeBytePosition</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonny -->
+    <stateVariable sendEvents="no">
+      <name>X_DLNA_AbsoluteBytePosition</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonny -->
+    <stateVariable sendEvents="no">
+      <name>X_DLNA_CurrentTrackSize</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <!-- Sonos -->
+    <action>
+      <name>AddMultipleURIsToQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumberOfURIs</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIs</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_LIST_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIsMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_LIST_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ContainerURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ContainerMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredFirstTrackNumberEnqueued</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueueAsNext</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EnqueueAsNext</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>FirstTrackNumberEnqueued</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumTracksAdded</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>AddURIToQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredFirstTrackNumberEnqueued</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueueAsNext</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EnqueueAsNext</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>FirstTrackNumberEnqueued</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumTracksAdded</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>AddURIToSavedQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AddAtIndex</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumTracksAdded</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>BackupQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>BecomeCoordinatorOfStandaloneGroup</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>BecomeGroupCoordinator</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentGroupID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_GroupID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>OtherMembers</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberList</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TransportSettings</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TransportSettings</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>SleepTimerState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SleepTimerState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AlarmState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>StreamRestartState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_StreamRestartState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentQueueTrackList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Queue</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>BecomeGroupCoordinatorAndSource</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentGroupID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_GroupID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>OtherMembers</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberList</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>SleepTimerState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SleepTimerState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AlarmState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>StreamRestartState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_StreamRestartState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentAVTTrackList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Queue</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentQueueTrackList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Queue</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSourceState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SourceState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ResumePlayback</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ResumePlayback</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ChangeCoordinator</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewTransportSettings</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TransportSettings</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ChangeTransportSettings</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewTransportSettings</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TransportSettings</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentAVTransportURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_CurrentAVTransportURI</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ConfigureSleepTimer</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewSleepTimerDuration</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ISO8601Time</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>CreateSavedQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Title</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SavedQueueTitle</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumTracksAdded</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AssignedObjectID</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>DelegateGroupCoordinationTo</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RejoinGroup</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_RejoinGroup</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetCrossfadeMode</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CrossfadeMode</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentCrossfadeMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetCurrentTransportActions</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Actions</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTransportActions</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetDeviceCapabilities</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PlayMedia</name>
+          <direction>out</direction>
+          <relatedStateVariable>PossiblePlaybackStorageMedia</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RecMedia</name>
+          <direction>out</direction>
+          <relatedStateVariable>PossibleRecordStorageMedia</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RecQualityModes</name>
+          <direction>out</direction>
+          <relatedStateVariable>PossibleRecordQualityModes</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetMediaInfo</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NrTracks</name>
+          <direction>out</direction>
+          <relatedStateVariable>NumberOfTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>MediaDuration</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentMediaDuration</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURI</name>
+          <direction>out</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURIMetaData</name>
+          <direction>out</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NextURI</name>
+          <direction>out</direction>
+          <relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NextURIMetaData</name>
+          <direction>out</direction>
+          <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PlayMedium</name>
+          <direction>out</direction>
+          <relatedStateVariable>PlaybackStorageMedium</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RecordMedium</name>
+          <direction>out</direction>
+          <relatedStateVariable>RecordStorageMedium</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>WriteStatus</name>
+          <direction>out</direction>
+          <relatedStateVariable>RecordMediumWriteStatus</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <action>
+      <name>GetPositionInfo</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Track</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTrack</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TrackDuration</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTrackDuration</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TrackMetaData</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTrackMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TrackURI</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTrackURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RelTime</name>
+          <direction>out</direction>
+          <relatedStateVariable>RelativeTimePosition</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AbsTime</name>
+          <direction>out</direction>
+          <relatedStateVariable>AbsoluteTimePosition</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RelCount</name>
+          <direction>out</direction>
+          <relatedStateVariable>RelativeCounterPosition</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AbsCount</name>
+          <direction>out</direction>
+          <relatedStateVariable>AbsoluteCounterPosition</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetRemainingSleepTimerDuration</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RemainingSleepTimerDuration</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_ISO8601Time</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSleepTimerGeneration</name>
+          <direction>out</direction>
+          <relatedStateVariable>SleepTimerGeneration</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetRunningAlarmProperties</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AlarmID</name>
+          <direction>out</direction>
+          <relatedStateVariable>AlarmIDRunning</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>GroupID</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_GroupID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>LoggedStartTime</name>
+          <direction>out</direction>
+          <relatedStateVariable>AlarmLoggedStartTime</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetTransportInfo</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentTransportState</name>
+          <direction>out</direction>
+          <relatedStateVariable>TransportState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentTransportStatus</name>
+          <direction>out</direction>
+          <relatedStateVariable>TransportStatus</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSpeed</name>
+          <direction>out</direction>
+          <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetTransportSettings</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PlayMode</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RecQualityMode</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <action>
+      <name>Next</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>NextProgrammedRadioTracks</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>NextSection</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>NotifyDeletedURI</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DeletedURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Pause</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Play</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Speed</name>
+          <direction>in</direction>
+          <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Previous</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>PreviousSection</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Record</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RemoveAllTracksFromQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RemoveTrackFromQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RemoveTrackRangeFromQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>StartingIndex</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumberOfTracks</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ReorderTracksInQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>StartingIndex</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumberOfTracks</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>InsertBefore</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ReorderTracksInSavedQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TrackList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackList</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewPositionList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackList</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>QueueLenghtChange</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracksChange</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RunAlarm</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AlarmID</name>
+          <direction>in</direction>
+          <relatedStateVariable>AlarmIDRunning</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>LoggedStartTime</name>
+          <direction>in</direction>
+          <relatedStateVariable>AlarmLoggedStartTime</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Duration</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ISO8601Time</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PlayMode</name>
+          <direction>in</direction>
+          <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Volume</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmVolume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>IncludeLinkedZones</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmIncludeLinkedZones</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SaveQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Title</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SavedQueueTitle</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AssignedObjectID</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Seek</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Unit</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SeekMode</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Target</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SeekTarget</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetAVTransportURI</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetCrossfadeMode</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CrossfadeMode</name>
+          <direction>in</direction>
+          <relatedStateVariable>CurrentCrossfadeMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetNextAVTransportURI</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NextURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NextURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetPlayMode</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewPlayMode</name>
+          <direction>in</direction>
+          <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetRecordQualityMode</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewRecordQualityMode</name>
+          <direction>in</direction>
+          <relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SnoozeAlarm</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Duration</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ISO8601Time</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>StartAutoplay</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Volume</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmVolume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>IncludeLinkedZones</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmIncludeLinkedZones</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ResetVolumeAfter</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ResetVolumeAfter</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Stop</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/0.4/S_RenderingControl1.xml
===================================================================
--- /tags/0.4/S_RenderingControl1.xml	(revision 30)
+++ /tags/0.4/S_RenderingControl1.xml	(revision 30)
@@ -0,0 +1,1247 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <!-- $Id: S_SonosRenderingControl1.xml 121 2013-10-01 19:33:24Z lolodomo $ -->
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Channel</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>Master</allowedValue>
+        <allowedValue>LF</allowedValue>
+        <allowedValue>RF</allowedValue>
+        <allowedValue>CF</allowedValue>
+        <allowedValue>LFE</allowedValue>
+        <allowedValue>LS</allowedValue>
+        <allowedValue>RS</allowedValue>
+        <allowedValue>LFC</allowedValue>
+        <allowedValue>RFC</allowedValue>
+        <allowedValue>SD</allowedValue>
+        <allowedValue>SL</allowedValue>
+        <allowedValue>SR</allowedValue>
+        <allowedValue>T</allowedValue>
+        <allowedValue>B</allowedValue>
+        <!-- Sonos -->
+        <allowedValue>SpeakerOnly</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ChannelMap</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_EQType</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_InstanceID</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_LeftVolume</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <maximum>100</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_PresetName</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>FactoryDefaults</allowedValue>
+        <allowedValue>InstallationDefaults</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ProgramURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_RampTimeSeconds</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_RampType</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>SLEEP_TIMER_RAMP_TYPE</allowedValue>
+        <allowedValue>ALARM_RAMP_TYPE</allowedValue>
+        <allowedValue>AUTOPLAY_RAMP_TYPE</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ResetVolumeAfter</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_RightVolume</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <maximum>100</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_VolumeAdjustment</name>
+      <dataType>i4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AudioDelay</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AudioDelayLeftRear</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AudioDelayRightRear</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>Bass</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <minimum>-10</minimum>
+        <maximum>10</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>DialogLevel</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>EQValue</name>
+      <dataType>i2</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>HeadphoneConnected</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>LastChange</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Loudness</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Mute</name>
+      <shortCode>mute</shortCode>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>NightMode</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>OutputFixed</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PresetNameList</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SpeakerSize</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SubCrossover</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SubEnabled</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SubGain</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SubPolarity</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SupportsOutputFixed</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SurroundEnabled</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SurroundLevel</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>Treble</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <minimum>-10</minimum>
+        <maximum>10</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Volume</name>
+      <shortCode>volume</shortCode>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>VolumeDB</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <minimum>-32767</minimum>
+        <maximum>32767</maximum>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Brightness</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Contrast</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Sharpness</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RedVideoGain</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>GreenVideoGain</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>BlueVideoGain</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RedVideoBlackLevel</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>GreenVideoBlackLevel</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>BlueVideoBlackLevel</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>ColorTemperature</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>HorizontalKeystone</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>VerticalKeystone</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetBass</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentBass</name>
+          <direction>out</direction>
+          <relatedStateVariable>Bass</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetEQ</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EQType</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EQType</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentValue</name>
+          <direction>out</direction>
+          <relatedStateVariable>EQValue</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetHeadphoneConnected</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentHeadphoneConnected</name>
+          <direction>out</direction>
+          <relatedStateVariable>HeadphoneConnected</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetLoudness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentLoudness</name>
+          <direction>out</direction>
+          <relatedStateVariable>Loudness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetMute</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentMute</name>
+          <direction>out</direction>
+          <relatedStateVariable>Mute</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetOutputFixed</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentFixed</name>
+          <direction>out</direction>
+          <relatedStateVariable>OutputFixed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetSupportsOutputFixed</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSupportsFixed</name>
+          <direction>out</direction>
+          <relatedStateVariable>SupportsOutputFixed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetTreble</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentTreble</name>
+          <direction>out</direction>
+          <relatedStateVariable>Treble</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetVolume</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>Volume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetVolumeDB</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>VolumeDB</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetVolumeDBRange</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>MinValue</name>
+          <direction>out</direction>
+          <relatedStateVariable>VolumeDB</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>MaxValue</name>
+          <direction>out</direction>
+          <relatedStateVariable>VolumeDB</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetBrightness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentBrightness</name>
+          <direction>out</direction>
+          <relatedStateVariable>Brightness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetContrast</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentContrast</name>
+          <direction>out</direction>
+          <relatedStateVariable>Contrast</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetSharpness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSharpness</name>
+          <direction>out</direction>
+          <relatedStateVariable>Sharpness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetRedVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentRedVideoGain</name>
+          <direction>out</direction>
+          <relatedStateVariable>RedVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetGreenVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentGreenVideoGain</name>
+          <direction>out</direction>
+          <relatedStateVariable>GreenVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetBlueVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentBlueVideoGain</name>
+          <direction>out</direction>
+          <relatedStateVariable>BlueVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetRedVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentRedVideoBlackLevel</name>
+          <direction>out</direction>
+          <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetGreenVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentGreenVideoBlackLevel</name>
+          <direction>out</direction>
+          <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetBlueVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentBlueVideoBlackLevel</name>
+          <direction>out</direction>
+          <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetColorTemperature </name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentColorTemperature</name>
+          <direction>out</direction>
+          <relatedStateVariable>ColorTemperature</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetHorizontalKeystone</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentHorizontalKeystone</name>
+          <direction>out</direction>
+          <relatedStateVariable>HorizontalKeystone</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetVerticalKeystone</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentVerticalKeystone</name>
+          <direction>out</direction>
+          <relatedStateVariable>VerticalKeystone</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>ListPresets</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentPresetNameList</name>
+          <direction>out</direction>
+          <relatedStateVariable>PresetNameList</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+    <action>
+      <name>RampToVolume</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RampType</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_RampType</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredVolume</name>
+          <direction>in</direction>
+          <relatedStateVariable>Volume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ResetVolumeAfter</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ResetVolumeAfter</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ProgramURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RampTime</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_RampTimeSeconds</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ResetBasicEQ</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Bass</name>
+          <direction>out</direction>
+          <relatedStateVariable>Bass</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Treble</name>
+          <direction>out</direction>
+          <relatedStateVariable>Treble</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Loudness</name>
+          <direction>out</direction>
+          <relatedStateVariable>Loudness</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>LeftVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_LeftVolume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RightVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_RightVolume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ResetExtEQ</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EQType</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EQType</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RestoreVolumePriorToRamp</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SelectPreset</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PresetName</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_PresetName</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetBass</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredBass</name>
+          <direction>in</direction>
+          <relatedStateVariable>Bass</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetChannelMap</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ChannelMap</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ChannelMap</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetEQ</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EQType</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EQType</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredValue</name>
+          <direction>in</direction>
+          <relatedStateVariable>EQValue</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetLoudness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredLoudness</name>
+          <direction>in</direction>
+          <relatedStateVariable>Loudness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetMute</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredMute</name>
+          <direction>in</direction>
+          <relatedStateVariable>Mute</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetOutputFixed</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredFixed</name>
+          <direction>in</direction>
+          <relatedStateVariable>OutputFixed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetRelativeVolume</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Adjustment</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_VolumeAdjustment</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>Volume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetTreble</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredTreble</name>
+          <direction>in</direction>
+          <relatedStateVariable>Treble</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetVolume</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredVolume</name>
+          <direction>in</direction>
+          <relatedStateVariable>Volume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetVolumeDB</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredVolume</name>
+          <direction>in</direction>
+          <relatedStateVariable>VolumeDB</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetBrightness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredBrightness</name>
+          <direction>in</direction>
+          <relatedStateVariable>Brightness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetContrast</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredContrast</name>
+          <direction>in</direction>
+          <relatedStateVariable>Contrast</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetSharpness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredSharpness</name>
+          <direction>in</direction>
+          <relatedStateVariable>Sharpness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetRedVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredRedVideoGain</name>
+          <direction>in</direction>
+          <relatedStateVariable>RedVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetGreenVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredGreenVideoGain</name>
+          <direction>in</direction>
+          <relatedStateVariable>GreenVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetBlueVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredBlueVideoGain</name>
+          <direction>in</direction>
+          <relatedStateVariable>BlueVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetRedVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredRedVideoBlackLevel</name>
+          <direction>in</direction>
+          <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetGreenVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredGreenVideoBlackLevel</name>
+          <direction>in</direction>
+          <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetBlueVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredBlueVideoBlackLevel</name>
+          <direction>in</direction>
+          <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetColorTemperature</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredColorTemperature</name>
+          <direction>in</direction>
+          <relatedStateVariable>ColorTemperature</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetHorizontalKeystone</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredHorizontalKeystone</name>
+          <direction>in</direction>
+          <relatedStateVariable>HorizontalKeystone</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetVerticalKeystone</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredVerticalKeystone</name>
+          <direction>in</direction>
+          <relatedStateVariable>VerticalKeystone</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+  </actionList>
+</scpd>
+
Index: /tags/0.4/L_DLNAUPnPUtility.lua
===================================================================
--- /tags/0.4/L_DLNAUPnPUtility.lua	(revision 30)
+++ /tags/0.4/L_DLNAUPnPUtility.lua	(revision 30)
@@ -0,0 +1,1045 @@
+module("L_DLNAUPnPUtility", package.seeall)
+
+local url = require("socket.url")
+local socket = require("socket")
+local http = require("socket.http")
+local ltn12 = require("ltn12")
+
+-- 5 Second timeout
+http.TIMEOUT = 5
+
+local UPNP_DISCOVERY = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 5\r\nST: %s\r\n\r\n"
+
+local UPNP_REQUEST = [[<?xml version="1.0" encoding="utf-8"?>
+<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
+<s:Body>
+<u:%s xmlns:u="%s">%s</u:%s>
+</s:Body>
+</s:Envelope>]]
+
+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 VeraIP = ""
+local log = print
+local warning = print
+local error = print
+local contentType
+local Services = {}
+
+function initialize(veraIP, logger, warningLogger, errorLogger, ct)
+  VeraIP = veraIP
+  log = logger
+  warning = warningLogger
+  error = errorLogger
+  contentType = ct or 'text/xml; charset="utf-8"'
+end
+
+function decode(val)
+      return val:gsub("&#38;", '&')
+                :gsub("&#60;", '<')
+                :gsub("&#62;", '>')
+                :gsub("&#34;", '"')
+                :gsub("&#39;", "'")
+                :gsub("&lt;", "<")
+                :gsub("&gt;", ">")
+                :gsub("&quot;", '"')
+                :gsub("&apos;", "'")
+                :gsub("&amp;", "&")
+end
+
+local function encode(val)
+      return val:gsub("&", "&amp;")
+                :gsub("<", "&lt;")
+                :gsub(">", "&gt;")
+                :gsub('"', "&quot;")
+                :gsub("'", "&apos;")
+end
+
+function unformatXML(val)
+      return val:gsub("^%s+<", "<")
+                :gsub(">%s+$", ">")
+                :gsub(">%s+<", "><")
+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
+
+-- Send a UPnP SUBSCRIBE to the UPnP device,
+-- Return value on success:
+--   SID (subscription ID) provided by the device.
+--   Duration (in seconds) before the subscription must be renewed
+-- Return value on failure:
+--   nil
+--   Reason for failure (string)
+-- Code coming from Wemo plugin (function subscribeToDevice) and adjusted
+function UPnP_subscribe(ipAddress, ipPort, serverURL, callbackURL, renewalSID)
+	local eventSubURL = serverURL:format(ipAddress, ipPort)
+
+	-- Create a socket with a timeout.
+	local sock = function()
+		local s = socket.tcp()
+		s:settimeout(http.TIMEOUT)
+		return s
+	end
+
+	local headers = {
+		["TIMEOUT"] = "Second-3600",
+	}
+
+	if (renewalSID) then
+		-- Renewing, include SID header.
+		headers["SID"] = renewalSID
+	else
+		-- New subscription, include CALLBACK and NT headers
+		headers["CALLBACK"] = string.format("<%s>", callbackURL)
+		headers["NT"] = "upnp:event"
+	end
+
+	-- Ask the device to inform the proxy about status changes.
+	local request, code, headers = http.request({
+		url = eventSubURL,
+		method = "SUBSCRIBE",
+		headers = headers,
+		create = sock,
+	})
+
+	if (request == nil and code ~= "closed") then
+		error("Failed to subscribe to " .. eventSubURL .. ": " .. code)
+		return nil, code
+	elseif (code ~= 200) then
+		error("Failed to subscribe to " .. eventSubURL .. ": " .. code)
+		return nil, code
+	else
+		local duration = headers["timeout"]:match("Second%-(%d+)")
+		log("Subscription confirmed, SID = " .. headers["sid"] .. " with timeout " .. duration)
+		return headers["sid"], tonumber(duration)
+	end
+end
+  
+  
+function UPnP_request(ipAddress, ipPort, serverURL, action, servicetype, args)
+  local function table2XML(value)
+    local result = ""
+
+    if (args == nil) then
+        return result
+    end
+
+    --
+    -- Convert all the Number, Boolean and Table objects, and escape all the string
+    -- values in the XML output stream
+    --
+    -- If value table has an entry OrderedArgs, we consider that all the values
+    -- are set in this special entry and we bypass all the other values of the value table.
+    -- In this particular case, this entry itself is a table of strings, each element of
+    -- the table following the format "parameter=value"
+    --
+    if (value.OrderedArgs ~= nil and type(value.OrderedArgs) == "table") then
+        for i, val in ipairs(value.OrderedArgs) do
+            local e, v = val:match("([^=]+)=(.*)")
+            if (e ~= nil) then
+                if (v == nil) then
+                    result = result .. string.format("<%s />", e)
+                elseif (type(v) == "table") then
+                    result = result .. table2XML(v)
+                elseif (type(v) == "number") then
+                    result = result .. string.format("<%s>%.0f</%s>", e, v, e)
+                elseif (type(v) == "boolean") then
+                    result = result .. string.format("<%s>%s</%s>", e, (v and "1" or "0"), e)
+                else
+                    result = result .. string.format("<%s>%s</%s>", e, encode(v), e)
+                end
+            end
+        end
+    else
+        for e, v in pairs(value) do
+            if (v == nil) then
+                result = result .. string.format("<%s />", e)
+            elseif (type(v) == "table") then
+                result = result .. table2XML(v)
+            elseif (type(v) == "number") then
+                result = result .. string.format("<%s>%.0f</%s>", e, v, e)
+            elseif (type(v) == "boolean") then
+                result = result .. string.format("<%s>%s</%s>", e, (v and "1" or "0"), e)
+            else
+                result = result .. string.format("<%s>%s</%s>", e, encode(v), e)
+            end
+        end
+    end
+
+    return result
+  end
+
+  local postBody = string.format(UPNP_REQUEST, action, servicetype, table2XML(args), action)
+  serverURL = serverURL:format(ipAddress, ipPort)
+  log(string.format("UPnP_request: url=[%s], body=[%s]", serverURL, postBody))
+
+  --
+  -- Execute the resulting URL, and collect the results as a Table
+  --
+  local resultTable = {}
+  local status, statusMsg = http.request{
+    url = serverURL,
+    sink = ltn12.sink.table(resultTable),
+    method = "POST",
+    headers = {["Accept"] = "*/*",
+               ["SOAPAction"] = '"' .. servicetype .. "#" .. action .. '"',
+               ["Connection"] = "close",
+               ["Content-Length"] = postBody:len(),
+               ["Content-Type"] = contentType},
+    source = ltn12.source.string(postBody),
+  }
+
+  --
+  -- Flatten the resultTable into a regular string
+  --
+  local data = ""
+  for i, v in ipairs(resultTable) do
+    data = data .. v
+  end
+  
+  if (status == nil) then
+    --
+    -- Handle TIMEOUT
+    --
+    return false, statusMsg or "An error occurred during the UPnP call"
+  end
+  
+  if (tostring(statusMsg) == "200") then
+    --
+    -- Handle SUCCESS
+    --
+    log(string.format("UPnP_request: status=%s statusMsg=%s result=[%s]", status or "no status",
+                      statusMsg or "no message",
+                      data or "no result"))
+
+    if (data == nil or data == "") then
+      return true, ""
+    else
+      local pattern = string.format('<.-:Body><.-:%sResponse%%sxmlns:.-="urn:.-">(.*)</.-:%sResponse></.-:Body></.-:Envelope>',
+                                    action, action)
+      local value = unformatXML(data):match(pattern) or ""
+
+      -- TODO: Handle UPnP Error responses
+      -- TODO: Handle Non-Scalar results
+
+      -- 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 true, value
+    end
+  else
+    --
+    -- UNKNOWN ERROR
+    --
+    error(string.format("UPnP_request (%s, %s): status=%s statusMsg=%s result=[%s]",
+                      action or "no action",
+                      servicetype or "no servicetype",
+                      status or "no status",
+                      statusMsg or "no message",
+                      data or "no result"))
+
+    assert("Unhandled Response statusMsg=" .. statusMsg)
+  end
+end -- function UPnP_request
+
+--
+-- Return a handle to a UPnP Service object.
+-- This can be used to call arbitrary Service Methods on that service using dynamic naming foo.
+--
+function service(ip, port, controlURL, servicetype, actions)
+    local self = {}
+    local mt = {}
+
+    mt.__index = function(table, key)
+        log("service.__index: Accessing non-existing function " .. key)
+
+        local fn = function(...)
+            if (actions[key]) then
+                log(string.format("%s('%s', %d, '%s', '%s') Called with parameter count=%d", key, ip, port, controlURL, servicetype, select("#", ...)))
+                return UPnP_request(ip, port, controlURL, key, servicetype, ...)
+            else
+                return false, "action not available"
+            end
+        end
+
+        table[key] = fn
+        return fn
+    end
+
+    setmetatable(self, mt)
+
+    return self
+end
+
+
+--------------------------------------------------------------------------------
+-- UPnP discovery
+--------------------------------------------------------------------------------
+
+
+function UPnP_discover(target)
+    local devices = {}
+    local udp = socket.udp()
+    if (udp ~= nil) then
+        local result, message = udp:sendto(UPNP_DISCOVERY:format(target), "239.255.255.250", 1900)
+        if (result ~= nil) then
+            udp:settimeout(5)
+            for i=1,500 do
+                result, message = udp:receivefrom()
+                if (result == nil) then
+                    break
+                else
+                    local location, ip, port = result:match("[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:%s?(http://([%d%.]-):(%d+)/.-)\r\n")
+                    local st = result:match("[Ss][Tt]:%s?(.-)\r\n")
+                    if (location ~= nil and ip ~= nil and port ~= nil and st ~= nil) then
+                        local new = true
+                        for i,device in ipairs(devices) do
+                            if (device.descriptionURL == location and device.st == st) then
+                                new = false
+                                break
+                            end
+                        end
+                        if (new) then
+                            table.insert(devices, { descriptionURL=location, ip=ip, port=port, st=st })
+                        end
+                    end
+                end
+            end
+        end
+        udp:close()
+    end
+    return devices
+end
+
+
+function scanUPnPDevices(deviceType, infos)
+    local xml = "<devices>"
+    local devices = UPnP_discover(deviceType)
+    for i,dev in ipairs(devices) do
+        local descrXML = UPnP_getDeviceDescription(dev.descriptionURL)
+        if (descrXML ~= nil) then
+            xml = xml .. "<device>"
+            xml = xml .. "<ip>" .. (dev.ip or "") .. "</ip>"
+            xml = xml .. "<port>" .. (dev.port or "") .. "</port>"
+            xml = xml .. "<descriptionURL>" .. (dev.descriptionURL or "") .. "</descriptionURL>"
+            local values = getInfosFromDescription(descrXML, deviceType, infos)
+            if (infos ~= nil) then
+                for i,tag in ipairs(infos) do
+                    xml = xml .. "<" .. tag .. ">" .. (values[tag] or "") .. "</" .. tag .. ">"
+                end
+            end
+            xml = xml .. "</device>"
+        end
+    end
+    xml = xml .. "</devices>"
+    return xml
+end
+
+
+function searchUPnPDevices(deviceType, name, ip)
+    local devices = UPnP_discover(deviceType)
+    for i,dev in ipairs(devices) do
+        if (ip == nil or ip == "" or ip == dev.ip) then
+            local descrXML = UPnP_getDeviceDescription(dev.descriptionURL)
+            if (descrXML ~= nil) then
+                local values = getInfosFromDescription(descrXML, deviceType, { "modelName" })
+                if (values.modelName == name) then
+                    return dev.descriptionURL
+                end
+            end
+        end
+    end
+    return nil
+end
+
+
+--------------------------------------------------------------------------------
+-- UPnP device and services description
+--------------------------------------------------------------------------------
+
+
+function UPnP_getDeviceDescription(descriptionURL)
+    local status, xml = luup.inet.wget(descriptionURL, 2)
+    if (status ~= 0) then
+        xml = nil
+    end
+    return xml
+end
+
+
+function getInfosFromDescription(descriptionXML, deviceType, infos)
+    local data = {}
+    local tag, value, devices, device
+    if (infos ~= nil) then
+        local rootDevPart1, embeddedDevices, rootDevPart2 = descriptionXML:match("(<device%s?[^>]->.-)<deviceList%s?[^>]->(.-)</deviceList>(.-</device>)")
+        if (rootDevPart1 == nil or rootDevPart2 == nil or embeddedDevices == nil) then
+            devices = descriptionXML:match("(<device%s?[^>]->.*</device>)") or ""
+        elseif (deviceType ~= nil) then
+            devices = rootDevPart1 .. rootDevPart2 .. embeddedDevices
+        else
+            devices = rootDevPart1 .. rootDevPart2
+        end
+        for device in devices:gmatch("<device%s?[^>]->(.-)</device>") do
+            value = device:match("<deviceType>(.+)</deviceType>")
+            if (deviceType == nil or value == deviceType) then
+                for i,tag in ipairs(infos) do
+                    value = device:match("<"..tag..">(.+)</"..tag..">")
+                    if (value ~= nil) then
+                        data[tag] = value
+                    end
+                end
+            end
+        end
+    end
+    return data
+end
+
+
+function getIconFromDescription(descriptionXML)
+    local resultURL = nil
+    local rootDevice, icon, size, height, width, icnoURL
+    local rootDevPart1, embeddedDevices, rootDevPart2 = descriptionXML:match("(<device%s?[^>]->.-)<deviceList%s?[^>]->(.-)</deviceList>(.-</device>)")
+    if (rootDevPart1 == nil or rootDevPart2 == nil or embeddedDevices == nil) then
+        rootDevice = descriptionXML:match("(<device%s?[^>]->.*</device>)") or ""
+    else
+        rootDevice = rootDevPart1 .. rootDevPart2
+    end
+    size = 0
+    for icon in rootDevice:gmatch("<icon%s?[^>]->(.-)</icon>") do
+        height = icon:match("<height>(.+)</height>")
+        width = icon:match("<width>(.+)</width>")
+        icnoURL = icon:match("<url>(.+)</url>")
+        if (icnoURL ~= nil and height ~= nil and width ~= nil) then
+            height = tonumber(height)
+            width = tonumber(width)
+            if (height >= width and height > size) then
+                size = tonumber(height)
+                resultURL = icnoURL
+            elseif (height < width and width > size) then
+                size = tonumber(width)
+                resultURL = icnoURL
+            end
+        end
+    end
+    return resultURL
+end
+
+
+function getActionsFromSCPD(scpdURL)
+    local actions = {}
+    local action, name
+    local status, xml = luup.inet.wget(scpdURL, 2)
+    if (status == 0) then
+        for action in xml:gmatch("<action>(.-)</action>") do
+            name = action:match("<name>(.-)</name>")
+            if (name ~= nil) then
+                actions[name] = true
+            end
+        end
+    end
+    return actions
+end
+
+
+function getServicesFromDescription(descriptionXML, deviceType, baseURL, subsetServices)
+    local services = {}
+    local value, devices, device, service, service2, serviceType, serviceId, controlURL, eventSubURL
+    if (subsetServices ~= nil) then
+        local rootDevPart1, embeddedDevices, rootDevPart2 = descriptionXML:match("(<device%s?[^>]->.-)<deviceList%s?[^>]->(.-)</deviceList>(.-</device>)")
+        if (rootDevPart1 == nil or rootDevPart2 == nil or embeddedDevices == nil) then
+            devices = descriptionXML:match("(<device%s?[^>]->.*</device>)") or ""
+        elseif (deviceType ~= nil) then
+            devices = rootDevPart1 .. rootDevPart2 .. embeddedDevices
+        else
+            devices = rootDevPart1 .. rootDevPart2
+        end
+        for device in devices:gmatch("<device%s?[^>]->(.-)</device>") do
+            value = device:match("<deviceType>(.+)</deviceType>")
+            if (deviceType == nil or value == deviceType) then
+                for service in device:gmatch("<service%s?[^>]->(.-)</service>") do
+                    serviceType = service:match("<serviceType>(.*)</serviceType>")
+                    serviceId = service:match("<serviceId>(.*)</serviceId>")
+                    controlURL = service:match("<controlURL>(.*)</controlURL>")
+                    eventSubURL = service:match("<eventSubURL>(.*)</eventSubURL>")
+                    scpdURL = service:match("<SCPDURL>(.*)</SCPDURL>")
+                    if (serviceType ~= nil and serviceId ~= nil
+                           and controlURL ~= nil and eventSubURL ~= nil
+                           and scpdURL ~= nil) then
+                        for i,service2 in ipairs(subsetServices) do
+                            if (service2 == serviceType) then
+                               services[serviceType] = {
+                                       controlURL = url.absolute(baseURL, controlURL),
+                                       eventSubURL = url.absolute(baseURL, eventSubURL),
+                                       scpdURL = url.absolute(baseURL, scpdURL),
+                                       serviceId = serviceId,
+                                       actions = getActionsFromSCPD(url.absolute(baseURL, scpdURL)),
+                                       object = nil }
+                                break
+                            end
+                        end
+                    end
+                end
+            end
+        end
+    end
+    return services
+end
+
+
+--------------------------------------------------------------------------------
+-- Services management
+--------------------------------------------------------------------------------
+
+
+  function aresServicesLoaded(uuid)
+    if (uuid ~= nil and uuid ~= "" and Services[uuid] ~= nil) then
+        return true
+    else
+        return false
+    end
+  end
+
+  function getService(uuid, serviceType)
+    local s = nil
+    if (aresServicesLoaded(uuid) and Services[uuid][serviceType] ~= nil) then
+        s = Services[uuid][serviceType].object
+    end
+    return s
+  end
+
+  function resetServices(uuid)
+    if (aresServicesLoaded(uuid)) then
+        Services[uuid] = nil
+    end
+  end
+
+  function addServices(uuid, ip, port, descriptionXML, deviceType, subsetServices)
+    if (uuid == nil or uuid == "") then
+        return
+    end
+
+    if (Services[uuid] == nil) then
+        Services[uuid] = {}
+    end
+
+    local services = getServicesFromDescription(
+                              descriptionXML, deviceType,
+                              string.format("http://%s:%s/", ip, port),
+                              subsetServices)
+    for k,v in pairs(services) do
+        Services[uuid][k] = v
+    end
+
+    for k,v in pairs(Services[uuid]) do
+        log(k .. " " .. v.serviceId .. " " .. v.controlURL .. " " .. v.eventSubURL .. " " .. v.scpdURL)
+        if (v.object == nil) then
+            v.object = service(ip, port, v.controlURL, k, v.actions)
+        end
+    end
+  end
+
+
+
+  function testURL(URLtoBeChecked, headers)
+    local sock = function()
+        local s = socket.tcp()
+        s:settimeout(2)
+        return s
+    end
+
+    if (headers == nil) then
+        headers = {}
+    end
+
+    local resultHeaders
+    local request, code, resultHeaders = http.request({
+        url = URLtoBeChecked,
+        method = "HEAD",
+        headers = headers,
+        create = sock
+    })
+
+    if (request ~= nil and code == 200) then
+        return true, resultHeaders
+    else
+        return false, {}
+    end
+  end
+
+
+  --------------------------------------------------------------------------------
+  -- UPnP AV metadata (DIDL-Lite parsing)
+  --------------------------------------------------------------------------------
+
+
+  -- parseElt(value, tag, subTag)
+  -- Parse XML data at the second or third depth level
+  -- If there are several elements, only the first is taken in consideration
+  -- Parameters:
+  --   value is the XML data
+  --   tag is the first depth level XML tag
+  --   subTag is the second depth level XML tag or nil if data have to be retrieved from the second depth level
+  -- Return values:
+  --   multilines string containing all parsed values; each line is formatted like that: tag="value"
+  --   table containing parsed data (attributes + values) indexed either by the tag or tag@attribute
+  function parseFirstElt(value, tag, subTag)
+      local pattern, tmp, attributes, token, value1, attr, value2
+      pattern = string.format("<%s(%%s?[^>]-)>(.*)</%s>", tag, tag)
+      attributes, tmp = value:match(pattern)
+      if (tmp ~= nil and subTag ~= nil) then
+          -- Get the content of the first sub tag
+          pattern = string.format("<%s(%%s?[^>]-)>(.-)</%s>", subTag, subTag)
+          attributes, tmp = tmp:match(pattern)
+      end
+
+      if (tmp == nil) then
+          return nil, {}
+      end
+
+      local elts, eltsTable = "", {}
+
+      for attr, value1 in attributes:gmatch('%s(.-)="(.-)"') do
+          eltsTable["@" .. attr] = value1
+      end
+	
+      for token, attributes, value1 in tmp:gmatch("<([a-zA-Z0-9:]+)(%s?[^>]-)>(.-)</[^>]->") do
+          elts = elts .. string.format('%s="%s"\n', token, value1)
+          eltsTable[token] = value1
+          for attr, value2 in attributes:gmatch('%s(.-)="(.-)"') do
+              eltsTable[token .. "@" .. attr] = value2
+          end
+      end
+
+      return elts, eltsTable
+  end
+
+  -- parseDIDLItem(value)
+  -- Parse XML data for the item of a DIDL UPnP AV defining metadata XML meta data
+  -- Parameters:
+  --   value is the DIDL XML data
+  -- Return values:
+  --   multilines string containing all parsed values; each line is formatted like that: tag="value"
+  --   table containing parsed data (attributes + values) indexed either by the tag or tag@attribute
+  function parseDIDLItem(value)
+      return parseFirstElt(value, "DIDL%-Lite", "item")
+  end
+
+
+  function extractElement(tag, xml, default)
+    if (xml == nil) then
+      return(default)
+    else
+      local pattern = string.format("<%s%%s?[^>]->(.-)</%s>", tag, tag)
+      local result = xml:match(pattern)
+      if (result == nil) then
+          result = default
+      else
+          result = decode(result)
+      end
+      return result
+    end
+  end
+
+
+  local function mergeDIDL(xml1, xml2)
+    local result = ""
+    local val1 = nil
+    local val2 = nil
+    local val3 = nil
+    local val4 = nil
+    local val5 = nil
+    local val6 = nil
+    local tag = "DIDL%-Lite"
+    if (xml1 ~= nil and xml1 ~= "") then
+        val1, val2, val3 = xml1:match("(<"..tag.."%s?[^>]->)(.-)(</"..tag..">)")
+    end
+    if (xml2 ~= nil and xml2 ~= "") then
+        val4, val5, val6 = xml2:match("(<"..tag.."%s?[^>]->)(.-)(</"..tag..">)")
+    end
+    if (val1 and val2 and val3 and val4 and val5 and val6) then
+        result = val1 .. val2 .. val5 .. val3
+    elseif (val1 and val2 and val3) then
+        result = xml1
+    elseif (val4 and val5 and val6) then
+        result = xml2
+    elseif (xml1:match("(<"..tag.."%s?[^>]-/>)") ~= nil) then
+        result = xml1
+    elseif (xml2:match("(<"..tag.."%s?[^>]-/>)") ~= nil) then
+        result = xml2
+    end
+    return result
+  end
+
+
+  function browseContent(uuid, serviceType, browseObj, onlyMetadata)
+      local result = ""
+      local ContentDirectory = getService(uuid, serviceType)
+      if (ContentDirectory == nil) then
+          return result
+      end
+
+      local browseFlag
+      if (onlyMetadata) then
+          browseFlag = "BrowseMetadata"
+      else
+          browseFlag = "BrowseDirectChildren"
+      end
+
+      local fetched = 0
+      local total = 0
+      local status, tmp
+      repeat
+          status, tmp = ContentDirectory.Browse({OrderedArgs={
+                                 "ObjectID=" .. browseObj,
+                                 "BrowseFlag=" .. browseFlag,
+                                 "Filter=*",
+                                 "StartingIndex=" .. string.format("%d", fetched),
+                                 "RequestedCount=100",
+                                 "SortCriteria="}})
+          if (status == true) then
+              result = mergeDIDL(result, extractElement("Result", tmp, ""))
+              fetched = fetched + tonumber(extractElement("NumberReturned", tmp, ""))
+              total = tonumber(extractElement("TotalMatches", tmp, ""))
+          else
+              total = 0
+          end
+      until fetched >= total
+      return result
+  end
+
+
+  --------------------------------------------------------------------------------
+  -- UPnP Event Proxy
+  --------------------------------------------------------------------------------
+
+
+  -- XML pattern used when notifying the UPnP event proxy of the the subscription to an event
+  local PROXY_REQUEST = "<subscription expiry='%d'><variable name='%s' host='localhost' deviceId='%d' serviceId='%s' action='%s' parameter='%s' sidParameter='sid'/></subscription>"
+
+  -- Version of the UPnP event proxy, or nil if proxy is not running or not used
+  local ProxyApiVersion = nil
+
+
+  -- getProxyApiVersion()
+  -- Calls the proxy with GET /version.
+  -- Sets the ProxyApiVersion global variable to the value received
+  -- Return value:
+  --   nil if the proxy is not running.
+  --   The proxy API version (as a string) otherwise.
+  function getProxyApiVersion()
+    local sock = function()
+        local s = socket.tcp()
+        s:settimeout(2)
+        return s
+    end
+
+    local t = {}
+    local request, code = http.request({
+        url = "http://localhost:2529/version",
+        create = sock,
+        sink = ltn12.sink.table(t)
+    })
+
+    if (request == nil and code == "timeout") then
+        -- Proxy may be busy.
+        warning("Temporarily cannot communicate with proxy")
+        return nil
+    elseif (request == nil and code ~= "closed") then
+        -- Proxy not running.
+        error("Cannot contact UPnP event proxy: " .. code)
+        return ""
+    else
+        -- Proxy is running, note its version number.
+        ProxyApiVersion = table.concat(t)
+        return ProxyApiVersion
+    end
+  end
+
+
+  -- WeMo plugin contrib
+  -- proxyVersionAtLeast(n)
+  -- Returns true if the proxy is running and is at least version n.
+  function proxyVersionAtLeast(n)
+    if (ProxyApiVersion and tonumber(ProxyApiVersion:match("^(%d+)")) >= n) then
+        return true
+    end
+    return false
+  end
+
+
+  -- unuseProxy()
+  -- Disable the usage of the UPnP event proxy by resetting the variable ProxyApiVersion to nil
+  -- Return value: none
+  function unuseProxy()
+    ProxyApiVersion = nil
+    warning("UPnP event proxy is now unused")
+  end
+
+
+  -- subscribeToUPnPEvent(device, ip, port, serverURL, eventVariable, actionServiceId, actionName, renewalSID)
+  -- Process a new subscription to an event for an UPnP device, or renew an active subscription
+  -- First send a subscription request to the UPnP device and then notify the proxy of this new subscription
+  -- Parameters:
+  --   device is the device id that the UPnP event proxy will notify
+  --   ip is the IP address of the UPnP device
+  --   port is the port to be used to communicate with the UPnP device
+  --   serverURL is the URL to be used to subscribe to the UPnP device
+  --   eventVariable is the UPnP variable name we subscribe to
+  --   actionServiceId is the service id of the action to be called by the UPnP event proxy
+  --   actionName is the action name to be called by the UPnP event proxy
+  --   renewalSID is the subscription id to be renewed or nil if it is an initial subscription
+  -- Return values:
+  --   subscription id or nil if the subscribe process failed
+  --   expiry date of the new or renewed subscription; nil if the subscribe process failed
+  --   live duration of the new or renewed subscription; nil if the subscribe process failed 
+  function subscribeToUPnPEvent(device, ip, port, serverURL, eventVariable, actionServiceId, actionName, renewalSID)
+    log("Subscribing to event " .. serverURL .. " for variable " .. eventVariable .. " with SID " .. (renewalSID or "nil"))
+
+	local sock = function()
+		local s = socket.tcp()
+		s:settimeout(5)
+		return s
+	end
+
+	-- Ask the device to inform the proxy about status changes.
+	local expiry = nil
+	local sid, duration = UPnP_subscribe(ip, port, serverURL, "http://" .. VeraIP .. ":2529/upnp/event", renewalSID)
+	if (sid) then
+		expiry = os.time() + duration
+
+		-- Tell proxy about this subscription and the variable we care about.
+		-- Volume is the variable we care about.
+		local proxyRequestBody = PROXY_REQUEST:format(expiry, eventVariable, device, actionServiceId, actionName, eventVariable)
+		local request, code = http.request({
+			url = "http://localhost:2529/upnp/event/" .. url.escape(sid),
+			create = sock,
+			method = "PUT",
+			headers = {
+				["Content-Type"] = "text/xml",
+				["Content-Length"] = proxyRequestBody:len(),
+			},
+			source = ltn12.source.string(proxyRequestBody),
+			sink = ltn12.sink.null(),
+		})
+-- TODO retry if timeout
+		if (request == nil and code ~= "closed") then
+			error("Failed to notify proxy of subscription: " .. code)
+			sid = nil
+			expiry = nil
+		elseif (code ~= 200) then
+			error("Failed to notify proxy of subscription: " .. code)
+			sid = nil
+			expiry = nil
+		else
+			log("Successfully notified proxy of subscription")
+		end
+	end
+
+	return sid, expiry, duration
+  end
+
+
+  -- WeMo plugin contrib
+  -- cancelProxySubscription(sid)
+  -- Sends a DELETE /upnp/event/[sid] message to the UPnP event proxy,
+  -- Return value:
+  --   nil if the proxy timed out (should try again).
+  --   false if the proxy refused our request (permanently).
+  --   true if the proxy agreed to our request.
+  function cancelProxySubscription(sid)
+    log("Cancelling subscription for sid " .. sid)
+    local sock = function()
+        local s = socket.tcp()
+        s:settimeout(2)
+        return s
+    end
+
+    local request, code = http.request({
+        url = "http://localhost:2529/upnp/event/" .. url.escape(sid),
+        create = sock,
+        method = "DELETE",
+        source = ltn12.source.empty(),
+        sink = ltn12.sink.null(),
+    })
+    if (request == nil and code ~= "closed") then
+        error("Failed to cancel subscription: " .. code)
+        return nil
+    elseif (code ~= 200) then
+        error("Failed to cancel subscription: " .. code)
+        return false
+    else
+        log("Successfully cancelled subscription")
+        return true
+    end
+  end
+
+
+  -- subscribeToEvents(device, ip, port, subscriptions, actionServiceId, uuid)
+  -- Process the subscription to several events for an UPnP device, or renew these subscriptions
+  -- The subscriptions are defined in a table (subscriptions).
+  -- Each element of this table is enhanced with subsciption data (id, expiry date, ...).
+  -- Before calling this function, the UPnP services for the UPnP device have to be loaded. Only
+  -- events associated to a loaded and registered service will be subscribed to.
+  -- If one event subscription fails, all subscriptions are finally cancelled.
+  -- A timer is set to call the function renewSubscriptions, so that all the subscritopns can be
+  -- renewed in time. So this function "renewSubscriptions" must be declared by the plugin.
+  -- Parameters:
+  --   device is the device id that the UPnP event proxy will notify
+  --   ip is the IP address of the UPnP device
+  --   port is the port to be used to communicate with the UPnP device
+  --   subscriptions is a table defining the events to be subscribed to
+  --   actionServiceId is the service id for all actions to be called by the UPnP event proxy
+  --   uuid is the UUID of the UPnP device
+  -- Return value:
+  --   true if UPnP event proxy is not running or not used
+  --   true if the UPnP services are not yet loaded and registered
+  --   true if all subscriptions succeeded
+  --   false if subscriptions failed
+  function subscribeToEvents(device, ip, port, subscriptions, actionServiceId, uuid)
+    log("Subscribing to all events")
+
+    if (proxyVersionAtLeast(1) == false) then
+        return true
+    end
+    if (aresServicesLoaded(uuid) == false) then
+        return true
+    end
+
+    local result = true
+
+    local sid, expiry, duration
+
+    local nbSubscriptions = 0
+    local minDuration = 3600
+
+    for i,subscription in ipairs(subscriptions) do
+        if (Services[uuid][subscription.service] ~= nil) then
+            sid = nil
+            if (subscription.id ~= "") then
+                sid = subscription.id
+            end
+            sid, expiry, duration = subscribeToUPnPEvent(device,
+                                                         ip,
+                                                         port,
+                                                         Services[uuid][subscription.service].eventSubURL,
+                                                         subscription.eventVariable,
+                                                         actionServiceId,
+                                                         subscription.actionName,
+                                                         sid)
+            if (sid ~= nil) then
+                log("Event subscription succeeded => SID " .. sid .. " duration " .. duration .. " expiry " .. expiry)
+                subscription.id = sid
+                subscription.expiry = expiry
+                nbSubscriptions = nbSubscriptions + 1
+                if (duration < minDuration) then
+                    minDuration = duration
+                end
+            else
+                error("Event subscription failed: " .. Services[uuid][subscription.service].eventSubURL)
+                cancelProxySubscriptions(subscriptions)
+                nbSubscriptions = 0
+                result = false
+                break
+            end
+        end
+    end
+
+    if (nbSubscriptions > 0) then
+        if (minDuration >= 600) then
+            minDuration = minDuration - 300
+        end
+
+        luup.call_delay("renewSubscriptions", minDuration, device .. ":" .. uuid)
+    end
+
+    return result
+  end
+
+
+  -- cancelProxySubscriptions(subscriptions)
+  -- Sends a DELETE /upnp/event/[sid] message to the UPnP event proxy for all subscriptions
+  -- defined in the table subscriptions.
+  -- Subscription data for each element of this table are reset.
+  -- Parameters:
+  --   subscriptions is a table containing the current subscription data
+  -- Return value: none
+  function cancelProxySubscriptions(subscriptions)
+    log("Cancelling all event subscriptions")
+
+    if (proxyVersionAtLeast(1) == false) then
+        return
+    end
+
+    for i,subscription in ipairs(subscriptions) do
+        if (subscription.id ~= "") then
+            cancelProxySubscription(subscription.id)
+            subscription.id = ""
+            subscription.expiry = ""
+        end
+    end
+  end
+
+
+  -- isValidNotification(notifyAction, sid, subscriptions)
+  -- Check whether a proxy notification is valid (the subscription id has to be declared in the
+  -- table containing current subscription data)
+  -- If not, a delayed action cancelProxySubscription is called to unsubscribe this subscription.
+  -- So this function "cancelProxySubscription" must be declared by the plugin.
+  -- Parameters:
+  --   notifyAction is the action called by the UPnP event proxy
+  --   sid is the subscription id provided by the UPnP event proxy
+  --   subscriptions is a table containing the current subscription data
+  -- Return value:
+  --   true if the proxy notification is valid
+  --   false if the proxy notification is not valid
+  function isValidNotification(notifyAction, sid, subscriptions)
+    local valid = false
+    if (proxyVersionAtLeast(1) == false) then
+        warning("Call to " .. notifyAction .. " while proxy is not used")
+    else
+        for i,subscription in ipairs(subscriptions) do
+            if (subscription.id == sid) then
+                valid = true
+                break
+            end
+        end
+        if (not valid) then
+            warning("Call to " .. notifyAction .. " with bad SID " .. sid)
+        end
+    end
+    if (not valid) then
+        -- Try to shut the proxy up, we don't care about this SID.
+        luup.call_delay("cancelProxySubscription", 1, sid)
+    end
+    return valid
+  end
Index: /tags/0.4/S_DLNAMediaController1.xml
===================================================================
--- /tags/0.4/S_DLNAMediaController1.xml	(revision 30)
+++ /tags/0.4/S_DLNAMediaController1.xml	(revision 30)
@@ -0,0 +1,193 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_URI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Volume</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <maximum>100</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Duration</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Text</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Language</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Name</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_IP</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ObjectID</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Protocol</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_LastChange</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <action>
+      <name>Play</name>
+      <argumentList>
+        <argument>
+          <name>URI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Protocol</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Protocol</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Volume</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Volume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Duration</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Duration</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Say</name>
+      <argumentList>
+        <argument>
+          <name>Text</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Text</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Language</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Language</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Volume</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Volume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SavePlaybackContext</name>
+    </action>
+    <action>
+      <name>RestorePlaybackContext</name>
+    </action>
+    <action>
+      <name>StartDMRDiscovery</name>
+    </action>
+    <action>
+      <name>SelectDMRDevice</name>
+      <argumentList>
+        <argument>
+          <name>URL</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SearchAndSelectDMRDevice</name>
+      <argumentList>
+        <argument>
+          <name>Name</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Name</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>IP</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_IP</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>StartDMSDiscovery</name>
+    </action>
+    <action>
+      <name>BrowseDMS</name>
+      <argumentList>
+        <argument>
+          <name>DescriptionURL</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>PlayDMSMedia</name>
+      <argumentList>
+        <argument>
+          <name>DescriptionURL</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Protocol</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Protocol</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>NotifyRenderingChange</name>
+      <argumentList>
+        <argument>
+          <name>LastChange</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_LastChange</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>NotifyAVTransportChange</name>
+      <argumentList>
+        <argument>
+          <name>LastChange</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_LastChange</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/0.4/D_DLNAMediaController1.xml
===================================================================
--- /tags/0.4/D_DLNAMediaController1.xml	(revision 30)
+++ /tags/0.4/D_DLNAMediaController1.xml	(revision 30)
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <friendlyName>DLNA DMC</friendlyName>
+    <manufacturer>Digital Living Network Alliance</manufacturer>
+    <manufacturerURL>http://www.dlna.org</manufacturerURL>
+    <modelDescription>DLNA Digital Media Controller</modelDescription>
+
+    <deviceType>urn:dlna-org:device:DLNAMediaController:1</deviceType>
+	<staticJson>D_DLNAMediaController1.json</staticJson>
+
+    <serviceList>
+      <service>
+        <serviceType>urn:dlna-org:service:DLNAMediaController:1</serviceType>
+        <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+        <SCPDURL>S_DLNAMediaController1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:micasaverde-com:service:MediaNavigation:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+        <SCPDURL>S_MediaNavigation1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:micasaverde-com:service:Volume:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
+        <SCPDURL>S_Volume1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+        <SCPDURL>S_AVTransport1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+        <SCPDURL>S_RenderingControl1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:schemas-micasaverde-com:service:HaDevice:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+        <SCPDURL>S_HaDevice1.xml</SCPDURL>
+      </service>
+
+    </serviceList>
+
+    <implementationList>
+      <implementationFile>I_DLNAMediaController1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/0.4/J_DLNAMediaController1.js
===================================================================
--- /tags/0.4/J_DLNAMediaController1.js	(revision 30)
+++ /tags/0.4/J_DLNAMediaController1.js	(revision 30)
@@ -0,0 +1,1124 @@
+var DLNA_timeoutVar = undefined;
+var DLNA_timeoutVar2 = undefined;
+var DLNA_browserIE = false
+var DLNA_parseXml = undefined;
+
+var DLNA_prevControls = undefined;
+var DLNA_prevOnlineState = undefined;
+var DLNA_prevCurrentAlbumArtUrl = undefined;
+var DLNA_prevTitle = undefined;
+var DLNA_prevAlbum = undefined;
+var DLNA_prevArtist = undefined;
+var DLNA_prevDuration = undefined;
+var DLNA_prevPlaying = undefined;
+var DLNA_prevActions = undefined;
+var DLNA_prevMute = undefined;
+var DLNA_prevVolume = undefined;
+
+var DLNA_prevModelName = undefined;
+var DLNA_prevUrl = undefined;
+var DLNA_prevOnlineState2 = undefined;
+var DLNA_prevProxy = undefined;
+var DLNA_prevResultDiscovery = undefined;
+var DLNA_prevResultDiscovery2 = undefined;
+var DLNA_prevResultBrowse = undefined;
+var DLNA_prevSinkProtocols = undefined;
+var DLNA_prevResultPlayback = undefined;
+
+var DLNA_mediaObjects = new Array();
+var DLNA_selectedMedia = undefined;
+var DLNA_loading = false;
+var DLNA_lastSelectedDMS = '';
+var DLNA_lastPlayedMediaObject = '';
+var DLNA_lastUsedProtocol = '';
+
+var DLNA_DMC_SID = 'urn:dlna-org:serviceId:DLNAMediaController1';
+var AVTRANSPORT_SID = 'urn:upnp-org:serviceId:AVTransport';
+var RENDERING_CONTROL_SID = 'urn:upnp-org:serviceId:RenderingControl';
+var MEDIA_NAVIGATION_SID = 'urn:micasaverde-com:serviceId:MediaNavigation1';
+var VOLUME_SID = 'urn:micasaverde-com:serviceId:Volume1';
+var UPNP_CONNECTION_MANAGER_SID = 'urn:upnp-org:serviceId:ConnectionManager';
+
+function DLNA_showPlayer(device)
+{
+	if (typeof DLNA_timeoutVar != 'undefined') {
+		clearTimeout(DLNA_timeoutVar);
+	}
+
+	DLNA_detectBrowser();
+
+	DLNA_initXMLParser();
+
+	var html = '';
+
+	html += '<table>';
+	html += '<tr>';
+	html += '<td rowspan=4><img id="albumArt" width="100" height="100"/></td>';
+	html += '<td id="artistLabel">Artist:</td>';
+	html += '<td id="artist"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td id="albumLabel">Album:</td>';
+	html += '<td id="album"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td id="titleLabel">Track title:</td>';
+	html += '<td id="title"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td id="durationLabel"></td>';
+	html += '<td id="duration"></td>';
+	html += '</tr>';
+	html += '</table>';
+	html += '<DIV>'
+	html += '<table>';
+	html += '<tr>';
+	html += '<td>';
+	html += '<button id="prevTrack" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_prevTrack('+device+');">Prev</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="play" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_play('+device+');">Play</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="pause" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_pause('+device+');">Pause</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="stop" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_stop('+device+');">Stop</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="nextTrack" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_nextTrack('+device+');">Next</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="mute" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_mute('+device+');">Mute</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<input id="controls" type="checkbox" checked value="1">Buttons state controlled by Renderer';
+	html += '</td>';
+	html += '</tr>';
+	html += '</table>';
+	html += '</DIV>'
+	html += '<DIV>'
+	html += '<table>';
+	html += '<tr>';
+	html += '<td>URI:</td>';
+	html += '<td>';
+	html += '<input id="uri" type="text" style="margin-right: 15px; width: 227px"/>';
+	html += 'Duration:';
+	html += '<input id="duration1" type="text" value="" style="margin-left: 5px; margin-right: 16px; width: 35px"/>';
+	html += 'Volume:';
+	html += '<input id="volume1" type="text" value="" style="margin-left: 5px; width: 20px"/>';
+	html += '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Protocol:</td>';
+	html += '<td>';
+	html += '<select id="sinkProtocols"/>';
+	html += '<button id="playUri" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_playUri('+device+');">Play</button>';
+	html += '</td>';
+	html += '</tr>';
+
+	var languages = [	[ "en", "English" ],
+						[ "fr", "French" ],
+						[ "it", "Italian" ],
+						[ "es", "Spanish" ],
+						[ "ru", 'Russian' ] ];
+	var defaultLanguage = get_device_state(device, DLNA_DMC_SID, "DefaultLanguageTTS", 1);
+	if (defaultLanguage == undefined) {
+		defaultLanguage = 'en';
+	}
+
+	html += '<tr>';
+	html += '<td>Text To Speech:</td>';
+	html += '<td>';
+	html += '<input id="text" type="text" style="margin-right: 10px; width: 227px"/>';
+	html += '<select id="language1" style="margin-right: 5px;" onChange="DLNA_selectLang();">';
+	for (i=0; i<languages.length; i++) {
+		html += '<option';
+		if (languages[i][0] == defaultLanguage) {
+			html += ' selected';
+		}
+		html += ' value="' + languages[i][0] + '">' + languages[i][1] + '</option>';
+	}
+	html += '</select>';
+	html += '<input id="language" type="text" value="' + defaultLanguage + '" style="margin-right: 10px; width: 20px"/>';
+	html += 'Volume:';
+	html += '<input id="volume" type="text" value="" style="margin-left: 5px; width: 20px"/>';
+	html += '<button id="say" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_say('+device+');">Say</button>';
+	html += '</td>';
+	html += '</tr>';
+	html += '<td>Media:</td>';
+	html += '<td><select id="media" onChange="DLNA_selectMedia();"/></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Protocol:</td>';
+	html += '<td><select id="protocols"/></td>';
+	html += '</tr>';
+	html += '</table>';
+	html += '<table>';
+	html += '<tr>';
+	html += '<td>';
+	html += '<button id="discover" type="button" style="background-color:lightgrey; height: 25px; width: 150px" onclick="DLNA_startDMSDiscovery('+device+');">Discover Servers</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="browse" type="button" style="background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_browseMedia('+device+');">Browse</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="back" type="button" style="background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_browseBack('+device+');">Back</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="top" type="button" style="background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_browseTop('+device+');">Top</button>';
+	html += '</td>';
+	html += '<td><label id="status"/></td>';
+	html += '</tr>';
+	html += '</table>';
+	html += '</DIV>'
+	
+	//html += '<p id="debug">';
+
+	set_panel_html(html);
+
+	DLNA_prevControls = undefined;
+	DLNA_prevOnlineState = undefined;
+	DLNA_prevCurrentAlbumArtUrl = undefined;
+	DLNA_prevTitle = undefined;
+	DLNA_prevAlbum = undefined;
+	DLNA_prevArtist = undefined;
+	DLNA_prevDuration = undefined;
+	DLNA_prevPlaying = undefined;
+	DLNA_prevActions = undefined;
+	DLNA_prevMute = undefined;
+	DLNA_prevVolume = undefined;
+	DLNA_prevResultDiscovery = undefined;
+	DLNA_prevResultBrowse = undefined;
+	DLNA_prevSinkProtocols = undefined;
+	DLNA_prevResultPlayback = undefined;
+
+	DLNA_refreshPlayer(device);
+}
+
+function DLNA_showSettings(device)
+{
+	if (typeof DLNA_timeoutVar2 != 'undefined') {
+		clearTimeout(DLNA_timeoutVar2);
+	}
+
+	DLNA_detectBrowser();
+
+	DLNA_initXMLParser();
+
+	var html = '';
+
+	html += '<table cellspacing="10">';
+	html += '<tr><td colspan=2><u>Current settings</u></td></tr>';
+	html += '<tr>';
+	html += '<td>DMR model:</td>';
+	html += '<td id="model"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Description URL:</td>';
+	html += '<td id="url"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>State:</td>';
+	html += '<td>';
+	html += '<label id="state"/>';
+	html += '<button id="checkState" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_checkState('+device+');">Check</button>';
+	html += '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>UPnP Event proxy:</td>';
+	html += '<td id="proxy"></td>';
+	html += '</tr>';
+	html += '<tr><td colspan=2><u>New settings</u></td></tr>';
+	html += '<tr><td colspan=2>Push "discover" button, then select your Media Renderer in the list and finally push "Select" button.';
+	html += '<BR>If UPnP discovery is not working, fill in your DMR description URL and finally push "Select" button.</td></tr>';
+	html += '<tr>';
+	html += '<td>';
+	html += '<button id="DMRdiscover" type="button" style="background-color:lightgrey; height: 25px; width: 90px" onclick="DLNA_startDMRDiscovery('+device+');">Discover</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<select id="DMRdiscovery"/>';
+	html += '<button id="selectDMRDiscovery" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_selectDMRDiscovery('+device+');">Select</button>';
+	html += '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>New URL:</td>';
+	html += '<td>';
+	html += '<input id="newURL" type="text" style="width: 380px"/>';
+	html += '<button id="selectURL" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_selectURL('+device+');">Select</button>';
+	html += '</td>';
+	html += '</tr>';
+	html += '</table>';
+
+	//html += '<p id="debug">';
+
+	set_panel_html(html);
+
+	DLNA_prevModelName = undefined;
+	DLNA_prevUrl = undefined;
+	DLNA_prevOnlineState2 = undefined;
+	DLNA_prevProxy = undefined;
+	DLNA_prevResultDiscovery2 = undefined;
+
+	DLNA_refreshDiscovery(device);
+}
+
+function DLNA_showHelp(device)
+{
+	var html = '<table cellspacing="10">';
+	html += '<tr>';
+	html += '<td>DMS description URL:</td>';
+	html += '<td>' + DLNA_lastSelectedDMS + '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Last server media played:</td>';
+	html += '<td>' + DLNA_lastPlayedMediaObject + '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Selected protocol:</td>';
+	html += '<td>' + DLNA_lastUsedProtocol + '</td>';
+	html += '</tr>';
+	html += '</table>';
+
+	var variables = [	[ AVTRANSPORT_SID, "AVTransportURI" ],
+						[ AVTRANSPORT_SID, "AVTransportURIMetaData" ],
+						[ AVTRANSPORT_SID, "CurrentTrackURI" ],
+						[ AVTRANSPORT_SID, "CurrentTrackMetaData" ],
+						[ UPNP_CONNECTION_MANAGER_SID, 'SinkProtocolInfo' ] ];
+	html += '<table border="1">';
+	html += '<tr align="center" bgcolor="LightBlue">';
+	html += '<th>Variable</td>';
+	html += '<th>Value</td>';
+	html += '</tr>';
+	for (i=0; i<variables.length; i++) {
+		var value = get_device_state(device, variables[i][0], variables[i][1], 1);
+		if (value == undefined) {
+			value = '';
+		}
+		html += '<tr>';
+		html += '<td>' + variables[i][1] + '</td>';
+		html += '<td>' + DLNA_escapeHtmlSpecialChars(value) + '</td>';
+		html += '</tr>';
+	}
+	html += '</table>';
+
+	html += '<BR>';
+
+	set_panel_html(html);
+}
+
+function DLNA_detectBrowser()
+{
+	var place = navigator.userAgent.toLowerCase().indexOf('msie');
+	if (place == -1) {
+		DLNA_browserIE = false;
+	}
+	else {
+		DLNA_browserIE = true;
+	}
+}
+
+function DLNA_initXMLParser()
+{
+	if (typeof DLNA_parseXml == 'undefined') {
+		if (typeof window.DOMParser != "undefined") {
+			DLNA_parseXml = function(xmlStr) {
+				return ( new window.DOMParser() ).parseFromString(xmlStr, "text/xml");
+			};
+		}
+		else if (typeof window.ActiveXObject != "undefined" && new window.ActiveXObject("Microsoft.XMLDOM")) {
+			DLNA_parseXml = function(xmlStr) {
+				var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
+				xmlDoc.async = "false";
+				xmlDoc.loadXML(xmlStr);
+				return xmlDoc;
+			};
+		}
+		else {
+			DLNA_parseXml = function(xmlStr) {
+				return undefined;
+			};
+		}
+	}
+}
+
+function DLNA_extractXmlTag(parent, tag)
+{
+	var value = undefined;
+	for (j=0; j<parent.childNodes.length; j++) {
+		if (parent.childNodes[j].tagName == tag) {
+			value = parent.childNodes[j].textContent;
+			break;
+		}
+	}
+	return value;
+}
+
+function DLNA_extractXmlAttribute(node, attribute)
+{
+	var value = node.getAttribute(attribute);
+	return value;
+}
+
+function DLNA_escapeHtmlSpecialChars(unsafe)
+{
+	return unsafe
+			.replace(/&/g, "&amp;")
+			.replace(/</g, "&lt;")
+			.replace(/>/g, "&gt;")
+			.replace(/"/g, "&quot;");
+}
+
+function DLNA_refreshPlayer(device)
+{
+	var controls = $('controls').checked;
+	var onlineState = get_device_state(device, DLNA_DMC_SID, "Online", 1);
+	if (onlineState == undefined) {
+		onlineState = '1';
+	}
+	var currentAlbumArtUrl = get_device_state(device, AVTRANSPORT_SID, "CurrentAlbumArt2", 1);
+	if (currentAlbumArtUrl == undefined) {
+		currentAlbumArtUrl = '';
+	}
+	var title = get_device_state(device, AVTRANSPORT_SID, "CurrentTitle", 1);
+	if (title == undefined) {
+		title = '';
+	}
+	var album = get_device_state(device, AVTRANSPORT_SID, "CurrentAlbum", 1);
+	if (album == undefined) {
+		album = '';
+	}
+	var artist = get_device_state(device, AVTRANSPORT_SID, "CurrentArtist", 1);
+	if (artist == undefined) {
+		artist = '';
+	}
+	var duration = get_device_state(device, AVTRANSPORT_SID, "CurrentTrackDuration", 1);
+	if (duration == undefined || duration == 'NOT_IMPLEMENTED'
+			|| duration == '0:00:00' || duration == '00:00:00') {
+		duration = '';
+	}
+	var playing = get_device_state(device, AVTRANSPORT_SID, "TransportState", 1);
+	if (playing == undefined) {
+		playing = '';
+	}
+	var actions = get_device_state(device, AVTRANSPORT_SID, "CurrentTransportActions", 1);
+	if (actions == undefined) {
+		actions = '';
+	}
+	actions = actions.toLowerCase();
+	var mute = get_device_state(device, RENDERING_CONTROL_SID, "Mute", 1);
+	if (mute == undefined || mute == "") {
+		mute = '0';
+	}
+	var volume = get_device_state(device, RENDERING_CONTROL_SID, "Volume", 1);
+	if (volume == undefined || volume == "") {
+		volume = 0;
+	}
+	var resultDiscovery = get_device_state(device, DLNA_DMC_SID, "DMSDiscoveryResult", 1);
+	if (resultDiscovery == undefined) {
+		resultDiscovery = '';
+	}
+	var resultBrowse = get_device_state(device, DLNA_DMC_SID, "BrowseResult", 1);
+	if (resultBrowse == undefined) {
+		resultBrowse = '';
+	}
+	var sinkProtocols = get_device_state(device, UPNP_CONNECTION_MANAGER_SID, "SinkProtocolInfo", 1);
+	if (sinkProtocols == undefined) {
+		sinkProtocols = '';
+	}
+	var resultPlayback = get_device_state(device, DLNA_DMC_SID, "PlaybackResult", 1);
+	if (resultPlayback == undefined) {
+		resultPlayback = '';
+	}
+
+	if (controls != DLNA_prevControls
+		|| onlineState != DLNA_prevOnlineState
+		|| currentAlbumArtUrl != DLNA_prevCurrentAlbumArtUrl
+		|| title != DLNA_prevTitle
+		|| album != DLNA_prevAlbum
+		|| artist != DLNA_prevArtist
+		|| duration != DLNA_prevDuration
+		|| playing != DLNA_prevPlaying
+		|| actions != DLNA_prevActions
+		|| mute != DLNA_prevMute
+		|| volume != DLNA_prevVolume
+		|| resultDiscovery != DLNA_prevResultDiscovery
+		|| resultBrowse != DLNA_prevResultBrowse
+		|| sinkProtocols != DLNA_prevSinkProtocols
+		|| resultPlayback != DLNA_prevResultPlayback) {
+
+		$('albumArt').src = currentAlbumArtUrl;
+
+		var label;
+
+		if (title != '') {
+			label = 'Title:';
+		}
+		else {
+			label = '';
+		}
+		$('titleLabel').innerHTML = label;
+		$('title').innerHTML = title;
+
+		if (album != '') {
+			label = 'Album:';
+		}
+		else {
+			label = '';
+		}
+		$('albumLabel').innerHTML = label;
+		$('album').innerHTML = album;
+
+		if (artist != '') {
+			label = 'Artist:';
+		}
+		else {
+			label = '';
+		}
+		$('artistLabel').innerHTML = label;
+		$('artist').innerHTML = artist;
+
+		if (duration != '') {
+			label = 'Duration:';
+		}
+		else {
+			label = '';
+		}
+		$('durationLabel').innerHTML = label;
+		$('duration').innerHTML = duration;
+
+		if (onlineState != '1') {
+			$('titleLabel').innerHTML = 'Offline';
+			$('title').innerHTML = '';
+			$('play').style.backgroundColor = 'lightgrey';
+			$('pause').style.backgroundColor = 'lightgrey';
+			$('stop').style.backgroundColor = 'lightgrey';
+			$('play').disabled = true;
+			$('pause').disabled = true;
+			$('stop').disabled = true;
+			$('prevTrack').disabled = true;
+			$('nextTrack').disabled = true;
+			$('controls').disabled = true;
+		}
+		else {
+			$('controls').disabled = false;
+			if (playing == 'PLAYING' || playing == 'TRANSITIONING') {
+				$('play').style.backgroundColor = 'darkgray';
+				$('pause').style.backgroundColor = 'lightgrey';
+				$('stop').style.backgroundColor = 'lightgrey';
+			}
+			else if (playing == 'PAUSED_PLAYBACK') {
+				$('play').style.backgroundColor = 'lightgrey';
+				$('pause').style.backgroundColor = 'darkgray';
+				$('stop').style.backgroundColor = 'lightgrey';
+			}
+			else if (playing == 'STOPPED') {
+				$('play').style.backgroundColor = 'lightgrey';
+				$('pause').style.backgroundColor = 'lightgrey';
+				$('stop').style.backgroundColor = 'darkgray';
+			}
+			else {
+				$('play').style.backgroundColor = 'lightgrey';
+				$('pause').style.backgroundColor = 'lightgrey';
+				$('stop').style.backgroundColor = 'lightgrey';
+			}
+
+			if (controls == false) {
+				$('play').disabled = false;
+				$('pause').disabled = false;
+				$('stop').disabled = false;
+				$('prevTrack').disabled = false;
+				$('nextTrack').disabled = false;
+			}
+			else {
+				if (actions.indexOf('play', 0) >= 0) {
+					$('play').disabled = false;
+				}
+				else {
+					$('play').disabled = true;
+				}
+				if (actions.indexOf('pause', 0) >= 0) {
+					$('pause').disabled = false;
+				}
+				else {
+					$('pause').disabled = true;
+				}
+				if (actions.indexOf('stop', 0) >= 0) {
+					$('stop').disabled = false;
+				}
+				else {
+					$('stop').disabled = true;
+				}
+				if (actions.indexOf('previous', 0) < 0) {
+					$('prevTrack').disabled = true;
+				}
+				else {
+					$('prevTrack').disabled = false;
+				}
+				if (actions.indexOf('next', 0) < 0) {
+					$('nextTrack').disabled = true;
+				}
+				else {
+					$('nextTrack').disabled = false;
+				}
+			}
+		}
+
+		if (mute == '1' || mute == 'true' || mute == 'yes') {
+			$('mute').style.backgroundColor = 'darkgray';
+		}
+		else {
+			$('mute').style.backgroundColor = 'lightgrey';
+		}
+
+		if (onlineState == '1') {
+			$('mute').disabled = false;
+			$('playUri').disabled = false;
+			$('say').disabled = false;
+		}
+		else {
+			$('mute').disabled = true;
+			$('playUri').disabled = true;
+			$('say').disabled = true;
+		}
+
+		if (sinkProtocols != DLNA_prevSinkProtocols) {
+			var html = '';
+			var values;
+			var protocol;
+			html = '<option selected value="">None</option>';
+			values = sinkProtocols.split(',');
+			for (i=0; i<values.length; i++) {
+				if (values[i].length > 50) {
+					protocol = values[i].substr(0, 50) + '...';
+				}
+				else {
+					protocol = values[i];
+				}
+				html += '<option value="' + values[i] + '">' + protocol + '</option>';
+			}
+			$('sinkProtocols').innerHTML = html;
+			if (DLNA_browserIE) {
+				$('sinkProtocols').outerHTML=$('sinkProtocols').outerHTML.replace($('sinkProtocols').innerHTML+'</select>',html+'</select>');
+			}
+		}
+
+		if (resultDiscovery != DLNA_prevResultDiscovery
+			|| resultBrowse != DLNA_prevResultBrowse) {
+
+			if (resultBrowse == '') {
+				DLNA_selectedMedia = undefined;
+				DLNA_mediaObjects = new Array();
+			}
+
+			if (DLNA_mediaObjects.length == 0) {
+				if (resultDiscovery == 'scanning') {
+					DLNA_loading = true;
+					$('media').innerHTML = '';
+					$('discover').disabled = true;
+					$('browse').disabled = true;
+					$('back').disabled = true;
+					$('top').disabled = true;
+				}
+				else if (resultDiscovery == '') {
+					DLNA_loading = false;
+					$('media').innerHTML = '';
+					$('discover').disabled = false;
+					$('browse').disabled = true;
+					$('back').disabled = true;
+					$('top').disabled = true;
+				}
+				else {
+					$('discover').disabled = false;
+					$('browse').disabled = false;
+					$('back').disabled = false;
+					$('top').disabled = false;
+
+					var html = '';
+					var xml = DLNA_parseXml(resultDiscovery);
+					if (typeof xml != 'undefined') {
+						var items = xml.getElementsByTagName("device");
+						for (i=0; i<items.length; i++) {
+							url = DLNA_extractXmlTag(items[i], 'descriptionURL');
+							if (url == undefined) {
+								url = '';
+							}
+							modelName = DLNA_extractXmlTag(items[i], 'friendlyName');
+							if (modelName == undefined) {
+								modelName = '';
+							}
+							// var ip = DLNA_extractXmlTag(items[i], 'ip');
+							// if (ip == undefined) {
+								// ip = '';
+							// }
+							url = 'server:' + url;
+							html += '<option ';
+							if (url == DLNA_selectedMedia) {
+								html += 'selected ';
+							}
+							// html += 'value="' + url + '">' + modelName + ' (IP ' + ip + ')</option>';
+							html += 'value="' + url + '">' + modelName + '</option>';
+						}
+					}
+					$('media').innerHTML = html;
+					if (DLNA_browserIE) {
+						$('media').outerHTML=$('media').outerHTML.replace($('media').innerHTML+'</select>',html+'</select>');
+					}
+					DLNA_loading = false;
+				}
+			}
+			else if (resultBrowse == 'loading') {
+				DLNA_disableDMSCmds();
+			}
+			else {
+				$('discover').disabled = false;
+				$('browse').disabled = false;
+				$('back').disabled = false;
+				$('top').disabled = false;
+
+				var html = '';
+				var xml = DLNA_parseXml(resultBrowse);
+				if (typeof xml != 'undefined') {
+					var title2;
+					var id;
+					var containers = xml.getElementsByTagName("container");
+					for (i=0; i<containers.length; i++) {
+						title2 = DLNA_extractXmlTag(containers[i], 'dc:title');
+						if (title2 == undefined) {
+							title2 = '';
+						}
+						if (title2.length > 60) {
+							title2 = title2.substr(0, 60) + '...';
+						}
+						id = DLNA_extractXmlAttribute(containers[i], 'id');
+						if (id == undefined) {
+							id = '';
+						}
+						id = 'container:' + id;
+						html += '<option ';
+						if (id == DLNA_selectedMedia) {
+							html += 'selected ';
+						}
+						html += 'value="' + id + '">+ ' + title2 + '</option>';
+					}
+					var items = xml.getElementsByTagName("item");
+					for (i=0; i<items.length; i++) {
+						title2 = DLNA_extractXmlTag(items[i], 'dc:title');
+						if (title2 == undefined) {
+							title2 = '';
+						}
+						if (title2.length > 60) {
+							title2 = title2.substr(0, 60) + '...';
+						}
+						id = DLNA_extractXmlAttribute(items[i], 'id');
+						if (id == undefined) {
+							id = '';
+						}
+						id = 'item:' + id;
+						html += '<option ';
+						if (id == DLNA_selectedMedia) {
+							html += 'selected ';
+						}
+						html += 'value="' + id + '">' + title2 + '</option>';
+					}
+				}
+				$('media').innerHTML = html;
+				if (DLNA_browserIE) {
+					$('media').outerHTML=$('media').outerHTML.replace($('media').innerHTML+'</select>',html+'</select>');
+				}
+				DLNA_loading = false;
+			}
+			DLNA_prevResultBrowse = resultBrowse;
+			DLNA_prevOnlineState = onlineState;
+			DLNA_selectMedia();
+		}
+
+		if (DLNA_loading == false) {
+			$('status').innerHTML = resultPlayback;
+			if (resultPlayback == 'Media OK' || resultPlayback == 'Done') {
+				$('status').style.color = "green";
+			}
+			else {
+				$('status').style.color = "red";
+			}
+		}
+
+		DLNA_prevControls = controls;
+		DLNA_prevOnlineState = onlineState;
+		DLNA_prevCurrentAlbumArtUrl = currentAlbumArtUrl;
+		DLNA_prevTitle = title;
+		DLNA_prevAlbum = album;
+		DLNA_prevArtist = artist;
+		DLNA_prevDuration = duration;
+		DLNA_prevPlaying = playing;
+		DLNA_prevActions = actions;
+		DLNA_prevMute = mute;
+		DLNA_prevVolume = volume;
+		DLNA_prevResultDiscovery = resultDiscovery;
+		DLNA_prevResultBrowse = resultBrowse;
+		DLNA_prevSinkProtocols = sinkProtocols;
+		DLNA_prevResultPlayback = resultPlayback;
+	}
+
+	DLNA_timeoutVar = setTimeout("DLNA_refreshPlayer("+device+")", 1000);
+}
+
+function DLNA_refreshDiscovery(device)
+{
+	var modelName = get_device_state(device, DLNA_DMC_SID, "ModelName", 1);
+	if (modelName == undefined) {
+		modelName = '';
+	}
+	var url = get_device_state(device, DLNA_DMC_SID, "DescriptionURL", 1);
+	if (url == undefined) {
+		url = '';
+	}
+	var onlineState = get_device_state(device, DLNA_DMC_SID, "Online", 1);
+	if (onlineState == '1') {
+		onlineState = 'ON';
+	}
+	else {
+		onlineState = 'OFF';
+	}
+	var proxy = get_device_state(device, DLNA_DMC_SID, "ProxyUsed", 1);
+	if (proxy == undefined) {
+		proxy = '';
+	}
+	var resultDiscovery = get_device_state(device, DLNA_DMC_SID, "DMRDiscoveryResult", 1);
+	if (resultDiscovery == undefined) {
+		resultDiscovery = '';
+	}
+	if (modelName != DLNA_prevModelName
+		|| url != DLNA_prevUrl
+		|| onlineState != DLNA_prevOnlineState2
+		|| proxy != DLNA_prevProxy
+		|| resultDiscovery != DLNA_prevResultDiscovery2) {
+
+		DLNA_prevModelName = modelName;
+		DLNA_prevUrl = url;
+		DLNA_prevOnlineState2 = onlineState;
+		DLNA_prevProxy = proxy;
+		DLNA_prevResultDiscovery2 = resultDiscovery;
+
+		$('model').innerHTML = modelName;
+		$('url').innerHTML = url;
+		$('state').innerHTML = onlineState;
+		$('proxy').innerHTML = proxy;
+
+		if (resultDiscovery == 'scanning') {
+			$('DMRdiscover').disabled = true;
+			$('DMRdiscover').innerHTML = 'Scanning ...';
+			$('DMRdiscovery').innerHTML = '';
+			$('selectDMRDiscovery').disabled = true;
+		}
+		else if (resultDiscovery == "") {
+			$('DMRdiscover').disabled = false;
+			$('DMRdiscover').innerHTML = 'Discover';
+			$('selectDMRDiscovery').disabled = true;
+			$('DMRdiscovery').innerHTML = '';
+		}
+		else {
+			$('DMRdiscover').disabled = false;
+			$('DMRdiscover').innerHTML = 'Discover';
+			$('selectDMRDiscovery').disabled = false;
+
+			var html = '';
+			var xml = DLNA_parseXml(resultDiscovery);
+			if (typeof xml != 'undefined') {
+				var items = xml.getElementsByTagName("device");
+				for (i=0; i<items.length; i++) {
+					var descrUrl = DLNA_extractXmlTag(items[i], 'descriptionURL');
+					if (descrUrl == undefined) {
+						descrUrl = '';
+					}
+					var selected = '';
+					if (descrUrl == url) {
+						selected = ' selected';
+					}
+					var name = DLNA_extractXmlTag(items[i], 'friendlyName');
+					if (name == undefined) {
+						name = '';
+					}
+					html += '<option ' + selected + ' value="' + descrUrl + '">' + name + '</option>';
+				}
+			}
+			$('DMRdiscovery').innerHTML = html;
+			if (DLNA_browserIE) {
+				$('DMRdiscovery').outerHTML=$('DMRdiscovery').outerHTML.replace($('DMRdiscovery').innerHTML+'</select>',html+'</select>');
+			}
+		}
+	}
+
+	DLNA_timeoutVar2 = setTimeout("DLNA_refreshDiscovery("+device+")", 1000);
+}
+
+function DLNA_play(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'Play', {} );
+}
+
+function DLNA_pause(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'Pause', {} );
+}
+
+function DLNA_stop(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'Stop', {} );
+}
+
+function DLNA_prevTrack(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'SkipUp', {} );
+}
+
+function DLNA_nextTrack(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'SkipDown', {} );
+}
+
+function DLNA_mute(device)
+{
+	DLNA_callAction(device, RENDERING_CONTROL_SID, 'SetMute', {} );
+}
+
+function DLNA_playUri(device)
+{
+	var uri = encodeURIComponent($('uri').value);
+	var protocol = '';
+	if ($('sinkProtocols').selectedIndex >= 0) {
+		protocol = $('sinkProtocols').options[$('sinkProtocols').selectedIndex].value;
+	}
+	var duration = $('duration1').value;
+	var volume = $('volume1').value;
+	//$('debug').innerHTML = uri + ' ' + protocol + ' ' + duration + ' ' + volume;
+	if (uri != "") {
+		DLNA_callAction(device, DLNA_DMC_SID, 'Play', {'URI':uri, 'Protocol':protocol, 'Duration':duration, 'Volume':volume} );
+	}
+}
+
+function DLNA_selectLang()
+{
+	if ($('language1').selectedIndex >= 0) {
+		$('language').value = $('language1').options[$('language1').selectedIndex].value;
+	}
+}
+
+function DLNA_say(device)
+{
+	var text = $('text').value;
+	var lang = $('language').value;
+	var volume = $('volume').value;
+	//$('debug').innerHTML = text + ' ' + lang + ' ' + vol;
+	if (text != "") {
+		DLNA_callAction(device, DLNA_DMC_SID, 'Say', {'Text':text, 'Language':lang, 'Volume':volume} );
+	}
+}
+
+function DLNA_startDMRDiscovery(device)
+{
+	DLNA_callAction(device, DLNA_DMC_SID, 'StartDMRDiscovery', {} );
+}
+
+function DLNA_selectDMRDiscovery(device)
+{
+	if ($('DMRdiscovery').selectedIndex >= 0) {
+		var url = $('DMRdiscovery').options[$('DMRdiscovery').selectedIndex].value;
+		//$('debug').innerHTML = url;
+		DLNA_callAction(device, DLNA_DMC_SID, 'SelectDMRDevice', {'URL':url} );
+	}
+}
+
+function DLNA_selectURL(device)
+{
+	var url = $('newURL').value;
+	if (url != "") {
+		//$('debug').innerHTML = url;
+		DLNA_callAction(device, DLNA_DMC_SID, 'SelectDMRDevice', {'URL':url} );
+	}
+}
+
+function DLNA_checkState(device)
+{
+	var url = $('url').innerHTML;
+	if (url != "") {
+		//$('debug').innerHTML = url;
+		DLNA_callAction(device, DLNA_DMC_SID, 'SelectDMRDevice', {'URL':url} );
+	}
+}
+
+function DLNA_disableDMSCmds()
+{
+	DLNA_loading = true;
+	$('media').innerHTML = '';
+	$('protocols').innerHTML = '';
+	$('status').innerHTML = 'Loading...';
+	$('status').style.color = "red";
+	$('discover').disabled = true;
+	$('browse').disabled = true;
+	$('back').disabled = true;
+	$('top').disabled = true;
+}
+
+function DLNA_startDMSDiscovery(device)
+{
+	DLNA_selectedMedia = undefined;
+	DLNA_mediaObjects = new Array();
+	DLNA_disableDMSCmds();
+	DLNA_callAction(device, DLNA_DMC_SID, 'StartDMSDiscovery', {} );
+}
+
+function DLNA_browseMedia(device)
+{
+	var value;
+	var url;
+	var id;
+	var protocol;
+	if ($('media').selectedIndex >= 0) {
+		value = $('media').options[$('media').selectedIndex].value;
+		if (value.indexOf('server:') == 0) {
+			url = value.substr(7);
+			id = '0';
+			DLNA_mediaObjects.push(id);
+			DLNA_disableDMSCmds();
+			DLNA_lastSelectedDMS = url;
+			DLNA_lastPlayedMediaObject = '';
+			DLNA_lastUsedProtocol = '';
+			url = encodeURIComponent(url);
+			DLNA_callAction(device, DLNA_DMC_SID, 'BrowseDMS', {'DescriptionURL':url, 'ObjectID':id} );
+		}
+		else if (value.indexOf('container:') == 0) {
+			id = value.substr(10);
+			DLNA_mediaObjects.push(id);
+			DLNA_disableDMSCmds();
+			id = encodeURIComponent(id);
+			DLNA_callAction(device, DLNA_DMC_SID, 'BrowseDMS', {'ObjectID':id} );
+		}
+		else if (value.indexOf('item:') == 0 && $('protocols').selectedIndex >= 0) {
+			id = value.substr(5);
+			protocol = $('protocols').options[$('protocols').selectedIndex].value;
+			DLNA_lastPlayedMediaObject = id;
+			DLNA_lastUsedProtocol = protocol;
+			id = encodeURIComponent(id);
+			DLNA_callAction(device, DLNA_DMC_SID, 'PlayDMSMedia', {'ObjectID':id, 'Protocol':protocol} );
+		}
+	}
+}
+
+function DLNA_browseTop(device)
+{
+	if (DLNA_mediaObjects.length > 0) {
+		DLNA_selectedMedia = undefined;
+		DLNA_mediaObjects = new Array();
+		DLNA_prevResultBrowse = undefined;
+		DLNA_disableDMSCmds();
+	}
+}
+
+function DLNA_browseBack(device)
+{
+	var id;
+	if (DLNA_mediaObjects.length > 0) {
+		DLNA_selectedMedia = undefined;
+		DLNA_mediaObjects.pop();
+		if (DLNA_mediaObjects.length == 0) {
+			DLNA_prevResultBrowse = undefined;
+			DLNA_disableDMSCmds();
+		}
+		else {
+			id = encodeURIComponent(DLNA_mediaObjects[DLNA_mediaObjects.length-1]);
+			DLNA_disableDMSCmds();
+			DLNA_callAction(device, DLNA_DMC_SID, 'BrowseDMS', {'ObjectID':id} );
+		}
+	}
+}
+
+function DLNA_selectMedia()
+{
+    var html = '';
+	var buttonLabel = 'Browse';
+	var buttonDisabled = false;
+	var value;
+	if ($('media').selectedIndex >= 0) {
+		DLNA_selectedMedia = $('media').options[$('media').selectedIndex].value;
+		if (DLNA_selectedMedia.indexOf('item:') == 0) {
+			buttonLabel = 'Play';
+			if (DLNA_prevOnlineState == '1') {
+				buttonDisabled = false;
+			}
+			else {
+				buttonDisabled = true;
+			}
+			value = DLNA_selectedMedia.substr(5);
+			//$('debug').innerHTML = value;
+			html = '<option selected value="AUTO">Automatic</option>';
+			html += '<option value="">None</option>';
+			var xml = DLNA_parseXml(DLNA_prevResultBrowse);
+			if (typeof xml != 'undefined') {
+				var id;
+				var protocol;
+				var protocol2;
+				var items = xml.getElementsByTagName("item");
+				for (i=0; i<items.length; i++) {
+					id = DLNA_extractXmlAttribute(items[i], 'id');
+					if (id == value) {
+						for (j=0; j<items[i].childNodes.length; j++) {
+							if (items[i].childNodes[j].tagName == 'res') {
+								protocol = items[i].childNodes[j].getAttribute('protocolInfo');
+								if (protocol != undefined) {
+									if (protocol.length > 60) {
+										protocol2 = protocol.substr(0, 60) + '...';
+									}
+									else {
+										protocol2 = protocol;
+									}
+									html += '<option value="' + protocol + '">' + protocol2 + '</option>';
+								}
+							}
+						}
+						break;
+					}
+				}
+			}
+		}
+	}
+	$('protocols').innerHTML = html;
+	if (DLNA_browserIE) {
+		$('protocols').outerHTML=$('protocols').outerHTML.replace($('protocols').innerHTML+'</select>',html+'</select>');
+	}
+	$('browse').innerHTML = buttonLabel;
+	if (DLNA_loading == false) {
+		$('browse').disabled = buttonDisabled;
+	}
+}
+
+function DLNA_callAction(device, sid, actname, args) {
+	var q={
+		'id':'lu_action',
+		'output_format':'xml',
+		'DeviceNum':device,
+		'serviceId':sid,
+		'action':actname
+	};
+	var key, count = 0;
+	for(key in args) {
+		count++;
+	}
+	if (count > 0) {
+		Object.extend(q, args);
+	}
+    if (DLNA_browserIE) {
+    	Object.extend(q, {timestamp: new Date().getTime()}); //we need this to avoid IE caching of the AJAX get
+    }
+	new Ajax.Request (command_url+'/data_request', {
+		method: 'get',
+		parameters: q,
+		onSuccess: function (response) {
+		},
+		onFailure: function (response) {
+		},
+		onComplete: function (response) {
+		}
+	});
+}
+
Index: /tags/0.5/I_DLNAMediaController1.xml
===================================================================
--- /tags/0.5/I_DLNAMediaController1.xml	(revision 30)
+++ /tags/0.5/I_DLNAMediaController1.xml	(revision 30)
@@ -0,0 +1,2098 @@
+<?xml version="1.0"?>
+<implementation>
+
+<functions>
+  local socket = require("socket")
+  local url = require("socket.url")
+
+  local PLUGIN_VERSION = "0.5"
+
+  local MSG_CLASS = "DLNA"
+
+  local DEBUG_MODE = false
+  local taskHandle = -1
+
+  local TASK_ERROR = 2
+  local TASK_ERROR_PERM = -2
+  local TASK_SUCCESS = 4
+  local TASK_BUSY = 1
+
+  local VERA_IP
+  local VERA_WEB_PORT
+
+  local UPNP_AVTRANSPORT_SERVICE = 'urn:schemas-upnp-org:service:AVTransport:1'
+  local UPNP_RENDERING_CONTROL_SERVICE = 'urn:schemas-upnp-org:service:RenderingControl:1'
+  local UPNP_CONNECTION_MANAGER_SERVICE = 'urn:schemas-upnp-org:service:ConnectionManager:1'
+  local UPNP_MR_CONTENT_DIRECTORY_SERVICE = 'urn:schemas-upnp-org:service:ContentDirectory:1'
+
+  local UPNP_AVTRANSPORT_SID = 'urn:upnp-org:serviceId:AVTransport'
+  local UPNP_RENDERING_CONTROL_SID = 'urn:upnp-org:serviceId:RenderingControl'
+  local UPNP_CONNECTION_MANAGER_SID = 'urn:upnp-org:serviceId:ConnectionManager'
+  local UPNP_MR_CONTENT_DIRECTORY_SID = 'urn:upnp-org:serviceId:ContentDirectory'
+
+  if (package.path:find("/etc/cmh-ludl/?.lua;/etc/cmh-lu/?.lua", 1, true) == nil) then
+      package.path = package.path..";/etc/cmh-ludl/?.lua;/etc/cmh-lu/?.lua"
+  end
+  package.loaded.L_DLNAUPnPUtility1 = nil
+
+  local upnp = require("L_DLNAUPnPUtility")
+
+  local ipDMR = nil
+  local portDMR
+  local DMRDescriptionURL
+  local iconURL
+  local uuidDMR
+
+  local ipDMS
+  local portDMS
+  local DMSDescriptionURL
+  local uuidDMS
+
+  local HADEVICE_SID = "urn:micasaverde-com:serviceId:HaDevice1"
+  local DLNA_DMC_SID = "urn:dlna-org:serviceId:DLNAMediaController1"
+  local DLNA_DMC_DEVICE_TYPE = "urn:dlna-org:device:DLNAMediaController:1"
+
+  local EventSubscriptions = {
+      { service = UPNP_AVTRANSPORT_SERVICE,
+        eventVariable = "LastChange",
+        actionName = "NotifyAVTransportChange",
+        id = "",
+        expiry = "" },
+      { service = UPNP_RENDERING_CONTROL_SERVICE,
+        eventVariable = "LastChange",
+        actionName = "NotifyRenderingChange",
+        id = "",
+        expiry = "" }
+  }
+
+  local PLUGIN_ICON = "http://%s:%d/cmh/skins/default/icons/DLNA.png"
+
+  local SAY_TMP_FILE = "/tmp/Say.%s.%s.mp3"
+  local SAY_OUTPUT_FILE = "/www/Say.%s.mp3"
+  local SAY_OUTPUT_URI = "http://%s:%d/Say.%s.mp3"
+  local DEFAULT_METADATA = '&lt;DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/">'
+                           .. '&lt;item id="DLNA" parentID="-1" restricted="1">'
+                           .. '&lt;dc:title>%s&lt;/dc:title>'
+                           .. '%s&lt;/item>&lt;/DIDL-Lite>'
+
+  local SAY_EXECUTE = "rm " .. SAY_TMP_FILE .. " ; wget --output-document " .. SAY_TMP_FILE ..[[ \
+ --quiet \
+ --header "Accept-Charset: utf-8;q=0.7,*;q=0.3" \
+ --header "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
+ --user-agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11" \
+ "http://translate.google.com/translate_tts?tl=%s&amp;q=%s"]]
+
+  local CONCAT_EXECUTE = "cat %s > " .. SAY_OUTPUT_FILE .. " ; rm %s"
+  local DELETE_EXECUTE = "rm " .. SAY_OUTPUT_FILE
+
+  local playbackCxt = nil
+  local sayPlayback = nil
+  local sayQueue = {}
+
+  local dataTable = {}
+
+  local variableSidTable = {
+        TransportState = UPNP_AVTRANSPORT_SID,
+        TransportStatus = UPNP_AVTRANSPORT_SID,
+        TransportPlaySpeed = UPNP_AVTRANSPORT_SID,
+        CurrentPlayMode = UPNP_AVTRANSPORT_SID,
+        CurrentTransportActions = UPNP_AVTRANSPORT_SID,
+        NumberOfTracks = UPNP_AVTRANSPORT_SID,
+        CurrentMediaDuration = UPNP_AVTRANSPORT_SID,
+        AVTransportURI = UPNP_AVTRANSPORT_SID,
+        AVTransportURIMetaData = UPNP_AVTRANSPORT_SID,
+        CurrentTrack = UPNP_AVTRANSPORT_SID,
+        CurrentTrackDuration = UPNP_AVTRANSPORT_SID,
+        CurrentTrackURI = UPNP_AVTRANSPORT_SID,
+        CurrentTrackMetaData = UPNP_AVTRANSPORT_SID,
+        CurrentStatus = UPNP_AVTRANSPORT_SID,
+        CurrentTitle = UPNP_AVTRANSPORT_SID,
+        CurrentArtist = UPNP_AVTRANSPORT_SID,
+        CurrentAlbum = UPNP_AVTRANSPORT_SID,
+        CurrentAlbumArt = UPNP_AVTRANSPORT_SID,
+        CurrentAlbumArt2 = UPNP_AVTRANSPORT_SID,
+        RelativeTimePosition = UPNP_AVTRANSPORT_SID,
+
+        Mute = UPNP_RENDERING_CONTROL_SID,
+        Volume = UPNP_RENDERING_CONTROL_SID,
+
+        SinkProtocolInfo = UPNP_CONNECTION_MANAGER_SID,
+
+        Online = DLNA_DMC_SID,
+        ModelName = DLNA_DMC_SID,
+
+        ProxyUsed = DLNA_DMC_SID
+  }
+
+  local function log(stuff, level)
+    luup.log(string.format("%s: %s", MSG_CLASS, stuff), (level or 50))
+  end
+
+  local function warning(stuff)
+    log("warning: " .. stuff, 2)
+  end
+
+  local function error(stuff)
+    log("error: " .. stuff, 1)
+  end
+
+  local function debug(stuff)
+    if (DEBUG_MODE) then
+      log("debug: " .. stuff)
+    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
+
+  local function defaultValue(arr, val, default)
+    if (arr == nil or arr[val] == nil or arr[val] == "") then
+      return default
+    else
+      return arr[val]
+    end
+  end
+
+  local function setData(name, value, deviceId, default)
+      dataTable[name] = value
+
+      if (variableSidTable[name] == nil) then
+          return (default or false)
+      end
+
+      local curValue = luup.variable_get(variableSidTable[name], name, deviceId)
+
+      if ((value ~= curValue) or (curValue == nil)) then
+          luup.variable_set(variableSidTable[name], name, value, deviceId)
+          return true
+      else
+          return (default or false)
+      end
+  end
+
+  --
+  -- Put together a rudimentary status string for the Dashboard
+  --
+  local function getSimpleDIDLStatus(meta)
+      local title = ""
+      local artist = ""
+      local album = ""
+      local albumArt = ""
+      local duration = ""
+      local didl = nil
+      local didlTable = nil
+      local complement = ""
+      if (meta ~= nil and meta ~= "") then
+          didl, didlTable = upnp.parseDIDLItem(meta)
+
+          if (didlTable["upnp:class"] == "object.item") then
+              title = upnp.decode(didlTable["dc:title"] or title)
+              if (didlTable["upnp:albumArtURI"] ~= nil) then
+                  albumArt = upnp.decode(didlTable["upnp:albumArtURI"])
+              end
+              duration = didlTable["res@duration"] or duration
+          elseif ((didlTable["upnp:class"] == "object.item.audioItem.musicTrack")
+                  or (didlTable["upnp:class"] == "object.item.audioItem")) then
+              title = upnp.decode(didlTable["dc:title"] or title)
+              if (didlTable["dc:creator"] ~= "Unknown") then
+                  artist = upnp.decode(didlTable["dc:creator"] or artist)
+              end
+              if (didlTable["upnp:album"] ~= "Unknown") then
+                  album = upnp.decode(didlTable["upnp:album"] or album)
+              end
+              if (didlTable["upnp:albumArtURI"] ~= nil) then
+                  albumArt = upnp.decode(didlTable["upnp:albumArtURI"])
+              end
+              if (artist ~= "" or album ~= "") then
+                  complement = string.format(" (%s, %s)", artist, album)
+              end
+              duration = didlTable["res@duration"] or duration
+          elseif (didlTable["upnp:class"] == "object.item.audioItem.audioBroadcast") then
+              title = upnp.decode(didlTable["dc:title"] or title)
+          elseif (didlTable["upnp:class"] == "object.item.videoItem") then
+              title = upnp.decode(didlTable["dc:title"] or title)
+              if (didlTable["upnp:albumArtURI"] ~= nil) then
+                  albumArt = upnp.decode(didlTable["upnp:albumArtURI"])
+              end
+              duration = didlTable["res@duration"] or duration
+          end
+          duration = duration:match("(%d+:%d+:%d+).-") or ""
+      end
+      return complement, title, artist, album, albumArt, duration, didl, didlTable
+  end
+
+  local function deviceIsOnline(device)
+    local changed = setData("Online", "1", device, false)
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+    return changed
+  end
+
+  local function deviceIsOffline(device)
+    local changed = setData("Online", "0", device, false)
+    if (changed) then
+        iconURL = PLUGIN_ICON:format(VERA_IP, VERA_WEB_PORT)
+
+        changed = setData("TransportState", "STOPPED",  device, changed)
+        changed = setData("TransportStatus", "KO",  device, changed)
+        changed = setData("TransportPlaySpeed", "1",  device, changed)
+        changed = setData("CurrentPlayMode", "NORMAL", device, changed)
+        changed = setData("CurrentTransportActions", "", device, changed)
+        changed = setData("NumberOfTracks", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("CurrentMediaDuration", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("AVTransportURI", "", device, changed)
+        changed = setData("AVTransportURIMetaData", "", device, changed)
+        changed = setData("CurrentTrack", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("CurrentTrackDuration", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("CurrentTrackURI", "", device, changed)
+        changed = setData("CurrentTrackMetaData", "", device, changed)
+        changed = setData("CurrentStatus", "Offline", device, changed)
+        changed = setData("CurrentTitle", "", device, changed)
+        changed = setData("CurrentArtist", "", device, changed)
+        changed = setData("CurrentAlbum", "", device, changed)
+        changed = setData("CurrentAlbumArt", iconURL, device, changed)
+        changed = setData("CurrentAlbumArt2", iconURL, device, changed)
+        changed = setData("RelativeTimePosition", "NOT_IMPLEMENTED", device, changed)
+        changed = setData("Volume", "0", device, changed)
+        changed = setData("Mute", "0", device, changed)
+        changed = setData("SinkProtocolInfo", "", device, changed)
+
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+
+        upnp.cancelProxySubscriptions(EventSubscriptions)
+    end
+  end
+
+  local function commsFailure(device, text)
+    debug("commsFailure: Device offline? status=" .. text)
+
+    deviceIsOffline(device)
+  end
+
+  local function extractDataFromMetaData(device, currentUri, currentUriMetaData, trackUri, trackUriMetaData)
+    local statusString, info, title, title2, artist, album, albumArt, duration
+    if (currentUriMetaData ~= trackUriMetaData) then
+        info, title = getSimpleDIDLStatus(currentUriMetaData)
+    end
+    info, title2, artist, album, albumArt, duration = getSimpleDIDLStatus(trackUriMetaData)
+    if (currentUriMetaData == trackUriMetaData) then
+        title = title2
+    end
+    if (title ~= "" and title ~= title2 and string.sub(title2, 1, 10) == "x-sonosapi") then
+        title2 = title
+    end
+    if (title == "") then
+        title = title2
+    end
+    statusString = ""
+    if (title ~= "") then
+        statusString = statusString .. title
+    end
+    if (title2 ~= "" and title2 ~= title) then
+        if (statusString ~= "") then
+            statusString = statusString .. ": "
+        end
+        statusString = statusString .. title2
+    end
+    if (currentUri == "") then
+        info = "No media"
+    end
+    if (info ~= "") then
+        if (statusString ~= "") then
+            statusString = statusString .. ": "
+        end
+        statusString = statusString .. info
+    end
+    if (albumArt ~= "") then
+        albumArt = url.absolute(string.format("http://%s:%s/", ipDMR, portDMR), albumArt)
+    else
+        albumArt = iconURL
+    end
+    return statusString, title2, artist, album, albumArt, duration
+  end
+
+  local function refreshNow(device, force)
+    debug("refreshNow: start")
+
+    if (upnp.proxyVersionAtLeast(1) and force == false) then
+        return
+    end
+
+    if (uuidDMR == nil or uuidDMR == "") then
+        return
+    end
+
+    local status, tmp
+    local changed = false
+    local statusString, title, artist, album, albumArt, duration
+    local currentUri, currentUriMetaData, trackUri, trackUriMetaData
+
+    local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+    if (AVTransport ~= nil) then
+        -- GetCurrentTransportState  (PLAYING, STOPPED, etc)
+        status, tmp = AVTransport.GetTransportInfo({InstanceID="0"})
+        if (status ~= true) then
+            debug("AVTransport.GetTransportInfo failed: going offline")
+            commsFailure(device, tmp)
+            return
+        end
+        if (deviceIsOnline(device)) then
+            setup(device, true)
+        end
+        changed = setData("TransportState", upnp.extractElement("CurrentTransportState", tmp, ""), device, changed)
+        changed = setData("TransportStatus", upnp.extractElement("CurrentTransportStatus", tmp, ""), device, changed)
+        changed = setData("TransportPlaySpeed", upnp.extractElement("CurrentSpeed", tmp, ""), device, changed)
+
+        -- Get Playmode (NORMAL, REPEAT_ALL, SHUFFLE_NOREPEAT, SHUFFLE)
+        status, tmp = AVTransport.GetTransportSettings({InstanceID="0"})
+        changed = setData("CurrentPlayMode", upnp.extractElement("PlayMode", tmp, ""), device, changed)
+
+        -- Get Current Transport Actions (a CSV of valid Transport Action/Transitions)
+        status, tmp = AVTransport.GetCurrentTransportActions({InstanceID="0"})
+        changed = setData("CurrentTransportActions", upnp.extractElement("Actions", tmp, ""), device, changed)
+
+        -- Get Media Information
+        status, tmp = AVTransport.GetMediaInfo({InstanceID="0"})
+        currentUri = upnp.extractElement("CurrentURI", tmp, "")
+        currentUriMetaData = upnp.unformatXML(upnp.extractElement("CurrentURIMetaData", tmp, ""))
+        changed = setData("NumberOfTracks", upnp.extractElement("NrTracks", tmp, "NOT_IMPLEMENTED"), device, changed)
+        changed = setData("CurrentMediaDuration", upnp.extractElement("MediaDuration", tmp, "NOT_IMPLEMENTED"), device, changed)
+        changed = setData("AVTransportURI", currentUri, device, changed)
+        changed = setData("AVTransportURIMetaData", currentUriMetaData, device, changed)
+
+        -- Get Current URI - song or radio station etc
+        status, tmp = AVTransport.GetPositionInfo({InstanceID="0"})
+        trackUri = upnp.extractElement("TrackURI", tmp, "")
+        trackUriMetaData = upnp.unformatXML(upnp.extractElement("TrackMetaData", tmp, ""))
+        changed = setData("CurrentTrack", upnp.extractElement("Track", tmp, "NOT_IMPLEMENTED"), device, changed)
+        changed = setData("CurrentTrackDuration", upnp.extractElement("TrackDuration", tmp, "NOT_IMPLEMENTED"), device, changed)
+        changed = setData("CurrentTrackURI", trackUri, device, changed)
+        changed = setData("CurrentTrackMetaData", trackUriMetaData, device, changed)
+        changed = setData("RelativeTimePosition", upnp.extractElement("RelTime", tmp, "NOT_IMPLEMENTED"), device, changed)
+
+        statusString, title, artist, album, albumArt, duration =
+            extractDataFromMetaData(device, currentUri, currentUriMetaData, trackUri, trackUriMetaData)
+
+        changed = setData("CurrentStatus", statusString, device, changed)
+        changed = setData("CurrentTitle", title, device, changed)
+        changed = setData("CurrentArtist", artist, device, changed)
+        changed = setData("CurrentAlbum", album, device, changed)
+        changed = setData("CurrentAlbumArt", albumArt, device, changed)
+
+        if (albumArt == "" or not upnp.testURL(albumArt, nil)) then
+            albumArt = iconURL
+        end
+        changed = setData("CurrentAlbumArt2", albumArt, device, changed)
+    end
+
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+    if (Rendering ~= nil) then
+        -- Get Mute status
+        status, tmp = Rendering.GetMute({OrderedArgs={"InstanceID=0", "Channel=Master"}})
+        if (status ~= true) then
+            debug("Rendering.GetMute failed: going offline")
+            commsFailure(device, tmp)
+            return
+        end
+        if (deviceIsOnline(device)) then
+            setup(device, true)
+        end
+        changed = setData("Mute", upnp.extractElement("CurrentMute", tmp, ""), device, changed)
+
+        -- Get Volume
+        status, tmp = Rendering.GetVolume({OrderedArgs={"InstanceID=0", "Channel=Master"}})
+        changed = setData("Volume", upnp.extractElement("CurrentVolume", tmp, ""), device, changed)
+    end
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+  end
+
+  local function refreshVolumeNow(device)
+    debug("refreshVolumeNow: start")
+
+    if (upnp.proxyVersionAtLeast(1)) then
+        return
+    end
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+    if (Rendering == nil) then
+        return
+    end
+
+    local status, tmp, changed
+
+    -- Get Volume
+    status, tmp = Rendering.GetVolume({OrderedArgs={"InstanceID=0", "Channel=Master"}})
+
+    if (status ~= true) then
+        debug("Rendering.GetVolume failed: going offline")
+        commsFailure(device, tmp)
+        return
+    end
+
+    changed = setData("Volume", upnp.extractElement("CurrentVolume", tmp, ""), device, false)
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+  end
+
+  local function refreshMuteNow(device)
+    debug("refreshMuteNow: start")
+
+    if (upnp.proxyVersionAtLeast(1)) then
+        return
+    end
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+    if (Rendering == nil) then
+        return
+    end
+
+    local status, tmp, changed
+
+    -- Get Mute status
+    status, tmp = Rendering.GetMute({OrderedArgs={"InstanceID=0", "Channel=Master"}})
+
+    if (status ~= true) then
+        debug("Rendering.GetMute failed: going offline")
+        commsFailure(device, tmp)
+        return
+    end
+
+    changed = setData("Mute", upnp.extractElement("CurrentMute", tmp, ""), device, false)
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+  end
+
+ local function savePlaybackContext(device)
+    debug("savePlaybackContext: start")
+
+    local cxt = {}
+
+    refreshNow(device, true)
+    cxt.TransportState = dataTable.TransportState
+    cxt.TransportPlaySpeed = dataTable.TransportPlaySpeed
+    cxt.CurrentPlayMode = dataTable.CurrentPlayMode
+    cxt.CurrentTransportActions = dataTable.CurrentTransportActions
+    cxt.AVTransportURI = dataTable.AVTransportURI
+    cxt.AVTransportURIMetaData = dataTable.AVTransportURIMetaData
+    cxt.CurrentTrackDuration = dataTable.CurrentTrackDuration
+    cxt.RelativeTimePosition = dataTable.RelativeTimePosition
+    cxt.Mute = dataTable.Mute
+    cxt.Volume = dataTable.Volume
+
+    return cxt
+  end
+
+  local function restorePlaybackContext(device, cxt)
+    debug("restorePlaybackContext: start")
+    local instanceId="0"
+    local channel="Master"
+
+    if (cxt == nil) then
+        warning("Please save the context before restoring it !")
+        return
+    end
+
+    local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+
+    if (AVTransport ~= nil) then
+        AVTransport.Stop({InstanceID=instanceId})
+    end
+
+    if (Rendering ~= nil) then
+        local mute = cxt.Mute
+        if (mute == "true" or mute == "yes") then
+            mute = "1"
+        elseif (mute == "false" or mute == "no") then
+            mute = "0"
+        end
+        Rendering.SetMute(
+            {OrderedArgs={"InstanceID=" .. instanceId,
+                          "Channel=" .. channel,
+                          "DesiredMute=" .. mute}})
+
+        Rendering.SetVolume(
+            {OrderedArgs={"InstanceID=" .. instanceId,
+                          "Channel=" .. channel,
+                          "DesiredVolume=" .. cxt.Volume}})
+    end
+
+    if (AVTransport ~= nil) then
+        AVTransport.SetAVTransportURI(
+            {OrderedArgs={"InstanceID=" .. instanceId,
+                          "CurrentURI=" .. cxt.AVTransportURI,
+                          "CurrentURIMetaData=" .. cxt.AVTransportURIMetaData}})
+
+        if (cxt.AVTransportURI ~= "") then
+            if (cxt.TransportState == "PLAYING"
+                    or cxt.TransportState == "TRANSITIONING"
+                    or cxt.TransportState == "PAUSED_PLAYBACK") then
+                AVTransport.Play(
+                    {OrderedArgs={"InstanceID=" .. instanceId,
+                                  "Speed=" .. cxt.TransportPlaySpeed}})
+            end
+
+            if (cxt.CurrentTrackDuration ~= "NOT_IMPLEMENTED"
+                    and cxt.CurrentTrackDuration ~= "0:00:00"
+                    and cxt.CurrentTrackDuration ~= "00:00:00"
+                    and cxt.RelativeTimePosition ~= "NOT_IMPLEMENTED") then
+				-- Wait for 1s as it does not work without this delay for few DMR
+                luup.sleep(1000)
+                AVTransport.Seek(
+                    {OrderedArgs={"InstanceID=" .. instanceId,
+                                  "Unit=REL_TIME",
+                                  "Target=" .. cxt.RelativeTimePosition}})
+            end
+
+            -- Restore repeat, shuffle
+            AVTransport.SetPlayMode(
+                {OrderedArgs={"InstanceID=" .. instanceId,
+                              "NewPlayMode=" .. cxt.CurrentPlayMode}})
+
+            if (cxt.TransportState == "PLAYING"
+                    or cxt.TransportState == "TRANSITIONING") then
+                -- Nothing to do, Playback has already been started
+            elseif (cxt.TransportState == "PAUSED_PLAYBACK") then
+                AVTransport.Pause({InstanceID=instanceId})
+            else
+                AVTransport.Stop({InstanceID=instanceId})
+            end
+        end
+    end
+
+    refreshNow(device, false)
+  end
+
+  -- Output parameters:
+  --   + duration: duration of the produced audio file in seconds
+  --   + uri: Sonos URI for playing the audio file
+  --   + title: title information
+  --   + protocol: protocol information to be exchanged with the DMR
+  local function runTTSengine(engine, text, language, device)
+    local duration = 0
+    local uri = nil
+    local title = ""
+    local protocol = nil
+
+    if (engine == "Google") then
+        -- Cut the text in fragments of max 100 characters
+        local tableTextFragments = {}
+        local cutSize = 100	
+        local remaining = text
+        while (#remaining > cutSize) do
+            local pos = string.find(string.reverse(string.sub(remaining, 1, cutSize+1)), " ")
+            if (pos ~= nil) then
+                table.insert(tableTextFragments, string.sub(remaining, 1, cutSize+1-pos))
+                remaining = string.sub(remaining, cutSize+3-pos)
+            else
+                remaining = ""
+            end
+        end
+        if (#remaining > 0) then
+            table.insert(tableTextFragments, remaining)
+        end
+
+        -- Get the MP3 files from Google
+        local strFiles = ""
+        for i, v in ipairs(tableTextFragments)
+        do
+            strFiles = strFiles .. " " .. SAY_TMP_FILE:format(device, i)
+            os.execute(SAY_EXECUTE:format(device, i, device, i, language, url.escape(v)))
+        end
+        -- Concat the multiple MP3 files
+        os.execute(CONCAT_EXECUTE:format(strFiles, device, strFiles))
+
+        -- Get the file size to deduce its playback duration
+        local fh = io.open(SAY_OUTPUT_FILE:format(device), "a+")
+        local size = fh:seek("end")
+        fh:close()
+
+        -- Compute the play duration from the file size (32 kbps)
+        duration = math.ceil(size/4000)
+
+        uri = SAY_OUTPUT_URI:format(VERA_IP, VERA_WEB_PORT, device)
+
+        title = "Google TTS"
+        protocol = "http-get:*:audio/mpeg:*"
+    end
+
+    return duration, uri, title, protocol
+  end
+
+  local function play(device, parameters, saveAndRestore)
+    local defaultLang = luup.variable_get(DLNA_DMC_SID, "DefaultLanguageTTS", device) or "en"
+    local instanceId = defaultValue(parameters, "InstanceID", "0")
+    local volume = defaultValue(parameters, "Volume", nil)
+    local text = defaultValue(parameters, "Text", "The monkey is on the branch, the mouse is under the table")
+    local language = defaultValue(parameters, "Language", defaultLang)
+    local uri = defaultValue(parameters, "URI", nil)
+    local uriMetaData = defaultValue(parameters, "URIMetadata", nil)
+    local protocol = defaultValue(parameters, "Protocol", nil)
+    local duration = defaultValue(parameters, "Duration", "0")
+    local channel = "Master"
+    local title = ""
+    local data = "&lt;upnp:class>object.item&lt;/upnp:class>"
+
+    if (uri == nil) then
+        -- TTS case
+
+        duration, uri, title, protocol = runTTSengine("Google", text, language, device)
+        -- add 1 second to be sure to not cut the end of the text
+        duration = duration + 1
+    end
+
+    if (uri == nil or uri == "") then
+        return
+    end
+
+    if (uriMetaData == nil) then
+        local subURL = uri:match("^http:.*/([^/]-)$")
+        if (title == "") then
+            title = subURL or "No title"
+        end
+        if (protocol ~= nil and protocol ~= "") then
+            data = '&lt;res protocolInfo="' .. protocol ..'">' .. uri
+                       .. '&lt;/res>&lt;upnp:class>object.item&lt;/upnp:class>'
+        end
+        uriMetaData = DEFAULT_METADATA:format(title, data)
+    end
+
+    if (saveAndRestore == true and sayPlayback == nil) then
+        sayPlayback = savePlaybackContext(device)
+    end
+
+    local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+    local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+
+    if (AVTransport ~= nil) then
+        if (dataTable.TransportState ~= "NO_MEDIA_PRESENT") then
+            AVTransport.Stop({InstanceID=instanceId})
+        end
+
+        AVTransport.SetAVTransportURI(
+            {OrderedArgs={"InstanceID=" .. instanceId,
+                          "CurrentURI=" .. uri,
+                          "CurrentURIMetaData=" .. uriMetaData}})
+
+        if (volume ~= nil and Rendering ~= nil) then
+            Rendering.SetVolume(
+                {OrderedArgs={"InstanceID=" .. instanceId,
+                              "Channel=" .. channel,
+                              "DesiredVolume=" .. volume}})
+        end
+
+        local delay = tonumber(luup.variable_get(DLNA_DMC_SID, "DelayBeforePlayback", device) or "0")
+        if (delay ~= nil and delay > 0) then
+            luup.sleep(delay)
+        end
+
+        AVTransport.Play(
+           {OrderedArgs={"InstanceID=" .. instanceId,
+                         "Speed=1"}})
+    end
+
+    if (saveAndRestore == true) then
+        luup.call_delay("endSayAlert", duration, device)
+    end
+
+    refreshNow(device, false)
+  end
+
+  function endSayAlert(device)
+    device = tonumber(device)
+    table.remove(sayQueue, 1)
+    if (#sayQueue == 0) then
+        restorePlaybackContext(device, sayPlayback)
+        sayPlayback = nil
+        os.execute(DELETE_EXECUTE:format(device))
+    else
+        play(device, sayQueue[1], true)
+    end
+  end
+
+  function updateWithoutProxy(device)
+    if (upnp.proxyVersionAtLeast(1) == false) then
+        luup.call_delay("updateWithoutProxy", 15, device)
+    end
+    device = tonumber(device)
+
+    refreshNow(device, true)
+  end
+
+  function setup(device, init)
+    device = tonumber(device)
+
+    local changed = false
+
+    local newDevice = false
+    local newDescrURL = luup.variable_get(DLNA_DMC_SID, "DescriptionURL", device) or ""
+    local newIP, newPort = newDescrURL:match("http://([%d%.]-):(%d+)/.-")
+	if (newIP == nil or newPort == nil) then
+        luup.variable_set(DLNA_DMC_SID, "DescriptionURL", DMRDescriptionURL, device)
+    elseif (newDescrURL ~= DMRDescriptionURL) then
+        newDevice = true
+        upnp.resetServices(uuidDMR)
+        upnp.cancelProxySubscriptions(EventSubscriptions)
+        ipDMR = newIP
+        portDMR = newPort
+        DMRDescriptionURL = newDescrURL
+        uuidDMR = ""
+        changed = setData("ModelName", "", device, changed)
+        changed = setData("Online", "0", device, changed)
+        debug("Switching to a new DMR: setting variable Online to 0")
+    end
+
+    local descrXML = upnp.UPnP_getDeviceDescription(DMRDescriptionURL)
+    if (descrXML == nil) then
+        debug("upnp.UPnP_getDeviceDescription failed: going offline")
+        deviceIsOffline(device)
+        return
+    end
+
+    local newOnline = deviceIsOnline(device)
+
+    local values = upnp.getInfosFromDescription(
+                            descrXML,
+                            "urn:schemas-upnp-org:device:MediaRenderer:1",
+                            { "UDN", "modelName" })
+
+    uuidDMR = values.UDN:match("uuid:(.+)") or ""
+    changed = setData("ModelName", values.modelName or "", device, changed)
+
+    iconURL = upnp.getIconFromDescription(descrXML)
+    if (iconURL ~= nil) then
+        iconURL = url.absolute(string.format("http://%s:%s/", ipDMR, portDMR), iconURL)
+    else
+        iconURL = PLUGIN_ICON:format(VERA_IP, VERA_WEB_PORT)
+    end
+
+    if (upnp.aresServicesLoaded(uuidDMR) == false) then
+        upnp.addServices(uuidDMR, ipDMR, portDMR, descrXML,
+                         "urn:schemas-upnp-org:device:MediaRenderer:1",
+                         { UPNP_AVTRANSPORT_SERVICE,
+                           UPNP_RENDERING_CONTROL_SERVICE,
+                           UPNP_CONNECTION_MANAGER_SERVICE })
+    end
+
+    if (init or newDevice or newOnline) then
+        upnp.subscribeToEvents(device, ipDMR, portDMR, EventSubscriptions, DLNA_DMC_SID, uuidDMR)
+    end
+
+    if (upnp.proxyVersionAtLeast(1)) then
+        changed = setData("ProxyUsed", "proxy is used", device, changed)
+    else
+        changed = setData("ProxyUsed", "proxy is not used", device, changed)
+    end
+
+    if (init or newDevice or newOnline) then
+        local ConnectionManager = upnp.getService(uuidDMR, UPNP_CONNECTION_MANAGER_SERVICE)
+        if (ConnectionManager ~= nil) then
+            local status, tmp = ConnectionManager.GetProtocolInfo()
+            changed = setData("SinkProtocolInfo", upnp.extractElement("Sink", tmp, ""), device, changed)
+        end
+    end
+
+    if (changed) then
+        luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+    end
+
+    if (newDevice or newOnline) then
+        refreshNow(device, true)
+    end
+  end
+
+  local function setupDMS(descrURL)
+    if (descrURL ~= nil) then
+        local newIP, newPort = descrURL:match("http://([%d%.]-):(%d+)/.-")
+        if (newIP == nil or newPort == nil) then
+            return false, "Bad server description URL"
+        end
+
+        if (descrURL ~= DMSDescriptionURL) then
+            local descrXML = upnp.UPnP_getDeviceDescription(descrURL)
+            if (descrXML == nil) then
+                return false, "Server not available"
+            end
+
+            upnp.resetServices(uuidDMS)
+            ipDMS = newIP
+            portDMS = newPort
+            DMSDescriptionURL = descrURL
+            uuidDMS = ""
+
+            local values = upnp.getInfosFromDescription(
+                                descrXML,
+                                "urn:schemas-upnp-org:device:MediaServer:1",
+                                { "UDN" })
+
+            uuidDMS = values.UDN:match("uuid:(.+)") or ""
+
+            if (upnp.aresServicesLoaded(uuidDMS) == false) then
+                upnp.addServices(uuidDMS, ipDMS, portDMS, descrXML,
+                                 "urn:schemas-upnp-org:device:MediaServer:1",
+                                 { UPNP_MR_CONTENT_DIRECTORY_SERVICE })
+            end
+        end
+    end
+
+    local errorMsg = ""
+    local status = upnp.aresServicesLoaded(uuidDMS)
+    if (status == false) then
+        errorMsg = "Server access is not setup"
+    end
+    return status, errorMsg
+  end
+
+  local function browseDMS(device, descrURL, object)
+    local result = ""
+    luup.variable_set(DLNA_DMC_SID, "BrowseResult", "loading", device)
+    local status, errorMsg = setupDMS(descrURL)
+    if (status == true) then
+        errorMsg = "Done"
+        result = upnp.browseContent(uuidDMS, UPNP_MR_CONTENT_DIRECTORY_SERVICE, object, false)
+        if (result == "") then
+            errorMsg = "Server content not available"
+        end
+    end
+    luup.variable_set(DLNA_DMC_SID, "PlaybackResult", errorMsg, device)
+    luup.variable_set(DLNA_DMC_SID, "BrowseResult", result, device)
+  end
+
+  local function playDMS(device, descrURL, object, forceProtocol)
+    if (uuidDMR == "" or dataTable.SinkProtocolInfo == nil or dataTable.SinkProtocolInfo == "") then
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Renderer not available", device)
+        return
+    end
+
+    local status, errorMsg = setupDMS(descrURL)
+    if (status == false) then
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", errorMsg, device)
+        return
+    end
+
+    local item, DMRprotocol, protocol, uri
+
+    local xml = upnp.browseContent(uuidDMS, UPNP_MR_CONTENT_DIRECTORY_SERVICE, object, true)
+    debug("data from server: " .. (xml or "nil"))
+    if (xml == "") then
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media not available", device)
+        return
+    end
+    local startDIDL, item, endDIDL = xml:match("(&lt;DIDL%-Lite%s?[^>]->&lt;item%s?[^>]->.-)(&lt;res%s?[^>]->.*&lt;/res>)(.-&lt;/item>&lt;/DIDL%-Lite>)")
+    debug("startDIDL: " .. (startDIDL or "nil"))
+    debug("endDIDL: " .. (endDIDL or "nil"))
+    debug("res: " .. (item or "nil"))
+
+    local selectedProtocol = ""
+    local selectedUri = ""
+
+    if (item ~= nil) then
+        local DMRprotocols = {}
+        for DMRprotocol in dataTable.SinkProtocolInfo:gmatch("[^,]+") do
+            local protocol_p1, protocol_p2 = DMRprotocol:match("^([^:]-:[^:]-):([^:]-):.-$")
+            if (protocol_p1 ~= nil and protocol_p2 ~= nil) then
+                if (protocol_p2 ~= "*") then
+                    protocol_p2 = protocol_p2 .. "*"
+                end
+                DMRprotocol = protocol_p1 .. ":" .. protocol_p2
+                DMRprotocol = DMRprotocol:gsub("-", "%%-"):gsub("*", "[^:]-")
+                DMRprotocol = "^(" .. DMRprotocol .. ":.-)$"
+                DMRprotocols[DMRprotocol] = true
+            end
+        end
+
+        debug("selected protocol " .. (forceProtocol or "nil"))
+        for protocol, uri in item:gmatch('&lt;res%s?.-%s?protocolInfo="(.-)"%s?[^>]->(.-)&lt;/res>') do
+            debug("server protocol " .. protocol)
+            if (forceProtocol == nil or forceProtocol == ""
+                    or forceProtocol == "AUTO" or forceProtocol == protocol) then
+                debug("server protocol considered")
+                for DMRprotocol, v in pairs(DMRprotocols) do
+                    if (protocol:match(DMRprotocol) ~= nil) then
+                        debug("Matching OK with DMR protocol " .. DMRprotocol)
+                        selectedProtocol = protocol
+                        selectedUri = uri
+                        break
+                    else
+                        debug("No matching with DMR protocol " .. DMRprotocol)
+                    end
+                end
+                if (selectedProtocol ~= "") then
+                    break
+                end
+            else
+                debug("server protocol bypassed")
+            end
+        end
+    end
+
+    if (selectedUri ~= "" and selectedProtocol ~= ""
+            and startDIDL ~= nil and endDIDL ~= nil) then
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media OK", device)
+        local settings = {}
+        settings.URI = selectedUri
+        if (forceProtocol == nil or forceProtocol == "") then
+            settings.URIMetadata = startDIDL .. endDIDL
+        else
+            settings.URIMetadata = startDIDL .. '&lt;res protocolInfo="' .. selectedProtocol .. '">'
+                .. selectedUri .. '&lt;/res>' .. endDIDL
+        end
+        play(device, settings, false)
+    else
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media not compatible with renderer", device)
+        warning("Media cannot be rendered by the selected renderer.")
+    end
+  end
+
+  local function getCheckStateRate(device)
+    local rate = luup.variable_get(DLNA_DMC_SID, "CheckStateRate", device)
+	if (rate == nil or tonumber(rate) == nil) then
+        rate = "0"
+        luup.variable_set(DLNA_DMC_SID, "CheckStateRate", rate, device)
+    end
+    return tonumber(rate) * 60
+  end
+
+  function checkDeviceState(device)
+    device = tonumber(device)
+    local rate = getCheckStateRate(device)
+    if (rate > 0) then
+        luup.call_delay("checkDeviceState", rate, device)
+    	setup(device, false)
+    end
+  end
+
+  local function handleRenderingChange(device, event)
+      debug("handleRenderingChange for device " .. device .. " value " .. event)
+      local changed = false
+      local token, attributes, attr, value
+      local tmp = event:match("&lt;Event%s?[^>]->&lt;InstanceID%s?[^>]->(.+)&lt;/InstanceID>&lt;/Event>")
+      if (tmp ~= nil) then
+          for token, attributes in tmp:gmatch('&lt;([a-zA-Z0-9:]+)(%s?.-)/>') do
+              local attrTable = {}
+              for attr, value in attributes:gmatch('%s(.-)="(.-)"') do
+                  attrTable[attr] = value
+              end
+              if (attrTable.val ~= nil and
+                      (attrTable.Channel == "Master" or attrTable.channel == "Master" or
+                           (attrTable.Channel == nil and attrTable.channel == nil))) then
+                  changed = setData(token, upnp.decode(attrTable.val), device, changed)
+              end
+          end
+          for token, attributes in tmp:gmatch('&lt;([a-zA-Z0-9:]+)(%s?[^>]-)>&lt;/[^>]->') do
+              local attrTable = {}
+              for attr, value in attributes:gmatch('%s(.-)="(.-)"') do
+                  attrTable[attr] = value
+              end
+              if (attrTable.val ~= nil and
+                      (attrTable.Channel == "Master" or attrTable.channel == "Master" or
+                           (attrTable.Channel == nil and attrTable.channel == nil))) then
+                  changed = setData(token, upnp.decode(attrTable.val), device, changed)
+              end
+          end
+      end
+      if (changed) then
+          luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+      end
+  end
+
+  local function handleAVTransportChange(device, event)
+      debug("handleAVTransportChange for device " .. device .. " value " .. event)
+      local statusString, title, artist, album, albumArt, duration
+      local currentUri, currentUriMetaData, trackUri, trackUriMetaData, service, currentAlbumArt
+      local changed = false
+      local token, attributes, attr, value
+      local tmp = event:match("&lt;Event%s?[^>]->&lt;InstanceID%s?[^>]->(.+)&lt;/InstanceID>&lt;/Event>")
+      if (tmp ~= nil) then
+          local found = false
+          local found2 = false
+          for token, attributes in tmp:gmatch('&lt;([a-zA-Z0-9:]+)(%s?.-)/>') do
+              if (token == "RelativeTimePosition") then
+                  found = true
+              elseif (token == "CurrentTrackDuration") then
+                  found2 = true
+              end
+              local attrTable = {}
+              for attr, value in attributes:gmatch('%s(.-)="(.-)"') do
+                  attrTable[attr] = value
+              end
+              if (attrTable.val ~= nil) then
+                  changed = setData(token, upnp.decode(attrTable.val), device, changed)
+              end
+          end
+          for token, attributes in tmp:gmatch('&lt;([a-zA-Z0-9:]+)(%s?[^>]-)>&lt;/[^>]->') do
+              if (token == "RelativeTimePosition") then
+                  found = true
+              elseif (token == "CurrentTrackDuration") then
+                  found2 = true
+              end
+              local attrTable = {}
+              for attr, value in attributes:gmatch('%s(.-)="(.-)"') do
+                  attrTable[attr] = value
+              end
+              if (attrTable.val ~= nil) then
+                  changed = setData(token, upnp.decode(attrTable.val), device, changed)
+              end
+          end
+
+          currentUri = dataTable.AVTransportURI
+          currentUriMetaData = dataTable.AVTransportURIMetaData
+          trackUri = dataTable.CurrentTrackURI
+          trackUriMetaData = dataTable.CurrentTrackMetaData
+          currentAlbumArt = dataTable.CurrentAlbumArt
+          statusString, title, artist, album, albumArt, duration =
+              extractDataFromMetaData(device, currentUri, currentUriMetaData, trackUri, trackUriMetaData)
+          changed = setData("CurrentStatus", statusString, device, changed)
+          changed = setData("CurrentTitle", title, device, changed)
+          changed = setData("CurrentArtist", artist, device, changed)
+          changed = setData("CurrentAlbum", album, device, changed)
+          changed = setData("CurrentAlbumArt", albumArt, device, changed)
+          if (albumArt == currentAlbumArt) then
+              albumArt = dataTable.CurrentAlbumArt2
+          elseif (albumArt == "" or not upnp.testURL(albumArt, nil)) then
+              albumArt = iconURL
+          end
+          changed = setData("CurrentAlbumArt2", albumArt, device, changed)
+          -- As BubbleUPnP does not update the track duration, we got it from the meta data
+          if (dataTable.ModelName == "BubbleUPnP Media Renderer" and not found2 and duration ~= "") then
+              changed = setData("CurrentTrackDuration", duration, device, changed)
+          end
+
+          if (not found) then
+              local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+              if (AVTransport ~= nil) then
+                  local status, tmp2 = AVTransport.GetPositionInfo({InstanceID="0"})
+                  changed = setData("RelativeTimePosition", upnp.extractElement("RelTime", tmp2, "NOT_IMPLEMENTED"), device, changed)
+              end
+          end
+      end
+      if (changed) then
+          luup.variable_set(HADEVICE_SID, "LastUpdate", os.time(), device)
+      end
+  end
+
+  function renewSubscriptions(data)
+    local device, uuid = data:match("(%d+):(.*)")
+    if (device ~= nil and uuid ~= nil) then
+        device = tonumber(device)
+        debug("Renewal of all event subscriptions for device " .. device)
+        if (uuid ~= uuidDMR) then
+            debug("Renewal ignored for uuid " .. uuid)
+        elseif (upnp.subscribeToEvents(device, ipDMR, portDMR, EventSubscriptions, DLNA_DMC_SID, uuidDMR) == false) then
+            setup(device, true)
+        end
+    end
+  end
+
+  function cancelProxySubscription(sid)
+    upnp.cancelProxySubscription(sid)
+  end
+
+  function deferredDLNAStartup(device)
+    debug("deferredDLNAStartup: start " .. device)
+    device = tonumber(device)
+
+    -- Check that the proxy is running. 
+    for retries = 1, 3 do
+        local version = upnp.getProxyApiVersion()
+        if (version) then 
+            log("UPnP proxy event identified - API version " .. version)
+            break 
+        end 
+    end
+
+-- the next line has to be uncommented to force the old mode without the UPnP event proxy
+-- upnp.unuseProxy()
+
+    if (luup.devices[device].device_type == DLNA_DMC_DEVICE_TYPE) then
+        DMRDescriptionURL = luup.variable_get(DLNA_DMC_SID, "DescriptionURL", device) or ""
+        ipDMR, portDMR = DMRDescriptionURL:match("http://([%d%.]-):(%d+)/.-")
+    	if (ipDMR == nil or portDMR == nil) then
+            ipDMR = luup.attr_get("ip", device)
+            portDMR = 1367
+            DMRDescriptionURL = string.format("http://%s:%s/", ipDMR, portDMR)
+            luup.variable_set(DLNA_DMC_SID, "DescriptionURL", DMRDescriptionURL, device)
+        else
+            portDMR = tonumber(portDMR)
+        end
+
+        DMSDescriptionURL = ""
+
+        uuidDMR = ""
+        uuidDMS = ""
+
+        sayQueue = {}
+        dataTable = {}
+
+        local rate = getCheckStateRate(device)
+        if (rate > 0) then
+            luup.call_delay("checkDeviceState", rate, device)
+        end
+        setup(device, true)
+        updateWithoutProxy(device)
+    end
+  end
+
+  function DLNAStartup(lul_device)
+    log("#" .. lul_device .. " starting up with id " .. luup.devices[lul_device].id)
+
+	luup.variable_set(DLNA_DMC_SID, "PluginVersion", PLUGIN_VERSION, lul_device)
+
+    if (luup.variable_get(DLNA_DMC_SID, "DMRDiscoveryResult", lul_device) == nil
+            or luup.variable_get(DLNA_DMC_SID, "DMRDiscoveryResult", lul_device) == "scanning") then
+        luup.variable_set(DLNA_DMC_SID, "DMRDiscoveryResult", "", lul_device)
+    end
+    if (luup.variable_get(DLNA_DMC_SID, "DMSDiscoveryResult", lul_device) == nil
+            or luup.variable_get(DLNA_DMC_SID, "DMSDiscoveryResult", lul_device) == "scanning") then
+        luup.variable_set(DLNA_DMC_SID, "DMSDiscoveryResult", "", lul_device)
+    end
+    luup.variable_set(DLNA_DMC_SID, "BrowseResult", "", lul_device)
+    luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "", lul_device)
+
+    local routerIp = luup.variable_get(DLNA_DMC_SID, "RouterIp", lul_device)
+	if (routerIp == nil) then
+        routerIp = ""
+        luup.variable_set(DLNA_DMC_SID, "RouterIp", routerIp, lul_device)
+    end
+    local routerPort = luup.variable_get(DLNA_DMC_SID, "RouterPort", lul_device)
+	if (routerPort == nil or routerPort == "") then
+        routerPort = "80"
+        luup.variable_set(DLNA_DMC_SID, "RouterPort", routerPort, lul_device)
+    end
+    routerPort = tonumber(routerPort)
+
+    local delay = luup.variable_get(DLNA_DMC_SID, "DelayBeforePlayback", lul_device)
+	if (delay == nil or tonumber(delay) == nil) then
+        luup.variable_set(DLNA_DMC_SID, "DelayBeforePlayback", "0", lul_device)
+    end
+
+    local rate = luup.variable_get(DLNA_DMC_SID, "CheckStateRate", lul_device)
+	if (rate == nil or tonumber(rate) == nil) then
+        luup.variable_set(DLNA_DMC_SID, "CheckStateRate", "0", lul_device)
+    end
+
+    local lang = luup.variable_get(DLNA_DMC_SID, "DefaultLanguageTTS", lul_device)
+	if (lang == nil) then
+        luup.variable_set(DLNA_DMC_SID, "DefaultLanguageTTS", "en", lul_device)
+    end
+
+    local debugLogs = luup.variable_get(DLNA_DMC_SID, "DebugLogs", lul_device)
+	if (debugLogs == nil or tonumber(debugLogs) == nil) then
+        luup.variable_set(DLNA_DMC_SID, "DebugLogs", "0", lul_device)
+    end
+    if (luup.variable_get(DLNA_DMC_SID, "DebugLogs", lul_device) == "1") then
+        DEBUG_MODE = true
+    end
+
+    if (routerIp == "") then
+        --
+        -- Acquire the IP Address of Vera itself, needed for the Say method later on.
+        -- Note: We're assuming Vera is connected via it's WAN Port to the DLNA Media Renderer
+        --
+        local stdout = io.popen("GetNetworkState.sh ip_wan")
+        VERA_IP = stdout:read("*a")
+        stdout:close()
+        debug("DLNAStartup: Vera IP Address=" .. VERA_IP)
+        VERA_WEB_PORT = 80
+    else
+        VERA_IP = routerIp
+        VERA_WEB_PORT = routerPort
+    end
+    upnp.initialize(VERA_IP, debug, warning, error)
+
+    iconURL = PLUGIN_ICON:format(VERA_IP, VERA_WEB_PORT)
+
+    luup.call_delay("deferredDLNAStartup", 1, lul_device)
+  end
+</functions>
+<startup>DLNAStartup</startup>
+<actionList>
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>Play</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local speed = defaultValue(lul_settings, "Speed", "1")
+
+      AVTransport.Play(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Speed=" .. speed}})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Play</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local speed = defaultValue(lul_settings, "Speed", "1")
+
+      AVTransport.Play(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Speed=" .. speed}})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>Play</name>
+    <run>
+      local duration = tonumber(defaultValue(lul_settings, "Duration", "0"))
+      if (duration > 0) then
+          table.insert(sayQueue, lul_settings)
+          if (#sayQueue == 1) then
+              play(lul_device, lul_settings, true)
+          end
+      else
+          play(lul_device, lul_settings, false)
+      end
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>Say</name>
+    <run>
+      table.insert(sayQueue, lul_settings)
+      if (#sayQueue == 1) then
+          play(lul_device, lul_settings, true)
+      end
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Seek</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local unit = defaultValue(lul_settings, "Unit", "")
+      local target = defaultValue(lul_settings, "Target", "")
+
+      AVTransport.Seek(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Unit=" .. unit,
+                       "Target=" .. target}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>Pause</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Pause({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Pause</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Pause({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>Stop</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Stop({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Stop</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Stop({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Record</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Record({InstanceID=instanceId})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>SkipDown</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Next({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Next</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Next({InstanceID=instanceId})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+    <name>SkipUp</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Previous({InstanceID=instanceId})
+
+      refreshNow(lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>Previous</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      AVTransport.Previous({InstanceID=instanceId})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>GetPositionInfo</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+
+      local status, tmp = AVTransport.GetPositionInfo({InstanceID=instanceId})
+      setData("RelativeTimePosition", upnp.extractElement("RelTime", tmp, "NOT_IMPLEMENTED"), lul_device, false)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetPlayMode</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local newPlayMode = defaultValue(lul_settings, "NewPlayMode", "NORMAL")
+
+      AVTransport.SetPlayMode(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "NewPlayMode=" .. newPlayMode}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetRecordQualityMode</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local newRecordQualityMode = defaultValue(lul_settings, "NewRecordQualityMode", "NOT_IMPLEMENTED")
+
+      AVTransport.SetRecordQualityMode(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "NewRecordQualityMode=" .. newRecordQualityMode}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetAVTransportURI</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local currentURI = defaultValue(lul_settings, "CurrentURI", "")
+      local currentURIMetaData = defaultValue(lul_settings, "CurrentURIMetaData", "")
+
+      AVTransport.SetAVTransportURI(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "CurrentURI=" .. currentURI,
+                       "CurrentURIMetaData=" .. currentURIMetaData}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+    <name>SetNextAVTransportURI</name>
+    <run>
+      local AVTransport = upnp.getService(uuidDMR, UPNP_AVTRANSPORT_SERVICE)
+      if (AVTransport == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local nextURI = defaultValue(lul_settings, "NextURI", "")
+      local nextURIMetaData = defaultValue(lul_settings, "NextURIMetaData", "")
+
+      AVTransport.SetNextAVTransportURI(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "NextURI=" .. nextURI,
+                       "NextURIMetaData=" .. nextURIMetaData}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
+    <name>Mute</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+     -- Toggle Mute
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local isMuted = luup.variable_get(UPNP_RENDERING_CONTROL_SID, "Mute", lul_device)
+      if (isMuted == "true" or isMuted == "yes") then
+          isMuted = "1"
+      elseif (isMuted == "false" or isMuted == "no") then
+          isMuted = "0"
+      end
+      local desiredMute = 1 - (tonumber(isMuted) or 0)
+
+      Rendering.SetMute(
+          {OrderedArgs={"InstanceID=" .. instanceId,
+                        "Channel=" .. channel,
+                        "DesiredMute=" .. desiredMute}})
+
+      refreshMuteNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
+    <name>Up</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      -- Volume up
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+
+      local desiredVolume = luup.variable_get(UPNP_RENDERING_CONTROL_SID, "Volume", lul_device) or "100"
+      desiredVolume = tonumber(desiredVolume) + 5
+      if (desiredVolume > 100) then
+          desiredVolume = 100
+      end
+
+      Rendering.SetVolume(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredVolume=" .. desiredVolume}})
+
+      refreshVolumeNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
+    <name>Down</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      -- Volume down
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+
+      local desiredVolume = luup.variable_get(UPNP_RENDERING_CONTROL_SID, "Volume", lul_device) or "100"
+      desiredVolume = tonumber(desiredVolume) - 5
+      if (desiredVolume &lt; 0) then
+          desiredVolume = 0
+      end
+
+      Rendering.SetVolume(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredVolume=" .. desiredVolume}})
+
+      refreshVolumeNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetMute</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local desiredMute = defaultValue(lul_settings, "DesiredMute", nil)
+
+      -- If parameter is nill, we consider the callback as a toggle
+      if (desiredMute == nil) then
+          local isMuted = luup.variable_get(UPNP_RENDERING_CONTROL_SID, "Mute", lul_device)
+          if (isMuted == "true" or isMuted == "yes") then
+              isMuted = "1"
+          elseif (isMuted == "false" or isMuted == "no") then
+              isMuted = "0"
+          end
+          desiredMute = 1 - (tonumber(isMuted) or 0)
+      end
+
+      Rendering.SetMute(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredMute=" .. desiredMute}})
+
+      refreshMuteNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetVolume</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local desiredVolume = tonumber(defaultValue(lul_settings, "DesiredVolume", "5"))
+
+      Rendering.SetVolume(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredVolume=" .. desiredVolume}})
+
+      refreshVolumeNow(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetVolumeDB</name>
+    <run>
+      local Rendering = upnp.getService(UUIDs[lul_device], UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local desiredVolume = defaultValue(lul_settings, "DesiredVolume", "0")
+
+      Rendering.SetVolumeDB(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredVolume=" .. desiredVolume}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SelectPreset</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local presetName = defaultValue(lul_settings, "PresetName", "FactoryDefaults")
+
+      Rendering.SelectPreset(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "PresetName=" .. presetName}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetLoudness</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local channel = defaultValue(lul_settings, "Channel", "Master")
+      local desiredLoudness = defaultValue(lul_settings, "DesiredLoudness", "5")
+
+      Rendering.SetLoudness(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "Channel=" .. channel,
+                       "DesiredLoudness=" .. desiredLoudness}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetBrightness</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredBrightness = defaultValue(lul_settings, "DesiredBrightness", "0")
+
+      Rendering.SetBrightness(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredBrightness=" .. desiredBrightness}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetContrast</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredContrast = defaultValue(lul_settings, "DesiredContrast", "0")
+
+      Rendering.SetContrast(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredContrast=" .. desiredContrast}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetSharpness</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredSharpness = defaultValue(lul_settings, "DesiredSharpness", "0")
+
+      Rendering.SetSharpness(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredSharpness=" .. desiredSharpness}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetRedVideoGain</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredRedVideoGain = defaultValue(lul_settings, "DesiredRedVideoGain", "0")
+
+      Rendering.SetRedVideoGain(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredRedVideoGain=" .. desiredRedVideoGain}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetGreenVideoGain</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredGreenVideoGain = defaultValue(lul_settings, "DesiredGreenVideoGain", "0")
+
+      Rendering.SetGreenVideoGain(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredGreenVideoGain=" .. desiredGreenVideoGain}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetBlueVideoGain</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredBlueVideoGain = defaultValue(lul_settings, "DesiredBlueVideoGain", "0")
+
+      Rendering.SetBlueVideoGain(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredBlueVideoGain=" .. desiredBlueVideoGain}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetRedVideoBlackLevel</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredRedVideoBlackLevel = defaultValue(lul_settings, "DesiredRedVideoBlackLevel", "0")
+
+      Rendering.SetRedVideoBlackLevel(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredRedVideoBlackLevel=" .. desiredRedVideoBlackLevel}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetGreenVideoBlackLevel</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredGreenVideoBlackLevel = defaultValue(lul_settings, "DesiredGreenVideoBlackLevel", "0")
+
+      Rendering.SetGreenVideoBlackLevel(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredGreenVideoBlackLevel=" .. desiredGreenVideoBlackLevel}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetBlueVideoBlackLevel</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredBlueVideoBlackLevel = defaultValue(lul_settings, "DesiredBlueVideoBlackLevel", "0")
+
+      Rendering.SetBlueVideoBlackLevel(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredBlueVideoBlackLevel=" .. desiredBlueVideoBlackLevel}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetColorTemperature</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredColorTemperature = defaultValue(lul_settings, "DesiredColorTemperature", "0")
+
+      Rendering.SetColorTemperature(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredColorTemperature=" .. desiredColorTemperature}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetHorizontalKeystone</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredHorizontalKeystone = defaultValue(lul_settings, "DesiredHorizontalKeystone", "0")
+
+      Rendering.SetHorizontalKeystone(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredHorizontalKeystone=" .. desiredHorizontalKeystone}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+    <name>SetVerticalKeystone</name>
+    <run>
+      local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
+      if (Rendering == nil) then
+          return
+      end
+
+      local instanceId = defaultValue(lul_settings, "InstanceID", "0")
+      local desiredVerticalKeystone = defaultValue(lul_settings, "DesiredVerticalKeystone", "0")
+
+      Rendering.SetVerticalKeystone(
+         {OrderedArgs={"InstanceID=" .. instanceId,
+                       "DesiredVerticalKeystone=" .. desiredVerticalKeystone}})
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+    <name>Poll</name>
+    <job>
+      refreshNow(lul_device, true)
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+    <name>ToggleState</name>
+    <job>
+      -- TODO: This needs to be implemented to make the Icon cause a toggle action
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>SavePlaybackContext</name>
+    <run>
+      playbackCxt = savePlaybackContext(lul_device)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>RestorePlaybackContext</name>
+    <run>
+      restorePlaybackContext(lul_device, playbackCxt)
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>StartDMRDiscovery</name>
+    <job>
+      luup.variable_set(DLNA_DMC_SID, "DMRDiscoveryResult", "scanning", lul_device)
+      local xml = upnp.scanUPnPDevices("urn:schemas-upnp-org:device:MediaRenderer:1", { "modelName", "friendlyName" })
+      luup.variable_set(DLNA_DMC_SID, "DMRDiscoveryResult", xml, lul_device)
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>SelectDMRDevice</name>
+    <run>
+      local newDescrURL = lul_settings.URL or ""
+      local newIP, newPort = newDescrURL:match("http://([%d%.]-):(%d+)/.-")
+	  if (newIP ~= nil and newPort ~= nil) then
+          luup.variable_set(DLNA_DMC_SID, "DescriptionURL", newDescrURL, lul_device)
+          setup(lul_device, false)
+      end
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>SearchAndSelectDMRDevice</name>
+    <job>
+      if (lul_settings.Name == nil or lul_settings.Name == "") then
+          return
+      end
+
+      local descrURL = upnp.searchUPnPDevices("urn:schemas-upnp-org:device:MediaRenderer:1",
+                                              lul_settings.Name,
+                                              lul_settings.IP)
+      if (descrURL ~= nil) then
+          local newIP, newPort = descrURL:match("http://([%d%.]-):(%d+)/.-")
+	      if (newIP ~= nil and newPort ~= nil) then
+              luup.variable_set(DLNA_DMC_SID, "DescriptionURL", descrURL, lul_device)
+              setup(lul_device, false)
+          end
+      end
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>StartDMSDiscovery</name>
+    <job>
+      luup.variable_set(DLNA_DMC_SID, "DMSDiscoveryResult", "scanning", lul_device)
+      luup.variable_set(DLNA_DMC_SID, "BrowseResult", "", lul_device)
+      local xml = upnp.scanUPnPDevices("urn:schemas-upnp-org:device:MediaServer:1", { "modelName", "friendlyName" })
+      luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Done", lul_device)
+      luup.variable_set(DLNA_DMC_SID, "DMSDiscoveryResult", xml, lul_device)
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>BrowseDMS</name>
+    <run>
+      browseDMS(lul_device, lul_settings.DescriptionURL, lul_settings.ObjectID or "0")
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>PlayDMSMedia</name>
+    <run>
+      playDMS(lul_device, lul_settings.DescriptionURL, lul_settings.ObjectID or "", lul_settings.Protocol or "")
+    </run>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>NotifyRenderingChange</name>
+    <job>
+      if (upnp.isValidNotification("NotifyRenderingChange", lul_settings.sid, EventSubscriptions)) then
+          handleRenderingChange(lul_device, upnp.unformatXML(lul_settings.LastChange or ""))
+      end
+    </job>
+  </action>
+
+  <action>
+    <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+    <name>NotifyAVTransportChange</name>
+    <job>
+      if (upnp.isValidNotification("NotifyAVTransportChange", lul_settings.sid, EventSubscriptions)) then
+          handleAVTransportChange(lul_device, upnp.unformatXML(lul_settings.LastChange or ""))
+      end
+    </job>
+  </action>
+</actionList>
+</implementation>
Index: /tags/0.5/D_DLNAMediaController1.json
===================================================================
--- /tags/0.5/D_DLNAMediaController1.json	(revision 30)
+++ /tags/0.5/D_DLNAMediaController1.json	(revision 30)
@@ -0,0 +1,395 @@
+[
+    {
+        "flashicon": "icons\/DLNA.png",
+        "imgIconBody": "",
+        "imgIconDimmable": "",
+        "imgIconTurnable": "",
+        "imgIconMin": "",
+        "imgIconMax": "",
+        "state_icons": [],
+        "halloIconsDir": "pics/hallo",
+        "inScene": "1",
+        "DisplayStatus": {},
+        "doc_url": {
+            "doc_language": 1,
+            "doc_manual": 1,
+            "doc_version": 1,
+            "doc_platform": 0,
+            "doc_page": "devices"
+        },
+        "Tabs": [
+            {
+                "Label": {
+                    "lang_tag": "tabname_control",
+                    "text": "Control"
+                },
+                "Position": "0",
+                "TabType": "flash",
+                "SceneGroup": [
+                    {
+                        "id": "1",
+                        "top": "0.25",
+                        "left": "0",
+                        "x": "2",
+                        "y": "1"
+                    },
+                    {
+                        "id": "2",
+                        "top": "1",
+                        "left": "0",
+                        "x": "2",
+                        "y": "3"
+                    }
+                ],
+                "ControlGroup": [
+                    {
+                        "id": "1",
+                        "type": "info", 
+                        "scenegroup": "1"
+                    },
+                    {
+                        "id": "2",
+                        "scenegroup": "2",
+                        "isSingle": "1"
+                    }
+                ],
+                "Control": [
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "0",
+                        "left": "0",
+                        "Label": {
+                            "lang_tag": "cmd_play",
+                            "text": "Play"
+                        },
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:AVTransport",
+                            "Variable": "TransportState",
+                            "Value": "PLAYING",
+                            "Top": 30,
+                            "Left": 30,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "Play", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "0",
+                        "left": "1",
+                        "Label": {
+                            "lang_tag": "cmd_pause",
+                            "text": "Pause"
+                        },
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:AVTransport",
+                            "Variable": "TransportState",
+                            "Value": "PAUSED_PLAYBACK",
+                            "Top": 30,
+                            "Left": 90,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "Pause", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "1",
+                        "left": "0",
+                        "Label": {
+                            "lang_tag": "cmd_stop",
+                            "text": "Stop"
+                        },
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:AVTransport",
+                            "Variable": "TransportState",
+                            "Value": "STOPPED",
+                            "Top": 30,
+                            "Left": 150,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "Stop", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "1",
+                        "left": "1",
+                        "Label": {
+                            "lang_tag": "cmd_mute",
+                            "text": "Mute"
+                        },
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:RenderingControl",
+                            "Variable": "Mute",
+                            "Value": "1",
+                            "Top": 30,
+                            "Left": 250,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:upnp-org:serviceId:RenderingControl", 
+                            "Action": "SetMute", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlType": "slider",
+                        "Display": {
+                            "ID": "Volume",
+                            "Service": "urn:upnp-org:serviceId:RenderingControl",
+                            "Variable": "Volume",
+                            "MinValue": "0",
+                            "MaxValue": "100",
+                            "Top": 30,
+                            "Left": 340,
+                            "Width": 200,
+                            "Height": 20
+                        },
+                        "Command": {
+                            "Service": "urn:upnp-org:serviceId:RenderingControl",
+                            "Action": "SetVolume",
+                            "Parameters": [
+                                {
+                                    "Name": "DesiredVolume",
+                                    "ID": "Volume"
+                                }
+                            ]
+                        }
+                    },
+                    {
+                        "ControlType": "variable",
+                        "text_align": "left",
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:RenderingControl",
+                            "Variable": "Volume",
+                            "Top": 35,
+                            "Left": 310,
+                            "Width": 30,
+                            "Height": 20
+                        }
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "2",
+                        "left": "0",
+                        "Label": {
+                            "lang_tag": "cmd_prev",
+                            "text": "Prev"
+                        },
+                        "Display": {
+                            "Top": 60,
+                            "Left": 30,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "SkipUp", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlGroup": "2",
+                        "ControlType": "button",
+                        "top": "2",
+                        "left": "1",
+                        "Label": {
+                            "lang_tag": "cmd_next",
+                            "text": "Next"
+                        },
+                        "Display": {
+                            "Top": 60,
+                            "Left": 90,
+                            "Width": 50,
+                            "Height": 20 
+                        },
+                        "Command": { 
+                            "Service": "urn:micasaverde-com:serviceId:MediaNavigation1", 
+                            "Action": "SkipDown", 
+                            "Parameters": [] 
+                        } 
+                    },
+                    {
+                        "ControlType": "label",
+                        "text_align": "left",
+                        "Label": {
+                            "lang_tag": "playing",
+                            "text": "Playing..."
+                        },
+                        "Display": {
+                            "Top": 100,
+                            "Left": 30,
+                            "Width": 80,
+                            "Height": 20
+                        }
+                    },
+                    {
+                        "ControlGroup": "1",
+                        "ControlPair": "1",
+                        "ControlHeader": "1",
+                        "ControlType": "variable",
+                        "top": "0",
+                        "left": "0",
+                        "x": "2",
+                        "y": "1",
+                        "text_align": "left",
+                        "Display": {
+                            "Service": "urn:upnp-org:serviceId:AVTransport",
+                            "Variable": "CurrentStatus",
+                            "Top": 100,
+                            "Left": 110,
+                            "Width": 300,
+                            "Height": 60
+                        }
+                    }
+                ]
+            },
+            {
+                "Label": {
+                    "lang_tag": "tabname_player",
+                    "text": "Player"
+                },
+                "Position": "1",
+                "TabType": "javascript",
+                "ScriptName": "J_DLNAMediaController1.js",
+                "Function": "DLNA_showPlayer"
+            },
+            {
+                "Label": {
+                    "lang_tag": "settings",
+                    "text": "Settings"
+                },
+                "Position": "2",
+                "TabType": "javascript",
+                "ScriptName": "J_DLNAMediaController1.js",
+                "Function": "DLNA_showSettings"
+            },
+            {
+                "Label": {
+                    "lang_tag": "help",
+                    "text": "Help"
+                },
+                "Position": "3",
+                "TabType": "javascript",
+                "ScriptName": "J_DLNAMediaController1.js",
+                "Function": "DLNA_showHelp"
+            },
+            {
+                "Label": {
+                    "lang_tag": "advanced",
+                    "text": "Advanced"
+                },
+                "Position": "4",
+                "TabType": "javascript",
+                "ScriptName": "shared.js",
+                "Function": "advanced_device"
+            },
+            {
+                "Label": {
+                    "lang_tag": "device_options",
+                    "text": "Device Options"
+                },
+                "Position": "5",
+                "TabType": "javascript",
+                "ScriptName": "shared.js",
+                "Function": "device_zwave_options"
+            }
+        ],
+        "DeviceType": "urn:dlna-org:device:DLNAMediaController:1",
+        "eventList": {
+            "event_1": {
+                "label": "Playing state",
+                "serviceId": "urn:upnp-org:serviceId:AVTransport",
+                "argumentList" : {
+                    "argument_1" : {
+                        "dataType": "string",
+                        "defaultValue": "PLAYING",
+                        "allowedValueList" : {
+                            "PLAYING" : "PLAYING",
+                            "STOPPED" : "STOPPED",
+                            "TRANSITIONING" : "TRANSITIONING",
+                            "PAUSED_PLAYBACK" : "PAUSED_PLAYBACK"
+                        },
+                        "name": "TransportState",
+                        "comparisson": "=",
+                        "prefix": "",
+                        "suffix": ""
+                    }
+                }
+            }
+        },
+        "eventList2": [
+            {
+                "id": 2,
+                "label": {
+                    "lang_tag": "playing_state",
+                    "text": "Playing state"
+                },
+                "serviceId": "urn:upnp-org:serviceId:AVTransport",
+                "argumentList": [
+                    {
+                        "id": 1,
+                        "dataType": "string",
+                        "defaultValue": "PLAYING",
+                        "allowedValueList": [
+                            {
+                                "Playing": "PLAYING",
+                                "HumanFriendlyText": {
+                                    "lang_tag": "is_playing",
+                                    "text": "_DEVICE_NAME_ is Playing"
+                                }
+                            },
+                            {
+                                "Stopped": "STOPPED",
+                                "HumanFriendlyText": {
+                                    "lang_tag": "is_stopped",
+                                    "text": "_DEVICE_NAME_ is Stopped"
+                                }
+                            },
+                            {
+                                "Transitioning": "TRANSITIONING",
+                                "HumanFriendlyText": {
+                                    "lang_tag": "is_transitioning",
+                                    "text": "_DEVICE_NAME_ is Transitioning"
+                                }
+                            },
+                            {
+                                "Paused": "PAUSED_PLAYBACK",
+                                "HumanFriendlyText": {
+                                    "lang_tag": "is_paused",
+                                    "text": "_DEVICE_NAME_ is Paused"
+                                }
+                            }
+                        ],
+                        "name": "TransportState",
+                        "comparisson": "=",
+                        "prefix": {
+                            "lang_tag": "",
+                            "text": ""
+                        },
+                        "suffix": {}
+                    }
+                ]
+            }
+        ]
+    }
+]
Index: /tags/0.5/S_AVTransport1.xml
===================================================================
--- /tags/0.5/S_AVTransport1.xml	(revision 30)
+++ /tags/0.5/S_AVTransport1.xml	(revision 30)
@@ -0,0 +1,1731 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <!-- $Id: S_SonosAVTransport1.xml 121 2013-10-01 19:33:24Z lolodomo $ -->
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_AlarmIncludeLinkedZones</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_AlarmState</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_AlarmVolume</name>
+      <dataType>ui2</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_CurrentAVTransportURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_EnqueueAsNext</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_GroupID</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_InstanceID</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ISO8601Time</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_LIST_URI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_LIST_URIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_MemberID</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_MemberList</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_NumTracks</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_NumTracksChange</name>
+      <dataType>i4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ObjectID</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Queue</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_RejoinGroup</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ResetVolumeAfter</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ResumePlayback</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SavedQueueTitle</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SeekMode</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>TRACK_NR</allowedValue>
+        <allowedValue>ABS_TIME</allowedValue>
+        <allowedValue>REL_TIME</allowedValue>
+        <allowedValue>ABS_COUNT</allowedValue>
+        <allowedValue>REL_COUNT</allowedValue>
+        <allowedValue>CHANNEL_FREQ</allowedValue>
+        <allowedValue>TAPE-INDEX</allowedValue>
+        <allowedValue>FRAME</allowedValue>
+        <!-- Sonos -->
+        <allowedValue>SECTION</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SeekTarget</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SleepTimerState</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_SourceState</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_StreamRestartState</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_TrackList</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_TrackNumber</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_TransportSettings</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_URI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_URIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>AbsoluteCounterPosition</name>
+      <dataType>i4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>AbsoluteTimePosition</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AlarmIDRunning</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AlarmLoggedStartTime</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AlarmRunning</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>AVTransportURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>AVTransportURIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Vera plugin -->
+    <stateVariable sendEvents="no">
+      <name>CurrentAlbumArt</name>
+      <shortCode>currentalbumart</shortCode>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>CurrentCrossfadeMode</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentMediaDuration</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentPlayMode</name>
+      <dataType>string</dataType>
+      <defaultValue>NORMAL</defaultValue>
+      <allowedValueList>
+        <allowedValue>NORMAL</allowedValue>
+        <allowedValue>SHUFFLE</allowedValue>
+        <allowedValue>REPEAT_ONE</allowedValue>
+        <allowedValue>REPEAT_ALL</allowedValue>
+        <allowedValue>RANDOM</allowedValue>
+        <allowedValue>DIRECT_1</allowedValue>
+        <allowedValue>INTRO</allowedValue>
+        <!-- Sonos and XBMC -->
+        <allowedValue>SHUFFLE_NOREPEAT</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentRecordQualityMode</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>0:EP</allowedValue>
+        <allowedValue>1:LP</allowedValue>
+        <allowedValue>2:SP</allowedValue>
+        <allowedValue>0:BASIC</allowedValue>
+        <allowedValue>1:MEDIUM</allowedValue>
+        <allowedValue>2:HIGH</allowedValue>
+        <allowedValue>NOT_IMPLEMENTED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>CurrentSection</name>
+      <dataType>ui4</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <maximum>255</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <!-- Vera plugin -->
+    <stateVariable sendEvents="no">
+      <name>CurrentStatus</name>
+      <shortCode>currentstatus</shortCode>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTrack</name>
+      <dataType>ui4</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTrackDuration</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTrackMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTrackURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>CurrentTransportActions</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>EnqueuedTransportURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>EnqueuedTransportURIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>LastChange</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>NextAVTransportURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>NextAVTransportURIMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>NextTrackMetaData</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>NextTrackURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>NumberOfTracks</name>
+      <dataType>ui4</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PlaybackStorageMedium</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>UNKNOWN</allowedValue>
+        <allowedValue>DV</allowedValue>
+        <allowedValue>MINI-DV</allowedValue>
+        <allowedValue>VHS</allowedValue>
+        <allowedValue>W-VHS</allowedValue>
+        <allowedValue>S-VHS</allowedValue>
+        <allowedValue>D-VHS</allowedValue>
+        <allowedValue>VHSC</allowedValue>
+        <allowedValue>VIDEO8</allowedValue>
+        <allowedValue>HI8</allowedValue>
+        <allowedValue>CD-ROM</allowedValue>
+        <allowedValue>CD-DA</allowedValue>
+        <allowedValue>CD-R</allowedValue>
+        <allowedValue>CD-RW</allowedValue>
+        <allowedValue>VIDEO-CD</allowedValue>
+        <allowedValue>SACD</allowedValue>
+        <allowedValue>MD-AUDIO</allowedValue>
+        <allowedValue>MD-PICTURE</allowedValue>
+        <allowedValue>DVD-ROM</allowedValue>
+        <allowedValue>DVD-VIDEO</allowedValue>
+        <allowedValue>DVD-R</allowedValue>
+        <allowedValue>DVD+RW</allowedValue>
+        <allowedValue>DVD-RW</allowedValue>
+        <allowedValue>DVD-RAM</allowedValue>
+        <allowedValue>DVD-AUDIO</allowedValue>
+        <allowedValue>DAT</allowedValue>
+        <allowedValue>LD</allowedValue>
+        <allowedValue>HDD</allowedValue>
+        <allowedValue>MICRO_MV</allowedValue>
+        <allowedValue>NETWORK</allowedValue>
+        <allowedValue>NONE</allowedValue>
+        <allowedValue>NOT_IMPLEMENTED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PossiblePlaybackStorageMedia</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PossibleRecordQualityModes</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PossibleRecordStorageMedia</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>QueueUpdateID</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RecordMediumWriteStatus</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>WRITABLE</allowedValue>
+        <allowedValue>PROTECTED</allowedValue>
+        <allowedValue>NOT_WRITABLE</allowedValue>
+        <allowedValue>UNKNOWN</allowedValue>
+        <allowedValue>NOT_IMPLEMENTED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RecordStorageMedium</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>UNKNOWN</allowedValue>
+        <allowedValue>DV</allowedValue>
+        <allowedValue>MINI-DV</allowedValue>
+        <allowedValue>VHS</allowedValue>
+        <allowedValue>W-VHS</allowedValue>
+        <allowedValue>S-VHS</allowedValue>
+        <allowedValue>D-VHS</allowedValue>
+        <allowedValue>VHSC</allowedValue>
+        <allowedValue>VIDEO8</allowedValue>
+        <allowedValue>HI8</allowedValue>
+        <allowedValue>CD-ROM</allowedValue>
+        <allowedValue>CD-DA</allowedValue>
+        <allowedValue>CD-R</allowedValue>
+        <allowedValue>CD-RW</allowedValue>
+        <allowedValue>VIDEO-CD</allowedValue>
+        <allowedValue>SACD</allowedValue>
+        <allowedValue>MD-AUDIO</allowedValue>
+        <allowedValue>MD-PICTURE</allowedValue>
+        <allowedValue>DVD-ROM</allowedValue>
+        <allowedValue>DVD-VIDEO</allowedValue>
+        <allowedValue>DVD-R</allowedValue>
+        <allowedValue>DVD+RW</allowedValue>
+        <allowedValue>DVD-RW</allowedValue>
+        <allowedValue>DVD-RAM</allowedValue>
+        <allowedValue>DVD-AUDIO</allowedValue>
+        <allowedValue>DAT</allowedValue>
+        <allowedValue>LD</allowedValue>
+        <allowedValue>HDD</allowedValue>
+        <allowedValue>MICRO_MV</allowedValue>
+        <allowedValue>NETWORK</allowedValue>
+        <allowedValue>NONE</allowedValue>
+        <allowedValue>NOT_IMPLEMENTED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RelativeCounterPosition</name>
+      <dataType>i4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RelativeTimePosition</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>RestartPending</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SleepTimerGeneration</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SnoozeRunning</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>TransportErrorDescription</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>TransportErrorHttpCode</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>TransportErrorHttpHeaders</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>TransportErrorURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>TransportPlaySpeed</name>
+      <dataType>string</dataType>
+      <defaultValue>1</defaultValue>
+      <allowedValueList>
+        <allowedValue>1</allowedValue>
+        <!-- Sonny -->
+        <allowedValue>10</allowedValue>
+        <!-- Sonny -->
+        <allowedValue>-10</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>TransportState</name>
+      <shortCode>transportstate</shortCode>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>STOPPED</allowedValue>
+        <allowedValue>PAUSED_PLAYBACK</allowedValue>
+        <allowedValue>PLAYING</allowedValue>
+        <allowedValue>TRANSITIONING</allowedValue>
+        <allowedValue>PAUSED_RECORDING</allowedValue>
+        <allowedValue>RECORDING</allowedValue>
+        <allowedValue>NO_MEDIA_PRESENT</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>TransportStatus</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>OK</allowedValue>
+        <allowedValue>ERROR_OCCURRED</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonny -->
+    <stateVariable sendEvents="no">
+      <name>X_DLNA_RelativeBytePosition</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonny -->
+    <stateVariable sendEvents="no">
+      <name>X_DLNA_AbsoluteBytePosition</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonny -->
+    <stateVariable sendEvents="no">
+      <name>X_DLNA_CurrentTrackSize</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <!-- Sonos -->
+    <action>
+      <name>AddMultipleURIsToQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumberOfURIs</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIs</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_LIST_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIsMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_LIST_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ContainerURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ContainerMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredFirstTrackNumberEnqueued</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueueAsNext</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EnqueueAsNext</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>FirstTrackNumberEnqueued</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumTracksAdded</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>AddURIToQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredFirstTrackNumberEnqueued</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueueAsNext</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EnqueueAsNext</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>FirstTrackNumberEnqueued</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumTracksAdded</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>AddURIToSavedQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AddAtIndex</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumTracksAdded</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>BackupQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>BecomeCoordinatorOfStandaloneGroup</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>BecomeGroupCoordinator</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentGroupID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_GroupID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>OtherMembers</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberList</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TransportSettings</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TransportSettings</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>SleepTimerState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SleepTimerState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AlarmState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>StreamRestartState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_StreamRestartState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentQueueTrackList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Queue</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>BecomeGroupCoordinatorAndSource</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentGroupID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_GroupID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>OtherMembers</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberList</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>SleepTimerState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SleepTimerState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AlarmState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>StreamRestartState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_StreamRestartState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentAVTTrackList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Queue</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentQueueTrackList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Queue</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSourceState</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SourceState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ResumePlayback</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ResumePlayback</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ChangeCoordinator</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewTransportSettings</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TransportSettings</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ChangeTransportSettings</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewTransportSettings</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TransportSettings</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentAVTransportURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_CurrentAVTransportURI</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ConfigureSleepTimer</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewSleepTimerDuration</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ISO8601Time</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>CreateSavedQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Title</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SavedQueueTitle</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EnqueuedURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumTracksAdded</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AssignedObjectID</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>DelegateGroupCoordinationTo</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewCoordinator</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_MemberID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RejoinGroup</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_RejoinGroup</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetCrossfadeMode</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CrossfadeMode</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentCrossfadeMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetCurrentTransportActions</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Actions</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTransportActions</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetDeviceCapabilities</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PlayMedia</name>
+          <direction>out</direction>
+          <relatedStateVariable>PossiblePlaybackStorageMedia</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RecMedia</name>
+          <direction>out</direction>
+          <relatedStateVariable>PossibleRecordStorageMedia</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RecQualityModes</name>
+          <direction>out</direction>
+          <relatedStateVariable>PossibleRecordQualityModes</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetMediaInfo</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NrTracks</name>
+          <direction>out</direction>
+          <relatedStateVariable>NumberOfTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>MediaDuration</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentMediaDuration</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURI</name>
+          <direction>out</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURIMetaData</name>
+          <direction>out</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NextURI</name>
+          <direction>out</direction>
+          <relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NextURIMetaData</name>
+          <direction>out</direction>
+          <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PlayMedium</name>
+          <direction>out</direction>
+          <relatedStateVariable>PlaybackStorageMedium</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RecordMedium</name>
+          <direction>out</direction>
+          <relatedStateVariable>RecordStorageMedium</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>WriteStatus</name>
+          <direction>out</direction>
+          <relatedStateVariable>RecordMediumWriteStatus</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <action>
+      <name>GetPositionInfo</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Track</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTrack</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TrackDuration</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTrackDuration</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TrackMetaData</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTrackMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TrackURI</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentTrackURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RelTime</name>
+          <direction>out</direction>
+          <relatedStateVariable>RelativeTimePosition</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AbsTime</name>
+          <direction>out</direction>
+          <relatedStateVariable>AbsoluteTimePosition</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RelCount</name>
+          <direction>out</direction>
+          <relatedStateVariable>RelativeCounterPosition</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AbsCount</name>
+          <direction>out</direction>
+          <relatedStateVariable>AbsoluteCounterPosition</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetRemainingSleepTimerDuration</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RemainingSleepTimerDuration</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_ISO8601Time</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSleepTimerGeneration</name>
+          <direction>out</direction>
+          <relatedStateVariable>SleepTimerGeneration</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetRunningAlarmProperties</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AlarmID</name>
+          <direction>out</direction>
+          <relatedStateVariable>AlarmIDRunning</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>GroupID</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_GroupID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>LoggedStartTime</name>
+          <direction>out</direction>
+          <relatedStateVariable>AlarmLoggedStartTime</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetTransportInfo</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentTransportState</name>
+          <direction>out</direction>
+          <relatedStateVariable>TransportState</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentTransportStatus</name>
+          <direction>out</direction>
+          <relatedStateVariable>TransportStatus</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSpeed</name>
+          <direction>out</direction>
+          <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetTransportSettings</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PlayMode</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RecQualityMode</name>
+          <direction>out</direction>
+          <relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <action>
+      <name>Next</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>NextProgrammedRadioTracks</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>NextSection</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>NotifyDeletedURI</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DeletedURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Pause</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Play</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Speed</name>
+          <direction>in</direction>
+          <relatedStateVariable>TransportPlaySpeed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Previous</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>PreviousSection</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Record</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RemoveAllTracksFromQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RemoveTrackFromQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RemoveTrackRangeFromQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>StartingIndex</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumberOfTracks</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ReorderTracksInQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>StartingIndex</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NumberOfTracks</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>InsertBefore</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackNumber</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ReorderTracksInSavedQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>UpdateID</name>
+          <direction>in</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>TrackList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackList</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewPositionList</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_TrackList</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>QueueLenghtChange</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracksChange</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewQueueLength</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_NumTracks</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewUpdateID</name>
+          <direction>out</direction>
+          <relatedStateVariable>QueueUpdateID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RunAlarm</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AlarmID</name>
+          <direction>in</direction>
+          <relatedStateVariable>AlarmIDRunning</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>LoggedStartTime</name>
+          <direction>in</direction>
+          <relatedStateVariable>AlarmLoggedStartTime</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Duration</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ISO8601Time</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PlayMode</name>
+          <direction>in</direction>
+          <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Volume</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmVolume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>IncludeLinkedZones</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmIncludeLinkedZones</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SaveQueue</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Title</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SavedQueueTitle</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>AssignedObjectID</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Seek</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Unit</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SeekMode</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Target</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_SeekTarget</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetAVTransportURI</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetCrossfadeMode</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CrossfadeMode</name>
+          <direction>in</direction>
+          <relatedStateVariable>CurrentCrossfadeMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetNextAVTransportURI</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NextURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>NextAVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NextURIMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>NextAVTransportURIMetaData</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetPlayMode</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewPlayMode</name>
+          <direction>in</direction>
+          <relatedStateVariable>CurrentPlayMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetRecordQualityMode</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewRecordQualityMode</name>
+          <direction>in</direction>
+          <relatedStateVariable>CurrentRecordQualityMode</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SnoozeAlarm</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Duration</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ISO8601Time</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>StartAutoplay</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramMetaData</name>
+          <direction>in</direction>
+          <relatedStateVariable>AVTransportURIMetaData</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Volume</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmVolume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>IncludeLinkedZones</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_AlarmIncludeLinkedZones</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ResetVolumeAfter</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ResetVolumeAfter</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Stop</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/0.5/S_RenderingControl1.xml
===================================================================
--- /tags/0.5/S_RenderingControl1.xml	(revision 30)
+++ /tags/0.5/S_RenderingControl1.xml	(revision 30)
@@ -0,0 +1,1247 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <!-- $Id: S_SonosRenderingControl1.xml 121 2013-10-01 19:33:24Z lolodomo $ -->
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Channel</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>Master</allowedValue>
+        <allowedValue>LF</allowedValue>
+        <allowedValue>RF</allowedValue>
+        <allowedValue>CF</allowedValue>
+        <allowedValue>LFE</allowedValue>
+        <allowedValue>LS</allowedValue>
+        <allowedValue>RS</allowedValue>
+        <allowedValue>LFC</allowedValue>
+        <allowedValue>RFC</allowedValue>
+        <allowedValue>SD</allowedValue>
+        <allowedValue>SL</allowedValue>
+        <allowedValue>SR</allowedValue>
+        <allowedValue>T</allowedValue>
+        <allowedValue>B</allowedValue>
+        <!-- Sonos -->
+        <allowedValue>SpeakerOnly</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ChannelMap</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_EQType</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_InstanceID</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_LeftVolume</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <maximum>100</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_PresetName</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>FactoryDefaults</allowedValue>
+        <allowedValue>InstallationDefaults</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ProgramURI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_RampTimeSeconds</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_RampType</name>
+      <dataType>string</dataType>
+      <allowedValueList>
+        <allowedValue>SLEEP_TIMER_RAMP_TYPE</allowedValue>
+        <allowedValue>ALARM_RAMP_TYPE</allowedValue>
+        <allowedValue>AUTOPLAY_RAMP_TYPE</allowedValue>
+      </allowedValueList>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ResetVolumeAfter</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_RightVolume</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <maximum>100</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_VolumeAdjustment</name>
+      <dataType>i4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AudioDelay</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AudioDelayLeftRear</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>AudioDelayRightRear</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>Bass</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <minimum>-10</minimum>
+        <maximum>10</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>DialogLevel</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>EQValue</name>
+      <dataType>i2</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>HeadphoneConnected</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="yes">
+      <name>LastChange</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Loudness</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Mute</name>
+      <shortCode>mute</shortCode>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>NightMode</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>OutputFixed</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>PresetNameList</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SpeakerSize</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SubCrossover</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SubEnabled</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SubGain</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SubPolarity</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SupportsOutputFixed</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SurroundEnabled</name>
+      <dataType>boolean</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>SurroundLevel</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <!-- Sonos -->
+    <stateVariable sendEvents="no">
+      <name>Treble</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <minimum>-10</minimum>
+        <maximum>10</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Volume</name>
+      <shortCode>volume</shortCode>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>VolumeDB</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <minimum>-32767</minimum>
+        <maximum>32767</maximum>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Brightness</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Contrast</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>Sharpness</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RedVideoGain</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>GreenVideoGain</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>BlueVideoGain</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>RedVideoBlackLevel</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>GreenVideoBlackLevel</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>BlueVideoBlackLevel</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>ColorTemperature</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>HorizontalKeystone</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>VerticalKeystone</name>
+      <dataType>i2</dataType>
+      <allowedValueRange>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetBass</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentBass</name>
+          <direction>out</direction>
+          <relatedStateVariable>Bass</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetEQ</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EQType</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EQType</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentValue</name>
+          <direction>out</direction>
+          <relatedStateVariable>EQValue</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetHeadphoneConnected</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentHeadphoneConnected</name>
+          <direction>out</direction>
+          <relatedStateVariable>HeadphoneConnected</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetLoudness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentLoudness</name>
+          <direction>out</direction>
+          <relatedStateVariable>Loudness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetMute</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentMute</name>
+          <direction>out</direction>
+          <relatedStateVariable>Mute</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetOutputFixed</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentFixed</name>
+          <direction>out</direction>
+          <relatedStateVariable>OutputFixed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetSupportsOutputFixed</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSupportsFixed</name>
+          <direction>out</direction>
+          <relatedStateVariable>SupportsOutputFixed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+<!--
+    <action>
+      <name>GetTreble</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentTreble</name>
+          <direction>out</direction>
+          <relatedStateVariable>Treble</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+<!--
+    <action>
+      <name>GetVolume</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>Volume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetVolumeDB</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>VolumeDB</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetVolumeDBRange</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>MinValue</name>
+          <direction>out</direction>
+          <relatedStateVariable>VolumeDB</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>MaxValue</name>
+          <direction>out</direction>
+          <relatedStateVariable>VolumeDB</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetBrightness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentBrightness</name>
+          <direction>out</direction>
+          <relatedStateVariable>Brightness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetContrast</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentContrast</name>
+          <direction>out</direction>
+          <relatedStateVariable>Contrast</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetSharpness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentSharpness</name>
+          <direction>out</direction>
+          <relatedStateVariable>Sharpness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetRedVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentRedVideoGain</name>
+          <direction>out</direction>
+          <relatedStateVariable>RedVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetGreenVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentGreenVideoGain</name>
+          <direction>out</direction>
+          <relatedStateVariable>GreenVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetBlueVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentBlueVideoGain</name>
+          <direction>out</direction>
+          <relatedStateVariable>BlueVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetRedVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentRedVideoBlackLevel</name>
+          <direction>out</direction>
+          <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetGreenVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentGreenVideoBlackLevel</name>
+          <direction>out</direction>
+          <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetBlueVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentBlueVideoBlackLevel</name>
+          <direction>out</direction>
+          <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetColorTemperature </name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentColorTemperature</name>
+          <direction>out</direction>
+          <relatedStateVariable>ColorTemperature</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetHorizontalKeystone</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentHorizontalKeystone</name>
+          <direction>out</direction>
+          <relatedStateVariable>HorizontalKeystone</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>GetVerticalKeystone</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentVerticalKeystone</name>
+          <direction>out</direction>
+          <relatedStateVariable>VerticalKeystone</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>ListPresets</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>CurrentPresetNameList</name>
+          <direction>out</direction>
+          <relatedStateVariable>PresetNameList</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+ -->
+    <!-- Sonos -->
+    <action>
+      <name>RampToVolume</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RampType</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_RampType</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredVolume</name>
+          <direction>in</direction>
+          <relatedStateVariable>Volume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ResetVolumeAfter</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ResetVolumeAfter</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ProgramURI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ProgramURI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RampTime</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_RampTimeSeconds</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ResetBasicEQ</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Bass</name>
+          <direction>out</direction>
+          <relatedStateVariable>Bass</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Treble</name>
+          <direction>out</direction>
+          <relatedStateVariable>Treble</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Loudness</name>
+          <direction>out</direction>
+          <relatedStateVariable>Loudness</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>LeftVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_LeftVolume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>RightVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>A_ARG_TYPE_RightVolume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>ResetExtEQ</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EQType</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EQType</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>RestoreVolumePriorToRamp</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SelectPreset</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>PresetName</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_PresetName</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetBass</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredBass</name>
+          <direction>in</direction>
+          <relatedStateVariable>Bass</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetChannelMap</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ChannelMap</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ChannelMap</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetEQ</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>EQType</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_EQType</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredValue</name>
+          <direction>in</direction>
+          <relatedStateVariable>EQValue</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetLoudness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredLoudness</name>
+          <direction>in</direction>
+          <relatedStateVariable>Loudness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetMute</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredMute</name>
+          <direction>in</direction>
+          <relatedStateVariable>Mute</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetOutputFixed</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredFixed</name>
+          <direction>in</direction>
+          <relatedStateVariable>OutputFixed</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetRelativeVolume</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Adjustment</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_VolumeAdjustment</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>NewVolume</name>
+          <direction>out</direction>
+          <relatedStateVariable>Volume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <!-- Sonos -->
+    <action>
+      <name>SetTreble</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredTreble</name>
+          <direction>in</direction>
+          <relatedStateVariable>Treble</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetVolume</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredVolume</name>
+          <direction>in</direction>
+          <relatedStateVariable>Volume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetVolumeDB</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Channel</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Channel</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredVolume</name>
+          <direction>in</direction>
+          <relatedStateVariable>VolumeDB</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetBrightness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredBrightness</name>
+          <direction>in</direction>
+          <relatedStateVariable>Brightness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetContrast</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredContrast</name>
+          <direction>in</direction>
+          <relatedStateVariable>Contrast</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetSharpness</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredSharpness</name>
+          <direction>in</direction>
+          <relatedStateVariable>Sharpness</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetRedVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredRedVideoGain</name>
+          <direction>in</direction>
+          <relatedStateVariable>RedVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetGreenVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredGreenVideoGain</name>
+          <direction>in</direction>
+          <relatedStateVariable>GreenVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetBlueVideoGain</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredBlueVideoGain</name>
+          <direction>in</direction>
+          <relatedStateVariable>BlueVideoGain</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetRedVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredRedVideoBlackLevel</name>
+          <direction>in</direction>
+          <relatedStateVariable>RedVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetGreenVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredGreenVideoBlackLevel</name>
+          <direction>in</direction>
+          <relatedStateVariable>GreenVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetBlueVideoBlackLevel</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredBlueVideoBlackLevel</name>
+          <direction>in</direction>
+          <relatedStateVariable>BlueVideoBlackLevel</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetColorTemperature</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredColorTemperature</name>
+          <direction>in</direction>
+          <relatedStateVariable>ColorTemperature</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetHorizontalKeystone</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredHorizontalKeystone</name>
+          <direction>in</direction>
+          <relatedStateVariable>HorizontalKeystone</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SetVerticalKeystone</name>
+      <argumentList>
+        <argument>
+          <name>InstanceID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_InstanceID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>DesiredVerticalKeystone</name>
+          <direction>in</direction>
+          <relatedStateVariable>VerticalKeystone</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+  </actionList>
+</scpd>
+
Index: /tags/0.5/L_DLNAUPnPUtility.lua
===================================================================
--- /tags/0.5/L_DLNAUPnPUtility.lua	(revision 30)
+++ /tags/0.5/L_DLNAUPnPUtility.lua	(revision 30)
@@ -0,0 +1,1045 @@
+module("L_DLNAUPnPUtility", package.seeall)
+
+local url = require("socket.url")
+local socket = require("socket")
+local http = require("socket.http")
+local ltn12 = require("ltn12")
+
+-- 5 Second timeout
+http.TIMEOUT = 5
+
+local UPNP_DISCOVERY = "M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 5\r\nST: %s\r\n\r\n"
+
+local UPNP_REQUEST = [[<?xml version="1.0" encoding="utf-8"?>
+<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
+<s:Body>
+<u:%s xmlns:u="%s">%s</u:%s>
+</s:Body>
+</s:Envelope>]]
+
+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 VeraIP = ""
+local log = print
+local warning = print
+local error = print
+local contentType
+local Services = {}
+
+function initialize(veraIP, logger, warningLogger, errorLogger, ct)
+  VeraIP = veraIP
+  log = logger
+  warning = warningLogger
+  error = errorLogger
+  contentType = ct or 'text/xml; charset="utf-8"'
+end
+
+function decode(val)
+      return val:gsub("&#38;", '&')
+                :gsub("&#60;", '<')
+                :gsub("&#62;", '>')
+                :gsub("&#34;", '"')
+                :gsub("&#39;", "'")
+                :gsub("&lt;", "<")
+                :gsub("&gt;", ">")
+                :gsub("&quot;", '"')
+                :gsub("&apos;", "'")
+                :gsub("&amp;", "&")
+end
+
+local function encode(val)
+      return val:gsub("&", "&amp;")
+                :gsub("<", "&lt;")
+                :gsub(">", "&gt;")
+                :gsub('"', "&quot;")
+                :gsub("'", "&apos;")
+end
+
+function unformatXML(val)
+      return val:gsub("^%s+<", "<")
+                :gsub(">%s+$", ">")
+                :gsub(">%s+<", "><")
+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
+
+-- Send a UPnP SUBSCRIBE to the UPnP device,
+-- Return value on success:
+--   SID (subscription ID) provided by the device.
+--   Duration (in seconds) before the subscription must be renewed
+-- Return value on failure:
+--   nil
+--   Reason for failure (string)
+-- Code coming from Wemo plugin (function subscribeToDevice) and adjusted
+function UPnP_subscribe(ipAddress, ipPort, serverURL, callbackURL, renewalSID)
+	local eventSubURL = serverURL:format(ipAddress, ipPort)
+
+	-- Create a socket with a timeout.
+	local sock = function()
+		local s = socket.tcp()
+		s:settimeout(http.TIMEOUT)
+		return s
+	end
+
+	local headers = {
+		["TIMEOUT"] = "Second-3600",
+	}
+
+	if (renewalSID) then
+		-- Renewing, include SID header.
+		headers["SID"] = renewalSID
+	else
+		-- New subscription, include CALLBACK and NT headers
+		headers["CALLBACK"] = string.format("<%s>", callbackURL)
+		headers["NT"] = "upnp:event"
+	end
+
+	-- Ask the device to inform the proxy about status changes.
+	local request, code, headers = http.request({
+		url = eventSubURL,
+		method = "SUBSCRIBE",
+		headers = headers,
+		create = sock,
+	})
+
+	if (request == nil and code ~= "closed") then
+		error("Failed to subscribe to " .. eventSubURL .. ": " .. code)
+		return nil, code
+	elseif (code ~= 200) then
+		error("Failed to subscribe to " .. eventSubURL .. ": " .. code)
+		return nil, code
+	else
+		local duration = headers["timeout"]:match("Second%-(%d+)")
+		log("Subscription confirmed, SID = " .. headers["sid"] .. " with timeout " .. duration)
+		return headers["sid"], tonumber(duration)
+	end
+end
+  
+  
+function UPnP_request(ipAddress, ipPort, serverURL, action, servicetype, args)
+  local function table2XML(value)
+    local result = ""
+
+    if (args == nil) then
+        return result
+    end
+
+    --
+    -- Convert all the Number, Boolean and Table objects, and escape all the string
+    -- values in the XML output stream
+    --
+    -- If value table has an entry OrderedArgs, we consider that all the values
+    -- are set in this special entry and we bypass all the other values of the value table.
+    -- In this particular case, this entry itself is a table of strings, each element of
+    -- the table following the format "parameter=value"
+    --
+    if (value.OrderedArgs ~= nil and type(value.OrderedArgs) == "table") then
+        for i, val in ipairs(value.OrderedArgs) do
+            local e, v = val:match("([^=]+)=(.*)")
+            if (e ~= nil) then
+                if (v == nil) then
+                    result = result .. string.format("<%s />", e)
+                elseif (type(v) == "table") then
+                    result = result .. table2XML(v)
+                elseif (type(v) == "number") then
+                    result = result .. string.format("<%s>%.0f</%s>", e, v, e)
+                elseif (type(v) == "boolean") then
+                    result = result .. string.format("<%s>%s</%s>", e, (v and "1" or "0"), e)
+                else
+                    result = result .. string.format("<%s>%s</%s>", e, encode(v), e)
+                end
+            end
+        end
+    else
+        for e, v in pairs(value) do
+            if (v == nil) then
+                result = result .. string.format("<%s />", e)
+            elseif (type(v) == "table") then
+                result = result .. table2XML(v)
+            elseif (type(v) == "number") then
+                result = result .. string.format("<%s>%.0f</%s>", e, v, e)
+            elseif (type(v) == "boolean") then
+                result = result .. string.format("<%s>%s</%s>", e, (v and "1" or "0"), e)
+            else
+                result = result .. string.format("<%s>%s</%s>", e, encode(v), e)
+            end
+        end
+    end
+
+    return result
+  end
+
+  local postBody = string.format(UPNP_REQUEST, action, servicetype, table2XML(args), action)
+  serverURL = serverURL:format(ipAddress, ipPort)
+  log(string.format("UPnP_request: url=[%s], body=[%s]", serverURL, postBody))
+
+  --
+  -- Execute the resulting URL, and collect the results as a Table
+  --
+  local resultTable = {}
+  local status, statusMsg = http.request{
+    url = serverURL,
+    sink = ltn12.sink.table(resultTable),
+    method = "POST",
+    headers = {["Accept"] = "*/*",
+               ["SOAPAction"] = '"' .. servicetype .. "#" .. action .. '"',
+               ["Connection"] = "close",
+               ["Content-Length"] = postBody:len(),
+               ["Content-Type"] = contentType},
+    source = ltn12.source.string(postBody),
+  }
+
+  --
+  -- Flatten the resultTable into a regular string
+  --
+  local data = ""
+  for i, v in ipairs(resultTable) do
+    data = data .. v
+  end
+  
+  if (status == nil) then
+    --
+    -- Handle TIMEOUT
+    --
+    return false, statusMsg or "An error occurred during the UPnP call"
+  end
+  
+  if (tostring(statusMsg) == "200") then
+    --
+    -- Handle SUCCESS
+    --
+    log(string.format("UPnP_request: status=%s statusMsg=%s result=[%s]", status or "no status",
+                      statusMsg or "no message",
+                      data or "no result"))
+
+    if (data == nil or data == "") then
+      return true, ""
+    else
+      local pattern = string.format('<.-:Body><.-:%sResponse%%sxmlns:.-="urn:.-">(.*)</.-:%sResponse></.-:Body></.-:Envelope>',
+                                    action, action)
+      local value = unformatXML(data):match(pattern) or ""
+
+      -- TODO: Handle UPnP Error responses
+      -- TODO: Handle Non-Scalar results
+
+      -- 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 true, value
+    end
+  else
+    --
+    -- UNKNOWN ERROR
+    --
+    error(string.format("UPnP_request (%s, %s): status=%s statusMsg=%s result=[%s]",
+                      action or "no action",
+                      servicetype or "no servicetype",
+                      status or "no status",
+                      statusMsg or "no message",
+                      data or "no result"))
+
+    assert("Unhandled Response statusMsg=" .. statusMsg)
+  end
+end -- function UPnP_request
+
+--
+-- Return a handle to a UPnP Service object.
+-- This can be used to call arbitrary Service Methods on that service using dynamic naming foo.
+--
+function service(ip, port, controlURL, servicetype, actions)
+    local self = {}
+    local mt = {}
+
+    mt.__index = function(table, key)
+        log("service.__index: Accessing non-existing function " .. key)
+
+        local fn = function(...)
+            if (actions[key]) then
+                log(string.format("%s('%s', %d, '%s', '%s') Called with parameter count=%d", key, ip, port, controlURL, servicetype, select("#", ...)))
+                return UPnP_request(ip, port, controlURL, key, servicetype, ...)
+            else
+                return false, "action not available"
+            end
+        end
+
+        table[key] = fn
+        return fn
+    end
+
+    setmetatable(self, mt)
+
+    return self
+end
+
+
+--------------------------------------------------------------------------------
+-- UPnP discovery
+--------------------------------------------------------------------------------
+
+
+function UPnP_discover(target)
+    local devices = {}
+    local udp = socket.udp()
+    if (udp ~= nil) then
+        local result, message = udp:sendto(UPNP_DISCOVERY:format(target), "239.255.255.250", 1900)
+        if (result ~= nil) then
+            udp:settimeout(5)
+            for i=1,500 do
+                result, message = udp:receivefrom()
+                if (result == nil) then
+                    break
+                else
+                    local location, ip, port = result:match("[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:%s?(http://([%d%.]-):(%d+)/.-)\r\n")
+                    local st = result:match("[Ss][Tt]:%s?(.-)\r\n")
+                    if (location ~= nil and ip ~= nil and port ~= nil and st ~= nil) then
+                        local new = true
+                        for i,device in ipairs(devices) do
+                            if (device.descriptionURL == location and device.st == st) then
+                                new = false
+                                break
+                            end
+                        end
+                        if (new) then
+                            table.insert(devices, { descriptionURL=location, ip=ip, port=port, st=st })
+                        end
+                    end
+                end
+            end
+        end
+        udp:close()
+    end
+    return devices
+end
+
+
+function scanUPnPDevices(deviceType, infos)
+    local xml = "<devices>"
+    local devices = UPnP_discover(deviceType)
+    for i,dev in ipairs(devices) do
+        local descrXML = UPnP_getDeviceDescription(dev.descriptionURL)
+        if (descrXML ~= nil) then
+            xml = xml .. "<device>"
+            xml = xml .. "<ip>" .. (dev.ip or "") .. "</ip>"
+            xml = xml .. "<port>" .. (dev.port or "") .. "</port>"
+            xml = xml .. "<descriptionURL>" .. (dev.descriptionURL or "") .. "</descriptionURL>"
+            local values = getInfosFromDescription(descrXML, deviceType, infos)
+            if (infos ~= nil) then
+                for i,tag in ipairs(infos) do
+                    xml = xml .. "<" .. tag .. ">" .. (values[tag] or "") .. "</" .. tag .. ">"
+                end
+            end
+            xml = xml .. "</device>"
+        end
+    end
+    xml = xml .. "</devices>"
+    return xml
+end
+
+
+function searchUPnPDevices(deviceType, name, ip)
+    local devices = UPnP_discover(deviceType)
+    for i,dev in ipairs(devices) do
+        if (ip == nil or ip == "" or ip == dev.ip) then
+            local descrXML = UPnP_getDeviceDescription(dev.descriptionURL)
+            if (descrXML ~= nil) then
+                local values = getInfosFromDescription(descrXML, deviceType, { "modelName" })
+                if (values.modelName == name) then
+                    return dev.descriptionURL
+                end
+            end
+        end
+    end
+    return nil
+end
+
+
+--------------------------------------------------------------------------------
+-- UPnP device and services description
+--------------------------------------------------------------------------------
+
+
+function UPnP_getDeviceDescription(descriptionURL)
+    local status, xml = luup.inet.wget(descriptionURL, 2)
+    if (status ~= 0) then
+        xml = nil
+    end
+    return xml
+end
+
+
+function getInfosFromDescription(descriptionXML, deviceType, infos)
+    local data = {}
+    local tag, value, devices, device
+    if (infos ~= nil) then
+        local rootDevPart1, embeddedDevices, rootDevPart2 = descriptionXML:match("(<device%s?[^>]->.-)<deviceList%s?[^>]->(.-)</deviceList>(.-</device>)")
+        if (rootDevPart1 == nil or rootDevPart2 == nil or embeddedDevices == nil) then
+            devices = descriptionXML:match("(<device%s?[^>]->.*</device>)") or ""
+        elseif (deviceType ~= nil) then
+            devices = rootDevPart1 .. rootDevPart2 .. embeddedDevices
+        else
+            devices = rootDevPart1 .. rootDevPart2
+        end
+        for device in devices:gmatch("<device%s?[^>]->(.-)</device>") do
+            value = device:match("<deviceType>(.+)</deviceType>")
+            if (deviceType == nil or value == deviceType) then
+                for i,tag in ipairs(infos) do
+                    value = device:match("<"..tag..">(.+)</"..tag..">")
+                    if (value ~= nil) then
+                        data[tag] = value
+                    end
+                end
+            end
+        end
+    end
+    return data
+end
+
+
+function getIconFromDescription(descriptionXML)
+    local resultURL = nil
+    local rootDevice, icon, size, height, width, icnoURL
+    local rootDevPart1, embeddedDevices, rootDevPart2 = descriptionXML:match("(<device%s?[^>]->.-)<deviceList%s?[^>]->(.-)</deviceList>(.-</device>)")
+    if (rootDevPart1 == nil or rootDevPart2 == nil or embeddedDevices == nil) then
+        rootDevice = descriptionXML:match("(<device%s?[^>]->.*</device>)") or ""
+    else
+        rootDevice = rootDevPart1 .. rootDevPart2
+    end
+    size = 0
+    for icon in rootDevice:gmatch("<icon%s?[^>]->(.-)</icon>") do
+        height = icon:match("<height>(.+)</height>")
+        width = icon:match("<width>(.+)</width>")
+        icnoURL = icon:match("<url>(.+)</url>")
+        if (icnoURL ~= nil and height ~= nil and width ~= nil) then
+            height = tonumber(height)
+            width = tonumber(width)
+            if (height >= width and height > size) then
+                size = tonumber(height)
+                resultURL = icnoURL
+            elseif (height < width and width > size) then
+                size = tonumber(width)
+                resultURL = icnoURL
+            end
+        end
+    end
+    return resultURL
+end
+
+
+function getActionsFromSCPD(scpdURL)
+    local actions = {}
+    local action, name
+    local status, xml = luup.inet.wget(scpdURL, 2)
+    if (status == 0) then
+        for action in xml:gmatch("<action>(.-)</action>") do
+            name = action:match("<name>(.-)</name>")
+            if (name ~= nil) then
+                actions[name] = true
+            end
+        end
+    end
+    return actions
+end
+
+
+function getServicesFromDescription(descriptionXML, deviceType, baseURL, subsetServices)
+    local services = {}
+    local value, devices, device, service, service2, serviceType, serviceId, controlURL, eventSubURL
+    if (subsetServices ~= nil) then
+        local rootDevPart1, embeddedDevices, rootDevPart2 = descriptionXML:match("(<device%s?[^>]->.-)<deviceList%s?[^>]->(.-)</deviceList>(.-</device>)")
+        if (rootDevPart1 == nil or rootDevPart2 == nil or embeddedDevices == nil) then
+            devices = descriptionXML:match("(<device%s?[^>]->.*</device>)") or ""
+        elseif (deviceType ~= nil) then
+            devices = rootDevPart1 .. rootDevPart2 .. embeddedDevices
+        else
+            devices = rootDevPart1 .. rootDevPart2
+        end
+        for device in devices:gmatch("<device%s?[^>]->(.-)</device>") do
+            value = device:match("<deviceType>(.+)</deviceType>")
+            if (deviceType == nil or value == deviceType) then
+                for service in device:gmatch("<service%s?[^>]->(.-)</service>") do
+                    serviceType = service:match("<serviceType>(.*)</serviceType>")
+                    serviceId = service:match("<serviceId>(.*)</serviceId>")
+                    controlURL = service:match("<controlURL>(.*)</controlURL>")
+                    eventSubURL = service:match("<eventSubURL>(.*)</eventSubURL>")
+                    scpdURL = service:match("<SCPDURL>(.*)</SCPDURL>")
+                    if (serviceType ~= nil and serviceId ~= nil
+                           and controlURL ~= nil and eventSubURL ~= nil
+                           and scpdURL ~= nil) then
+                        for i,service2 in ipairs(subsetServices) do
+                            if (service2 == serviceType) then
+                               services[serviceType] = {
+                                       controlURL = url.absolute(baseURL, controlURL),
+                                       eventSubURL = url.absolute(baseURL, eventSubURL),
+                                       scpdURL = url.absolute(baseURL, scpdURL),
+                                       serviceId = serviceId,
+                                       actions = getActionsFromSCPD(url.absolute(baseURL, scpdURL)),
+                                       object = nil }
+                                break
+                            end
+                        end
+                    end
+                end
+            end
+        end
+    end
+    return services
+end
+
+
+--------------------------------------------------------------------------------
+-- Services management
+--------------------------------------------------------------------------------
+
+
+  function aresServicesLoaded(uuid)
+    if (uuid ~= nil and uuid ~= "" and Services[uuid] ~= nil) then
+        return true
+    else
+        return false
+    end
+  end
+
+  function getService(uuid, serviceType)
+    local s = nil
+    if (aresServicesLoaded(uuid) and Services[uuid][serviceType] ~= nil) then
+        s = Services[uuid][serviceType].object
+    end
+    return s
+  end
+
+  function resetServices(uuid)
+    if (aresServicesLoaded(uuid)) then
+        Services[uuid] = nil
+    end
+  end
+
+  function addServices(uuid, ip, port, descriptionXML, deviceType, subsetServices)
+    if (uuid == nil or uuid == "") then
+        return
+    end
+
+    if (Services[uuid] == nil) then
+        Services[uuid] = {}
+    end
+
+    local services = getServicesFromDescription(
+                              descriptionXML, deviceType,
+                              string.format("http://%s:%s/", ip, port),
+                              subsetServices)
+    for k,v in pairs(services) do
+        Services[uuid][k] = v
+    end
+
+    for k,v in pairs(Services[uuid]) do
+        log(k .. " " .. v.serviceId .. " " .. v.controlURL .. " " .. v.eventSubURL .. " " .. v.scpdURL)
+        if (v.object == nil) then
+            v.object = service(ip, port, v.controlURL, k, v.actions)
+        end
+    end
+  end
+
+
+
+  function testURL(URLtoBeChecked, headers)
+    local sock = function()
+        local s = socket.tcp()
+        s:settimeout(2)
+        return s
+    end
+
+    if (headers == nil) then
+        headers = {}
+    end
+
+    local resultHeaders
+    local request, code, resultHeaders = http.request({
+        url = URLtoBeChecked,
+        method = "HEAD",
+        headers = headers,
+        create = sock
+    })
+
+    if (request ~= nil and code == 200) then
+        return true, resultHeaders
+    else
+        return false, {}
+    end
+  end
+
+
+  --------------------------------------------------------------------------------
+  -- UPnP AV metadata (DIDL-Lite parsing)
+  --------------------------------------------------------------------------------
+
+
+  -- parseElt(value, tag, subTag)
+  -- Parse XML data at the second or third depth level
+  -- If there are several elements, only the first is taken in consideration
+  -- Parameters:
+  --   value is the XML data
+  --   tag is the first depth level XML tag
+  --   subTag is the second depth level XML tag or nil if data have to be retrieved from the second depth level
+  -- Return values:
+  --   multilines string containing all parsed values; each line is formatted like that: tag="value"
+  --   table containing parsed data (attributes + values) indexed either by the tag or tag@attribute
+  function parseFirstElt(value, tag, subTag)
+      local pattern, tmp, attributes, token, value1, attr, value2
+      pattern = string.format("<%s(%%s?[^>]-)>(.*)</%s>", tag, tag)
+      attributes, tmp = value:match(pattern)
+      if (tmp ~= nil and subTag ~= nil) then
+          -- Get the content of the first sub tag
+          pattern = string.format("<%s(%%s?[^>]-)>(.-)</%s>", subTag, subTag)
+          attributes, tmp = tmp:match(pattern)
+      end
+
+      if (tmp == nil) then
+          return nil, {}
+      end
+
+      local elts, eltsTable = "", {}
+
+      for attr, value1 in attributes:gmatch('%s(.-)="(.-)"') do
+          eltsTable["@" .. attr] = value1
+      end
+	
+      for token, attributes, value1 in tmp:gmatch("<([a-zA-Z0-9:]+)(%s?[^>]-)>(.-)</[^>]->") do
+          elts = elts .. string.format('%s="%s"\n', token, value1)
+          eltsTable[token] = value1
+          for attr, value2 in attributes:gmatch('%s(.-)="(.-)"') do
+              eltsTable[token .. "@" .. attr] = value2
+          end
+      end
+
+      return elts, eltsTable
+  end
+
+  -- parseDIDLItem(value)
+  -- Parse XML data for the item of a DIDL UPnP AV defining metadata XML meta data
+  -- Parameters:
+  --   value is the DIDL XML data
+  -- Return values:
+  --   multilines string containing all parsed values; each line is formatted like that: tag="value"
+  --   table containing parsed data (attributes + values) indexed either by the tag or tag@attribute
+  function parseDIDLItem(value)
+      return parseFirstElt(value, "DIDL%-Lite", "item")
+  end
+
+
+  function extractElement(tag, xml, default)
+    if (xml == nil) then
+      return(default)
+    else
+      local pattern = string.format("<%s%%s?[^>]->(.-)</%s>", tag, tag)
+      local result = xml:match(pattern)
+      if (result == nil) then
+          result = default
+      else
+          result = decode(result)
+      end
+      return result
+    end
+  end
+
+
+  local function mergeDIDL(xml1, xml2)
+    local result = ""
+    local val1 = nil
+    local val2 = nil
+    local val3 = nil
+    local val4 = nil
+    local val5 = nil
+    local val6 = nil
+    local tag = "DIDL%-Lite"
+    if (xml1 ~= nil and xml1 ~= "") then
+        val1, val2, val3 = xml1:match("(<"..tag.."%s?[^>]->)(.-)(</"..tag..">)")
+    end
+    if (xml2 ~= nil and xml2 ~= "") then
+        val4, val5, val6 = xml2:match("(<"..tag.."%s?[^>]->)(.-)(</"..tag..">)")
+    end
+    if (val1 and val2 and val3 and val4 and val5 and val6) then
+        result = val1 .. val2 .. val5 .. val3
+    elseif (val1 and val2 and val3) then
+        result = xml1
+    elseif (val4 and val5 and val6) then
+        result = xml2
+    elseif (xml1:match("(<"..tag.."%s?[^>]-/>)") ~= nil) then
+        result = xml1
+    elseif (xml2:match("(<"..tag.."%s?[^>]-/>)") ~= nil) then
+        result = xml2
+    end
+    return result
+  end
+
+
+  function browseContent(uuid, serviceType, browseObj, onlyMetadata)
+      local result = ""
+      local ContentDirectory = getService(uuid, serviceType)
+      if (ContentDirectory == nil) then
+          return result
+      end
+
+      local browseFlag
+      if (onlyMetadata) then
+          browseFlag = "BrowseMetadata"
+      else
+          browseFlag = "BrowseDirectChildren"
+      end
+
+      local fetched = 0
+      local total = 0
+      local status, tmp
+      repeat
+          status, tmp = ContentDirectory.Browse({OrderedArgs={
+                                 "ObjectID=" .. browseObj,
+                                 "BrowseFlag=" .. browseFlag,
+                                 "Filter=*",
+                                 "StartingIndex=" .. string.format("%d", fetched),
+                                 "RequestedCount=100",
+                                 "SortCriteria="}})
+          if (status == true) then
+              result = mergeDIDL(result, unformatXML(extractElement("Result", tmp, "")))
+              fetched = fetched + tonumber(extractElement("NumberReturned", tmp, ""))
+              total = tonumber(extractElement("TotalMatches", tmp, ""))
+          else
+              total = 0
+          end
+      until fetched >= total
+      return result
+  end
+
+
+  --------------------------------------------------------------------------------
+  -- UPnP Event Proxy
+  --------------------------------------------------------------------------------
+
+
+  -- XML pattern used when notifying the UPnP event proxy of the the subscription to an event
+  local PROXY_REQUEST = "<subscription expiry='%d'><variable name='%s' host='localhost' deviceId='%d' serviceId='%s' action='%s' parameter='%s' sidParameter='sid'/></subscription>"
+
+  -- Version of the UPnP event proxy, or nil if proxy is not running or not used
+  local ProxyApiVersion = nil
+
+
+  -- getProxyApiVersion()
+  -- Calls the proxy with GET /version.
+  -- Sets the ProxyApiVersion global variable to the value received
+  -- Return value:
+  --   nil if the proxy is not running.
+  --   The proxy API version (as a string) otherwise.
+  function getProxyApiVersion()
+    local sock = function()
+        local s = socket.tcp()
+        s:settimeout(2)
+        return s
+    end
+
+    local t = {}
+    local request, code = http.request({
+        url = "http://localhost:2529/version",
+        create = sock,
+        sink = ltn12.sink.table(t)
+    })
+
+    if (request == nil and code == "timeout") then
+        -- Proxy may be busy.
+        warning("Temporarily cannot communicate with proxy")
+        return nil
+    elseif (request == nil and code ~= "closed") then
+        -- Proxy not running.
+        error("Cannot contact UPnP event proxy: " .. code)
+        return ""
+    else
+        -- Proxy is running, note its version number.
+        ProxyApiVersion = table.concat(t)
+        return ProxyApiVersion
+    end
+  end
+
+
+  -- WeMo plugin contrib
+  -- proxyVersionAtLeast(n)
+  -- Returns true if the proxy is running and is at least version n.
+  function proxyVersionAtLeast(n)
+    if (ProxyApiVersion and tonumber(ProxyApiVersion:match("^(%d+)")) >= n) then
+        return true
+    end
+    return false
+  end
+
+
+  -- unuseProxy()
+  -- Disable the usage of the UPnP event proxy by resetting the variable ProxyApiVersion to nil
+  -- Return value: none
+  function unuseProxy()
+    ProxyApiVersion = nil
+    warning("UPnP event proxy is now unused")
+  end
+
+
+  -- subscribeToUPnPEvent(device, ip, port, serverURL, eventVariable, actionServiceId, actionName, renewalSID)
+  -- Process a new subscription to an event for an UPnP device, or renew an active subscription
+  -- First send a subscription request to the UPnP device and then notify the proxy of this new subscription
+  -- Parameters:
+  --   device is the device id that the UPnP event proxy will notify
+  --   ip is the IP address of the UPnP device
+  --   port is the port to be used to communicate with the UPnP device
+  --   serverURL is the URL to be used to subscribe to the UPnP device
+  --   eventVariable is the UPnP variable name we subscribe to
+  --   actionServiceId is the service id of the action to be called by the UPnP event proxy
+  --   actionName is the action name to be called by the UPnP event proxy
+  --   renewalSID is the subscription id to be renewed or nil if it is an initial subscription
+  -- Return values:
+  --   subscription id or nil if the subscribe process failed
+  --   expiry date of the new or renewed subscription; nil if the subscribe process failed
+  --   live duration of the new or renewed subscription; nil if the subscribe process failed 
+  function subscribeToUPnPEvent(device, ip, port, serverURL, eventVariable, actionServiceId, actionName, renewalSID)
+    log("Subscribing to event " .. serverURL .. " for variable " .. eventVariable .. " with SID " .. (renewalSID or "nil"))
+
+	local sock = function()
+		local s = socket.tcp()
+		s:settimeout(5)
+		return s
+	end
+
+	-- Ask the device to inform the proxy about status changes.
+	local expiry = nil
+	local sid, duration = UPnP_subscribe(ip, port, serverURL, "http://" .. VeraIP .. ":2529/upnp/event", renewalSID)
+	if (sid) then
+		expiry = os.time() + duration
+
+		-- Tell proxy about this subscription and the variable we care about.
+		-- Volume is the variable we care about.
+		local proxyRequestBody = PROXY_REQUEST:format(expiry, eventVariable, device, actionServiceId, actionName, eventVariable)
+		local request, code = http.request({
+			url = "http://localhost:2529/upnp/event/" .. url.escape(sid),
+			create = sock,
+			method = "PUT",
+			headers = {
+				["Content-Type"] = "text/xml",
+				["Content-Length"] = proxyRequestBody:len(),
+			},
+			source = ltn12.source.string(proxyRequestBody),
+			sink = ltn12.sink.null(),
+		})
+-- TODO retry if timeout
+		if (request == nil and code ~= "closed") then
+			error("Failed to notify proxy of subscription: " .. code)
+			sid = nil
+			expiry = nil
+		elseif (code ~= 200) then
+			error("Failed to notify proxy of subscription: " .. code)
+			sid = nil
+			expiry = nil
+		else
+			log("Successfully notified proxy of subscription")
+		end
+	end
+
+	return sid, expiry, duration
+  end
+
+
+  -- WeMo plugin contrib
+  -- cancelProxySubscription(sid)
+  -- Sends a DELETE /upnp/event/[sid] message to the UPnP event proxy,
+  -- Return value:
+  --   nil if the proxy timed out (should try again).
+  --   false if the proxy refused our request (permanently).
+  --   true if the proxy agreed to our request.
+  function cancelProxySubscription(sid)
+    log("Cancelling subscription for sid " .. sid)
+    local sock = function()
+        local s = socket.tcp()
+        s:settimeout(2)
+        return s
+    end
+
+    local request, code = http.request({
+        url = "http://localhost:2529/upnp/event/" .. url.escape(sid),
+        create = sock,
+        method = "DELETE",
+        source = ltn12.source.empty(),
+        sink = ltn12.sink.null(),
+    })
+    if (request == nil and code ~= "closed") then
+        error("Failed to cancel subscription: " .. code)
+        return nil
+    elseif (code ~= 200) then
+        error("Failed to cancel subscription: " .. code)
+        return false
+    else
+        log("Successfully cancelled subscription")
+        return true
+    end
+  end
+
+
+  -- subscribeToEvents(device, ip, port, subscriptions, actionServiceId, uuid)
+  -- Process the subscription to several events for an UPnP device, or renew these subscriptions
+  -- The subscriptions are defined in a table (subscriptions).
+  -- Each element of this table is enhanced with subsciption data (id, expiry date, ...).
+  -- Before calling this function, the UPnP services for the UPnP device have to be loaded. Only
+  -- events associated to a loaded and registered service will be subscribed to.
+  -- If one event subscription fails, all subscriptions are finally cancelled.
+  -- A timer is set to call the function renewSubscriptions, so that all the subscritopns can be
+  -- renewed in time. So this function "renewSubscriptions" must be declared by the plugin.
+  -- Parameters:
+  --   device is the device id that the UPnP event proxy will notify
+  --   ip is the IP address of the UPnP device
+  --   port is the port to be used to communicate with the UPnP device
+  --   subscriptions is a table defining the events to be subscribed to
+  --   actionServiceId is the service id for all actions to be called by the UPnP event proxy
+  --   uuid is the UUID of the UPnP device
+  -- Return value:
+  --   true if UPnP event proxy is not running or not used
+  --   true if the UPnP services are not yet loaded and registered
+  --   true if all subscriptions succeeded
+  --   false if subscriptions failed
+  function subscribeToEvents(device, ip, port, subscriptions, actionServiceId, uuid)
+    log("Subscribing to all events")
+
+    if (proxyVersionAtLeast(1) == false) then
+        return true
+    end
+    if (aresServicesLoaded(uuid) == false) then
+        return true
+    end
+
+    local result = true
+
+    local sid, expiry, duration
+
+    local nbSubscriptions = 0
+    local minDuration = 3600
+
+    for i,subscription in ipairs(subscriptions) do
+        if (Services[uuid][subscription.service] ~= nil) then
+            sid = nil
+            if (subscription.id ~= "") then
+                sid = subscription.id
+            end
+            sid, expiry, duration = subscribeToUPnPEvent(device,
+                                                         ip,
+                                                         port,
+                                                         Services[uuid][subscription.service].eventSubURL,
+                                                         subscription.eventVariable,
+                                                         actionServiceId,
+                                                         subscription.actionName,
+                                                         sid)
+            if (sid ~= nil) then
+                log("Event subscription succeeded => SID " .. sid .. " duration " .. duration .. " expiry " .. expiry)
+                subscription.id = sid
+                subscription.expiry = expiry
+                nbSubscriptions = nbSubscriptions + 1
+                if (duration < minDuration) then
+                    minDuration = duration
+                end
+            else
+                error("Event subscription failed: " .. Services[uuid][subscription.service].eventSubURL)
+                cancelProxySubscriptions(subscriptions)
+                nbSubscriptions = 0
+                result = false
+                break
+            end
+        end
+    end
+
+    if (nbSubscriptions > 0) then
+        if (minDuration >= 600) then
+            minDuration = minDuration - 300
+        end
+
+        luup.call_delay("renewSubscriptions", minDuration, device .. ":" .. uuid)
+    end
+
+    return result
+  end
+
+
+  -- cancelProxySubscriptions(subscriptions)
+  -- Sends a DELETE /upnp/event/[sid] message to the UPnP event proxy for all subscriptions
+  -- defined in the table subscriptions.
+  -- Subscription data for each element of this table are reset.
+  -- Parameters:
+  --   subscriptions is a table containing the current subscription data
+  -- Return value: none
+  function cancelProxySubscriptions(subscriptions)
+    log("Cancelling all event subscriptions")
+
+    if (proxyVersionAtLeast(1) == false) then
+        return
+    end
+
+    for i,subscription in ipairs(subscriptions) do
+        if (subscription.id ~= "") then
+            cancelProxySubscription(subscription.id)
+            subscription.id = ""
+            subscription.expiry = ""
+        end
+    end
+  end
+
+
+  -- isValidNotification(notifyAction, sid, subscriptions)
+  -- Check whether a proxy notification is valid (the subscription id has to be declared in the
+  -- table containing current subscription data)
+  -- If not, a delayed action cancelProxySubscription is called to unsubscribe this subscription.
+  -- So this function "cancelProxySubscription" must be declared by the plugin.
+  -- Parameters:
+  --   notifyAction is the action called by the UPnP event proxy
+  --   sid is the subscription id provided by the UPnP event proxy
+  --   subscriptions is a table containing the current subscription data
+  -- Return value:
+  --   true if the proxy notification is valid
+  --   false if the proxy notification is not valid
+  function isValidNotification(notifyAction, sid, subscriptions)
+    local valid = false
+    if (proxyVersionAtLeast(1) == false) then
+        warning("Call to " .. notifyAction .. " while proxy is not used")
+    else
+        for i,subscription in ipairs(subscriptions) do
+            if (subscription.id == sid) then
+                valid = true
+                break
+            end
+        end
+        if (not valid) then
+            warning("Call to " .. notifyAction .. " with bad SID " .. sid)
+        end
+    end
+    if (not valid) then
+        -- Try to shut the proxy up, we don't care about this SID.
+        luup.call_delay("cancelProxySubscription", 1, sid)
+    end
+    return valid
+  end
Index: /tags/0.5/S_DLNAMediaController1.xml
===================================================================
--- /tags/0.5/S_DLNAMediaController1.xml	(revision 30)
+++ /tags/0.5/S_DLNAMediaController1.xml	(revision 30)
@@ -0,0 +1,193 @@
+<?xml version="1.0"?>
+<scpd xmlns="urn:schemas-upnp-org:service-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <serviceStateTable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_URI</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Volume</name>
+      <dataType>ui2</dataType>
+      <allowedValueRange>
+        <minimum>0</minimum>
+        <maximum>100</maximum>
+        <step>1</step>
+      </allowedValueRange>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Duration</name>
+      <dataType>ui4</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Text</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Language</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Name</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_IP</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_ObjectID</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_Protocol</name>
+      <dataType>string</dataType>
+    </stateVariable>
+    <stateVariable sendEvents="no">
+      <name>A_ARG_TYPE_LastChange</name>
+      <dataType>string</dataType>
+    </stateVariable>
+  </serviceStateTable>
+  <actionList>
+    <action>
+      <name>Play</name>
+      <argumentList>
+        <argument>
+          <name>URI</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Protocol</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Protocol</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Volume</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Volume</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Duration</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Duration</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>Say</name>
+      <argumentList>
+        <argument>
+          <name>Text</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Text</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Language</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Language</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Volume</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Volume</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SavePlaybackContext</name>
+    </action>
+    <action>
+      <name>RestorePlaybackContext</name>
+    </action>
+    <action>
+      <name>StartDMRDiscovery</name>
+    </action>
+    <action>
+      <name>SelectDMRDevice</name>
+      <argumentList>
+        <argument>
+          <name>URL</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>SearchAndSelectDMRDevice</name>
+      <argumentList>
+        <argument>
+          <name>Name</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Name</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>IP</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_IP</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>StartDMSDiscovery</name>
+    </action>
+    <action>
+      <name>BrowseDMS</name>
+      <argumentList>
+        <argument>
+          <name>DescriptionURL</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>PlayDMSMedia</name>
+      <argumentList>
+        <argument>
+          <name>DescriptionURL</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>ObjectID</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
+        </argument>
+        <argument>
+          <name>Protocol</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_Protocol</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>NotifyRenderingChange</name>
+      <argumentList>
+        <argument>
+          <name>LastChange</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_LastChange</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+    <action>
+      <name>NotifyAVTransportChange</name>
+      <argumentList>
+        <argument>
+          <name>LastChange</name>
+          <direction>in</direction>
+          <relatedStateVariable>A_ARG_TYPE_LastChange</relatedStateVariable>
+        </argument>
+      </argumentList>
+    </action>
+  </actionList>
+</scpd>
Index: /tags/0.5/D_DLNAMediaController1.xml
===================================================================
--- /tags/0.5/D_DLNAMediaController1.xml	(revision 30)
+++ /tags/0.5/D_DLNAMediaController1.xml	(revision 30)
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<root xmlns="urn:schemas-upnp-org:device-1-0">
+  <specVersion>
+    <major>1</major>
+    <minor>0</minor>
+  </specVersion>
+  <device>
+    <friendlyName>DLNA DMC</friendlyName>
+    <manufacturer>Digital Living Network Alliance</manufacturer>
+    <manufacturerURL>http://www.dlna.org</manufacturerURL>
+    <modelDescription>DLNA Digital Media Controller</modelDescription>
+
+    <deviceType>urn:dlna-org:device:DLNAMediaController:1</deviceType>
+	<staticJson>D_DLNAMediaController1.json</staticJson>
+
+    <serviceList>
+      <service>
+        <serviceType>urn:dlna-org:service:DLNAMediaController:1</serviceType>
+        <serviceId>urn:dlna-org:serviceId:DLNAMediaController1</serviceId>
+        <SCPDURL>S_DLNAMediaController1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:micasaverde-com:service:MediaNavigation:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:MediaNavigation1</serviceId>
+        <SCPDURL>S_MediaNavigation1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:micasaverde-com:service:Volume:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:Volume1</serviceId>
+        <SCPDURL>S_Volume1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:schemas-upnp-org:service:AVTransport:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:AVTransport</serviceId>
+        <SCPDURL>S_AVTransport1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:schemas-upnp-org:service:RenderingControl:1</serviceType>
+        <serviceId>urn:upnp-org:serviceId:RenderingControl</serviceId>
+        <SCPDURL>S_RenderingControl1.xml</SCPDURL>
+      </service>
+
+      <service>
+        <serviceType>urn:schemas-micasaverde-com:service:HaDevice:1</serviceType>
+        <serviceId>urn:micasaverde-com:serviceId:HaDevice1</serviceId>
+        <SCPDURL>S_HaDevice1.xml</SCPDURL>
+      </service>
+
+    </serviceList>
+
+    <implementationList>
+      <implementationFile>I_DLNAMediaController1.xml</implementationFile>
+    </implementationList>
+  </device>
+</root>
Index: /tags/0.5/J_DLNAMediaController1.js
===================================================================
--- /tags/0.5/J_DLNAMediaController1.js	(revision 30)
+++ /tags/0.5/J_DLNAMediaController1.js	(revision 30)
@@ -0,0 +1,1133 @@
+var DLNA_timeoutVar = undefined;
+var DLNA_timeoutVar2 = undefined;
+var DLNA_browserIE = false
+var DLNA_parseXml = undefined;
+
+var DLNA_prevControls = undefined;
+var DLNA_prevOnlineState = undefined;
+var DLNA_prevCurrentAlbumArtUrl = undefined;
+var DLNA_prevTitle = undefined;
+var DLNA_prevAlbum = undefined;
+var DLNA_prevArtist = undefined;
+var DLNA_prevDuration = undefined;
+var DLNA_prevPlaying = undefined;
+var DLNA_prevActions = undefined;
+var DLNA_prevMute = undefined;
+var DLNA_prevVolume = undefined;
+
+var DLNA_prevModelName = undefined;
+var DLNA_prevUrl = undefined;
+var DLNA_prevOnlineState2 = undefined;
+var DLNA_prevProxy = undefined;
+var DLNA_prevResultDiscovery = undefined;
+var DLNA_prevResultDiscovery2 = undefined;
+var DLNA_prevResultBrowse = undefined;
+var DLNA_prevSinkProtocols = undefined;
+var DLNA_prevResultPlayback = undefined;
+
+var DLNA_mediaObjects = new Array();
+var DLNA_selectedMedia = undefined;
+var DLNA_loading = false;
+var DLNA_lastSelectedDMS = '';
+var DLNA_lastPlayedMediaObject = '';
+var DLNA_lastUsedProtocol = '';
+
+var DLNA_DMC_SID = 'urn:dlna-org:serviceId:DLNAMediaController1';
+var AVTRANSPORT_SID = 'urn:upnp-org:serviceId:AVTransport';
+var RENDERING_CONTROL_SID = 'urn:upnp-org:serviceId:RenderingControl';
+var MEDIA_NAVIGATION_SID = 'urn:micasaverde-com:serviceId:MediaNavigation1';
+var VOLUME_SID = 'urn:micasaverde-com:serviceId:Volume1';
+var UPNP_CONNECTION_MANAGER_SID = 'urn:upnp-org:serviceId:ConnectionManager';
+
+function DLNA_showPlayer(device)
+{
+	if (typeof DLNA_timeoutVar != 'undefined') {
+		clearTimeout(DLNA_timeoutVar);
+	}
+
+	DLNA_detectBrowser();
+
+	DLNA_initXMLParser();
+
+	var html = '';
+
+	html += '<table>';
+	html += '<tr>';
+	html += '<td rowspan=4><img id="albumArt" width="100" height="100"/></td>';
+	html += '<td id="artistLabel">Artist:</td>';
+	html += '<td id="artist"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td id="albumLabel">Album:</td>';
+	html += '<td id="album"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td id="titleLabel">Track title:</td>';
+	html += '<td id="title"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td id="durationLabel"></td>';
+	html += '<td id="duration"></td>';
+	html += '</tr>';
+	html += '</table>';
+	html += '<DIV>'
+	html += '<table>';
+	html += '<tr>';
+	html += '<td>';
+	html += '<button id="prevTrack" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_prevTrack('+device+');">Prev</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="play" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_play('+device+');">Play</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="pause" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_pause('+device+');">Pause</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="stop" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_stop('+device+');">Stop</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="nextTrack" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_nextTrack('+device+');">Next</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="mute" type="button" style="background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_mute('+device+');">Mute</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<input id="controls" type="checkbox" checked value="1">Buttons state controlled by Renderer';
+	html += '</td>';
+	html += '</tr>';
+	html += '</table>';
+	html += '</DIV>'
+	html += '<DIV>'
+	html += '<table>';
+	html += '<tr>';
+	html += '<td>URI:</td>';
+	html += '<td>';
+	html += '<input id="uri" type="text" style="margin-right: 15px; width: 227px"/>';
+	html += 'Duration:';
+	html += '<input id="duration1" type="text" value="" style="margin-left: 5px; margin-right: 16px; width: 35px"/>';
+	html += 'Volume:';
+	html += '<input id="volume1" type="text" value="" style="margin-left: 5px; width: 20px"/>';
+	html += '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Protocol:</td>';
+	html += '<td>';
+	html += '<select id="sinkProtocols"/>';
+	html += '<button id="playUri" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_playUri('+device+');">Play</button>';
+	html += '</td>';
+	html += '</tr>';
+
+	var languages = [	[ "en", "English" ],
+						[ "fr", "French" ],
+						[ "it", "Italian" ],
+						[ "es", "Spanish" ],
+						[ "ru", 'Russian' ] ];
+	var defaultLanguage = get_device_state(device, DLNA_DMC_SID, "DefaultLanguageTTS", 1);
+	if (defaultLanguage == undefined) {
+		defaultLanguage = 'en';
+	}
+
+	html += '<tr>';
+	html += '<td>Text To Speech:</td>';
+	html += '<td>';
+	html += '<input id="text" type="text" style="margin-right: 10px; width: 227px"/>';
+	html += '<select id="language1" style="margin-right: 5px;" onChange="DLNA_selectLang();">';
+	for (i=0; i<languages.length; i++) {
+		html += '<option';
+		if (languages[i][0] == defaultLanguage) {
+			html += ' selected';
+		}
+		html += ' value="' + languages[i][0] + '">' + languages[i][1] + '</option>';
+	}
+	html += '</select>';
+	html += '<input id="language" type="text" value="' + defaultLanguage + '" style="margin-right: 10px; width: 20px"/>';
+	html += 'Volume:';
+	html += '<input id="volume" type="text" value="" style="margin-left: 5px; width: 20px"/>';
+	html += '<button id="say" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_say('+device+');">Say</button>';
+	html += '</td>';
+	html += '</tr>';
+	html += '<td>Media:</td>';
+	html += '<td><select id="media" onChange="DLNA_selectMedia();"/></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Protocol:</td>';
+	html += '<td><select id="protocols"/></td>';
+	html += '</tr>';
+	html += '</table>';
+	html += '<table>';
+	html += '<tr>';
+	html += '<td>';
+	html += '<button id="discover" type="button" style="background-color:lightgrey; height: 25px; width: 150px" onclick="DLNA_startDMSDiscovery('+device+');">Discover Servers</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="browse" type="button" style="background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_browseMedia('+device+');">Browse</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="back" type="button" style="background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_browseBack('+device+');">Back</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<button id="top" type="button" style="background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_browseTop('+device+');">Top</button>';
+	html += '</td>';
+	html += '<td><label id="status"/></td>';
+	html += '</tr>';
+	html += '</table>';
+	html += '</DIV>'
+	
+	//html += '<p id="debug">';
+
+	set_panel_html(html);
+
+	DLNA_prevControls = undefined;
+	DLNA_prevOnlineState = undefined;
+	DLNA_prevCurrentAlbumArtUrl = undefined;
+	DLNA_prevTitle = undefined;
+	DLNA_prevAlbum = undefined;
+	DLNA_prevArtist = undefined;
+	DLNA_prevDuration = undefined;
+	DLNA_prevPlaying = undefined;
+	DLNA_prevActions = undefined;
+	DLNA_prevMute = undefined;
+	DLNA_prevVolume = undefined;
+	DLNA_prevResultDiscovery = undefined;
+	DLNA_prevResultBrowse = undefined;
+	DLNA_prevSinkProtocols = undefined;
+	DLNA_prevResultPlayback = undefined;
+
+	DLNA_refreshPlayer(device);
+}
+
+function DLNA_showSettings(device)
+{
+	if (typeof DLNA_timeoutVar2 != 'undefined') {
+		clearTimeout(DLNA_timeoutVar2);
+	}
+
+	DLNA_detectBrowser();
+
+	DLNA_initXMLParser();
+
+	var html = '';
+
+	html += '<table cellspacing="10">';
+	html += '<tr><td colspan=2><u>Current settings</u></td></tr>';
+	html += '<tr>';
+	html += '<td>DMR model:</td>';
+	html += '<td id="model"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Description URL:</td>';
+	html += '<td id="url"></td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>State:</td>';
+	html += '<td>';
+	html += '<label id="state"/>';
+	html += '<button id="checkState" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_checkState('+device+');">Check</button>';
+	html += '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>UPnP Event proxy:</td>';
+	html += '<td id="proxy"></td>';
+	html += '</tr>';
+	html += '<tr><td colspan=2><u>New settings</u></td></tr>';
+	html += '<tr><td colspan=2>Push "discover" button, then select your Media Renderer in the list and finally push "Select" button.';
+	html += '<BR>If UPnP discovery is not working, fill in your DMR description URL and finally push "Select" button.</td></tr>';
+	html += '<tr>';
+	html += '<td>';
+	html += '<button id="DMRdiscover" type="button" style="background-color:lightgrey; height: 25px; width: 90px" onclick="DLNA_startDMRDiscovery('+device+');">Discover</button>';
+	html += '</td>';
+	html += '<td>';
+	html += '<select id="DMRdiscovery"/>';
+	html += '<button id="selectDMRDiscovery" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 75px" onclick="DLNA_selectDMRDiscovery('+device+');">Select</button>';
+	html += '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>New URL:</td>';
+	html += '<td>';
+	html += '<input id="newURL" type="text" style="width: 380px"/>';
+	html += '<button id="selectURL" type="button" style="margin-left: 10px; background-color:lightgrey; height: 25px; width: 50px" onclick="DLNA_selectURL('+device+');">Select</button>';
+	html += '</td>';
+	html += '</tr>';
+	html += '</table>';
+
+	//html += '<p id="debug">';
+
+	set_panel_html(html);
+
+	DLNA_prevModelName = undefined;
+	DLNA_prevUrl = undefined;
+	DLNA_prevOnlineState2 = undefined;
+	DLNA_prevProxy = undefined;
+	DLNA_prevResultDiscovery2 = undefined;
+
+	DLNA_refreshDiscovery(device);
+}
+
+function DLNA_showHelp(device)
+{
+	var version = get_device_state(device, DLNA_DMC_SID, "PluginVersion", 1);
+	if (version == undefined) {
+		version = '';
+	}
+
+	var html = '<table cellspacing="10">';
+	html += '<tr>';
+	html += '<td>Plugin version:</td>';
+	html += '<td>' + version + '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>DMS description URL:</td>';
+	html += '<td>' + DLNA_lastSelectedDMS + '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Last server media played:</td>';
+	html += '<td>' + DLNA_lastPlayedMediaObject + '</td>';
+	html += '</tr>';
+	html += '<tr>';
+	html += '<td>Selected protocol:</td>';
+	html += '<td>' + DLNA_lastUsedProtocol + '</td>';
+	html += '</tr>';
+	html += '</table>';
+
+	var variables = [	[ AVTRANSPORT_SID, "AVTransportURI" ],
+						[ AVTRANSPORT_SID, "AVTransportURIMetaData" ],
+						[ AVTRANSPORT_SID, "CurrentTrackURI" ],
+						[ AVTRANSPORT_SID, "CurrentTrackMetaData" ],
+						[ UPNP_CONNECTION_MANAGER_SID, 'SinkProtocolInfo' ] ];
+	html += '<table border="1">';
+	html += '<tr align="center" bgcolor="LightBlue">';
+	html += '<th>Variable</td>';
+	html += '<th>Value</td>';
+	html += '</tr>';
+	for (i=0; i<variables.length; i++) {
+		var value = get_device_state(device, variables[i][0], variables[i][1], 1);
+		if (value == undefined) {
+			value = '';
+		}
+		html += '<tr>';
+		html += '<td>' + variables[i][1] + '</td>';
+		html += '<td>' + DLNA_escapeHtmlSpecialChars(value) + '</td>';
+		html += '</tr>';
+	}
+	html += '</table>';
+
+	html += '<BR>';
+
+	set_panel_html(html);
+}
+
+function DLNA_detectBrowser()
+{
+	var place = navigator.userAgent.toLowerCase().indexOf('msie');
+	if (place == -1) {
+		DLNA_browserIE = false;
+	}
+	else {
+		DLNA_browserIE = true;
+	}
+}
+
+function DLNA_initXMLParser()
+{
+	if (typeof DLNA_parseXml == 'undefined') {
+		if (typeof window.DOMParser != "undefined") {
+			DLNA_parseXml = function(xmlStr) {
+				return ( new window.DOMParser() ).parseFromString(xmlStr, "text/xml");
+			};
+		}
+		else if (typeof window.ActiveXObject != "undefined" && new window.ActiveXObject("Microsoft.XMLDOM")) {
+			DLNA_parseXml = function(xmlStr) {
+				var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
+				xmlDoc.async = "false";
+				xmlDoc.loadXML(xmlStr);
+				return xmlDoc;
+			};
+		}
+		else {
+			DLNA_parseXml = function(xmlStr) {
+				return undefined;
+			};
+		}
+	}
+}
+
+function DLNA_extractXmlTag(parent, tag)
+{
+	var value = undefined;
+	for (j=0; j<parent.childNodes.length; j++) {
+		if (parent.childNodes[j].tagName == tag) {
+			value = parent.childNodes[j].textContent;
+			break;
+		}
+	}
+	return value;
+}
+
+function DLNA_extractXmlAttribute(node, attribute)
+{
+	var value = node.getAttribute(attribute);
+	return value;
+}
+
+function DLNA_escapeHtmlSpecialChars(unsafe)
+{
+	return unsafe
+			.replace(/&/g, "&amp;")
+			.replace(/</g, "&lt;")
+			.replace(/>/g, "&gt;")
+			.replace(/"/g, "&quot;");
+}
+
+function DLNA_refreshPlayer(device)
+{
+	var controls = $('controls').checked;
+	var onlineState = get_device_state(device, DLNA_DMC_SID, "Online", 1);
+	if (onlineState == undefined) {
+		onlineState = '1';
+	}
+	var currentAlbumArtUrl = get_device_state(device, AVTRANSPORT_SID, "CurrentAlbumArt2", 1);
+	if (currentAlbumArtUrl == undefined) {
+		currentAlbumArtUrl = '';
+	}
+	var title = get_device_state(device, AVTRANSPORT_SID, "CurrentTitle", 1);
+	if (title == undefined) {
+		title = '';
+	}
+	var album = get_device_state(device, AVTRANSPORT_SID, "CurrentAlbum", 1);
+	if (album == undefined) {
+		album = '';
+	}
+	var artist = get_device_state(device, AVTRANSPORT_SID, "CurrentArtist", 1);
+	if (artist == undefined) {
+		artist = '';
+	}
+	var duration = get_device_state(device, AVTRANSPORT_SID, "CurrentTrackDuration", 1);
+	if (duration == undefined || duration == 'NOT_IMPLEMENTED'
+			|| duration == '0:00:00' || duration == '00:00:00') {
+		duration = '';
+	}
+	var playing = get_device_state(device, AVTRANSPORT_SID, "TransportState", 1);
+	if (playing == undefined) {
+		playing = '';
+	}
+	var actions = get_device_state(device, AVTRANSPORT_SID, "CurrentTransportActions", 1);
+	if (actions == undefined) {
+		actions = '';
+	}
+	actions = actions.toLowerCase();
+	var mute = get_device_state(device, RENDERING_CONTROL_SID, "Mute", 1);
+	if (mute == undefined || mute == "") {
+		mute = '0';
+	}
+	var volume = get_device_state(device, RENDERING_CONTROL_SID, "Volume", 1);
+	if (volume == undefined || volume == "") {
+		volume = 0;
+	}
+	var resultDiscovery = get_device_state(device, DLNA_DMC_SID, "DMSDiscoveryResult", 1);
+	if (resultDiscovery == undefined) {
+		resultDiscovery = '';
+	}
+	var resultBrowse = get_device_state(device, DLNA_DMC_SID, "BrowseResult", 1);
+	if (resultBrowse == undefined) {
+		resultBrowse = '';
+	}
+	var sinkProtocols = get_device_state(device, UPNP_CONNECTION_MANAGER_SID, "SinkProtocolInfo", 1);
+	if (sinkProtocols == undefined) {
+		sinkProtocols = '';
+	}
+	var resultPlayback = get_device_state(device, DLNA_DMC_SID, "PlaybackResult", 1);
+	if (resultPlayback == undefined) {
+		resultPlayback = '';
+	}
+
+	if (controls != DLNA_prevControls
+		|| onlineState != DLNA_prevOnlineState
+		|| currentAlbumArtUrl != DLNA_prevCurrentAlbumArtUrl
+		|| title != DLNA_prevTitle
+		|| album != DLNA_prevAlbum
+		|| artist != DLNA_prevArtist
+		|| duration != DLNA_prevDuration
+		|| playing != DLNA_prevPlaying
+		|| actions != DLNA_prevActions
+		|| mute != DLNA_prevMute
+		|| volume != DLNA_prevVolume
+		|| resultDiscovery != DLNA_prevResultDiscovery
+		|| resultBrowse != DLNA_prevResultBrowse
+		|| sinkProtocols != DLNA_prevSinkProtocols
+		|| resultPlayback != DLNA_prevResultPlayback) {
+
+		$('albumArt').src = currentAlbumArtUrl;
+
+		var label;
+
+		if (title != '') {
+			label = 'Title:';
+		}
+		else {
+			label = '';
+		}
+		$('titleLabel').innerHTML = label;
+		$('title').innerHTML = title;
+
+		if (album != '') {
+			label = 'Album:';
+		}
+		else {
+			label = '';
+		}
+		$('albumLabel').innerHTML = label;
+		$('album').innerHTML = album;
+
+		if (artist != '') {
+			label = 'Artist:';
+		}
+		else {
+			label = '';
+		}
+		$('artistLabel').innerHTML = label;
+		$('artist').innerHTML = artist;
+
+		if (duration != '') {
+			label = 'Duration:';
+		}
+		else {
+			label = '';
+		}
+		$('durationLabel').innerHTML = label;
+		$('duration').innerHTML = duration;
+
+		if (onlineState != '1') {
+			$('titleLabel').innerHTML = 'Offline';
+			$('title').innerHTML = '';
+			$('play').style.backgroundColor = 'lightgrey';
+			$('pause').style.backgroundColor = 'lightgrey';
+			$('stop').style.backgroundColor = 'lightgrey';
+			$('play').disabled = true;
+			$('pause').disabled = true;
+			$('stop').disabled = true;
+			$('prevTrack').disabled = true;
+			$('nextTrack').disabled = true;
+			$('controls').disabled = true;
+		}
+		else {
+			$('controls').disabled = false;
+			if (playing == 'PLAYING' || playing == 'TRANSITIONING') {
+				$('play').style.backgroundColor = 'darkgray';
+				$('pause').style.backgroundColor = 'lightgrey';
+				$('stop').style.backgroundColor = 'lightgrey';
+			}
+			else if (playing == 'PAUSED_PLAYBACK') {
+				$('play').style.backgroundColor = 'lightgrey';
+				$('pause').style.backgroundColor = 'darkgray';
+				$('stop').style.backgroundColor = 'lightgrey';
+			}
+			else if (playing == 'STOPPED') {
+				$('play').style.backgroundColor = 'lightgrey';
+				$('pause').style.backgroundColor = 'lightgrey';
+				$('stop').style.backgroundColor = 'darkgray';
+			}
+			else {
+				$('play').style.backgroundColor = 'lightgrey';
+				$('pause').style.backgroundColor = 'lightgrey';
+				$('stop').style.backgroundColor = 'lightgrey';
+			}
+
+			if (controls == false) {
+				$('play').disabled = false;
+				$('pause').disabled = false;
+				$('stop').disabled = false;
+				$('prevTrack').disabled = false;
+				$('nextTrack').disabled = false;
+			}
+			else {
+				if (actions.indexOf('play', 0) >= 0) {
+					$('play').disabled = false;
+				}
+				else {
+					$('play').disabled = true;
+				}
+				if (actions.indexOf('pause', 0) >= 0) {
+					$('pause').disabled = false;
+				}
+				else {
+					$('pause').disabled = true;
+				}
+				if (actions.indexOf('stop', 0) >= 0) {
+					$('stop').disabled = false;
+				}
+				else {
+					$('stop').disabled = true;
+				}
+				if (actions.indexOf('previous', 0) < 0) {
+					$('prevTrack').disabled = true;
+				}
+				else {
+					$('prevTrack').disabled = false;
+				}
+				if (actions.indexOf('next', 0) < 0) {
+					$('nextTrack').disabled = true;
+				}
+				else {
+					$('nextTrack').disabled = false;
+				}
+			}
+		}
+
+		if (mute == '1' || mute == 'true' || mute == 'yes') {
+			$('mute').style.backgroundColor = 'darkgray';
+		}
+		else {
+			$('mute').style.backgroundColor = 'lightgrey';
+		}
+
+		if (onlineState == '1') {
+			$('mute').disabled = false;
+			$('playUri').disabled = false;
+			$('say').disabled = false;
+		}
+		else {
+			$('mute').disabled = true;
+			$('playUri').disabled = true;
+			$('say').disabled = true;
+		}
+
+		if (sinkProtocols != DLNA_prevSinkProtocols) {
+			var html = '';
+			var values;
+			var protocol;
+			html = '<option selected value="">None</option>';
+			values = sinkProtocols.split(',');
+			for (i=0; i<values.length; i++) {
+				if (values[i].length > 50) {
+					protocol = values[i].substr(0, 50) + '...';
+				}
+				else {
+					protocol = values[i];
+				}
+				html += '<option value="' + values[i] + '">' + protocol + '</option>';
+			}
+			$('sinkProtocols').innerHTML = html;
+			if (DLNA_browserIE) {
+				$('sinkProtocols').outerHTML=$('sinkProtocols').outerHTML.replace($('sinkProtocols').innerHTML+'</select>',html+'</select>');
+			}
+		}
+
+		if (resultDiscovery != DLNA_prevResultDiscovery
+			|| resultBrowse != DLNA_prevResultBrowse) {
+
+			if (resultBrowse == '') {
+				DLNA_selectedMedia = undefined;
+				DLNA_mediaObjects = new Array();
+			}
+
+			if (DLNA_mediaObjects.length == 0) {
+				if (resultDiscovery == 'scanning') {
+					DLNA_loading = true;
+					$('media').innerHTML = '';
+					$('discover').disabled = true;
+					$('browse').disabled = true;
+					$('back').disabled = true;
+					$('top').disabled = true;
+				}
+				else if (resultDiscovery == '') {
+					DLNA_loading = false;
+					$('media').innerHTML = '';
+					$('discover').disabled = false;
+					$('browse').disabled = true;
+					$('back').disabled = true;
+					$('top').disabled = true;
+				}
+				else {
+					$('discover').disabled = false;
+					$('browse').disabled = false;
+					$('back').disabled = false;
+					$('top').disabled = false;
+
+					var html = '';
+					var xml = DLNA_parseXml(resultDiscovery);
+					if (typeof xml != 'undefined') {
+						var items = xml.getElementsByTagName("device");
+						for (i=0; i<items.length; i++) {
+							url = DLNA_extractXmlTag(items[i], 'descriptionURL');
+							if (url == undefined) {
+								url = '';
+							}
+							modelName = DLNA_extractXmlTag(items[i], 'friendlyName');
+							if (modelName == undefined) {
+								modelName = '';
+							}
+							// var ip = DLNA_extractXmlTag(items[i], 'ip');
+							// if (ip == undefined) {
+								// ip = '';
+							// }
+							url = 'server:' + url;
+							html += '<option ';
+							if (url == DLNA_selectedMedia) {
+								html += 'selected ';
+							}
+							// html += 'value="' + url + '">' + modelName + ' (IP ' + ip + ')</option>';
+							html += 'value="' + url + '">' + modelName + '</option>';
+						}
+					}
+					$('media').innerHTML = html;
+					if (DLNA_browserIE) {
+						$('media').outerHTML=$('media').outerHTML.replace($('media').innerHTML+'</select>',html+'</select>');
+					}
+					DLNA_loading = false;
+				}
+			}
+			else if (resultBrowse == 'loading') {
+				DLNA_disableDMSCmds();
+			}
+			else {
+				$('discover').disabled = false;
+				$('browse').disabled = false;
+				$('back').disabled = false;
+				$('top').disabled = false;
+
+				var html = '';
+				var xml = DLNA_parseXml(resultBrowse);
+				if (typeof xml != 'undefined') {
+					var title2;
+					var id;
+					var containers = xml.getElementsByTagName("container");
+					for (i=0; i<containers.length; i++) {
+						title2 = DLNA_extractXmlTag(containers[i], 'dc:title');
+						if (title2 == undefined) {
+							title2 = '';
+						}
+						if (title2.length > 60) {
+							title2 = title2.substr(0, 60) + '...';
+						}
+						id = DLNA_extractXmlAttribute(containers[i], 'id');
+						if (id == undefined) {
+							id = '';
+						}
+						id = 'container:' + id;
+						html += '<option ';
+						if (id == DLNA_selectedMedia) {
+							html += 'selected ';
+						}
+						html += 'value="' + id + '">+ ' + title2 + '</option>';
+					}
+					var items = xml.getElementsByTagName("item");
+					for (i=0; i<items.length; i++) {
+						title2 = DLNA_extractXmlTag(items[i], 'dc:title');
+						if (title2 == undefined) {
+							title2 = '';
+						}
+						if (title2.length > 60) {
+							title2 = title2.substr(0, 60) + '...';
+						}
+						id = DLNA_extractXmlAttribute(items[i], 'id');
+						if (id == undefined) {
+							id = '';
+						}
+						id = 'item:' + id;
+						html += '<option ';
+						if (id == DLNA_selectedMedia) {
+							html += 'selected ';
+						}
+						html += 'value="' + id + '">' + title2 + '</option>';
+					}
+				}
+				$('media').innerHTML = html;
+				if (DLNA_browserIE) {
+					$('media').outerHTML=$('media').outerHTML.replace($('media').innerHTML+'</select>',html+'</select>');
+				}
+				DLNA_loading = false;
+			}
+			DLNA_prevResultBrowse = resultBrowse;
+			DLNA_prevOnlineState = onlineState;
+			DLNA_selectMedia();
+		}
+
+		if (DLNA_loading == false) {
+			$('status').innerHTML = resultPlayback;
+			if (resultPlayback == 'Media OK' || resultPlayback == 'Done') {
+				$('status').style.color = "green";
+			}
+			else {
+				$('status').style.color = "red";
+			}
+		}
+
+		DLNA_prevControls = controls;
+		DLNA_prevOnlineState = onlineState;
+		DLNA_prevCurrentAlbumArtUrl = currentAlbumArtUrl;
+		DLNA_prevTitle = title;
+		DLNA_prevAlbum = album;
+		DLNA_prevArtist = artist;
+		DLNA_prevDuration = duration;
+		DLNA_prevPlaying = playing;
+		DLNA_prevActions = actions;
+		DLNA_prevMute = mute;
+		DLNA_prevVolume = volume;
+		DLNA_prevResultDiscovery = resultDiscovery;
+		DLNA_prevResultBrowse = resultBrowse;
+		DLNA_prevSinkProtocols = sinkProtocols;
+		DLNA_prevResultPlayback = resultPlayback;
+	}
+
+	DLNA_timeoutVar = setTimeout("DLNA_refreshPlayer("+device+")", 1000);
+}
+
+function DLNA_refreshDiscovery(device)
+{
+	var modelName = get_device_state(device, DLNA_DMC_SID, "ModelName", 1);
+	if (modelName == undefined) {
+		modelName = '';
+	}
+	var url = get_device_state(device, DLNA_DMC_SID, "DescriptionURL", 1);
+	if (url == undefined) {
+		url = '';
+	}
+	var onlineState = get_device_state(device, DLNA_DMC_SID, "Online", 1);
+	if (onlineState == '1') {
+		onlineState = 'ON';
+	}
+	else {
+		onlineState = 'OFF';
+	}
+	var proxy = get_device_state(device, DLNA_DMC_SID, "ProxyUsed", 1);
+	if (proxy == undefined) {
+		proxy = '';
+	}
+	var resultDiscovery = get_device_state(device, DLNA_DMC_SID, "DMRDiscoveryResult", 1);
+	if (resultDiscovery == undefined) {
+		resultDiscovery = '';
+	}
+	if (modelName != DLNA_prevModelName
+		|| url != DLNA_prevUrl
+		|| onlineState != DLNA_prevOnlineState2
+		|| proxy != DLNA_prevProxy
+		|| resultDiscovery != DLNA_prevResultDiscovery2) {
+
+		DLNA_prevModelName = modelName;
+		DLNA_prevUrl = url;
+		DLNA_prevOnlineState2 = onlineState;
+		DLNA_prevProxy = proxy;
+		DLNA_prevResultDiscovery2 = resultDiscovery;
+
+		$('model').innerHTML = modelName;
+		$('url').innerHTML = url;
+		$('state').innerHTML = onlineState;
+		$('proxy').innerHTML = proxy;
+
+		if (resultDiscovery == 'scanning') {
+			$('DMRdiscover').disabled = true;
+			$('DMRdiscover').innerHTML = 'Scanning ...';
+			$('DMRdiscovery').innerHTML = '';
+			$('selectDMRDiscovery').disabled = true;
+		}
+		else if (resultDiscovery == "") {
+			$('DMRdiscover').disabled = false;
+			$('DMRdiscover').innerHTML = 'Discover';
+			$('selectDMRDiscovery').disabled = true;
+			$('DMRdiscovery').innerHTML = '';
+		}
+		else {
+			$('DMRdiscover').disabled = false;
+			$('DMRdiscover').innerHTML = 'Discover';
+			$('selectDMRDiscovery').disabled = false;
+
+			var html = '';
+			var xml = DLNA_parseXml(resultDiscovery);
+			if (typeof xml != 'undefined') {
+				var items = xml.getElementsByTagName("device");
+				for (i=0; i<items.length; i++) {
+					var descrUrl = DLNA_extractXmlTag(items[i], 'descriptionURL');
+					if (descrUrl == undefined) {
+						descrUrl = '';
+					}
+					var selected = '';
+					if (descrUrl == url) {
+						selected = ' selected';
+					}
+					var name = DLNA_extractXmlTag(items[i], 'friendlyName');
+					if (name == undefined) {
+						name = '';
+					}
+					html += '<option ' + selected + ' value="' + descrUrl + '">' + name + '</option>';
+				}
+			}
+			$('DMRdiscovery').innerHTML = html;
+			if (DLNA_browserIE) {
+				$('DMRdiscovery').outerHTML=$('DMRdiscovery').outerHTML.replace($('DMRdiscovery').innerHTML+'</select>',html+'</select>');
+			}
+		}
+	}
+
+	DLNA_timeoutVar2 = setTimeout("DLNA_refreshDiscovery("+device+")", 1000);
+}
+
+function DLNA_play(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'Play', {} );
+}
+
+function DLNA_pause(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'Pause', {} );
+}
+
+function DLNA_stop(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'Stop', {} );
+}
+
+function DLNA_prevTrack(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'SkipUp', {} );
+}
+
+function DLNA_nextTrack(device)
+{
+	DLNA_callAction(device, MEDIA_NAVIGATION_SID, 'SkipDown', {} );
+}
+
+function DLNA_mute(device)
+{
+	DLNA_callAction(device, RENDERING_CONTROL_SID, 'SetMute', {} );
+}
+
+function DLNA_playUri(device)
+{
+	var uri = encodeURIComponent($('uri').value);
+	var protocol = '';
+	if ($('sinkProtocols').selectedIndex >= 0) {
+		protocol = $('sinkProtocols').options[$('sinkProtocols').selectedIndex].value;
+	}
+	var duration = $('duration1').value;
+	var volume = $('volume1').value;
+	//$('debug').innerHTML = uri + ' ' + protocol + ' ' + duration + ' ' + volume;
+	if (uri != "") {
+		DLNA_callAction(device, DLNA_DMC_SID, 'Play', {'URI':uri, 'Protocol':protocol, 'Duration':duration, 'Volume':volume} );
+	}
+}
+
+function DLNA_selectLang()
+{
+	if ($('language1').selectedIndex >= 0) {
+		$('language').value = $('language1').options[$('language1').selectedIndex].value;
+	}
+}
+
+function DLNA_say(device)
+{
+	var text = $('text').value;
+	var lang = $('language').value;
+	var volume = $('volume').value;
+	//$('debug').innerHTML = text + ' ' + lang + ' ' + vol;
+	if (text != "") {
+		DLNA_callAction(device, DLNA_DMC_SID, 'Say', {'Text':text, 'Language':lang, 'Volume':volume} );
+	}
+}
+
+function DLNA_startDMRDiscovery(device)
+{
+	DLNA_callAction(device, DLNA_DMC_SID, 'StartDMRDiscovery', {} );
+}
+
+function DLNA_selectDMRDiscovery(device)
+{
+	if ($('DMRdiscovery').selectedIndex >= 0) {
+		var url = $('DMRdiscovery').options[$('DMRdiscovery').selectedIndex].value;
+		//$('debug').innerHTML = url;
+		DLNA_callAction(device, DLNA_DMC_SID, 'SelectDMRDevice', {'URL':url} );
+	}
+}
+
+function DLNA_selectURL(device)
+{
+	var url = $('newURL').value;
+	if (url != "") {
+		//$('debug').innerHTML = url;
+		DLNA_callAction(device, DLNA_DMC_SID, 'SelectDMRDevice', {'URL':url} );
+	}
+}
+
+function DLNA_checkState(device)
+{
+	var url = $('url').innerHTML;
+	if (url != "") {
+		//$('debug').innerHTML = url;
+		DLNA_callAction(device, DLNA_DMC_SID, 'SelectDMRDevice', {'URL':url} );
+	}
+}
+
+function DLNA_disableDMSCmds()
+{
+	DLNA_loading = true;
+	$('media').innerHTML = '';
+	$('protocols').innerHTML = '';
+	$('status').innerHTML = 'Loading...';
+	$('status').style.color = "red";
+	$('discover').disabled = true;
+	$('browse').disabled = true;
+	$('back').disabled = true;
+	$('top').disabled = true;
+}
+
+function DLNA_startDMSDiscovery(device)
+{
+	DLNA_selectedMedia = undefined;
+	DLNA_mediaObjects = new Array();
+	DLNA_disableDMSCmds();
+	DLNA_callAction(device, DLNA_DMC_SID, 'StartDMSDiscovery', {} );
+}
+
+function DLNA_browseMedia(device)
+{
+	var value;
+	var url;
+	var id;
+	var protocol;
+	if ($('media').selectedIndex >= 0) {
+		value = $('media').options[$('media').selectedIndex].value;
+		if (value.indexOf('server:') == 0) {
+			url = value.substr(7);
+			id = '0';
+			DLNA_mediaObjects.push(id);
+			DLNA_disableDMSCmds();
+			DLNA_lastSelectedDMS = url;
+			DLNA_lastPlayedMediaObject = '';
+			DLNA_lastUsedProtocol = '';
+			url = encodeURIComponent(url);
+			DLNA_callAction(device, DLNA_DMC_SID, 'BrowseDMS', {'DescriptionURL':url, 'ObjectID':id} );
+		}
+		else if (value.indexOf('container:') == 0) {
+			id = value.substr(10);
+			DLNA_mediaObjects.push(id);
+			DLNA_disableDMSCmds();
+			id = encodeURIComponent(id);
+			DLNA_callAction(device, DLNA_DMC_SID, 'BrowseDMS', {'ObjectID':id} );
+		}
+		else if (value.indexOf('item:') == 0 && $('protocols').selectedIndex >= 0) {
+			id = value.substr(5);
+			protocol = $('protocols').options[$('protocols').selectedIndex].value;
+			DLNA_lastPlayedMediaObject = id;
+			DLNA_lastUsedProtocol = protocol;
+			id = encodeURIComponent(id);
+			DLNA_callAction(device, DLNA_DMC_SID, 'PlayDMSMedia', {'ObjectID':id, 'Protocol':protocol} );
+		}
+	}
+}
+
+function DLNA_browseTop(device)
+{
+	if (DLNA_mediaObjects.length > 0) {
+		DLNA_selectedMedia = undefined;
+		DLNA_mediaObjects = new Array();
+		DLNA_prevResultBrowse = undefined;
+		DLNA_disableDMSCmds();
+	}
+}
+
+function DLNA_browseBack(device)
+{
+	var id;
+	if (DLNA_mediaObjects.length > 0) {
+		DLNA_selectedMedia = undefined;
+		DLNA_mediaObjects.pop();
+		if (DLNA_mediaObjects.length == 0) {
+			DLNA_prevResultBrowse = undefined;
+			DLNA_disableDMSCmds();
+		}
+		else {
+			id = encodeURIComponent(DLNA_mediaObjects[DLNA_mediaObjects.length-1]);
+			DLNA_disableDMSCmds();
+			DLNA_callAction(device, DLNA_DMC_SID, 'BrowseDMS', {'ObjectID':id} );
+		}
+	}
+}
+
+function DLNA_selectMedia()
+{
+    var html = '';
+	var buttonLabel = 'Browse';
+	var buttonDisabled = false;
+	var value;
+	if ($('media').selectedIndex >= 0) {
+		DLNA_selectedMedia = $('media').options[$('media').selectedIndex].value;
+		if (DLNA_selectedMedia.indexOf('item:') == 0) {
+			buttonLabel = 'Play';
+			if (DLNA_prevOnlineState == '1') {
+				buttonDisabled = false;
+			}
+			else {
+				buttonDisabled = true;
+			}
+			value = DLNA_selectedMedia.substr(5);
+			//$('debug').innerHTML = value;
+			html = '<option selected value="AUTO">Automatic</option>';
+			html += '<option value="">None</option>';
+			var xml = DLNA_parseXml(DLNA_prevResultBrowse);
+			if (typeof xml != 'undefined') {
+				var id;
+				var protocol;
+				var protocol2;
+				var items = xml.getElementsByTagName("item");
+				for (i=0; i<items.length; i++) {
+					id = DLNA_extractXmlAttribute(items[i], 'id');
+					if (id == value) {
+						for (j=0; j<items[i].childNodes.length; j++) {
+							if (items[i].childNodes[j].tagName == 'res') {
+								protocol = items[i].childNodes[j].getAttribute('protocolInfo');
+								if (protocol != undefined) {
+									if (protocol.length > 60) {
+										protocol2 = protocol.substr(0, 60) + '...';
+									}
+									else {
+										protocol2 = protocol;
+									}
+									html += '<option value="' + protocol + '">' + protocol2 + '</option>';
+								}
+							}
+						}
+						break;
+					}
+				}
+			}
+		}
+	}
+	$('protocols').innerHTML = html;
+	if (DLNA_browserIE) {
+		$('protocols').outerHTML=$('protocols').outerHTML.replace($('protocols').innerHTML+'</select>',html+'</select>');
+	}
+	$('browse').innerHTML = buttonLabel;
+	if (DLNA_loading == false) {
+		$('browse').disabled = buttonDisabled;
+	}
+}
+
+function DLNA_callAction(device, sid, actname, args) {
+	var q={
+		'id':'lu_action',
+		'output_format':'xml',
+		'DeviceNum':device,
+		'serviceId':sid,
+		'action':actname
+	};
+	var key, count = 0;
+	for(key in args) {
+		count++;
+	}
+	if (count > 0) {
+		Object.extend(q, args);
+	}
+    if (DLNA_browserIE) {
+    	Object.extend(q, {timestamp: new Date().getTime()}); //we need this to avoid IE caching of the AJAX get
+    }
+	new Ajax.Request (command_url+'/data_request', {
+		method: 'get',
+		parameters: q,
+		onSuccess: function (response) {
+		},
+		onFailure: function (response) {
+		},
+		onComplete: function (response) {
+		}
+	});
+}
+
Index: /trunk/I_DLNAMediaController1.xml
===================================================================
--- /trunk/I_DLNAMediaController1.xml	(revision 20)
+++ /trunk/I_DLNAMediaController1.xml	(revision 30)
@@ -5,4 +5,6 @@
   local socket = require("socket")
   local url = require("socket.url")
+
+  local PLUGIN_VERSION = "pre 0.6"
 
   local MSG_CLASS = "DLNA"
@@ -128,14 +130,14 @@
 
   local function warning(stuff)
-    log("warning " .. stuff, 2)
+    log("warning: " .. stuff, 2)
   end
 
   local function error(stuff)
-    log("error " .. stuff, 1)
+    log("error: " .. stuff, 1)
   end
 
   local function debug(stuff)
     if (DEBUG_MODE) then
-      log("debug " .. stuff, 35)
+      log("debug: " .. stuff)
     end
   end
@@ -350,4 +352,5 @@
         status, tmp = AVTransport.GetTransportInfo({InstanceID="0"})
         if (status ~= true) then
+            debug("AVTransport.GetTransportInfo failed: going offline")
             commsFailure(device, tmp)
             return
@@ -371,5 +374,5 @@
         status, tmp = AVTransport.GetMediaInfo({InstanceID="0"})
         currentUri = upnp.extractElement("CurrentURI", tmp, "")
-        currentUriMetaData = upnp.extractElement("CurrentURIMetaData", tmp, "")
+        currentUriMetaData = upnp.unformatXML(upnp.extractElement("CurrentURIMetaData", tmp, ""))
         changed = setData("NumberOfTracks", upnp.extractElement("NrTracks", tmp, "NOT_IMPLEMENTED"), device, changed)
         changed = setData("CurrentMediaDuration", upnp.extractElement("MediaDuration", tmp, "NOT_IMPLEMENTED"), device, changed)
@@ -380,5 +383,5 @@
         status, tmp = AVTransport.GetPositionInfo({InstanceID="0"})
         trackUri = upnp.extractElement("TrackURI", tmp, "")
-        trackUriMetaData = upnp.extractElement("TrackMetaData", tmp, "")
+        trackUriMetaData = upnp.unformatXML(upnp.extractElement("TrackMetaData", tmp, ""))
         changed = setData("CurrentTrack", upnp.extractElement("Track", tmp, "NOT_IMPLEMENTED"), device, changed)
         changed = setData("CurrentTrackDuration", upnp.extractElement("TrackDuration", tmp, "NOT_IMPLEMENTED"), device, changed)
@@ -407,4 +410,5 @@
         status, tmp = Rendering.GetMute({OrderedArgs={"InstanceID=0", "Channel=Master"}})
         if (status ~= true) then
+            debug("Rendering.GetMute failed: going offline")
             commsFailure(device, tmp)
             return
@@ -442,4 +446,5 @@
 
     if (status ~= true) then
+        debug("Rendering.GetVolume failed: going offline")
         commsFailure(device, tmp)
         return
@@ -470,4 +475,5 @@
 
     if (status ~= true) then
+        debug("Rendering.GetMute failed: going offline")
         commsFailure(device, tmp)
         return
@@ -507,5 +513,5 @@
 
     if (cxt == nil) then
-        log("Please save the context before restoring it !")
+        warning("Please save the context before restoring it !")
         return
     end
@@ -580,4 +586,60 @@
 
     refreshNow(device, false)
+  end
+
+  -- Output parameters:
+  --   + duration: duration of the produced audio file in seconds
+  --   + uri: Sonos URI for playing the audio file
+  --   + title: title information
+  --   + protocol: protocol information to be exchanged with the DMR
+  local function runTTSengine(engine, text, language, device)
+    local duration = 0
+    local uri = nil
+    local title = ""
+    local protocol = nil
+
+    if (engine == "Google") then
+        -- Cut the text in fragments of max 100 characters
+        local tableTextFragments = {}
+        local cutSize = 100	
+        local remaining = text
+        while (#remaining > cutSize) do
+            local pos = string.find(string.reverse(string.sub(remaining, 1, cutSize+1)), " ")
+            if (pos ~= nil) then
+                table.insert(tableTextFragments, string.sub(remaining, 1, cutSize+1-pos))
+                remaining = string.sub(remaining, cutSize+3-pos)
+            else
+                remaining = ""
+            end
+        end
+        if (#remaining > 0) then
+            table.insert(tableTextFragments, remaining)
+        end
+
+        -- Get the MP3 files from Google
+        local strFiles = ""
+        for i, v in ipairs(tableTextFragments)
+        do
+            strFiles = strFiles .. " " .. SAY_TMP_FILE:format(device, i)
+            os.execute(SAY_EXECUTE:format(device, i, device, i, language, url.escape(v)))
+        end
+        -- Concat the multiple MP3 files
+        os.execute(CONCAT_EXECUTE:format(strFiles, device, strFiles))
+
+        -- Get the file size to deduce its playback duration
+        local fh = io.open(SAY_OUTPUT_FILE:format(device), "a+")
+        local size = fh:seek("end")
+        fh:close()
+
+        -- Compute the play duration from the file size (32 kbps)
+        duration = math.ceil(size/4000)
+
+        uri = SAY_OUTPUT_URI:format(VERA_IP, VERA_WEB_PORT, device)
+
+        title = "Google TTS"
+        protocol = "http-get:*:audio/mpeg:*"
+    end
+
+    return duration, uri, title, protocol
   end
 
@@ -599,44 +661,10 @@
         -- TTS case
 
-        -- Cut the text in fragments of max 100 characters
-        local tableTextFragments = {}
-        local cutSize = 100	
-        local remaining = text
-        while (#remaining > cutSize) do
-            local pos = string.find(string.reverse(string.sub(remaining, 1, cutSize+1)), " ")
-            if (pos ~= nil) then
-                table.insert(tableTextFragments, string.sub(remaining, 1, cutSize+1-pos))
-                remaining = string.sub(remaining, cutSize+3-pos)
-            else
-                remaining = ""
-            end
-        end
-        if (#remaining > 0) then
-            table.insert(tableTextFragments, remaining)
-        end
-
-        -- Get the MP3 files from Google
-        local strFiles = ""
-        for i, v in ipairs(tableTextFragments)
-        do
-            strFiles = strFiles .. " " .. SAY_TMP_FILE:format(device, i)
-            os.execute(SAY_EXECUTE:format(device, i, device, i, language, url.escape(v)))
-        end
-        -- Concat the multiple MP3 files
-        os.execute(CONCAT_EXECUTE:format(strFiles, device, strFiles))
-
-        -- Get the file size to deduce its playback duration
-        local fh = io.open(SAY_OUTPUT_FILE:format(device), "a+")
-        local size = fh:seek("end")
-        fh:close()
-
-        -- Compute the play duration from the file size (32 kbps)
-        -- and add 1 second to be sure to not cut the end of the text
-        duration = math.ceil(size/4000) + 1
-
-        uri = SAY_OUTPUT_URI:format(VERA_IP, VERA_WEB_PORT, device)
-        title = "Google TTS"
-        protocol = "http-get:*:audio/mpeg:*"
-    elseif (uri == "") then
+        duration, uri, title, protocol = runTTSengine("Google", text, language, device)
+        -- add 1 second to be sure to not cut the end of the text
+        duration = duration + 1
+    end
+
+    if (uri == nil or uri == "") then
         return
     end
@@ -661,5 +689,5 @@
     local Rendering = upnp.getService(uuidDMR, UPNP_RENDERING_CONTROL_SERVICE)
 
-    if (AVTransport ~= nil and uri ~= nil and uri ~= "") then
+    if (AVTransport ~= nil) then
         if (dataTable.TransportState ~= "NO_MEDIA_PRESENT") then
             AVTransport.Stop({InstanceID=instanceId})
@@ -723,6 +751,6 @@
     local newDevice = false
     local newDescrURL = luup.variable_get(DLNA_DMC_SID, "DescriptionURL", device) or ""
-    local newIP, newPort = newDescrURL:match("http://([%d%.]-):(%d+)/.-")
-	if (newIP == nil or newPort == nil) then
+    local baseURL, newIP, newPort, baseDirectory = newDescrURL:match("(http://([%d%.]-):(%d+))(/.-)[^/]*$")
+	if (baseURL == nil or newIP == nil or newPort == nil or baseDirectory == nil) then
         luup.variable_set(DLNA_DMC_SID, "DescriptionURL", DMRDescriptionURL, device)
     elseif (newDescrURL ~= DMRDescriptionURL) then
@@ -736,8 +764,10 @@
         changed = setData("ModelName", "", device, changed)
         changed = setData("Online", "0", device, changed)
+        debug("Switching to a new DMR: setting variable Online to 0")
     end
 
     local descrXML = upnp.UPnP_getDeviceDescription(DMRDescriptionURL)
     if (descrXML == nil) then
+        debug("upnp.UPnP_getDeviceDescription failed: going offline")
         deviceIsOffline(device)
         return
@@ -756,5 +786,5 @@
     iconURL = upnp.getIconFromDescription(descrXML)
     if (iconURL ~= nil) then
-        iconURL = url.absolute(string.format("http://%s:%s/", ipDMR, portDMR), iconURL)
+        iconURL = upnp.buildURL(baseURL, baseDirectory, iconURL)
     else
         iconURL = PLUGIN_ICON:format(VERA_IP, VERA_WEB_PORT)
@@ -762,5 +792,5 @@
 
     if (upnp.aresServicesLoaded(uuidDMR) == false) then
-        upnp.addServices(uuidDMR, ipDMR, portDMR, descrXML,
+        upnp.addServices(uuidDMR, DMRDescriptionURL, descrXML,
                          "urn:schemas-upnp-org:device:MediaRenderer:1",
                          { UPNP_AVTRANSPORT_SERVICE,
@@ -798,6 +828,6 @@
   local function setupDMS(descrURL)
     if (descrURL ~= nil) then
-        local newIP, newPort = descrURL:match("http://([%d%.]-):(%d+)/.-")
-        if (newIP == nil or newPort == nil) then
+        local baseURL, newIP, newPort, baseDirectory = descrURL:match("(http://([%d%.]-):(%d+))(/.-)[^/]*$")
+	    if (baseURL == nil or newIP == nil or newPort == nil or baseDirectory == nil) then
             return false, "Bad server description URL"
         end
@@ -823,5 +853,5 @@
 
             if (upnp.aresServicesLoaded(uuidDMS) == false) then
-                upnp.addServices(uuidDMS, ipDMS, portDMS, descrXML,
+                upnp.addServices(uuidDMS, DMSDescriptionURL, descrXML,
                                  "urn:schemas-upnp-org:device:MediaServer:1",
                                  { UPNP_MR_CONTENT_DIRECTORY_SERVICE })
@@ -867,43 +897,14 @@
     local item, DMRprotocol, protocol, uri
 
-    -- local item = nil
-    -- local elt, id, startItem, endItem, startDIDL, endDIDL
-    -- local directory = luup.variable_get(DLNA_DMC_SID, "BrowseResult", device) or ""
-    -- for elt, id in directory:gmatch('(&lt;item.-%sid="([^"]-)"[^>]->.-&lt;/item>)') do
-        -- if (upnp.decode(id) == object) then
-            -- startItem, item, endItem = elt:match("(&lt;item%s?[^>]->.-)(&lt;res%s?[^>]->.*&lt;/res>)(.-&lt;/item>)")
-            -- if (startItem ~= nil and endItem ~= nil) then
-                -- startDIDL = directory:match("(&lt;DIDL%-Lite%s?[^>]->)") .. startItem
-                -- endDIDL = endItem .. '&lt;/DIDL-Lite>'
--- log("compare")
--- local xml = upnp.browseContent(uuidDMS, UPNP_MR_CONTENT_DIRECTORY_SERVICE, object, true)
--- local startDIDL2, item2, endDIDL2 = xml:match("(&lt;DIDL%-Lite%s?[^>]->&lt;item%s?[^>]->.-)(&lt;res%s?[^>]->.*&lt;/res>)(.-&lt;/item>&lt;/DIDL%-Lite>)")
--- if (startDIDL ~= startDIDL2) then
--- log("diff startDIDL")
--- log("startDIDL " .. startDIDL)
--- log("startDIDL2 " .. startDIDL2)
--- end
--- if (endDIDL ~= endDIDL2) then
--- log("diff endDIDL")
--- log("endDIDL " .. endDIDL)
--- log("endDIDL2 " .. endDIDL2)
--- end
--- if (item ~= item2) then
--- log("diff item")
--- log("item " .. item)
--- log("item2 " .. item2)
--- end
-            -- end
-            -- break
-        -- end
-    -- end
-    -- if (item == nil) then
-        local xml = upnp.browseContent(uuidDMS, UPNP_MR_CONTENT_DIRECTORY_SERVICE, object, true)
-        if (xml == "") then
-            luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media not available", device)
-            return
-        end
-        local startDIDL, item, endDIDL = xml:match("(&lt;DIDL%-Lite%s?[^>]->&lt;item%s?[^>]->.-)(&lt;res%s?[^>]->.*&lt;/res>)(.-&lt;/item>&lt;/DIDL%-Lite>)")
-    -- end
+    local xml = upnp.browseContent(uuidDMS, UPNP_MR_CONTENT_DIRECTORY_SERVICE, object, true)
+    debug("data from server: " .. (xml or "nil"))
+    if (xml == "") then
+        luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media not available", device)
+        return
+    end
+    local startDIDL, item, endDIDL = xml:match("(&lt;DIDL%-Lite%s?[^>]->&lt;item%s?[^>]->.-)(&lt;res%s?[^>]->.*&lt;/res>)(.-&lt;/item>&lt;/DIDL%-Lite>)")
+    debug("startDIDL: " .. (startDIDL or "nil"))
+    debug("endDIDL: " .. (endDIDL or "nil"))
+    debug("res: " .. (item or "nil"))
 
     local selectedProtocol = ""
@@ -925,16 +926,18 @@
         end
 
---log("selected protocol " .. (forceProtocol or "nil"))
+        debug("selected protocol " .. (forceProtocol or "nil"))
         for protocol, uri in item:gmatch('&lt;res%s?.-%s?protocolInfo="(.-)"%s?[^>]->(.-)&lt;/res>') do
---log("server protocol " .. protocol)
+            debug("server protocol " .. protocol)
             if (forceProtocol == nil or forceProtocol == ""
                     or forceProtocol == "AUTO" or forceProtocol == protocol) then
---log("server protocol considered")
+                debug("server protocol considered")
                 for DMRprotocol, v in pairs(DMRprotocols) do
                     if (protocol:match(DMRprotocol) ~= nil) then
---log("Matching with DMR protocol " .. DMRprotocol)
+                        debug("Matching OK with DMR protocol " .. DMRprotocol)
                         selectedProtocol = protocol
                         selectedUri = uri
                         break
+                    else
+                        debug("No matching with DMR protocol " .. DMRprotocol)
                     end
                 end
@@ -942,6 +945,6 @@
                     break
                 end
-            -- else
--- log("server protocol bypassed")
+            else
+                debug("server protocol bypassed")
             end
         end
@@ -962,5 +965,5 @@
     else
         luup.variable_set(DLNA_DMC_SID, "PlaybackResult", "Media not compatible with renderer", device)
-        log("Media cannot be rendered by the selected renderer.", 2)
+        warning("Media cannot be rendered by the selected renderer.")
     end
   end
@@ -1097,7 +1100,7 @@
     if (device ~= nil and uuid ~= nil) then
         device = tonumber(device)
-        log("Renewal of all event subscriptions for device " .. device)
+        debug("Renewal of all event subscriptions for device " .. device)
         if (uuid ~= uuidDMR) then
-            log("Renewal ignored for uuid " .. uuid)
+            debug("Renewal ignored for uuid " .. uuid)
         elseif (upnp.subscribeToEvents(device, ipDMR, portDMR, EventSubscriptions, DLNA_DMC_SID, uuidDMR) == false) then
             setup(device, true)
@@ -1158,4 +1161,6 @@
     log("#" .. lul_device .. " starting up with id " .. luup.devices[lul_device].id)
 
+	luup.variable_set(DLNA_DMC_SID, "PluginVersion", PLUGIN_VERSION, lul_device)
+
     if (luup.variable_get(DLNA_DMC_SID, "DMRDiscoveryResult", lul_device) == nil
             or luup.variable_get(DLNA_DMC_SID, "DMRDiscoveryResult", lul_device) == "scanning") then
@@ -1194,4 +1199,12 @@
 	if (lang == nil) then
         luup.variable_set(DLNA_DMC_SID, "DefaultLanguageTTS", "en", lul_device)
+    end
+
+    local debugLogs = luup.variable_get(DLNA_DMC_SID, "DebugLogs", lul_device)
+	if (debugLogs == nil or tonumber(debugLogs) == nil) then
+        luup.variable_set(DLNA_DMC_SID, "DebugLogs", "0", lul_device)
+    end
+    if (luup.variable_get(DLNA_DMC_SID, "DebugLogs", lul_device) == "1") then
+        DEBUG_MODE = true
     end
 
Index: /trunk/L_DLNAUPnPUtility.lua
===================================================================
--- /trunk/L_DLNAUPnPUtility.lua	(revision 20)
+++ /trunk/L_DLNAUPnPUtility.lua	(revision 30)
@@ -441,11 +441,11 @@
         icnoURL = icon:match("<url>(.+)</url>")
         if (icnoURL ~= nil and height ~= nil and width ~= nil) then
-            height = tonumber(height)
-            width = tonumber(width)
+            height = tonumber(height) or 0
+            width = tonumber(width) or 0
             if (height >= width and height > size) then
-                size = tonumber(height)
+                size = height
                 resultURL = icnoURL
             elseif (height < width and width > size) then
-                size = tonumber(width)
+                size = width
                 resultURL = icnoURL
             end
@@ -472,5 +472,13 @@
 
 
-function getServicesFromDescription(descriptionXML, deviceType, baseURL, subsetServices)
+function buildURL(baseURL, baseDirectory, path)
+    if (path:sub(1,1) ~= "/") then
+        path = baseDirectory .. path
+    end
+    return url.absolute(baseURL, path)
+end
+
+
+function getServicesFromDescription(descriptionXML, deviceType, baseURL, baseDirectory, subsetServices)
     local services = {}
     local value, devices, device, service, service2, serviceType, serviceId, controlURL, eventSubURL
@@ -499,9 +507,9 @@
                             if (service2 == serviceType) then
                                services[serviceType] = {
-                                       controlURL = url.absolute(baseURL, controlURL),
-                                       eventSubURL = url.absolute(baseURL, eventSubURL),
-                                       scpdURL = url.absolute(baseURL, scpdURL),
+                                       controlURL = buildURL(baseURL, baseDirectory, controlURL),
+                                       eventSubURL = buildURL(baseURL, baseDirectory, eventSubURL),
+                                       scpdURL = buildURL(baseURL, baseDirectory, scpdURL),
                                        serviceId = serviceId,
-                                       actions = getActionsFromSCPD(url.absolute(baseURL, scpdURL)),
+                                       actions = getActionsFromSCPD(buildURL(baseURL, baseDirectory, scpdURL)),
                                        object = nil }
                                 break
@@ -544,9 +552,11 @@
   end
 
-  function addServices(uuid, ip, port, descriptionXML, deviceType, subsetServices)
+  function addServices(uuid, descriptionURL, descriptionXML, deviceType, subsetServices)
     if (uuid == nil or uuid == "") then
         return
     end
 
+    local baseURL, ip, port, baseDirectory = descriptionURL:match("(http://([%d%.]-):(%d+))(/.-)[^/]*$")
+
     if (Services[uuid] == nil) then
         Services[uuid] = {}
@@ -555,5 +565,5 @@
     local services = getServicesFromDescription(
                               descriptionXML, deviceType,
-                              string.format("http://%s:%s/", ip, port),
+                              baseURL, baseDirectory,
                               subsetServices)
     for k,v in pairs(services) do
@@ -728,5 +738,5 @@
                                  "SortCriteria="}})
           if (status == true) then
-              result = mergeDIDL(result, extractElement("Result", tmp, ""))
+              result = mergeDIDL(result, unformatXML(extractElement("Result", tmp, "")))
               fetched = fetched + tonumber(extractElement("NumberReturned", tmp, ""))
               total = tonumber(extractElement("TotalMatches", tmp, ""))
Index: /trunk/J_DLNAMediaController1.js
===================================================================
--- /trunk/J_DLNAMediaController1.js	(revision 20)
+++ /trunk/J_DLNAMediaController1.js	(revision 30)
@@ -266,5 +266,14 @@
 function DLNA_showHelp(device)
 {
+	var version = get_device_state(device, DLNA_DMC_SID, "PluginVersion", 1);
+	if (version == undefined) {
+		version = '';
+	}
+
 	var html = '<table cellspacing="10">';
+	html += '<tr>';
+	html += '<td>Plugin version:</td>';
+	html += '<td>' + version + '</td>';
+	html += '</tr>';
 	html += '<tr>';
 	html += '<td>DMS description URL:</td>';
