{"name":"SONOS Woonkamer 2","type":"virtual_device","properties":{"deviceIcon":8,"currentIcon":"8","log":"","logTemp":"","mainLoop":"-------------------------------------------------------------------------------------------\n-------------------------------------------------------------------------------------------\n-- SONOS Remote & Text To Speech (TTS)\n--\n-- Copyright (C) 2014-2015 Jean-Christophe Vermandé\n--\n-- Version 1.0.1 beta\n-------------------------------------------------------------------------------------------\n-------------------------------------------------------------------------------------------\n-- CHANGE LOGS: \n--\n-- Version 1.0.1 beta\n-- Fix: Handling percent signs in radio title\n-- Fix: Handling aac radio channels \n--\n-- Version 1.0.0 beta\n-- Improvement: Auto configuration\n-- Improvement: Multiple VD support\n-- Improvement: Low latency when triggering commands.\n-- Improvement: Some code enhancement / refactoring.\n-- New: TTS now use Voice RSS or ResponsiveVoice API with advanced options (duration, \n-- volume, auto Resume) \n-- New: Play stream with advanced options (duration, volume, auto Resume)\n--\n-- Version 0.1.0\n-- Fix Google TTS with client=t in URI\n--\n-- Version 0.0.9\n-- New: TTS version 2 added with external server support and notable improvement\n-- \n-- Version 0.0.8\n-- Improvement: Play TSS with auto stop mode (set dr parameter to \"auto\") now works as expected.\n-- Improvement: Play TTS with fidex duration (set dr parameter to \"xx\" seconds) now works as expected.\n-- Patch: Main image is now fixed after pressing a button, thanks to Labomatik & JM13.\n-- Patch: Bug with XMl parsing for BrowseDirectChildren.\n-- Warning: To operate the radio shortcuts you must choose at least two favorite radios.\n\n-- Version 0.0.7\n-- Refresh process run faster and more efficiently.\n-- Patch line 892: attempt to index local 'value' (a function value)\n-- Patch line 1256: attempt to concatenate a nil value\n-- Show plugin current version in debug window on startup\n-- Add LED control \"On\" or \"Off\" -> Sonos:ledState(\"On\");\n-- \n-------------------------------------------------------------------------------------------\n-------------------------------------------------------------------------------------------\nlocal _f = fibaro;\n\nif not Toolkit then Toolkit={\n __header=\"Toolkit\",\n __version=\"1.0.6\",\n __luaBase=\"5.2.0\",\n __copyright=\"Jean-Christophe Vermandé\",\n __licence=[[\n \n\t Copyright (C) 2013-2015 Jean-Christophe Vermandé\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU General Public License for more details.\n\n You should have received a copy of the GNU General Public License\n along with this program. If not, see .\n]],__frameworkHeader=function(self)self:traceEx(\"green\",\"-------------------------------------------------------------------------\")self:traceEx(\"green\",\"-- HC2 Toolkit Framework version %s\",self.__version)self:traceEx(\"green\",\"-- Current interpreter version is %s\",self.getInterpreterVersion())self:traceEx(\"green\",\"-- Total memory in use by Lua: %.2f Kbytes\",self.getCurrentMemoryUsed())self:traceEx(\"green\",\"-------------------------------------------------------------------------\")end,chars=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\",hex=\"0123456789abcdef\",now=os.date,toUnixTimestamp=function(a)return os.time(a)end,fromUnixTimestamp=function(b)return os.date(\"%c\",ts)end,currentTime=function()return tonumber(os.date(\"%H%M%S\"))end,comparableTime=function(d,e,f)return tonumber(string.format(\"%02d%02d%02d\",d,e,f))end,isTraceEnabled=true,isAutostartTrigger=function()local a=fibaro:getSourceTrigger()return a[\"type\"]==\"autostart\"end,isOtherTrigger=function()local a=fibaro:getSourceTrigger()return a[\"type\"]==\"other\"end,raiseError=function(g,h)error(g,h)end,colorSetToRgbw=function(self,j)self.assertArg(\"colorSet\",j,\"string\")local a,i={},1;for k in string.gmatch(j,\"(%d+)\")do a[i]=k;i=i+1 end;return a[1],a[2],a[3],a[4]end,isValidJson=function(self,l,m)self.assertArg(\"data\",l,\"string\")self.assertArg(\"raise\",m,\"boolean\")if string.len(l)>0 then if pcall(function()return json.decode(l)end)then return true else if m then self.raiseError(\"invalid json\",2)end end end;return false end,assertArg=function(name,value,n)if type(value)~=n then Tk.raiseError(\"argument \"..name..\" must be \"..n,2)end end,trace=function(self,value,...)if self.isTraceEnabled then if value~=nil then value2= string.gsub(value, \"100%%\", \"100%%%%\");return fibaro:debug(string.format(value2,...)) end end end,traceEx=function(self,o,value,...)self:trace(string.format('<%s style=\"color:%s;\">%s',\"span\",o,string.format(value,...),\"span\"))end,getInterpreterVersion=function()return _VERSION end,getCurrentMemoryUsed=function()return collectgarbage(\"count\")end,trim=function(b)Tk.assertArg(\"value\",b,\"string\")return string.gsub(b,\"^%s*(.-)%s*$\",\"%1\")end,isNaN=function(p)return p~=p end,filterByPredicate=function(table,q)Tk.assertArg(\"table\",table,\"table\")Tk.assertArg(\"predicate\",q,\"function\")local r,s=1,{}for i=1,#table do local k=table[i]if k~=nil then if q(k)then s[r]=k;r=r+1 end end end;return s,#s end}Toolkit:__frameworkHeader()Tk=Toolkit end;if not Toolkit.Debug then Toolkit.Debug={__header=\"Toolkit.Debug\",__version=\"1.0.1\",__clocks={[\"fragment\"]=os.clock(),[\"all\"]=os.clock()},benchmarkPoint=function(self,name)__clocks[name]=os.clock()end,benchmark=function(self,g,t,name,u)Toolkit.assertArg(\"message\",g,\"string\")Toolkit.assertArg(\"template\",g,\"string\")if u~=nil then Toolkit.assertArg(\"reset\",u,type(true))end;Toolkit:traceEx(\"yellow\",\"Benchmark [\"..g..\"]: \"..string.format(t,os.clock()-self.__clocks[name]))if u==true then self.__clocks[name]=os.clock()end end}Toolkit:traceEx(\"red\",Toolkit.Debug.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.Debug.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end;if not Toolkit then error(\"You must add Toolkit\",2)end;if not Toolkit.Collections then Toolkit.Collections={}end;if not Toolkit.Collections.Queue then Toolkit.Collections.Queue={__header=\"Toolkit.Collections.Queue\",__version=\"1.0.0\",__base={__first=0,__count=0,toArray=function(self)local v={}for i=1,self:count()do v[i]=self[i]end;return v end,clear=function(self)for i=1,self:count()do self[i]=nil end;self.__first=0;self.__count=0 end,enqueue=function(self,value)assert(value~=nil)local r=self.__first+1;self.__count=self.__count+1;self.__first=r;self[r]=value;Tk:trace(\"add at pos %d value with %s type\",r,tostring(self[r]))end,dequeue=function(self)local w=self:peek()self[1]=nil;self.__first=self.__first-1;self.__count=self.__count-1;for i=1,self:count()do self[i]=self[i+1]end;return w end,contains=function(self,value)assert(value~=nil)for i=1,self:count()do if self[i]==value then return true end end;return false end,peek=function(self)return self[1]end,count=function(self)return tonumber(self.__count)end,clone=function(self)return self end},new=function()collectgarbage(\"collect\")return Toolkit.Collections.Queue.__base end,version=function()return Toolkit.Collections.Queue.__version end}Toolkit:traceEx(\"red\",Toolkit.Collections.Queue.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.Collections.Queue.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end;if not Toolkit then error(\"You must add Toolkit\",2)end;if not Toolkit.Net then Toolkit.Net={__header=\"Toolkit.Net\",__version=\"1.0.3\",__cr=string.char(13),__lf=string.char(10),__crLf=string.char(13,10),__host=nil,__port=nil,__trace=function(k,...)if Toolkit.Net.isTraceEnabled then Toolkit:trace(k,...)end end,__writeHeader=function(x,l)assert(tostring(l)or l==nil or l==\"\",\"Invalid header found: \"..l)local y=tostring(l)x:write(y..Toolkit.Net.__crLf)Toolkit.Net.__trace(\"%s.%s::request > Add header [%s]\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,y)end,__decodeChunks=function(z)resp=\"\"line=\"0\"lenline=0;len=string.len(z)i=1;while i<=len do c=string.sub(z,i,i)if lenline==0 then if c==Toolkit.Net.__lf then lenline=tonumber(line,16)if lenline==null then lenline=0 end;line=0 elseif c==Toolkit.Net.__cr then lenline=0 else line=line..c end else resp=resp..c;lenline=lenline-1 end;i=i+1 end;return resp end,__readHeader=function(l)if l==nil then error(\"Couldn't find header\")end;local A=\"\"local B={}local i,len=1,string.len(l)while i<=len do local z=l:sub(i,i)or\"\"local C=l:sub(i+1,i+1)or\"\"if z..C==Toolkit.Net.__crLf then i=i+1;table.insert(B,A)A=\"\"else A=A..z end;i=i+1 end;return B end,__readSocket=function(x)local D,len=0,1;local A,l=\"\",\"\"while D==0 and len>0 do l,D=x:read()len=string.len(l)A=A..l end;return A,D end,__Http={__header=\"HttpRequest\",__version=\"1.0.3\",__tcpSocket=nil,__timeout=250,__waitBeforeReadMs=25,__isConnected=false,__isChunked=false,__url=nil,__method=\"GET\",__headers={},__body=nil,__authorization=nil,setBasicAuthentication=function(self,E,F)Toolkit.assertArg(\"username\",E,\"string\")Toolkit.assertArg(\"password\",F,\"string\")self.__authorization=Toolkit.Crypto.Base64:encode(tostring(E..\":\"..F))end,setBasicAuthenticationEncoded=function(self,G)Toolkit.assertArg(\"base64String\",G,\"string\")self.__authorization=G end,setWaitBeforeReadMs=function(self,H)Toolkit.assertArg(\"ms\",H,\"integer\")self.__waitBeforeReadMs=H;Toolkit.Net.__trace(\"%s.%s::setWaitBeforeReadMs > set to %d ms\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,H)end,getWaitBeforeReadMs=function(self)return self.__waitBeforeReadMs end,setReadTimeout=function(self,H)Toolkit.assertArg(\"ms\",H,\"number\")self.__timeout=H;Toolkit.Net.__trace(\"%s.%s::setReadTimeout > Timeout set to %d ms\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,H)end,getReadTimeout=function(self)return self.__timeout end,disconnect=function(self)self.__tcpSocket:disconnect()self.__isConnected=false;Toolkit.Net.__trace(\"%s.%s::disconnect > Connected: %s\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,tostring(self.__isConnected))end,request=function(self,I,J,B,K)Toolkit.assertArg(\"method\",I,\"string\")assert(I==\"GET\"or I==\"POST\"or I==\"PUT\"or I==\"DELETE\"or I==\"SUBSCRIBE\")assert(J~=nil or J==\"\")self.__isChunked=false;self.__tcpSocket:setReadTimeout(self.__timeout)self.__url=J;self.__method=I;self.__headers=B or{}self.__body=K or nil;local v=self.__method..\" \"..self.__url..\" HTTP/1.1\"Toolkit.Net.__trace(\"%s.%s::request > %s with method %s\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,self.__url,self.__method)local L=\"\"if Toolkit.Net.__port~=nil then L=\":\"..tostring(Toolkit.Net.__port)end;local M=\"Host: \"..Toolkit.Net.__host..L;Toolkit.Net.__writeHeader(self.__tcpSocket,v)Toolkit.Net.__writeHeader(self.__tcpSocket,M)for i=1,#self.__headers do Toolkit.Net.__writeHeader(self.__tcpSocket,self.__headers[i])end;if self.__authorization~=nil then Toolkit.Net.__writeHeader(self.__tcpSocket,\"Authorization: Basic \"..self.__authorization)end;if self.__body~=nil then Toolkit.Net.__writeHeader(self.__tcpSocket,\"Content-Length: \"..string.len(self.__body))Toolkit.Net.__trace(\"%s.%s::request > Body length is %d\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,string.len(self.__body))end;self.__tcpSocket:write(Toolkit.Net.__crLf..Toolkit.Net.__crLf)if self.__body~=nil then self.__tcpSocket:write(self.__body)end;fibaro:sleep(self.__waitBeforeReadMs)local N,D=Toolkit.Net.__readSocket(self.__tcpSocket)Toolkit.Net.__trace(\"%s.%s::receive > Length of result: %d\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,string.len(N))local O,P;if string.len(N)>0 then local Q=string.find(N,Toolkit.Net.__crLf..Toolkit.Net.__crLf)local R=string.sub(N,1,Q+2)if string.len(R)then P=string.sub(R,10,13)Toolkit.Net.__trace(\"%s.%s::receive > Status %s\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,P)Toolkit.Net.__trace(\"%s.%s::receive > Length of headers reponse %d\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,string.len(R))__headers=Toolkit.Net.__readHeader(R)for S,k in pairs(__headers)do if string.find(string.lower(k or\"\"),\"chunked\")then self.__isChunked=true;Toolkit.Net.__trace(\"%s.%s::receive > Transfer-Encoding: chunked\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,string.len(N))end end end;local T=string.sub(N,Q+4)if self.__isChunked then O=Toolkit.Net.__decodeChunks(T)D=0 else O=T;D=0 end end;return O,P,D end,version=function()return Toolkit.Net.__Http.__version end,dispose=function(self)if self.__isConnected then self.__tcpSocket:disconnect()end;self.__tcpSocket=nil;self.__url=nil;self.__headers=nil;self.__body=nil;self.__method=nil;if pcall(function()assert(self.__tcpSocket~=Net.FTcpSocket)end)then Toolkit.Net.__trace(\"%s.%s::dispose > Successfully disposed\",Toolkit.Net.__header,Toolkit.Net.__Http.__header)end;collectgarbage(\"collect\")Toolkit.Net.__trace(\"%s.%s::dispose > Total memory in use by Lua: %.2f Kbytes\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,collectgarbage(\"count\"))end},isTraceEnabled=false,HttpRequest=function(U,V)assert(U~=Toolkit.Net,\"Cannot call HttpRequest like that!\")assert(U~=nil,\"host invalid input\")assert(V==nil or tonumber(V),\"port invalid input\")collectgarbage(\"collect\")Toolkit.Net.__host=U;Toolkit.Net.__port=V;local W=Toolkit.Net.__Http;W.__tcpSocket=Net.FTcpSocket(U,V)W.__isConnected=true;Toolkit.Net.__trace(\"%s.%s > Total memory in use by Lua: %.2f Kbytes\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,collectgarbage(\"count\"))Toolkit.Net.__trace(\"%s.%s > Create Session on port: %d, host: %s\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,V,U)return W end,version=function()return Toolkit.Net.__version end}Toolkit:traceEx(\"red\",Toolkit.Net.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.Net.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end;if not Toolkit then error(\"You must add Toolkit\",2)end;if not Toolkit.Xml then Toolkit.Xml={__header=\"Toolkit.Xml\",__version=\"1.0.1\",__node=function(name)local node={}node.___value=nil;node.___name=name;node.___children={}node.___props={}function node:value()return self.___value end;function node:setValue(X)self.___value=X end;function node:name()return self.___name end;function node:setName(name)self.___name=name end;function node:childNodes()return self.___children end;function node:addChild(Y)if self[Y:name()]~=nil then if type(self[Y:name()].name)==\"function\"then local Z={}table.insert(Z,self[Y:name()])self[Y:name()]=Z end;table.insert(self[Y:name()],Y)else self[Y:name()]=Y end;table.insert(self.___children,Y)end;function node:properties()return self.___props end;function node:addProperty(name,value)local _=\"@\"..name;if self[_]~=nil then if type(self[_])==\"string\"then local Z={}table.insert(Z,self[_])self[_]=Z end;table.insert(self[_],value)else self[_]=value end;table.insert(self.___props,{name=name,value=value})end;return node end,Node=function(self,name)return self.__node(name)end,ToXmlString=function(value)value=string.gsub(value,\"&\",\"&\"..\"amp;\")value=string.gsub(value,\"<\",\"&\"..\"lt;\")value=string.gsub(value,\">\",\"&\"..\"gt;\")value=string.gsub(value,\"\\\"\",\"&\"..\"quot;\")value=string.gsub(value,\"([^%w%&%;%p%\\t% ])\",function(c)return string.format(\"&#x%X;\",string.byte(c))end)return value end,FromXmlString=function(value)value=string.gsub(value,\"&#x([%x]+)%;\",function(M)return string.char(tonumber(M,16))end)value=string.gsub(value,\"&#([0-9]+)%;\",function(M)return string.char(tonumber(M,10))end)value=string.gsub(value,\"&\"..\"quot;\",\"\\\"\")value=string.gsub(value,\"&\"..\"apos;\",\"'\")value=string.gsub(value,\"&\"..\"gt;\",\">\")value=string.gsub(value,\"&\"..\"lt;\",\"<\")value=string.gsub(value,\"&\"..\"amp;\",\"&\")return value end,ParseArgs=function(node,b)string.gsub(b,\"(%w+)=([\\\"'])(.-)%2\",function(a0,a1,z)node:addProperty(a0,Toolkit.Xml.FromXmlString(z))end)end,ParseXmlText=function(self,a2)local a3={}local a4=Toolkit.Xml:Node()table.insert(a3,a4)local a5,c,a6,a7,a8;local i,a9=1,1;while true do a5,a9,c,a6,a7,a8=string.find(a2,\"<(%/?)([%w_:]+)(.-)(%/?)>\",i)if not a5 then break end;local aa=string.sub(a2,i,a5-1)if not string.find(aa,\"^%s*$\")then local ab=(a4:value()or\"\")..Toolkit.Xml.FromXmlString(aa)a3[#a3]:setValue(ab)end;if a8==\"/\"then local ac=Toolkit.Xml:Node(a6)Toolkit.Xml.ParseArgs(ac,a7)a4:addChild(ac)elseif c==\"\"then local ac=Toolkit.Xml:Node(a6)Toolkit.Xml.ParseArgs(ac,a7)table.insert(a3,ac)a4=ac else local ad=table.remove(a3)a4=a3[#a3]if#a3<1 then error(\"XmlParser: nothing to close with \"..a6)end;if ad:name()~=a6 then error(\"XmlParser: trying to close \"..ad:name()..\" with \"..a6)end;a4:addChild(ad)end;i=a9+1 end;local aa=string.sub(a2,i)if#a3>1 then error(\"XmlParser: unclosed \"..a3[#a3]:name())end;return a4 end,version=function()return Toolkit.Xml.__version end}Toolkit:traceEx(\"red\",Toolkit.Xml.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.Xml.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end;if not Toolkit.HttpUtility then Toolkit.HttpUtility={__header=\"Toolkit.HttpUtility\",__version=\"1.0.0\",urlEncode=function(ae)if ae then ae=string.gsub(ae,\"\\n\",\"\\r\\n\")ae=string.gsub(ae,\"([^%w ])\",function(c)return string.format(\"%%%02X\",string.byte(c))end)ae=string.gsub(ae,\" \",\"+\")end;return ae end,urlDecode=function(ae)if ae then ae=string.gsub(ae,\"+\",\" \")ae=string.gsub(ae,\"%%(%x%x)\",function(M)return string.char(tonumber(M,16))end)ae=string.gsub(ae,\"\\r\\n\",\"\\n\")end;return ae end}Toolkit:traceEx(\"red\",Toolkit.HttpUtility.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.HttpUtility.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end\n\nif (Sonos == nil) then \n Sonos={__header=\"SONOS Advanced Remote\",__version=\"1.0.0\",_commands=Tk.Collections.Queue.new(),_isPlaying=false,_isMuted=false,_debugProcess=true,transportState=\"\",transportStatus=\"\",volume=0,mediaInfo={title=\"\"},eq={loudness=false},zpStatus={zoneName=\"\",localUID=\"\",macAddress=\"\"},zonePlayers={{ip=\"\",uuid=\"\",coordinator=false,group=\"\"}},radioStations={},currentTrack={isRadio=false,isFile=false,absCount=nil,artist=nil,album=nil,creator=nil,duration=nil,originalTrackNumber=nil,relTime=nil,relCount=nil,title=nil,track=nil,uri=nil},refreshTime=6,defaultRefreshTime=12,props={controlURL={ServerContentDirectory=\"/MediaServer/ContentDirectory/Control\",RendererAVTransport=\"/MediaRenderer/AVTransport/Control\",RendererRenderingControl=\"/MediaRenderer/RenderingControl/Control\",RendererConnectionManager=\"/MediaRenderer/ConnectionManager/Control\",RendererQueue=\"/MediaRenderer/Queue/Control\",DeviceProperties=\"/DeviceProperties/Control\"},serviceType={MediaRenderer=\"urn:schemas-upnp-org:device:MediaRenderer:1\",AVTransport=\"urn:schemas-upnp-org:service:AVTransport:1\",RenderingControl=\"urn:schemas-upnp-org:service:RenderingControl:1\",Queue=\"urn:schemas-sonos-com:service:Queue:1\",GroupRenderingControl=\"urn:schemas-upnp-org:service:GroupRenderingControl:1\",ContentDirectory=\"urn:schemas-upnp-org:service:ContentDirectory:1\",DeviceProperties=\"urn:schemas-upnp-org:service:DeviceProperties:1\"},actions={GetTransportInfo=\"GetTransportInfo\",GetPositionInfo=\"GetPositionInfo\",GetMediaInfo=\"GetMediaInfo\",GetVolume=\"GetVolume\",SetVolume=\"SetVolume\",GetMute=\"GetMute\",SetMute=\"SetMute\",SetLoudness=\"SetLoudness\",GetLoudness=\"GetLoudness\",SetAVTransportURI=\"SetAVTransportURI\",Play=\"Play\",Pause=\"Pause\",Stop=\"Stop\",Previous=\"Previous\",Next=\"Next\",Seek=\"Seek\",Browse=\"Browse\",SetLEDState=\"SetLEDState\",GetLEDState=\"GetLEDState\"},transportState={playing=\"PLAYING\",stopped=\"STOPPED\",pausedPlayback=\"PAUSED_PLAYBACK\",transitioning=\"TRANSITIONING\"},transportStatus={ok=\"OK\"}}} \n Tk:traceEx(\"green\", \"-------------------------------------------------------------------------\");\n Tk:traceEx(\"green\", \"-- %s version %s\", Sonos.__header, Sonos.__version);\n Tk:traceEx(\"green\", \"-------------------------------------------------------------------------\");\n Sonos.browseDirectory=function(a)Tk:trace(\"Get browseDirectory request\")return sendSoapMessage(a.props.controlURL.ServerContentDirectory,a.props.serviceType.ContentDirectory,{name=a.props.actions.Browse,service=a.props.serviceType.ContentDirectory},\"R:0/0BrowseDirectChildren05\",function(b)local c=decode(tostring(b:match(\"(.+)\"))or\"\")if c~=nil or c~=\"\"then local d=Toolkit.Xml:ParseXmlText(c)local e=d[\"DIDL\"]local f=1;if e~=nil then local g=e[\"item\"]or{}for h,i in ipairs(g)do Tk:trace(\"key:\"..tostring(h))Tk:trace(\"value type:\"..type(i))Tk:trace(\"children value:\"..type(g[h]))if i~=nil then local j,k=\"\",\"\"if i[\"dc:title\"]~=nil then j=i[\"dc:title\"]:value()end;if i[\"res\"]~=nil then rsRe=i[\"res\"]:value()end;a.radioStations[f]={title=j,res=rsRe}Tk:traceEx(\"yellow\",\"radio station #%d - %s\",f,a.radioStations[f].title)f=f+1 end end;DataPersistence:set(tostring(_f:getSelfId()),{radioStations=a.radioStations})else Tk:traceEx(\"red\",\"DIDL-Lite node not created!\")end else Tk:traceEx(\"red\",\"Radio Stations results not found!\")end end)end\n Sonos.getTransportState=function(a)Tk:trace(\"Get transport state request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.GetTransportInfo,service=a.props.serviceType.AVTransport},\"0\",function(b)a.transportState=b:match(\"(.+)\")or\"\"a.transportStatus=b:match(\"(.+)\")or\"\"end)end\n Sonos.getCurrentTrack=function(a)Tk:trace(\"get current track request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.GetPositionInfo,service=a.props.serviceType.AVTransport},\"0Master\",function(b)a.currentTrack.track=tonumber(b:match(\"(.+)\")or 0)a.currentTrack.duration=tostring(b:match(\"(.+)\")or\"\")a.currentTrack.relTime=tostring(b:match(\"(.+)\")or\"00:00:00\")a.currentTrack.relCount=tonumber(b:match(\"(.+)\")or 0)a.currentTrack.absCount=tonumber(b:match(\"(.+)\")or 0)a.currentTrack.uri=tostring(b:match(\"(.+)\")or\"\")local c=decode(tostring(b:match(\"(.+)\")))a.currentTrack.title=string.gsub(decode(tostring(c:match(\"(.+)\")or\"\")),1,22)a.currentTrack.creator=decode(tostring(c:match(\"(.+)\")or\"\"))a.currentTrack.artist=decode(tostring(c:match(\"(.+)\")or\"\"))a.currentTrack.album=decode(tostring(c:match(\"(.+)\")or\"\"))a.currentTrack.originalTrackNumber=tostring(c:match(\"(.+)\")or\"\")Tk:trace(decode(a.currentTrack.uri or\"\"))if (string.find(decode(a.currentTrack.uri or\"\"),\"x-rincon-mp3radio:\",1,true)~=nil or string.find(decode(a.currentTrack.uri or\"\"),\"aac:\",1,true)~=nil)then Tk:trace(\"MP3 or AAC Radio\")a.currentTrack.isRadio=true;a.currentTrack.isFile=false;if a:getMediaInfo()then Tk:trace(a.mediaInfo.title)a.currentTrack.title=string.sub(a.mediaInfo.title,1,25)end elseif string.find(decode(a.currentTrack.uri or\"\"),\"x-file-cifs:\",1,true)~=nil then Tk:trace(\"Track\")a.currentTrack.isRadio=false;a.currentTrack.isFile=true else Tk:trace(\"Unknown\")a.currentTrack.isRadio=false;a.currentTrack.isFile=false end end)end \n Sonos.getMediaInfo=function(a)Tk:trace(\"Get current media info request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.GetMediaInfo,service=a.props.serviceType.AVTransport},\"0Master\",function(b)local c=decode(tostring(b:match('(.+)')))a.mediaInfo.title=decode(tostring(c:match('(.+)')or''))end)end\n Sonos.getZonePlayer=function(a,b,c)local d={}if c~=nil and c==\"ip\"then for e,f in ipairs(a.zonePlayers)do if f.ip==tostring(b)then d=f;break end end end;return d end\n Sonos.getZpStatus=function(a,b)Tk:trace(\"Get zone player status\")b=b or 0;local c=Tk.Net.HttpRequest(_ip,_port)c:setReadTimeout(2000)local d,e,f=c:request(\"GET\",\"/status/zp\",{'Content-Type: text/xml; charset=\"utf-8\"'})c:disconnect()c:dispose()c=nil;if f==0 then if tonumber(e)==200 then a.zpStatus.zoneName=tostring(d:match(\"(.+)\"))a.zpStatus.localUID=tostring(d:match(\"(.+)\"))a.zpStatus.macAddress=tostring(d:match(\"(.+)\"))return true else Tk:trace(\"status: %s\",e)end else Tk:traceEx(\"red\",\"Communication error code: \"..f)if b<10 then Tk:trace(\"retry #%d\",b)_f:sleep(500)return a:getZpStatus(b+1)else Tk:trace(\"Error: Code returned %s\",tostring(errorcode or\"n.c\"))end end;return false end\n Sonos.getSpeakersIP=function(a,b)Tk:trace(\"Get status topology, look for speakers IP\")b=b or 0;local c=Tk.Net.HttpRequest(_ip,_port)c:setReadTimeout(2000)local d,e,f=c:request(\"GET\",\"/status/topology\",{'Content-Type: text/xml; charset=\"utf-8\"'})c:disconnect()c:dispose()c=nil;if f==0 then if tonumber(e)==200 then local g=tostring(d:match(\"(.+)\"))or\"\"if g~=nil or g~=\"\"then local h=Tk.Xml:ParseXmlText(g)for i,j in pairs(h:childNodes())do Tk:trace(\"Node name: \"..j:name())if j:name()==\"ZonePlayer\"then local k={}k.name=j:value()or\"unknown\"Tk:traceEx(\"green\",\"Node value: %s\",k.name)local l=j:properties()if l~=nil and#l>0 then for m,n in pairs(l)do if n.name==\"group\"then k.group=n.value;Tk:trace(n.name..\"=\"..n.value)elseif n.name==\"coordinator\"then k.coordinator=n.value;Tk:trace(n.name..\"=\"..n.value)elseif n.name==\"location\"then Tk:trace(n.name..\"=\"..n.value)local o=n.value:match(\"http://(%d+.%d+.%d+.%d+):1400\")k.ip=o;Tk:trace(\"IP=\"..o)elseif n.name==\"uuid\"then k.uuid=n.value;Tk:trace(n.name..\"=\"..n.value)end end;table.insert(a.zonePlayers,k)end end end;return true end else Tk:trace(\"status: %s\",e)end else Tk:traceEx(\"red\",\"Communication error code: \"..f)if b<10 then Tk:trace(\"retry #%d\",b)_f:sleep(500)return a:getSpeakersIP(b+1)else Tk:trace(\"Error: Code returned %s\",tostring(errorcode or\"n.c\"))end end;return false end\n Sonos.getMute=function(a)Tk:trace(\"Get mute state request\")return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.RenderingControl,{name=a.props.actions.GetMute,service=a.props.serviceType.RenderingControl},\"0Master\",function(b)local c=tonumber(b:match(\"(.+)\")or 0)if c==0 then a._isMuted=false elseif c==1 then a._isMuted=true end;Tk:trace(\"mute: %s\",tostring(a._isMuted))end)end\n Sonos.getVolume=function(a)Tk:trace(\"Get volume request\")return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.AVTransport,{name=a.props.actions.GetVolume,service=a.props.serviceType.RenderingControl},\"0Master\",function(b)a.volume=tonumber(b:match(\"(.+)\")or 0)Tk:trace(\"volume: %d\",a.volume)end)end\n Sonos.getLoudness=function(a)Tk:trace(\"Get loudness request\")return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.AVTransport,{name=a.props.actions.GetLoudness,service=a.props.serviceType.RenderingControl},\"0Master\",function(b)local c=tonumber(b:match(\"(.+)\")or 0)if c==0 then Tk:trace(\"Loudness is OFF\")a.eq.loudness=false elseif c==1 then Tk:trace(\"Loudness is ON\")a.eq.loudness=true else Tk:trace(\"Loudness N.C\")a.eq.loudness=false end end)end\n ----------------------------------------------------------------------------------------\n -- PREPARE EVENTS SUBSCRIPTION SUPPORT but Fibaro HC2 is not compatible yet :(\n ----------------------------------------------------------------------------------------\n Sonos.eventUrl=function(a,b)return string.format(\"http://%s:%s/%s\",_ip,_port,b)end\n Sonos.subscribeToUPnPEvent=function(a,b,c)Tk:trace(\"Subscribes to UPNP events: %s\",b)local d=\"http://192.168.1.250:81/api/...\"Tk.Net.isTraceEnabled=true;local e=Tk.Net.HttpRequest(_ip,_port)e:setReadTimeout(2000)local f,g,h=e:request(\"SUBSCRIBE\",a:eventUrl(b),{'CALLBACK: <'..d..'>','NT: upnp:event','TIMEOUT: Second-60'})e:disconnect()e:dispose()e=nil;if h==0 then if tonumber(g)==200 then Tk:trace(\"Subscription response ok. status: %s\",g)else Tk:trace(\"status: %s\",g)end else Tk:traceEx(\"red\",\"Communication error code: \"..h)if c<10 then Tk:trace(\"retry #%d\",c)_f:sleep(500)return a.subscribeToUPnPEvent(b,c+1)else Tk:trace(\"Error: Code returned %s\",tostring(errorcode or\"n.c\"))end end end\n ----------------------------------------------------------------------------------------\n Tk:traceEx(\"red\",Sonos.__header..\" V \"..Sonos.__version..\" loaded in memory...\")if Tk.Debug then Tk.Debug:benchmark(Sonos.__header..\" V \"..Sonos.__version..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end\n ----------------------------------------------------------------------------------------\nend;\ntablelength=function(a)local b=0;for c in pairs(a)do b=b+1 end;return b end\nprocessResponse=function(d,e)if d==nil then return nil end;return d(e)end\nsendSoapMessage=function(f,g,h,e,i,j,k)k=k or 0;local l=Tk.Net.HttpRequest(_ip,_port)l:setReadTimeout(2000)local m=[[\n \n \t]]..string.format('%s',h.name,h.service,tostring(e or\"\"),h.name)..[[\n \n ]]local n,o,p=l:request(\"POST\",f,{'Content-Type: text/xml; charset=\"utf-8\"','SOAPAction: \"'..g..'#'..h.name..'\"'},m)l:disconnect()l:dispose()l=nil;if p==0 then if tonumber(o)==200 then if i~=nil then processResponse(i,n)end;return true else Tk:trace(\"status: %s\",o)end else Tk:traceEx(\"red\",\"Communication error code: \"..p)if j~=nil and j==true then return true end;if k<10 then Tk:trace(\"retry #%d action: %s\",k,h.name)_f:sleep(1000)return sendSoapMessage(f,g,h,e,i,j,k+1)else Tk:trace(\"Error: Code returned %s\",tostring(errorcode or\"n.c\"))end end;return false end\nencode=function(q)if q~=nil then return q:gsub(\"&\",\"&\"..\"amp;\"):gsub(\"<\",\"&\"..\"lt;\"):gsub(\">\",\"&\"..\"gt;\"):gsub('\"',\"&\"..\"quot;\"):gsub(\"'\",\"&\"..\"apos;\")end;return\"\"end\ndecode=function(q)if q~=nil then return q:gsub(\"&\"..\"#38;\",'&'):gsub(\"&\"..\"#60;\",'<'):gsub(\"&\"..\"#62;\",'>'):gsub(\"&\"..\"#34;\",'\"'):gsub(\"&\"..\"#39;\",\"'\"):gsub(\"&\"..\"lt;\",\"<\"):gsub(\"&\"..\"gt;\",\">\"):gsub(\"&\"..\"quot;\",'\"'):gsub(\"&\"..\"apos;\",\"'\"):gsub(\"&\"..\"amp;\",\"&\")end;return\"\"end\nclockToSeconds=function(r)if r==nil then return end;local len=string.len(r or\"\")local s=1;local t=0;for a in r:gmatch(\"([^:]+)\")do if len==7 or len==8 then if s==1 then t=t+a*3600 end;if s==2 then t=t+a*60 end;if s==3 then t=t+a end elseif len==4 or len==5 then if s==1 then t=t+a*60 end;if s==2 then t=t+a end elseif len==1 or len==2 then if s==1 then t=t+a end end;s=s+1 end;return t end\nsecondsToClock=function(t)local t=tonumber(t)if t==0 then return\"0:00:00\"else nHours=string.format(\"%2.f\",math.floor(t/3600))nMins=string.format(\"%02.f\",math.floor(t/60-nHours*60))nSecs=string.format(\"%02.f\",math.floor(t-nHours*3600-nMins*60))return nHours..\":\"..nMins..\":\"..nSecs end end\nrefresh=function()Sonos:getMute()Sonos:getVolume()Sonos:getLoudness()if Sonos:getTransportState()then Tk:trace(\"transport state: %s\",Sonos.transportState)if Sonos.transportState==\"PLAYING\"or Sonos.transportState==\"TRANSITIONING\"then Sonos._isPlaying=true;Sonos:getCurrentTrack()Sonos.refreshTime=5 else Sonos._isPlaying=false;Sonos.refreshTime=Sonos.defaultRefreshTime;Sonos.currentTrack={absCount=0,artist=\"\",album=\"\",creator=\"\",duration=0,originalTrackNumber=\"\",relTime=\"00:00:00\",relCount=0,title=\"\",track=\"\",uri=\"\"}end;refreshUI()end end\nrefreshUI=function()_f:call(_selfId,\"setProperty\",\"ui.lblPosition.value\",Sonos.currentTrack.relTime or\"n.c\")local a=\"\"if Sonos.currentTrack.isRadio then a=a..\"Radio \"end;if Sonos.transportState==Sonos.props.transportState.playing then a=a..\"Playing\"elseif Sonos.transportState==Sonos.props.transportState.pausedPlayback then a=a..\"Paused\"elseif Sonos.transportState==Sonos.props.transportState.transitioning then a=a..\"Transitioning\"elseif Sonos.transportState==Sonos.props.transportState.stopped then a=a..\"Stopped\"end;if Sonos._isMuted==true then a=a..\" (mute)\"end;local b=\"\"if Sonos.eq.loudness==true then b=\"Loudness ON\"elseif Sonos.eq.loudness==false then b=\"Loudness OFF\"else b=\"---\"end;_f:call(_selfId,\"setProperty\",\"ui.lblEq.value\",b)_f:call(_selfId,\"setProperty\",\"ui.lblState.value\",a)_f:call(_selfId,\"setProperty\",\"ui.slVolume.value\",Sonos.volume or\"n.c\")local c=\"\"if string.len(Sonos.currentTrack.track)>0 and Sonos.currentTrack.isRadio==false then c=c..Sonos.currentTrack.track..\"-\"end;if string.len(Sonos.currentTrack.originalTrackNumber)>0 then c=c..string.format(\"%s - %s\",Sonos.currentTrack.originalTrackNumber,Sonos.currentTrack.title)else c=c..string.format(\"%s\",Sonos.currentTrack.title or\"n.c\")end;_f:call(_selfId,\"setProperty\",\"ui.lblTitle.value\",c)_f:call(_selfId,\"setProperty\",\"ui.lblZone.value\",Sonos.zpStatus.zoneName)_f:call(_selfId,\"setProperty\",\"ui.lblArtist.value\",Sonos.currentTrack.artist or\"n.c\")_f:call(_selfId,\"setProperty\",\"ui.lblAlbum.value\",Sonos.currentTrack.album or\"n.c\")end\nif DataPersistence==nil then DataPersistence={root=\"x_sonos_object\"}DataPersistence.load=function(a)local b=_f:getGlobalValue(a.root)if string.len(b)>0 then local c=json.decode(b)if c and type(c)==\"table\"then return c else Tk:traceEx(\"red\",\"Unable to process data, check variable\")end else Tk:traceEx(\"red\",\"No data found!\")end end;DataPersistence.remove=function(a,d)local e=a:load()if e[tostring(d)]then e[tostring(d)]=nil;_f:setGlobal(a.root,json.encode(e))end end;DataPersistence.set=function(a,d,c)local e=a:load()if e[tostring(d)]then for f,g in pairs(c)do e[tostring(d)][f]=g end else e[tostring(d)]=c end;_f:setGlobal(a.root,json.encode(e))end;DataPersistence.get=function(a,d)local e=a:load()if e and type(e)==\"table\"then for f,g in pairs(e)do if tostring(f)==tostring(d or\"\")then return g end end end;return nil end;DataPersistence.reset=function(a)_f:setGlobal(a.root,json.encode({}))end;DataPersistence.isEmptyGlobalVariable=function(d)local b=_f:getGlobalValue(d)if b and string.len(tostring(b))==0 then return true else return false end end;DataPersistence.updateGlobalVariable=function(a,d,b,h)Tk:traceEx(\"green\",\"Operations are in progress, please wait...\")_f:setGlobal(d,b)if h then _f:sleep(h)end end;DataPersistence.createGlobalVariable=function(a,d,b)local i=Net.FHttp('127.0.0.1',11111)local j,k,l=i:GET(\"/api/globalVariables\")if tonumber(k)==200 and tonumber(l)==0 then if j~=nil then local json=json.decode(j)for f=1,#json do if string.lower(json[f].name or\"\")==string.lower(d or\"\")then Tk:traceEx(\"green\",\"%s already exist\",tostring(json[f].name))return 0 end end;local m,n,o=i:POST(\"/api/globalVariables\",\"{\\\"name\\\":\\\"\"..d..\"\\\",\\\"value\\\":\\\"\"..b..\"\\\"}\")if(tonumber(n)==200 or tonumber(n)==201)and tonumber(o)==0 then Tk:traceEx(\"yellow\",\"Global variable %s has been created successfully!\",d)return 1 else Tk:traceEx(\"red\",\"Status update %s, Error update %s\",n,o)return 2 end end else Tk:traceEx(\"red\",\"Error code: \"..l)return 2 end end end\nmain=function()if _refreshExecTime==nil then _refreshExecTime=tonumber(os.time()-Sonos.refreshTime)end;if _garbageExecTime==nil then _garbageExecTime=tonumber(os.time()-1800)end;if _count==nil then Tk:trace(\"HC2 start script at \"..os.date())if DataPersistence.isEmptyGlobalVariable(\"x_sonos_object\")then local a=DataPersistence:createGlobalVariable(\"x_sonos_object\",\"{}\")if a==2 then Tk:traceEx(\"red\",\"Please check variable in panel\")os.exit()end end;_count=0 else _count=_count+1 end;if _selfId==nil then _selfId=_f:getSelfId()_ip=_f:get(_selfId,\"IPAddress\")_port=_f:get(_selfId,\"TCPPort\")_icons={main=_f:get(_selfId,\"deviceIcon\")}if _ip==nil or _port==nil then Tk:traceEx(\"red\",\"You must configure IPAddress and TCPPort first\")return end;data={zpStatus={zoneName=\"\",localUID=\"\",macAddress=\"\"},zonePlayers={group=\"\",coordinator=false,location=\"\",ip=\"\",uuid=\"\"},radioStations={},tts=\"\",action=\"\",stream=\"\"}Sonos:getSpeakersIP()Sonos:getZpStatus()Sonos:browseDirectory()local b=Sonos:getZonePlayer(_ip,\"ip\")data.zonePlayers.group=b.group or\"null\"data.zonePlayers.coordinator=b.coordinator or false;data.zonePlayers.ip=b.ip or _ip;data.zonePlayers.uuid=b.uuid or\"RINCON_00000000000000000\"data.zpStatus.zoneName=Sonos.zpStatus.zoneName or\"null\"data.zpStatus.localUID=Sonos.zpStatus.localUID or\"RINCON_00000000000000000\"data.zpStatus.macAddress=Sonos.zpStatus.macAddress or\"00:00:00:00:00:00\"data.radioStations=Sonos.radioStations;DataPersistence:set(tostring(_f:getSelfId()),data)local c=DataPersistence:load()if c and type(c)==\"table\"then for d,e in pairs(c)do Tk:traceEx(\"green\",\"remote ID: \"..d)Tk:traceEx(\"green\",\"zoneName: \"..e.zpStatus.zoneName)Tk:traceEx(\"green\",\"localUID: \"..e.zpStatus.localUID)Tk:traceEx(\"green\",\"macAddress: \"..e.zpStatus.macAddress)Tk:traceEx(\"green\",\"group: \"..e.zonePlayers.group)Tk:traceEx(\"green\",\"coordinator: \"..tostring(e.zonePlayers.coordinator))Tk:traceEx(\"green\",\"ip: \"..e.zonePlayers.ip)Tk:traceEx(\"green\",\"uuid: \"..e.zonePlayers.uuid)Tk:traceEx(\"green\",\"radio stations count: \"..#e.radioStations)end end end;local f=os.difftime(os.time(),_refreshExecTime or 0)if f>=Sonos.refreshTime then _refreshExecTime=os.time()refresh()end;local f=os.difftime(os.time(),_garbageExecTime or 0)if f>=1800 then collectgarbage(\"collect\")_garbageExecTime=os.time()Tk:traceEx(\"yellow\",\"Collect Garbage at \"..os.date())local g=Net.FHttp(\"127.0.0.1\",11111)local h=g:GET(\"/api/virtualDevices\")local i={}if h~=nil then i=json.decode(h)end;local c=DataPersistence:load()if c and type(c)==\"table\"then for d,e in pairs(c)do local j=false;Tk:traceEx(\"green\",\"remote ID: \"..d)if i~=nil then for k,e in pairs(i)do if tostring(e['id'])==tostring(d)then j=true end end end;if not j then Tk:traceEx(\"red\",\"Remote ID:\"..d..\" not found, data persist object purge process\")DataPersistence:remove(d)end end end end end\n-- Debug / Trace\nTk.isTraceEnabled = true;\n-- Start\nmain();\n--EOF","ui.lblAlbum.value":"","ui.lblArtist.value":"","ui.lblDebug.value":"Set radio station to 538NONSTOP","ui.lblEq.value":"Loudness ON","ui.lblPosition.value":"0:00:15","ui.lblState.value":"Radio Playing","ui.lblTitle.value":"538 Non Stop","ui.lblZone.value":"Woonkamer (L)","ui.slVolume.value":7,"visible":"true","rows":[{"type":"label","elements":[{"id":1,"lua":false,"waitForResponse":false,"caption":"Zone:","name":"lblZone","favourite":false,"main":false}]},{"type":"label","elements":[{"id":2,"lua":false,"waitForResponse":false,"caption":"State:","name":"lblState","favourite":false,"main":true}]},{"type":"label","elements":[{"id":3,"lua":false,"waitForResponse":false,"caption":"Position:","name":"lblPosition","favourite":false,"main":false}]},{"type":"label","elements":[{"id":4,"lua":false,"waitForResponse":false,"caption":"Title:","name":"lblTitle","favourite":false,"main":false}]},{"type":"label","elements":[{"id":5,"lua":false,"waitForResponse":false,"caption":"Artist:","name":"lblArtist","favourite":false,"main":false}]},{"type":"label","elements":[{"id":6,"lua":false,"waitForResponse":false,"caption":"Album:","name":"lblAlbum","favourite":false,"main":false}]},{"type":"button","elements":[{"id":7,"lua":true,"waitForResponse":false,"caption":"› Play","name":"btnPlay","empty":false,"msg":"-- PLAY\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"PLAY\", \"Play command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":true},{"id":8,"lua":true,"waitForResponse":false,"caption":"• Pause","name":"btnPause","empty":false,"msg":"-- PAUSE\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"PAUSE\", \"Pause command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":9,"favourite":false,"main":false},{"id":9,"lua":true,"waitForResponse":false,"caption":"■ Stop","name":"btnStop","empty":false,"msg":"-- STOP\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"STOP\", \"Stop command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":9,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":10,"lua":true,"waitForResponse":false,"caption":"◄ Prev","name":"btnPrev","empty":false,"msg":"-- PREV\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"PREV\", \"Prev command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":11,"lua":true,"waitForResponse":false,"caption":"Next ►","name":"btnNext","empty":false,"msg":"-- NEXT\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"NEXT\", \"Next command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":12,"lua":true,"waitForResponse":false,"caption":"OFF","name":"lblPowerOff","empty":false,"msg":"-- POWER OFF\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"PWOFF\", \"Power off command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":9,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":13,"lua":true,"waitForResponse":false,"caption":"← Seek","name":"btnSeekLeft","empty":false,"msg":"-- SEEK LEFT\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"SEEKL\", \"Seek left command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":14,"lua":true,"waitForResponse":false,"caption":"Seek →","name":"btnSeekRight","empty":false,"msg":"-- SEEK RIGHT\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"SEEKR\", \"Seek right command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false}]},{"type":"slider","elements":[{"id":15,"lua":true,"waitForResponse":false,"caption":"Volume","name":"slVolume","msg":"-- SET VOLUME\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal vol = _sliderValue_;\nlocal cmd, log = \"VOL\"..vol, \"Set volume to \"..vol..\" command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"value":7,"favourite":false,"main":true}]},{"type":"button","elements":[{"id":16,"lua":true,"waitForResponse":false,"caption":"Mute","name":"btnMute","empty":false,"msg":"-- MUTE\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"MUTE\", \"Mute command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":9,"favourite":false,"main":false},{"id":17,"lua":true,"waitForResponse":false,"caption":"UnMute","name":"btnUnMute","empty":false,"msg":"-- UNMUTE\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"UNMUTE\", \"Unmute command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":18,"lua":true,"waitForResponse":false,"caption":"Toggle","name":"btnToggleMute","empty":false,"msg":"-- TOGGLE MUTE\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"TMUTE\", \"Toggle mute command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false}]},{"type":"label","elements":[{"id":19,"lua":false,"waitForResponse":false,"caption":"EQ:","name":"lblEq","favourite":false,"main":false}]},{"type":"button","elements":[{"id":20,"lua":true,"waitForResponse":false,"caption":"Loudness ON","name":"btnLoudnessOn","empty":false,"msg":"-- LOUDNESS ON\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"LNON\", \"Loudness On command was sent\"\nlocal _x = {\n root = \"x_sonos_object\",\n load = (function(self)\n local value = _f:getGlobalValue(self.root)\n if (string.len(value) > 0) then\n local object = json.decode(value)\n if (object and type(object)==\"table\") then\n return object\n else\n _f:debug(\"Unable to process data, check variable\")\n end\n else\n _f:debug(\"No data found!\");\n end\n end),\n set = (function(self, name, object)\n local current = self:load()\n if (current[tostring(name)]) then\n _f:debug(name .. \" found, try update object\");\n for i, v in pairs(object) do\n current[tostring(name)][i] = v\n end\n else\n _f:debug(name .. \" not found, insert new object\");\n current[tostring(name)] = object\n end \n _f:setGlobal(self.root, json.encode(current))\n end),\n get = (function(self, name)\n local current = self:load()\n if (current and type(current)==\"table\") then\n for i, v in pairs(current) do\n if (tostring(i) == tostring(name or \"\")) then\n return v\n end \n end\n end\n return nil;\n end)\n}\n\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":21,"lua":true,"waitForResponse":false,"caption":"Loudness OFF","name":"btnLoudnessOff","empty":false,"msg":"-- LOUDNESS OFF\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"LNOFF\", \"Loudness Off command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false}]},{"type":"button","elements":[{"id":22,"lua":true,"waitForResponse":false,"caption":"♫ 1","name":"btnMyRadioStation1","empty":false,"msg":"-- PLAY RADIO STATION 1\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"RST1\", \"Play radio 1 command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":23,"lua":true,"waitForResponse":false,"caption":"♫ 2","name":"btnMyRadioStation2","empty":false,"msg":"-- PLAY RADIO STATION 2\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"RST2\", \"Play radio 2 command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":24,"lua":true,"waitForResponse":false,"caption":"♫ 3","name":"btnMyRadioStation3","empty":false,"msg":"-- PLAY RADIO STATION 3\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"RST3\", \"Play radio 3 command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":25,"lua":true,"waitForResponse":false,"caption":"♫ 4","name":"btnMyRadioStation4","empty":false,"msg":"-- PLAY RADIO STATION 4\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"RST4\", \"Play radio 4 command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false},{"id":26,"lua":true,"waitForResponse":false,"caption":"♫ 5","name":"btnMyRadioStation5","empty":false,"msg":"-- PLAY RADIO STATION 5\nlocal _f = fibaro\nlocal sid, bid = _f:getSelfId(), 28\nlocal cmd, log = \"RST5\", \"Play radio 5 command was sent\"\nlocal _x ={root=\"x_sonos_object\",load=function(b)local c=_f:getGlobalValue(b.root)if string.len(c)>0 then local d=json.decode(c)if d and type(d)==\"table\"then return d else _f:debug(\"Unable to process data, check variable\")end else _f:debug(\"No data found!\")end end,set=function(b,e,d)local f=b:load()if f[e]then for g,h in pairs(d)do f[e][g]=h end else f[e]=d end;_f:setGlobal(b.root,json.encode(f))end,get=function(b,e)local f=b:load()if f and type(f)==\"table\"then for g,h in pairs(f)do if tostring(g)==tostring(e or\"\")then return h end end end;return nil end}\n_x:set(tostring(sid), { action = tostring(cmd ..\" \")})\n_f:log(log);\n_f:call(sid, \"setProperty\", \"ui.lblDebug.value\", log);\n_f:call(sid, \"pressButton\", bid);","buttonIcon":8,"favourite":false,"main":false}]},{"type":"label","elements":[{"id":27,"lua":false,"waitForResponse":false,"caption":"","name":"lblDebug","favourite":false,"main":false}]},{"type":"button","elements":[{"id":28,"lua":true,"waitForResponse":false,"caption":"Process","name":"Process","empty":false,"msg":"-------------------------------------------------------------------------------------------\n-------------------------------------------------------------------------------------------\n-- SONOS Remote & Text To Speech (TTS)\n--\n-- Copyright (C) 2014-2015 Jean-Christophe Vermandé\n--\n-- Version 1.0.1 beta\n-------------------------------------------------------------------------------------------\n-------------------------------------------------------------------------------------------\n-- CHANGE LOGS: \n--\n-- Version 1.0.1 beta\n-- Fix: Handling percent signs in radio title\n-- Fix: Handling aac radio channels \n--\n-- Version 1.0.0 beta\n-- Improvement: Auto configuration\n-- Improvement: Multiple VD support\n-- Improvement: Low latency when triggering commands.\n-- Improvement: Some code enhancement / refactoring.\n-- New: TTS now use Voice RSS or ResponsiveVoice API with advanced options (duration, \n-- volume, auto Resume) \n-- New: Play stream with advanced options (duration, volume, auto Resume)\n--\n-- Version 0.1.0\n-- Fix Google TTS with client=t in URI\n--\n-- Version 0.0.9\n-- New: TTS version 2 added with external server support and notable improvement\n-- \n-- Version 0.0.8\n-- Improvement: Play TSS with auto stop mode (set dr parameter to \"auto\") now works as expected.\n-- Improvement: Play TTS with fidex duration (set dr parameter to \"xx\" seconds) now works as expected.\n-- Patch: Main image is now fixed after pressing a button, thanks to Labomatik & JM13.\n-- Patch: Bug with XMl parsing for BrowseDirectChildren.\n-- Warning: To operate the radio shortcuts you must choose at least two favorite radios.\n\n-- Version 0.0.7\n-- Refresh process run faster and more efficiently.\n-- Patch line 892: attempt to index local 'value' (a function value)\n-- Patch line 1256: attempt to concatenate a nil value\n-- Show plugin current version in debug window on startup\n-- Add LED control \"On\" or \"Off\" -> Sonos:ledState(\"On\");\n-- \n-------------------------------------------------------------------------------------------\n-------------------------------------------------------------------------------------------\n\nUserParams = {\n -- Voice RSS API Key (Free Registration: http://www.voicerss.org/registration.aspx)\n voiceRssApiKey = \"000000000000000000000000000000\",\n -- Sound quality: low, medium, high\n voiceRssSoundQuality = \"medium\"\n}\n\n-------------------------------------------------------------------------------------------\n-------------------------------------------------------------------------------------------\nlocal _f = fibaro;\n\nif not Toolkit then Toolkit={\n __header=\"Toolkit\",\n __version=\"1.0.6\",\n __luaBase=\"5.2.0\",\n __copyright=\"Jean-Christophe Vermandé\",\n __licence=[[ \n\t Copyright (C) 2013-2015 Jean-Christophe Vermandé\n\n This program is free software: you can redistribute it and/or modify\n it under the terms of the GNU General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n This program is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU General Public License for more details.\n\n You should have received a copy of the GNU General Public License\n along with this program. If not, see .\n]],__frameworkHeader=function(self)self:traceEx(\"green\",\"-------------------------------------------------------------------------\")self:traceEx(\"green\",\"-- HC2 Toolkit Framework version %s\",self.__version)self:traceEx(\"green\",\"-- Current interpreter version is %s\",self.getInterpreterVersion())self:traceEx(\"green\",\"-- Total memory in use by Lua: %.2f Kbytes\",self.getCurrentMemoryUsed())self:traceEx(\"green\",\"-------------------------------------------------------------------------\")end,chars=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\",hex=\"0123456789abcdef\",now=os.date,toUnixTimestamp=function(a)return os.time(a)end,fromUnixTimestamp=function(b)return os.date(\"%c\",ts)end,currentTime=function()return tonumber(os.date(\"%H%M%S\"))end,comparableTime=function(d,e,f)return tonumber(string.format(\"%02d%02d%02d\",d,e,f))end,isTraceEnabled=true,isAutostartTrigger=function()local a=fibaro:getSourceTrigger()return a[\"type\"]==\"autostart\"end,isOtherTrigger=function()local a=fibaro:getSourceTrigger()return a[\"type\"]==\"other\"end,raiseError=function(g,h)error(g,h)end,colorSetToRgbw=function(self,j)self.assertArg(\"colorSet\",j,\"string\")local a,i={},1;for k in string.gmatch(j,\"(%d+)\")do a[i]=k;i=i+1 end;return a[1],a[2],a[3],a[4]end,isValidJson=function(self,l,m)self.assertArg(\"data\",l,\"string\")self.assertArg(\"raise\",m,\"boolean\")if string.len(l)>0 then if pcall(function()return json.decode(l)end)then return true else if m then self.raiseError(\"invalid json\",2)end end end;return false end,assertArg=function(name,value,n)if type(value)~=n then Tk.raiseError(\"argument \"..name..\" must be \"..n,2)end end,trace=function(self,value,...)if self.isTraceEnabled then if value~=nil then return fibaro:debug(string.format(value,...))end end end,traceEx=function(self,o,value,...)self:trace(string.format('<%s style=\"color:%s;\">%s',\"span\",o,string.format(value,...),\"span\"))end,getInterpreterVersion=function()return _VERSION end,getCurrentMemoryUsed=function()return collectgarbage(\"count\")end,trim=function(b)Tk.assertArg(\"value\",b,\"string\")return string.gsub(b,\"^%s*(.-)%s*$\",\"%1\")end,isNaN=function(p)return p~=p end,filterByPredicate=function(table,q)Tk.assertArg(\"table\",table,\"table\")Tk.assertArg(\"predicate\",q,\"function\")local r,s=1,{}for i=1,#table do local k=table[i]if k~=nil then if q(k)then s[r]=k;r=r+1 end end end;return s,#s end}Toolkit:__frameworkHeader()Tk=Toolkit end;if not Toolkit.Debug then Toolkit.Debug={__header=\"Toolkit.Debug\",__version=\"1.0.1\",__clocks={[\"fragment\"]=os.clock(),[\"all\"]=os.clock()},benchmarkPoint=function(self,name)__clocks[name]=os.clock()end,benchmark=function(self,g,t,name,u)Toolkit.assertArg(\"message\",g,\"string\")Toolkit.assertArg(\"template\",g,\"string\")if u~=nil then Toolkit.assertArg(\"reset\",u,type(true))end;Toolkit:traceEx(\"yellow\",\"Benchmark [\"..g..\"]: \"..string.format(t,os.clock()-self.__clocks[name]))if u==true then self.__clocks[name]=os.clock()end end}Toolkit:traceEx(\"red\",Toolkit.Debug.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.Debug.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end;if not Toolkit then error(\"You must add Toolkit\",2)end;if not Toolkit.Collections then Toolkit.Collections={}end;if not Toolkit.Collections.Queue then Toolkit.Collections.Queue={__header=\"Toolkit.Collections.Queue\",__version=\"1.0.0\",__base={__first=0,__count=0,toArray=function(self)local v={}for i=1,self:count()do v[i]=self[i]end;return v end,clear=function(self)for i=1,self:count()do self[i]=nil end;self.__first=0;self.__count=0 end,enqueue=function(self,value)assert(value~=nil)local r=self.__first+1;self.__count=self.__count+1;self.__first=r;self[r]=value;Tk:trace(\"add at pos %d value with %s type\",r,tostring(self[r]))end,dequeue=function(self)local w=self:peek()self[1]=nil;self.__first=self.__first-1;self.__count=self.__count-1;for i=1,self:count()do self[i]=self[i+1]end;return w end,contains=function(self,value)assert(value~=nil)for i=1,self:count()do if self[i]==value then return true end end;return false end,peek=function(self)return self[1]end,count=function(self)return tonumber(self.__count)end,clone=function(self)return self end},new=function()collectgarbage(\"collect\")return Toolkit.Collections.Queue.__base end,version=function()return Toolkit.Collections.Queue.__version end}Toolkit:traceEx(\"red\",Toolkit.Collections.Queue.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.Collections.Queue.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end;if not Toolkit then error(\"You must add Toolkit\",2)end;if not Toolkit.Net then Toolkit.Net={__header=\"Toolkit.Net\",__version=\"1.0.3\",__cr=string.char(13),__lf=string.char(10),__crLf=string.char(13,10),__host=nil,__port=nil,__trace=function(k,...)if Toolkit.Net.isTraceEnabled then Toolkit:trace(k,...)end end,__writeHeader=function(x,l)assert(tostring(l)or l==nil or l==\"\",\"Invalid header found: \"..l)local y=tostring(l)x:write(y..Toolkit.Net.__crLf)Toolkit.Net.__trace(\"%s.%s::request > Add header [%s]\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,y)end,__decodeChunks=function(z)resp=\"\"line=\"0\"lenline=0;len=string.len(z)i=1;while i<=len do c=string.sub(z,i,i)if lenline==0 then if c==Toolkit.Net.__lf then lenline=tonumber(line,16)if lenline==null then lenline=0 end;line=0 elseif c==Toolkit.Net.__cr then lenline=0 else line=line..c end else resp=resp..c;lenline=lenline-1 end;i=i+1 end;return resp end,__readHeader=function(l)if l==nil then error(\"Couldn't find header\")end;local A=\"\"local B={}local i,len=1,string.len(l)while i<=len do local z=l:sub(i,i)or\"\"local C=l:sub(i+1,i+1)or\"\"if z..C==Toolkit.Net.__crLf then i=i+1;table.insert(B,A)A=\"\"else A=A..z end;i=i+1 end;return B end,__readSocket=function(x)local D,len=0,1;local A,l=\"\",\"\"while D==0 and len>0 do l,D=x:read()len=string.len(l)A=A..l end;return A,D end,__Http={__header=\"HttpRequest\",__version=\"1.0.3\",__tcpSocket=nil,__timeout=250,__waitBeforeReadMs=25,__isConnected=false,__isChunked=false,__url=nil,__method=\"GET\",__headers={},__body=nil,__authorization=nil,setBasicAuthentication=function(self,E,F)Toolkit.assertArg(\"username\",E,\"string\")Toolkit.assertArg(\"password\",F,\"string\")self.__authorization=Toolkit.Crypto.Base64:encode(tostring(E..\":\"..F))end,setBasicAuthenticationEncoded=function(self,G)Toolkit.assertArg(\"base64String\",G,\"string\")self.__authorization=G end,setWaitBeforeReadMs=function(self,H)Toolkit.assertArg(\"ms\",H,\"integer\")self.__waitBeforeReadMs=H;Toolkit.Net.__trace(\"%s.%s::setWaitBeforeReadMs > set to %d ms\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,H)end,getWaitBeforeReadMs=function(self)return self.__waitBeforeReadMs end,setReadTimeout=function(self,H)Toolkit.assertArg(\"ms\",H,\"number\")self.__timeout=H;Toolkit.Net.__trace(\"%s.%s::setReadTimeout > Timeout set to %d ms\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,H)end,getReadTimeout=function(self)return self.__timeout end,disconnect=function(self)self.__tcpSocket:disconnect()self.__isConnected=false;Toolkit.Net.__trace(\"%s.%s::disconnect > Connected: %s\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,tostring(self.__isConnected))end,request=function(self,I,J,B,K)Toolkit.assertArg(\"method\",I,\"string\")assert(I==\"GET\"or I==\"POST\"or I==\"PUT\"or I==\"DELETE\"or I==\"SUBSCRIBE\")assert(J~=nil or J==\"\")self.__isChunked=false;self.__tcpSocket:setReadTimeout(self.__timeout)self.__url=J;self.__method=I;self.__headers=B or{}self.__body=K or nil;local v=self.__method..\" \"..self.__url..\" HTTP/1.1\"Toolkit.Net.__trace(\"%s.%s::request > %s with method %s\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,self.__url,self.__method)local L=\"\"if Toolkit.Net.__port~=nil then L=\":\"..tostring(Toolkit.Net.__port)end;local M=\"Host: \"..Toolkit.Net.__host..L;Toolkit.Net.__writeHeader(self.__tcpSocket,v)Toolkit.Net.__writeHeader(self.__tcpSocket,M)for i=1,#self.__headers do Toolkit.Net.__writeHeader(self.__tcpSocket,self.__headers[i])end;if self.__authorization~=nil then Toolkit.Net.__writeHeader(self.__tcpSocket,\"Authorization: Basic \"..self.__authorization)end;if self.__body~=nil then Toolkit.Net.__writeHeader(self.__tcpSocket,\"Content-Length: \"..string.len(self.__body))Toolkit.Net.__trace(\"%s.%s::request > Body length is %d\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,string.len(self.__body))end;self.__tcpSocket:write(Toolkit.Net.__crLf..Toolkit.Net.__crLf)if self.__body~=nil then self.__tcpSocket:write(self.__body)end;fibaro:sleep(self.__waitBeforeReadMs)local N,D=Toolkit.Net.__readSocket(self.__tcpSocket)Toolkit.Net.__trace(\"%s.%s::receive > Length of result: %d\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,string.len(N))local O,P;if string.len(N)>0 then local Q=string.find(N,Toolkit.Net.__crLf..Toolkit.Net.__crLf)local R=string.sub(N,1,Q+2)if string.len(R)then P=string.sub(R,10,13)Toolkit.Net.__trace(\"%s.%s::receive > Status %s\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,P)Toolkit.Net.__trace(\"%s.%s::receive > Length of headers reponse %d\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,string.len(R))__headers=Toolkit.Net.__readHeader(R)for S,k in pairs(__headers)do if string.find(string.lower(k or\"\"),\"chunked\")then self.__isChunked=true;Toolkit.Net.__trace(\"%s.%s::receive > Transfer-Encoding: chunked\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,string.len(N))end end end;local T=string.sub(N,Q+4)if self.__isChunked then O=Toolkit.Net.__decodeChunks(T)D=0 else O=T;D=0 end end;return O,P,D end,version=function()return Toolkit.Net.__Http.__version end,dispose=function(self)if self.__isConnected then self.__tcpSocket:disconnect()end;self.__tcpSocket=nil;self.__url=nil;self.__headers=nil;self.__body=nil;self.__method=nil;if pcall(function()assert(self.__tcpSocket~=Net.FTcpSocket)end)then Toolkit.Net.__trace(\"%s.%s::dispose > Successfully disposed\",Toolkit.Net.__header,Toolkit.Net.__Http.__header)end;collectgarbage(\"collect\")Toolkit.Net.__trace(\"%s.%s::dispose > Total memory in use by Lua: %.2f Kbytes\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,collectgarbage(\"count\"))end},isTraceEnabled=false,HttpRequest=function(U,V)assert(U~=Toolkit.Net,\"Cannot call HttpRequest like that!\")assert(U~=nil,\"host invalid input\")assert(V==nil or tonumber(V),\"port invalid input\")collectgarbage(\"collect\")Toolkit.Net.__host=U;Toolkit.Net.__port=V;local W=Toolkit.Net.__Http;W.__tcpSocket=Net.FTcpSocket(U,V)W.__isConnected=true;Toolkit.Net.__trace(\"%s.%s > Total memory in use by Lua: %.2f Kbytes\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,collectgarbage(\"count\"))Toolkit.Net.__trace(\"%s.%s > Create Session on port: %d, host: %s\",Toolkit.Net.__header,Toolkit.Net.__Http.__header,V,U)return W end,version=function()return Toolkit.Net.__version end}Toolkit:traceEx(\"red\",Toolkit.Net.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.Net.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end;if not Toolkit then error(\"You must add Toolkit\",2)end;if not Toolkit.Xml then Toolkit.Xml={__header=\"Toolkit.Xml\",__version=\"1.0.1\",__node=function(name)local node={}node.___value=nil;node.___name=name;node.___children={}node.___props={}function node:value()return self.___value end;function node:setValue(X)self.___value=X end;function node:name()return self.___name end;function node:setName(name)self.___name=name end;function node:childNodes()return self.___children end;function node:addChild(Y)if self[Y:name()]~=nil then if type(self[Y:name()].name)==\"function\"then local Z={}table.insert(Z,self[Y:name()])self[Y:name()]=Z end;table.insert(self[Y:name()],Y)else self[Y:name()]=Y end;table.insert(self.___children,Y)end;function node:properties()return self.___props end;function node:addProperty(name,value)local _=\"@\"..name;if self[_]~=nil then if type(self[_])==\"string\"then local Z={}table.insert(Z,self[_])self[_]=Z end;table.insert(self[_],value)else self[_]=value end;table.insert(self.___props,{name=name,value=value})end;return node end,Node=function(self,name)return self.__node(name)end,ToXmlString=function(value)value=string.gsub(value,\"&\",\"&\"..\"amp;\")value=string.gsub(value,\"<\",\"&\"..\"lt;\")value=string.gsub(value,\">\",\"&\"..\"gt;\")value=string.gsub(value,\"\\\"\",\"&\"..\"quot;\")value=string.gsub(value,\"([^%w%&%;%p%\\t% ])\",function(c)return string.format(\"&#x%X;\",string.byte(c))end)return value end,FromXmlString=function(value)value=string.gsub(value,\"&#x([%x]+)%;\",function(M)return string.char(tonumber(M,16))end)value=string.gsub(value,\"&#([0-9]+)%;\",function(M)return string.char(tonumber(M,10))end)value=string.gsub(value,\"&\"..\"quot;\",\"\\\"\")value=string.gsub(value,\"&\"..\"apos;\",\"'\")value=string.gsub(value,\"&\"..\"gt;\",\">\")value=string.gsub(value,\"&\"..\"lt;\",\"<\")value=string.gsub(value,\"&\"..\"amp;\",\"&\")return value end,ParseArgs=function(node,b)string.gsub(b,\"(%w+)=([\\\"'])(.-)%2\",function(a0,a1,z)node:addProperty(a0,Toolkit.Xml.FromXmlString(z))end)end,ParseXmlText=function(self,a2)local a3={}local a4=Toolkit.Xml:Node()table.insert(a3,a4)local a5,c,a6,a7,a8;local i,a9=1,1;while true do a5,a9,c,a6,a7,a8=string.find(a2,\"<(%/?)([%w_:]+)(.-)(%/?)>\",i)if not a5 then break end;local aa=string.sub(a2,i,a5-1)if not string.find(aa,\"^%s*$\")then local ab=(a4:value()or\"\")..Toolkit.Xml.FromXmlString(aa)a3[#a3]:setValue(ab)end;if a8==\"/\"then local ac=Toolkit.Xml:Node(a6)Toolkit.Xml.ParseArgs(ac,a7)a4:addChild(ac)elseif c==\"\"then local ac=Toolkit.Xml:Node(a6)Toolkit.Xml.ParseArgs(ac,a7)table.insert(a3,ac)a4=ac else local ad=table.remove(a3)a4=a3[#a3]if#a3<1 then error(\"XmlParser: nothing to close with \"..a6)end;if ad:name()~=a6 then error(\"XmlParser: trying to close \"..ad:name()..\" with \"..a6)end;a4:addChild(ad)end;i=a9+1 end;local aa=string.sub(a2,i)if#a3>1 then error(\"XmlParser: unclosed \"..a3[#a3]:name())end;return a4 end,version=function()return Toolkit.Xml.__version end}Toolkit:traceEx(\"red\",Toolkit.Xml.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.Xml.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end;if not Toolkit.HttpUtility then Toolkit.HttpUtility={__header=\"Toolkit.HttpUtility\",__version=\"1.0.0\",urlEncode=function(ae)if ae then ae=string.gsub(ae,\"\\n\",\"\\r\\n\")ae=string.gsub(ae,\"([^%w ])\",function(c)return string.format(\"%%%02X\",string.byte(c))end)ae=string.gsub(ae,\" \",\"+\")end;return ae end,urlDecode=function(ae)if ae then ae=string.gsub(ae,\"+\",\" \")ae=string.gsub(ae,\"%%(%x%x)\",function(M)return string.char(tonumber(M,16))end)ae=string.gsub(ae,\"\\r\\n\",\"\\n\")end;return ae end}Toolkit:traceEx(\"red\",Toolkit.HttpUtility.__header..\" loaded in memory...\")if Toolkit.Debug then Toolkit.Debug:benchmark(Toolkit.HttpUtility.__header..\" lib\",\"elapsed time: %.3f cpu secs\\n\",\"fragment\",true)end end\n\n_selfId = _f:getSelfId(); \n_ip = _f:get(_selfId, \"IPAddress\");\n_port = _f:get(_selfId, \"TCPPort\");\n_icons = {\n main = _f:get(_selfId, \"deviceIcon\");\n} \n-- Check IP and PORT before\nif (_ip == nil or _port == nil) then\n Tk:traceEx(\"red\", \"You must configure IPAddress and TCPPort first\");\n os.exit();\nend\n\nSonos={__header=\"SONOS Advanced Remote\",__version=\"1.0.0\",_commands=Tk.Collections.Queue.new(),_isPlaying=false,_isPlayingStream=false,_isMuted=false,_debugProcess=true,transportState=\"\",transportStatus=\"\",volume=0,lastVolume=0,lastMuteState=0,lastTransportState=\"\",lastTrack=nil,streamVolumeIsDifferent=false,mediaInfo={title=\"\"},eq={loudness=false},zpStatus={zoneName=\"\",localUID=\"\",macAddress=\"\"},zonePlayers={},radioStations={},currentTrack={isRadio=false,isFile=false,absCount=nil,artist=nil,album=nil,creator=nil,duration=nil,originalTrackNumber=nil,relTime=nil,relCount=nil,title=nil,track=nil,uri=nil},refreshTime=6,defaultRefreshTime=12,props={controlURL={ServerContentDirectory=\"/MediaServer/ContentDirectory/Control\",RendererAVTransport=\"/MediaRenderer/AVTransport/Control\",RendererRenderingControl=\"/MediaRenderer/RenderingControl/Control\",RendererConnectionManager=\"/MediaRenderer/ConnectionManager/Control\",RendererQueue=\"/MediaRenderer/Queue/Control\",DeviceProperties=\"/DeviceProperties/Control\"},serviceType={MediaRenderer=\"urn:schemas-upnp-org:device:MediaRenderer:1\",AVTransport=\"urn:schemas-upnp-org:service:AVTransport:1\",RenderingControl=\"urn:schemas-upnp-org:service:RenderingControl:1\",Queue=\"urn:schemas-sonos-com:service:Queue:1\",GroupRenderingControl=\"urn:schemas-upnp-org:service:GroupRenderingControl:1\",ContentDirectory=\"urn:schemas-upnp-org:service:ContentDirectory:1\",DeviceProperties=\"urn:schemas-upnp-org:service:DeviceProperties:1\"},actions={GetTransportInfo=\"GetTransportInfo\",GetPositionInfo=\"GetPositionInfo\",GetMediaInfo=\"GetMediaInfo\",GetVolume=\"GetVolume\",SetVolume=\"SetVolume\",GetMute=\"GetMute\",SetMute=\"SetMute\",SetLoudness=\"SetLoudness\",GetLoudness=\"GetLoudness\",SetAVTransportURI=\"SetAVTransportURI\",Play=\"Play\",Pause=\"Pause\",Stop=\"Stop\",Previous=\"Previous\",Next=\"Next\",Seek=\"Seek\",Browse=\"Browse\",SetLEDState=\"SetLEDState\",GetLEDState=\"GetLEDState\"},transportState={playing=\"PLAYING\",stopped=\"STOPPED\",pausedPlayback=\"PAUSED_PLAYBACK\",transitioning=\"TRANSITIONING\"},transportStatus={ok=\"OK\"}}}\nSonos.play=function(a,b)Tk:trace(\"Play request\")b=b or function(c)Tk:trace(\"Play sent\")end;a.refreshTime=2;return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.Play,service=a.props.serviceType.AVTransport},\"01\",b)end\nSonos.pause=function(a)Tk:trace(\"Pause request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.Pause,service=a.props.serviceType.AVTransport},\"01\",function(b)Tk:trace(\"Pause sent\")end)end\nSonos.stop=function(a)Tk:trace(\"Stop request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.Stop,service=a.props.serviceType.AVTransport},\"01\",function(b)Tk:trace(\"Stop was sent\")end)end\nSonos.previous=function(a)Tk:trace(\"Previous request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.Previous,service=a.props.serviceType.AVTransport},\"01\",function(b)Tk:trace(\"Previous sent\")end)end\nSonos.next=function(a)Tk:trace(\"Next request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.Next,service=a.props.serviceType.AVTransport},\"01\",function(b)Tk:trace(\"Next sent\")end)end\nSonos.seek=function(a,b,c)if a.currentTrack.isRadio then Tk:traceEx(\"Yellow\",\"Cannot seek in radio mode!\")return end;Tk:trace(\"seek to %s\",tostring(c))return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.Seek,service=a.props.serviceType.AVTransport},\"0\"..b..\"\"..c..\"\",function(d)Tk:trace(\"seek was sent\")end)end\nSonos.seekL=function(a)if a.currentTrack~=nil then local b=a.currentTrack.relTime;local c=clockToSeconds(b)if c>=30 then c=c-30 end;a:seek(\"REL_TIME\",secondsToClock(c))else Tk:traceEx(\"yellow\",\"There is no current track loaded.\")end end\nSonos.seekR=function(a)if a.currentTrack~=nil then local b=a.currentTrack.relTime;local c=clockToSeconds(b)or 0;local d=clockToSeconds(a.currentTrack.duration or\"00:00\")or 0;c=c+30;if c>=d then Tk:traceEx(\"yellow\",\"try to seek out of range!\")return end;a:seek(\"REL_TIME\",secondsToClock(c))Tk:trace(\"seek right request\")else Tk:traceEx(\"yellow\",\"There is no current track loaded.\")end end\nSonos.loudness=function(a,b)Tk:trace(\"Set Loudness: \"..tostring(b))local c=0;if b==true then c=1 end;return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.RenderingControl,{name=a.props.actions.SetLoudness,service=a.props.serviceType.RenderingControl},\"0Master\"..c..\"\",function(d)if c==0 then Tk:trace(\"Loudness OFF was sent\")a.eq.loudness=false else Tk:trace(\"Loudness ON was sent\")a.eq.loudness=true end end)end\nSonos.getLoudness=function(a)Tk:trace(\"Get loudness request\")return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.AVTransport,{name=a.props.actions.GetLoudness,service=a.props.serviceType.RenderingControl},\"0Master\",function(b)local c=tonumber(b:match(\"(.+)\")or 0)if c==0 then Tk:trace(\"Loudness is OFF\")a.eq.loudness=false elseif c==1 then Tk:trace(\"Loudness is ON\")a.eq.loudness=true else Tk:trace(\"Loudness N.C\")a.eq.loudness=false end end)end\nSonos.mute=function(a,b)Tk:trace(\"Mute request\")local c=0;if b==true then c=1 end;return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.RenderingControl,{name=a.props.actions.SetMute,service=a.props.serviceType.RenderingControl},\"0Master\"..c..\"\",function(d)if c==0 then Tk:trace(\"UnMute was sent\")a._isMuted=false else Tk:trace(\"Mute was sent\")a._isMuted=true end end)end\nSonos.muteInvert=function(a)a:getMute()if a._isMuted==true then a:mute(false)elseif a._isMuted==false then a:mute(true)end end\nSonos.getMute=function(a)Tk:trace(\"Get mute state request\")return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.RenderingControl,{name=a.props.actions.GetMute,service=a.props.serviceType.RenderingControl},\"0Master\",function(b)local c=tonumber(b:match(\"(.+)\")or 0)if c==0 then a._isMuted=false elseif c==1 then a._isMuted=true end;Tk:trace(\"mute: %s\",tostring(a._isMuted))end)end\nSonos.getVolume=function(a)Tk:trace(\"Get volume request\")return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.AVTransport,{name=a.props.actions.GetVolume,service=a.props.serviceType.RenderingControl},\"0Master\",function(b)a.volume=tonumber(b:match(\"(.+)\")or 0)Tk:trace(\"volume: %d\",a.volume)end)end\nSonos.setVolume=function(a,b)Tk:trace(\"Set volume to %s\",tostring(b))return sendSoapMessage(a.props.controlURL.RendererRenderingControl,a.props.serviceType.RenderingControl,{name=a.props.actions.SetVolume,service=a.props.serviceType.RenderingControl},\"0Master\"..tostring(b)..\"\",function(c)a.volume=tonumber(b)_f:call(_selfId,\"setProperty\",\"ui.slVolume.value\",a.volume)Tk:trace(\"Volume set to %d\",b)end)end\nSonos.ledState=function(a,b)Tk:trace(\"Set LED state request\")return sendSoapMessage(a.props.controlURL.DeviceProperties,a.props.serviceType.DeviceProperties,{name=a.props.actions.SetLEDState,service=a.props.serviceType.DeviceProperties},\"\"..(b or\"ON\")..\"\",function(c)Tk:trace(\"Set LED state sent\")end)end\nSonos.getTransportState=function(a)Tk:trace(\"Get transport state request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.GetTransportInfo,service=a.props.serviceType.AVTransport},\"0\",function(b)a.transportState=b:match(\"(.+)\")or\"\"a.transportStatus=b:match(\"(.+)\")or\"\"end)end\nSonos.setCurrentTrack=function(a,b)Tk:trace(\"Set current track to %s\",Tk.HttpUtility.urlDecode(b))return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.SetAVTransportURI,service=a.props.serviceType.AVTransport},\"0,\"..b..\",\")end\nSonos.getCurrentTrack=function(a)Tk:trace(\"get current track request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.GetPositionInfo,service=a.props.serviceType.AVTransport},\"0Master\",function(b)a.currentTrack.track=tonumber(b:match(\"(.+)\")or 0)a.currentTrack.duration=tostring(b:match(\"(.+)\")or\"\")a.currentTrack.relTime=tostring(b:match(\"(.+)\")or\"00:00:00\")a.currentTrack.relCount=tonumber(b:match(\"(.+)\")or 0)a.currentTrack.absCount=tonumber(b:match(\"(.+)\")or 0)a.currentTrack.uri=tostring(b:match(\"(.+)\")or\"\")local c=decode(tostring(b:match(\"(.+)\")))a.currentTrack.title=string.gsub(decode(tostring(c:match(\"(.+)\")or\"\")),1,22)a.currentTrack.creator=decode(tostring(c:match(\"(.+)\")or\"\"))a.currentTrack.artist=decode(tostring(c:match(\"(.+)\")or\"\"))a.currentTrack.album=decode(tostring(c:match(\"(.+)\")or\"\"))a.currentTrack.originalTrackNumber=tostring(c:match(\"(.+)\")or\"\")if (string.find(decode(a.currentTrack.uri or\"\"),\"x-rincon-mp3radio:\",1,true)~=nil or string.find(decode(a.currentTrack.uri or\"\"),\"aac:\",1,true)~=nil)then a.currentTrack.isRadio=true;a.currentTrack.isFile=false;if a:getMediaInfo()then a.currentTrack.title=string.sub(a.mediaInfo.title,1,25)end elseif string.find(decode(a.currentTrack.uri or\"\"),\"x-file-cifs:\",1,true)~=nil then a.currentTrack.isRadio=false;a.currentTrack.isFile=true else a.currentTrack.isRadio=false;a.currentTrack.isFile=false end end)end\nSonos.getMediaInfo=function(a)Tk:trace(\"Get current media info request\")return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.GetMediaInfo,service=a.props.serviceType.AVTransport},\"0Master\",function(b)local c=decode(tostring(b:match('(.+)')))a.mediaInfo.title=decode(tostring(c:match('(.+)')or''))end)end\nSonos.createMetaData=function(a)local b={}table.insert(b,'')table.insert(b,'')table.insert(b,''..encode(a.title or\"Unknown title\")..'')table.insert(b,''..encode(a.uri)..'')table.insert(b,'\"'..encode(a.class or\"object.item.audioItem.musicTrack\")..'\"')table.insert(b,'\"'..encode(a.creator or\"Unknown creator\")..'\"')table.insert(b,'\"'..encode(a.artist or\"Unknown artist\")..'\"')table.insert(b,'')table.insert(b,'')return encode(table.concat(b))end\nSonos.playRadio=function(a,b,c)Tk:trace(\"Set Radio request\")a:stop()callback=callback or function(d)Tk:trace(\"Play sent\")end;local e=encode(''..c..'object.item.audioItem.audioBroadcastSA_RINCON65031_')return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.SetAVTransportURI,service=a.props.serviceType.AVTransport},\"0\"..b..\"\"..e..\"\",function()Tk:traceEx(\"Yellow\",\"Radio: \"..c..\" pushed\")_f:sleep(1000)a:play()_f:sleep(1000)a:play()_f:sleep(1000)a:play()end,true)end\nSonos.playStream=function(a,b,c)Tk:traceEx(\"green\",\"Play Stream request uri: [%s]\",Tk.HttpUtility.urlDecode(b))a:getCurrentTrack()a.lastTrack=a.currentTrack;local d=a.volume;if c.volume~=nil then d=c.volume;a:getVolume()a.lastVolume=a.volume end;a:getMute()a.lastMuteState=a._isMuted;a:getTransportState()a.lastTransportState=a.transportState;a._isPlayingStream=true;local e=\"auto\"if c.duration~=nil then e=c.duration end;local f=\"x-file-cifs\"if c.source~=nil then if c.source==\"local\"then f=\"x-file-cifs\"elseif c.source==\"http\"then f=\"x-rincon-mp3radio\"else f=\"x-sonosapi-stream\"end end;local g=Sonos.createMetaData({title=b,protocol=f..\":*:audio/mpeg:*\",uri=f..\":\"..b,creator=\"_f-hc2\"})return sendSoapMessage(a.props.controlURL.RendererAVTransport,a.props.serviceType.AVTransport,{name=a.props.actions.SetAVTransportURI,service=a.props.serviceType.AVTransport},\"0,\"..f..\":\"..b..\",\"..g..\"\",function(h)if a._isMuted==true then a:mute(false)end;_f:sleep(250)if d~=nil and d~=a.volume then a:setVolume(d)a.streamVolumeIsDifferent=true end;_f:sleep(250)a:play(function(h)if e~=nil then if type(e)==\"string\"and e==\"auto\"then Tk:trace(\"Play TSS with auto stop mode\")local i=0;a.transportState=\"TRANSITIONING\"while a.transportState==\"TRANSITIONING\"do if i>20 then break end;a:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(1000)i=i+1 end;a.transportState=\"PLAYING\"while a.transportState==\"PLAYING\"do if i>120 then break end;a:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(1000)i=i+1 end;_f:sleep(500)a:stop()elseif type(e)==\"number\"then e=tonumber(e)Tk:trace(\"Play Stream for \"..e..\" seconds\")_f:sleep(e)local i=0;a.transportState=\"TRANSITIONING\"while a.transportState==\"TRANSITIONING\"do if i>20 then break end;a:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(1000)i=i+1 end;if e>1 then e=e-1 end;_f:sleep(e*1000)a:stop()end else Tk:trace(\"play sent\")local i=0;a.transportState=\"TRANSITIONING\"while a.transportState==\"TRANSITIONING\"do if i>30 then break end;a:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(500)i=i+1 end;local j=0;a.transportState=\"PLAYING\"while a.transportState==\"PLAYING\"do if j>60 then break end;a:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(2000)j=j+1 end;_f:sleep(500)a:stop()end;_f:sleep(100)if a.streamVolumeIsDifferent==true then a:setVolume(Sonos.lastVolume)a.streamVolumeIsDifferent=false end;_f:sleep(100)if a:setCurrentTrack(a.lastTrack.uri)then if not a.lastTrack.isRadio then a:seek(\"REL_TIME\",a.lastTrack.relTime)_f:sleep(250)end;if a.lastTransportState==a.props.transportState.playing then a:play()elseif a.lastTransportState==a.props.transportState.stopped then a:stop()elseif a.lastTransportState==a.props.transportState.pausedPlayback then a:pause()end end;_f:sleep(250)if a.lastMuteState==false then a:mute(false)else a:mute(true)end;a._isPlayingStream=false end)end)end\nSonos.externalTTSService=function(a,b,c)Tk:trace(\"Request external TTS service, try to create tts response and return meta object.\")c=c or 0;local d=Tk.Net.HttpRequest(b.host,b.port)d:setReadTimeout(2000)local e,f,g=d:request(\"GET\",b.root..'?'..b.args,{\"User-Agent: FibaroHC2/1.0\",\"Accept: application/json\",\"Accept-Charset: utf-8\"})d:disconnect()d:dispose()d=nil;if g==0 then if tonumber(f)==200 then return json.decode(e)else Tk:trace(\"status: %s\",f)end else Tk:traceEx(\"red\",\"Communication error code: \"..g)if c<10 then Tk:trace(\"retry #%d\",c)_f:sleep(500)return a:externalTTSService(b,c+1)else Tk:trace(\"Error: Code returned %s\",tostring(errorcode or\"n.c\"))end end;Tk:traceEx(\"green\",\"uri:%s\",string.format(\"%s:%s/%s?%s\",b.host,b.port,b.root,b.args))return{success=false}end\nSonos.parseOnDemandTTS=function(a,b)if string.len(b)>0 then local c={}local d,e;for d,e in string.gmatch(b,\"(%w+)=([%w%s?!.,;:-]+)|\")do Tk:trace(\"%s -> %s\",tostring(d),tostring(e))c[d]=tostring(e)end;return c end;return nil end\nSonos.getZonePlayer=function(a,b,c)local d={}if c~=nil and c==\"ip\"then for e,f in ipairs(a.zonePlayers)do if f.ip==tostring(b)then d=f;break end end end;return d end\nSonos.process=function(a)local b=false;if a._debugProcess and Tk.isTraceEnabled~=true then Tk.isTraceEnabled=true;b=true end;local c=nil;local d=DataPersistence:get(_selfId)if d and type(d)==\"table\"then if d.action~=nil and string.len(d.action)>0 then c=d.action;Tk:traceEx(\"green\",\"pending action: \"..c)end end;if c~=nil and string.len(c)>0 then local e=\"\"for f in string.gmatch(c,\"[^%s]+\")do if f~=e then a._commands:enqueue(f)else Tk:traceEx(\"red\",\"duplicate '%s' detected and removed from actions queue:\",f)end;e=f end;DataPersistence:set(_selfId,{action=\"\"})while a._commands:count()>0 do Tk:traceEx(\"green\",\"dequeue command %s\",a._commands:peek())local g=nil;g=exec(a._commands:dequeue())while g==nil do _f:sleep(250)end;Tk:traceEx(\"green\",\"command result is %s\",tostring(g))end;_f:call(_selfId,\"setProperty\",\"currentIcon\",_icons.main)end;local h=nil;if d and type(d)==\"table\"then if d.stream~=nil and type(d.stream)==\"table\"then h=d.stream;Tk:traceEx(\"green\",\"Stream pending\")end end;if h~=nil then Tk:traceEx(\"green\",\"STREAM data found, please wait...\")DataPersistence:set(_selfId,{stream=\"\"})local i=h;if i~=nil then if i.stream~=nil and i.stream==\"\"then Tk:traceEx(\"red\",\"stream location is empty !\")os.exit()end;Tk:traceEx(\"yellow\",\"stream location: %s\",i.stream or\"n.c\")if i.duration~=nil and type(i.duration)==\"number\"then Tk:traceEx(\"yellow\",\"stream duration set to: %s seconds\",i.duration or\"n.c\")elseif i.duration~=nil and type(i.duration)==\"string\"then Tk:traceEx(\"yellow\",\"stream duration mode: %s seconds\",i.duration or\"n.c\")end;if i.volume~=nil then Tk:traceEx(\"yellow\",\"stream volume: %s\",i.volume or\"n.c\")end;a:playStream(i.stream,i)end end;local j=nil;if d and type(d)==\"table\"then if d.tts~=nil and type(d.tts)==\"table\"then j=d.tts;Tk:traceEx(\"green\",\"TTS pending\")end end;if j~=nil then Tk:traceEx(\"green\",\"TTS data found, please wait...\")DataPersistence:set(_selfId,{tts=\"\"})local i=j;if i~=nil then if i.message~=nil and i.message==\"\"then Tk:traceEx(\"red\",\"TTS message is empty !\")os.exit()end;Tk:traceEx(\"yellow\",\"TTS message: %s\",i.message or\"n.c\")if i.duration~=nil and type(i.duration)==\"number\"then Tk:traceEx(\"yellow\",\"TTS duration set to: %s seconds\",i.duration or\"n.c\")elseif i.duration~=nil and type(i.duration)==\"string\"then Tk:traceEx(\"yellow\",\"TTS duration mode: %s seconds\",i.duration or\"n.c\")end;if i.volume~=nil then Tk:traceEx(\"yellow\",\"TTS volume: %s\",i.volume or\"n.c\")end;if i.language~=nil then Tk:traceEx(\"yellow\",\"TTS language: %s\",i.language or\"n.c\")end;local k;if UserParams.voiceRssApiKey~=nil and string.len(UserParams.voiceRssApiKey)>0 then local l=\"11khz_8bit_mono\"if UserParams.voiceRssSoundQuality~=nil and string.len(UserParams.voiceRssApiKey)>0 then if UserParams.voiceRssSoundQuality==\"low\"then l=\"8khz_8bit_mono\"elseif UserParams.voiceRssSoundQuality==\"medium\"then l=\"22khz_8bit_mono\"elseif UserParams.voiceRssSoundQuality==\"high\"then l=\"44khz_16bit_mono\"end end;k=\"//api.voicerss.org/?key=\"..UserParams.voiceRssApiKey..\"&hl=\"..tostring(i.language or\"fr-fr\")..\"&c=mp3&f=\"..l..\"&src=\"..tostring(Tk.HttpUtility.urlEncode(i.message or\"\"))else k=\"//responsivevoice.org/responsivevoice/getvoice.php?t=\"..tostring(Tk.HttpUtility.urlEncode(i.message or\"\"))..\"&l=\"..tostring(i.language or\"fr-fr\")end;i.source=\"http\"a:playStream(k,i)end end end\nif DataPersistence==nil then DataPersistence={root=\"x_sonos_object\"}DataPersistence.load=function(a)local b=_f:getGlobalValue(a.root)if string.len(b)>0 then local c=json.decode(b)if c and type(c)==\"table\"then return c else Tk:traceEx(\"red\",\"Unable to process data, check variable\")end else Tk:traceEx(\"red\",\"No data found!\")end end;DataPersistence.set=function(a,d,c)local e=a:load()if e[tostring(d)]then for f,g in pairs(c)do e[tostring(d)][f]=g end else e[tostring(d)]=c end;_f:setGlobal(a.root,json.encode(e))end;DataPersistence.get=function(a,d)local e=a:load()if e and type(e)==\"table\"then for f,g in pairs(e)do if tostring(f)==tostring(d or\"\")then return g end end end;return nil end;DataPersistence.reset=function(a)_f:setGlobal(a.root,json.encode({}))end end\nprocessResponse=function(a,b)if a==nil then return nil end;return a(b)end\nsendSoapMessage=function(a,b,c,d,e,f,g)g=g or 0;local h=Tk.Net.HttpRequest(_ip,_port)h:setReadTimeout(2000)local i=[[\n \n \t]]..string.format('%s',c.name,c.service,tostring(d or\"\"),c.name)..[[\n \n ]]local j,k,l=h:request(\"POST\",a,{'Content-Type: text/xml; charset=\"utf-8\"','SOAPAction: \"'..b..'#'..c.name..'\"'},i)h:disconnect()h:dispose()h=nil;if l==0 then if tonumber(k)==200 then if e~=nil then processResponse(e,j)end;return true else Tk:trace(\"status: %s\",k)end else Tk:traceEx(\"red\",\"Communication error code: \"..l)if f~=nil and f==true then return true end;if g<10 then Tk:trace(\"retry #%d action: %s\",g,c.name)_f:sleep(1000)return sendSoapMessage(a,b,c,d,e,f,g+1)else Tk:trace(\"Error: Code returned %s\",tostring(errorcode or\"n.c\"))end end;return false end\nencode=function(a)if a~=nil then return a:gsub(\"&\",\"&\"..\"amp;\"):gsub(\"<\",\"&\"..\"lt;\"):gsub(\">\",\"&\"..\"gt;\"):gsub('\"',\"&\"..\"quot;\"):gsub(\"'\",\"&\"..\"apos;\")end;return\"\"end\ndecode=function(a)if a~=nil then return a:gsub(\"&\"..\"#38;\",'&'):gsub(\"&\"..\"#60;\",'<'):gsub(\"&\"..\"#62;\",'>'):gsub(\"&\"..\"#34;\",'\"'):gsub(\"&\"..\"#39;\",\"'\"):gsub(\"&\"..\"lt;\",\"<\"):gsub(\"&\"..\"gt;\",\">\"):gsub(\"&\"..\"quot;\",'\"'):gsub(\"&\"..\"apos;\",\"'\"):gsub(\"&\"..\"amp;\",\"&\")end;return\"\"end\nexec=function(a)if a==\"PLAY\"then Tk:traceEx(\"yellow\",\"Execute command: Play\")if Sonos:play()then _f:log(\"Play\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Play\")return true end elseif a==\"PAUSE\"then Tk:traceEx(\"yellow\",\"Execute command: Pause\")if Sonos:pause()then _f:log(\"Pause\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Pause\")return true end elseif a==\"STOP\"then Tk:traceEx(\"yellow\",\"Execute command: Stop\")if Sonos:stop()then _f:log(\"Stop\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Stop\")return true end elseif a==\"PREV\"then Tk:traceEx(\"yellow\",\"Execute command: Previous\")if Sonos:previous()then _f:log(\"Previous\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Previous\")return true end elseif a==\"NEXT\"then Tk:traceEx(\"yellow\",\"Execute command: Next\")if Sonos:next()then _f:log(\"Next\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Next\")return true end elseif a==\"SEEKL\"then Tk:traceEx(\"yellow\",\"Execute command: Seek left\")if Sonos:seekL()then _f:log(\"Seek left\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Seek left\")return true end elseif a==\"SEEKR\"then Tk:traceEx(\"yellow\",\"Execute command: Seek Right\")if Sonos:seekR()then _f:log(\"Seek Right\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Seek Right\")return true end elseif a==\"LNON\"then Tk:traceEx(\"yellow\",\"Execute command: Loudness On\")if Sonos:loudness(true)then _f:log(\"Loudness On\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Loudness On\")return true end elseif a==\"LNOFF\"then Tk:traceEx(\"yellow\",\"Execute command: Loudness Off\")if Sonos:loudness(false)then _f:log(\"Loudness Off\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Loudness Off\")return true end elseif a==\"MUTE\"then Tk:traceEx(\"yellow\",\"Execute command: Mute\")if Sonos:mute(true)then _f:log(\"Mute\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Mute\")return true end elseif a==\"UNMUTE\"then Tk:traceEx(\"yellow\",\"Execute command: UnMute\")if Sonos:mute(false)then _f:log(\"UnMute\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"UnMute\")return true end elseif a==\"TMUTE\"then Tk:traceEx(\"yellow\",\"Execute command: Toggle mute\")if Sonos:muteInvert()then _f:log(\"Toggle mute\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Toggle mute\")return true end else local b=a:match(\"VOL(%d+)\")if b~=nil then Tk:traceEx(\"yellow\",\"Execute command: Set volume\")_f:call(_selfId,\"setProperty\",\"ui.slVolume.value\",tonumber(b))if Sonos:setVolume(tonumber(b))then _f:log(\"Set volume to \"..Sonos.volume or\"n.c\")_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",\"Set volume to \"..Sonos.volume or\"n.c\")return true end end;local b=a:match(\"RST(%d+)\")if b~=nil then Tk:traceEx(\"yellow\",\"Execute command: Set radio station\")local c=DataPersistence:get(_selfId)if c~=nil and c.radioStations~=nil then local d=#c.radioStations;if d==0 or tonumber(b)>d then Tk:traceEx(\"yellow\",\"Cannot execute command: no radio station to play!\")os.exit()end;Tk:traceEx(\"yellow\",\"Number of radio: \"..d)local e=c.radioStations[tonumber(b)]Tk:traceEx(\"yellow\",\"Try to play radio: \"..e.title)local f=encode(e.res)if Sonos:playRadio(f,encode(e.title))then local g=\"Set radio station to \"..e.title or\"n.c\"_f:log(g)_f:call(_selfId,\"setProperty\",\"ui.lblDebug.value\",g)return true end else Tk:traceEx(\"red\",\"No data found!\")end end end;return false end\n-- Debug / Trace\nTk.isTraceEnabled = false;\n-- Start\nSonos:process();\n--EOF","buttonIcon":8,"favourite":false,"main":false}]}]},"actions":{"pressButton":1,"setSlider":2,"setProperty":2}}