Natürlich, das habe ich schon gemacht.
Hast Du im Process-Button von dem VD Deinen voiceRssApiKey hinterlegt?
Was gibt der Debug dort aus?
Das folgende bekomme ich raus:
[DEBUG] 16:21:52: -------------------------------------------------------------------------
[DEBUG] 16:21:52: -- HC2 Toolkit Framework version 1.0.6
[DEBUG] 16:21:52: -- Current interpreter version is Lua 5.1
[DEBUG] 16:21:52: -- Total memory in use by Lua: 258.98 Kbytes
[DEBUG] 16:21:52: -------------------------------------------------------------------------
[DEBUG] 16:21:52: Toolkit.Debug loaded in memory...
[DEBUG] 16:21:52: Benchmark [Toolkit.Debug lib]: elapsed time: 0.010 cpu secs
[DEBUG] 16:21:52: Toolkit.Collections.Queue loaded in memory...
[DEBUG] 16:21:52: Benchmark [Toolkit.Collections.Queue lib]: elapsed time: 0.000 cpu secs
[DEBUG] 16:21:52: Toolkit.Net loaded in memory...
[DEBUG] 16:21:52: Benchmark [Toolkit.Net lib]: elapsed time: 0.000 cpu secs
[DEBUG] 16:21:52: Toolkit.Xml loaded in memory...
[DEBUG] 16:21:52: Benchmark [Toolkit.Xml lib]: elapsed time: 0.000 cpu secs
[DEBUG] 16:21:52: Toolkit.HttpUtility loaded in memory...
[DEBUG] 16:21:52: Benchmark [Toolkit.HttpUtility lib]: elapsed time: 0.000 cpu secs
Läuft also ohne Fehler.
Dein TTS-Script hast du richtig aufgesetzt? (https://forum.fibaro.com/index.php?/topic/20662-sonos-remote-v100-beta/&)
Fehler im Debug?
Moin Hoggle,
ich habe dieses Script genommen:
%% properties
%% globals
--]]
-- 112 is the Virtual Device ID
-- 28 is the Process button ID
local sid, bid = 112, 28
local temp = tostring(fibaro:getValue(144, "value"))
--local temp=tostring(fibaro:getGlobalValue("OWM_Temp"));
--fibaro:debug(temp);
tempcurr=string.gsub(temp, "%.", ",", 1);
fibaro:debug(tempcurr);
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- Funktion zur Ermittlung des Datums.
-- Version 1.0
-- Copyright (c) 2015 Nicolas Dörig
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
function germanDate( _type ) -- l=longDate("Montag, 12.September"), s=shortDate("Mo, 12.Sep 2011"), N=numericLong("12.09.2011"), n=numericShort("12.09.11")
local longWochentag = {'Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'}
local shortWochentag = {'So','Mo','Di','Mi','Do','Fr','Sa'}
local longMon = {'Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'}
local shortMon = {'Jan','Feb','Mrz','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'}
local indexTag = os.date( "%w" ) +1
local indexMon = math.abs( os.date( "%m" ) )
if _type == 's' then
return os.date( shortWochentag[indexTag]..", %d."..shortMon[indexMon].." %Y" )
elseif _type == 'l' then
return os.date( longWochentag[indexTag]..", %d."..longMon[indexMon] )
elseif _type == 'N' then
return os.date( "%d.%m.%Y" )
elseif _type == 'n' then
return os.date( "%d.%m.%y" )
end
end
--print ( 'Kurzdatum mit Wochentag\t' .. germanDate('s') )
--print ( 'Langdatum mit Wochentag\t' .. germanDate('l') )
--print ( 'Datum numerisch lang\t' .. germanDate('N') )
--print ( 'Datum numerisch kurz\t' .. germanDate('n') )
-------------------------------------------------------------------------------------------
--playTTS("de", "Guten Morgen. Es ist ".. germanDate('l') .." und draussen sind es " .. getWeather( 't' ) .." Grad " .. getWeather( 'c' ) ..". Ich starte das Radio fuer dich.", 13000, 15);
--local weather = fibaro:getValue(3, "WeatherConditionConverted")
local weather = fibaro:getGlobal("OW_Weather")
local weather_de
fibaro:debug(weather)
if weather == "overcast clouds" then
weather_de = "und es ist bewoelkt."
elseif weather == "few clouds" then
weather_de = "mit ein paar Wolken."
elseif weather == "scattered clouds" then
weather_de = "bei aufgelockerter Bewölkung."
elseif weather == "broken clouds" then
weather_de = "bei durchbrochener Bewölkung."
elseif weather == "light rain" then
weather_de = "es regnet leicht, vergiss den Schirm nicht!"
elseif weather == "moderate rain" then
weather_de = "es regnet, vergiss den Schirm nicht!"
elseif weather == "heavy intensity rain" then
weather_de = "es regnet stark, vergiss den Schirm nicht!"
elseif weather == "clear sky" then
weather_de = "und der Himmel ist klar."
elseif weather == "snow" then
weather_de = ". Es schenit."
elseif weather == "sleet" then
weather_de = ". Schneeregen."
elseif weather == "drizzle" then
weather_de = ". Nieselregen."
else
--weather_de = "."
weather_de = weather
end
fibaro:debug(weather_de)
---------------------------------------------------------------------------
-- Create TTS params object
--message = "Die aktuelle Temperatur draußen beträgt "..tempcurr.."°Celsius.",
local params = {
--TTS Message
message = "Es ist ".. germanDate('l') .." und draussen sind "..tempcurr.." Grad."..weather_de..".";
--message = "Es ist ".. germanDate('l') .." und draussen sind "..tempcurr.."°Celsius.".. getWeather( 'c' ) ..". Ich starte das Radio fuer dich.";
duration = 'auto'; -- Duration: "auto", xx seconds
language = "de-DE"; -- Language: see http://www.voicerss.org/api/documentation.aspx to get your language code
volume = 18; -- Volume
}
local _f = fibaro
local _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}
-- Make a request to the remote to process params object instantly
_x:set(tostring(sid), { tts = params })
_f:call(sid, "pressButton", bid)
Im Debug bekomme ich dann:
[DEBUG] 18:19:57: line 1: unexpected symbol near '%'
Ich nutze die SONOS Remote 1.0.1 dementsprechend müsste dass auch mit der Device ID passen.
Leider finde ich auch nicht die Funktion wie man die Radiotasten neu gelegt, dazu noch eine Idee?
Bei deinem Skript fehlt der öffnende Kommentar, weshalb das Skript diesen Denug auswirft.
Gruß
Und, ist die 112 wirklich die ID Deines VDs?
Hallo, ich würde mich gern mal mit einklinken.
Ich habe das gleiche Problem, dass der Button “Process” keine Funktion hat. Meine TTS Ansage funkioniert auch, wenn ich nur die Scene starte (siehe Bild).
Drücke ich den Button “Process” aus dem VD heraus, bleibt alles stumm. Der Debug des Buttons sieht so aus(siehe Bild), weiß aber nicht ob es so aussehen muss
Woran könnte es liegen?
Gruß
Hi,
die Szene wird benötigt, um den Text an das VD zu senden, das ihn dann in Sprache “übersetzt”.
Für sich alleine hat der Process-Button keine Funktion, nur in Verbindung mit der/einer Szene, die den Text aufbereitet und an das VD gibt.
VG Hoggle
Hi,
ich hab ja meine Scnene die ja auch für sich alleine funktioniert. Nur verstehe ich nicht warum ich beim drücken des Button, keine Reaktion erhalte.
Hruß secundani
Weil es keinen Text gibt.
Hi Hoggle,
der Text steht doch in der Scene und wird ja auch vom Sonos ausgegeben.
ja, Szene, aber nicht VD…
Bzw. die Variable wird durch das “stehen in de Szene” nicht aktualisiert.
heißt das, dass ich alles was in der Scene steht in das VD kopieren muss? und wenn ja an welche Stelle des Lua Codes.
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- SONOS Remote & Text To Speech (TTS)
--
-- Copyright (C) 2014-2015 Jean-Christophe Vermandé
--
-- Version 1.0.1
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- CHANGE LOGS:
--
-- Version 1.0.1 rc
-- Fix vd creation > HC2 v4.080
--
-- Version 1.0.0 beta
-- Improvement: Auto configuration
-- Improvement: Multiple VD support
-- Improvement: Low latency when triggering commands.
-- Improvement: Some code enhancement / refactoring.
-- New: TTS now use Voice RSS or ResponsiveVoice API with advanced options (duration,
-- volume, auto Resume)
-- New: Play stream with advanced options (duration, volume, auto Resume)
--
-- Version 0.1.0
-- Fix Google TTS with client=t in URI
--
-- Version 0.0.9
-- New: TTS version 2 added with external server support and notable improvement
--
-- Version 0.0.8
-- Improvement: Play TSS with auto stop mode (set dr parameter to "auto") now works as expected.
-- Improvement: Play TTS with fidex duration (set dr parameter to "xx" seconds) now works as expected.
-- Patch: Main image is now fixed after pressing a button, thanks to Labomatik & JM13.
-- Patch: Bug with XMl parsing for BrowseDirectChildren.
-- Warning: To operate the radio shortcuts you must choose at least two favorite radios.
-- Version 0.0.7
-- Refresh process run faster and more efficiently.
-- Patch line 892: attempt to index local 'value' (a function value)
-- Patch line 1256: attempt to concatenate a nil value
-- Show plugin current version in debug window on startup
-- Add LED control "On" or "Off" -> Sonos:ledState("On");
--
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
UserParams = {
-- Voice RSS API Key (Free Registration: http://www.voicerss.org/registration.aspx)
voiceRssApiKey = "5c43d746f8a049bfaafc3e5269cccf65",
-- Sound quality: low, medium, high
voiceRssSoundQuality = "medium"
}
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
local _f = fibaro;
if not Toolkit then Toolkit={
__header="Toolkit",
__version="1.0.6",
__luaBase="5.2.0",
__copyright="Jean-Christophe Vermandé",
__licence=[[
Minified version : Compression ratio 50.90%
Copyright (C) 2013-2015 Jean-Christophe Vermandé
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses></http:>.
]],__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</%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
_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")os.exit()end;Sonos={__header="SONOS Advanced Remote",__version="1.0.1",_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"}}}Sonos.play=function(self,callback)Tk:trace("Play request")callback=callback or function(a)Tk:trace("Play sent")end;self.refreshTime=2;return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.Play,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><Speed>1</Speed>",callback)end;Sonos.pause=function(self)Tk:trace("Pause request")return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.Pause,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><Speed>1</Speed>",function(a)Tk:trace("Pause sent")end)end;Sonos.stop=function(self)Tk:trace("Stop request")return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.Stop,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><Speed>1</Speed>",function(a)Tk:trace("Stop was sent")end)end;Sonos.previous=function(self)Tk:trace("Previous request")return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.Previous,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><Speed>1</Speed>",function(a)Tk:trace("Previous sent")end)end;Sonos.next=function(self)Tk:trace("Next request")return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.Next,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><Speed>1</Speed>",function(a)Tk:trace("Next sent")end)end;Sonos.seek=function(self,type,b)if self.currentTrack.isRadio then Tk:traceEx("Yellow","Cannot seek in radio mode!")return end;Tk:trace("seek to %s",tostring(b))return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.Seek,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><Unit>"..type.."</Unit><Target>"..b.."</Target>",function(a)Tk:trace("seek was sent")end)end;Sonos.seekL=function(self)if self.currentTrack~=nil then local c=self.currentTrack.relTime;local d=clockToSeconds(c)if d>=30 then d=d-30 end;self:seek("REL_TIME",secondsToClock(d))else Tk:traceEx("yellow","There is no current track loaded.")end end;Sonos.seekR=function(self)if self.currentTrack~=nil then local c=self.currentTrack.relTime;local e=clockToSeconds(c)or 0;local f=clockToSeconds(self.currentTrack.duration or"00:00")or 0;e=e+30;if e>=f then Tk:traceEx("yellow","try to seek out of range!")return end;self:seek("REL_TIME",secondsToClock(e))Tk:trace("seek right request")else Tk:traceEx("yellow","There is no current track loaded.")end end;Sonos.loudness=function(self,g)Tk:trace("Set Loudness: "..tostring(g))local h=0;if g==true then h=1 end;return sendSoapMessage(self.props.controlURL.RendererRenderingControl,self.props.serviceType.RenderingControl,{name=self.props.actions.SetLoudness,service=self.props.serviceType.RenderingControl},"<InstanceID>0</InstanceID><Channel>Master</Channel><DesiredLoudness>"..h.."</DesiredLoudness>",function(a)if h==0 then Tk:trace("Loudness OFF was sent")self.eq.loudness=false else Tk:trace("Loudness ON was sent")self.eq.loudness=true end end)end;Sonos.getLoudness=function(self)Tk:trace("Get loudness request")return sendSoapMessage(self.props.controlURL.RendererRenderingControl,self.props.serviceType.AVTransport,{name=self.props.actions.GetLoudness,service=self.props.serviceType.RenderingControl},"<InstanceID>0</InstanceID><Channel>Master</Channel>",function(a)local i=tonumber(a:match("<CurrentLoudness>(.+)</CurrentLoudness>")or 0)if i==0 then Tk:trace("Loudness is OFF")self.eq.loudness=false elseif i==1 then Tk:trace("Loudness is ON")self.eq.loudness=true else Tk:trace("Loudness N.C")self.eq.loudness=false end end)end;Sonos.mute=function(self,g)Tk:trace("Mute request")local h=0;if g==true then h=1 end;return sendSoapMessage(self.props.controlURL.RendererRenderingControl,self.props.serviceType.RenderingControl,{name=self.props.actions.SetMute,service=self.props.serviceType.RenderingControl},"<InstanceID>0</InstanceID><Channel>Master</Channel><DesiredMute>"..h.."</DesiredMute>",function(a)if h==0 then Tk:trace("UnMute was sent")self._isMuted=false else Tk:trace("Mute was sent")self._isMuted=true end end)end;Sonos.muteInvert=function(self)self:getMute()if self._isMuted==true then self:mute(false)elseif self._isMuted==false then self:mute(true)end end;Sonos.getMute=function(self)Tk:trace("Get mute state request")return sendSoapMessage(self.props.controlURL.RendererRenderingControl,self.props.serviceType.RenderingControl,{name=self.props.actions.GetMute,service=self.props.serviceType.RenderingControl},"<InstanceID>0</InstanceID><Channel>Master</Channel>",function(a)local h=tonumber(a:match("<CurrentMute>(.+)</CurrentMute>")or 0)if h==0 then self._isMuted=false elseif h==1 then self._isMuted=true end;Tk:trace("mute: %s",tostring(self._isMuted))end)end;Sonos.getVolume=function(self)Tk:trace("Get volume request")return sendSoapMessage(self.props.controlURL.RendererRenderingControl,self.props.serviceType.AVTransport,{name=self.props.actions.GetVolume,service=self.props.serviceType.RenderingControl},"<InstanceID>0</InstanceID><Channel>Master</Channel>",function(a)self.volume=tonumber(a:match("<CurrentVolume>(.+)</CurrentVolume>")or 0)Tk:trace("volume: %d",self.volume)end)end;Sonos.setVolume=function(self,j)Tk:trace("Set volume to %s",tostring(j))return sendSoapMessage(self.props.controlURL.RendererRenderingControl,self.props.serviceType.RenderingControl,{name=self.props.actions.SetVolume,service=self.props.serviceType.RenderingControl},"<InstanceID>0</InstanceID><Channel>Master</Channel><DesiredVolume>"..tostring(j).."</DesiredVolume>",function(a)self.volume=tonumber(j)_f:call(_selfId,"setProperty","ui.slVolume.value",self.volume)Tk:trace("Volume set to %d",j)end)end;Sonos.ledState=function(self,g)Tk:trace("Set LED state request")return sendSoapMessage(self.props.controlURL.DeviceProperties,self.props.serviceType.DeviceProperties,{name=self.props.actions.SetLEDState,service=self.props.serviceType.DeviceProperties},"<DesiredLEDState>"..(g or"ON").."</DesiredLEDState>",function(a)Tk:trace("Set LED state sent")end)end;Sonos.getTransportState=function(self)Tk:trace("Get transport state request")return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.GetTransportInfo,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID>",function(a)self.transportState=a:match("<CurrentTransportState>(.+)</CurrentTransportState>")or""self.transportStatus=a:match("<CurrentTransportStatus>(.+)</CurrentTransportStatus>")or""end)end;Sonos.setCurrentTrack=function(self,k)Tk:trace("Set current track to %s",Tk.HttpUtility.urlDecode(k))return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.SetAVTransportURI,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID>,<CurrentURI>"..k.."</CurrentURI>,<CurrentURIMetaData></CurrentURIMetaData>")end;Sonos.getCurrentTrack=function(self)Tk:trace("get current track request")return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.GetPositionInfo,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><Channel>Master</Channel>",function(a)self.currentTrack.track=tonumber(a:match("<Track>(.+)</Track>")or 0)self.currentTrack.duration=tostring(a:match("<TrackDuration>(.+)</TrackDuration>")or"")self.currentTrack.relTime=tostring(a:match("<RelTime>(.+)</RelTime>")or"00:00:00")self.currentTrack.relCount=tonumber(a:match("<RelCount>(.+)</RelCount>")or 0)self.currentTrack.absCount=tonumber(a:match("<AbsCount>(.+)</AbsCount>")or 0)self.currentTrack.uri=tostring(a:match("<TrackURI>(.+)</TrackURI>")or"")local l=decode(tostring(a:match("<TrackMetaData>(.+)</TrackMetaData>")))self.currentTrack.title=string.gsub(decode(tostring(l:match("<dc:title>(.+)</dc:title>")or"")),1,22)self.currentTrack.creator=decode(tostring(l:match("<dc:creator>(.+)</dc:creator>")or""))self.currentTrack.artist=decode(tostring(l:match("<r:albumArtist>(.+)</r:albumArtist>")or""))self.currentTrack.album=decode(tostring(l:match("<upnp:album>(.+)</upnp:album>")or""))self.currentTrack.originalTrackNumber=tostring(l:match("<upnp:originalTrackNumber>(.+)</upnp:originalTrackNumber>")or"")if string.find(decode(self.currentTrack.uri or""),"x-rincon-mp3radio:",1,true)~=nil then self.currentTrack.isRadio=true;self.currentTrack.isFile=false;if self:getMediaInfo()then self.currentTrack.title=string.gsub(self.mediaInfo.title,1,25)end elseif string.find(decode(self.currentTrack.uri or""),"x-file-cifs:",1,true)~=nil then self.currentTrack.isRadio=false;self.currentTrack.isFile=true else self.currentTrack.isRadio=false;self.currentTrack.isFile=false end end)end;Sonos.getMediaInfo=function(self)Tk:trace("Get current media info request")return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.GetMediaInfo,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><Channel>Master</Channel>",function(a)local l=decode(tostring(a:match('<CurrentURIMetaData>(.+)</CurrentURIMetaData>')))self.mediaInfo.title=decode(tostring(l:match('<dc:title>(.+)</dc:title>')or''))end)end;Sonos.createMetaData=function(m)local n={}table.insert(n,'<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:dlna="urn:schemas-dlna-org:metadata-1-0">')table.insert(n,'<item id="HC2" parentID="-1" restricted="1">')table.insert(n,'<dc:title>'..encode(m.title or"Unknown title")..'</dc:title>')table.insert(n,'<res protocolInfo="'..encode(m.protocol or"x-file-cifs:*:audio/mpeg:*")..'">'..encode(m.uri)..'</res>')table.insert(n,'<upnp:class>"'..encode(m.class or"object.item.audioItem.musicTrack")..'"</upnp:class>')table.insert(n,'<dc:creator>"'..encode(m.creator or"Unknown creator")..'"</dc:creator>')table.insert(n,'<r:albumArtist>"'..encode(m.artist or"Unknown artist")..'"</r:albumArtist>')table.insert(n,'</item>')table.insert(n,'</DIDL-Lite>')return encode(table.concat(n))end;Sonos.playRadio=function(self,k,o)Tk:trace("Set Radio request")self:stop()callback=callback or function(a)Tk:trace("Play sent")end;local p=encode('<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item><dc:title>'..o..'</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON65031_</desc></item></DIDL-Lite>')return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.SetAVTransportURI,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID><CurrentURI>"..k.."</CurrentURI><CurrentURIMetaData>"..p.."</CurrentURIMetaData>",function()Tk:traceEx("Yellow","Radio: "..o.." pushed")_f:sleep(1000)self:play()_f:sleep(1000)self:play()_f:sleep(1000)self:play()end,true)end;Sonos.playStream=function(self,k,m)Tk:traceEx("green","Play Stream request uri: [%s]",Tk.HttpUtility.urlDecode(k))self:getCurrentTrack()self.lastTrack=self.currentTrack;local q=self.volume;if m.volume~=nil then q=m.volume;self:getVolume()self.lastVolume=self.volume end;self:getMute()self.lastMuteState=self._isMuted;self:getTransportState()self.lastTransportState=self.transportState;self._isPlayingStream=true;local r="auto"if m.duration~=nil then r=m.duration end;local s="x-file-cifs"if m.source~=nil then if m.source=="local"then s="x-file-cifs"elseif m.source=="http"then s="x-rincon-mp3radio"else s="x-sonosapi-stream"end end;local t=Sonos.createMetaData({title=k,protocol=s..":*:audio/mpeg:*",uri=s..":"..k,creator="_f-hc2"})return sendSoapMessage(self.props.controlURL.RendererAVTransport,self.props.serviceType.AVTransport,{name=self.props.actions.SetAVTransportURI,service=self.props.serviceType.AVTransport},"<InstanceID>0</InstanceID>,<CurrentURI>"..s..":"..k.."</CurrentURI>,<CurrentURIMetaData>"..t.."</CurrentURIMetaData>",function(a)if self._isMuted==true then self:mute(false)end;_f:sleep(250)if q~=nil and q~=self.volume then self:setVolume(q)self.streamVolumeIsDifferent=true end;_f:sleep(250)self:play(function(a)if r~=nil then if type(r)=="string"and r=="auto"then Tk:trace("Play TSS with auto stop mode")local u=0;self.transportState="TRANSITIONING"while self.transportState=="TRANSITIONING"do if u>20 then break end;self:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(1000)u=u+1 end;self.transportState="PLAYING"while self.transportState=="PLAYING"do if u>120 then break end;self:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(1000)u=u+1 end;_f:sleep(500)self:stop()elseif type(r)=="number"then r=tonumber(r)Tk:trace("Play Stream for "..r.." seconds")_f:sleep(r)local u=0;self.transportState="TRANSITIONING"while self.transportState=="TRANSITIONING"do if u>20 then break end;self:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(1000)u=u+1 end;if r>1 then r=r-1 end;_f:sleep(r*1000)self:stop()end else Tk:trace("play sent")local u=0;self.transportState="TRANSITIONING"while self.transportState=="TRANSITIONING"do if u>30 then break end;self:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(500)u=u+1 end;local v=0;self.transportState="PLAYING"while self.transportState=="PLAYING"do if v>60 then break end;self:getTransportState()Tk:trace(Sonos.transportState)_f:sleep(2000)v=v+1 end;_f:sleep(500)self:stop()end;_f:sleep(100)if self.streamVolumeIsDifferent==true then self:setVolume(Sonos.lastVolume)self.streamVolumeIsDifferent=false end;_f:sleep(100)if self:setCurrentTrack(self.lastTrack.uri)then if not self.lastTrack.isRadio then self:seek("REL_TIME",self.lastTrack.relTime)_f:sleep(250)end;if self.lastTransportState==self.props.transportState.playing then self:play()elseif self.lastTransportState==self.props.transportState.stopped then self:stop()elseif self.lastTransportState==self.props.transportState.pausedPlayback then self:pause()end end;_f:sleep(250)if self.lastMuteState==false then self:mute(false)else self:mute(true)end;self._isPlayingStream=false end)end)end;Sonos.externalTTSService=function(self,w,x)Tk:trace("Request external TTS service, try to create tts response and return meta object.")x=x or 0;local y=Tk.Net.HttpRequest(w.host,w.port)y:setReadTimeout(2000)local a,z,A=y:request("GET",w.root..'?'..w.args,{"User-Agent: FibaroHC2/1.0","Accept: application/json","Accept-Charset: utf-8"})y:disconnect()y:dispose()y=nil;if A==0 then if tonumber(z)==200 then return json.decode(a)else Tk:trace("status: %s",z)end else Tk:traceEx("red","Communication error code: "..A)if x<10 then Tk:trace("retry #%d",x)_f:sleep(500)return self:externalTTSService(w,x+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",w.host,w.port,w.root,w.args))return{success=false}end;Sonos.parseOnDemandTTS=function(self,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;Sonos.getZonePlayer=function(self,j,F)local G={}if F~=nil and F=="ip"then for v,H in ipairs(self.zonePlayers)do if H.ip==tostring(j)then G=H;break end end end;return G end;Sonos.process=function(self)local I=false;if self._debugProcess and Tk.isTraceEnabled~=true then Tk.isTraceEnabled=true;I=true end;local J=nil;local K=DataPersistence:get(_selfId)if K and type(K)=="table"then if K.action~=nil and string.len(K.action)>0 then J=K.action;Tk:traceEx("green","pending action: "..J)end end;if J~=nil and string.len(J)>0 then local L=""for M in string.gmatch(J,"[^%s]+")do if M~=L then self._commands:enqueue(M)else Tk:traceEx("red","duplicate '%s' detected and removed from actions queue:",M)end;L=M end;DataPersistence:set(_selfId,{action=""})while self._commands:count()>0 do Tk:traceEx("green","dequeue command %s",self._commands:peek())local N=nil;N=exec(self._commands:dequeue())while N==nil do _f:sleep(250)end;Tk:traceEx("green","command result is %s",tostring(N))end;_f:call(_selfId,"setProperty","currentIcon",_icons.main)end;local O=nil;if K and type(K)=="table"then if K.stream~=nil and type(K.stream)=="table"then O=K.stream;Tk:traceEx("green","Stream pending")end end;if O~=nil then Tk:traceEx("green","STREAM data found, please wait...")DataPersistence:set(_selfId,{stream=""})local m=O;if m~=nil then if m.stream~=nil and m.stream==""then Tk:traceEx("red","stream location is empty !")os.exit()end;Tk:traceEx("yellow","stream location: %s",m.stream or"n.c")if m.duration~=nil and type(m.duration)=="number"then Tk:traceEx("yellow","stream duration set to: %s seconds",m.duration or"n.c")elseif m.duration~=nil and type(m.duration)=="string"then Tk:traceEx("yellow","stream duration mode: %s seconds",m.duration or"n.c")end;if m.volume~=nil then Tk:traceEx("yellow","stream volume: %s",m.volume or"n.c")end;self:playStream(m.stream,m)end end;local P=nil;if K and type(K)=="table"then if K.tts~=nil and type(K.tts)=="table"then P=K.tts;Tk:traceEx("green","TTS pending")end end;if P~=nil then Tk:traceEx("green","TTS data found, please wait...")DataPersistence:set(_selfId,{tts=""})local m=P;if m~=nil then if m.message~=nil and m.message==""then Tk:traceEx("red","TTS message is empty !")os.exit()end;Tk:traceEx("yellow","TTS message: %s",m.message or"n.c")if m.duration~=nil and type(m.duration)=="number"then Tk:traceEx("yellow","TTS duration set to: %s seconds",m.duration or"n.c")elseif m.duration~=nil and type(m.duration)=="string"then Tk:traceEx("yellow","TTS duration mode: %s seconds",m.duration or"n.c")end;if m.volume~=nil then Tk:traceEx("yellow","TTS volume: %s",m.volume or"n.c")end;if m.language~=nil then Tk:traceEx("yellow","TTS language: %s",m.language or"n.c")end;local k;if UserParams.voiceRssApiKey~=nil and string.len(UserParams.voiceRssApiKey)>0 then local Q="11khz_8bit_mono"if UserParams.voiceRssSoundQuality~=nil and string.len(UserParams.voiceRssApiKey)>0 then if UserParams.voiceRssSoundQuality=="low"then Q="8khz_8bit_mono"elseif UserParams.voiceRssSoundQuality=="medium"then Q="22khz_8bit_mono"elseif UserParams.voiceRssSoundQuality=="high"then Q="44khz_16bit_mono"end end;k="//api.voicerss.org/?key="..UserParams.voiceRssApiKey.."&hl="..tostring(m.language or"fr-fr").."&c=mp3&f="..Q.."&src="..tostring(Tk.HttpUtility.urlEncode(m.message or""))else k="//responsivevoice.org/responsivevoice/getvoice.php?t="..tostring(Tk.HttpUtility.urlEncode(m.message or"")).."&l="..tostring(m.language or"fr-fr")end;m.source="http"self:playStream(k,m)end end end;if DataPersistence==nil then DataPersistence={root="x_sonos_object"}DataPersistence.load=function(self)local j=_f:getGlobalValue(self.root)if j==nil then return end;if string.len(j)>0 then local K=json.decode(j)if K and type(K)=="table"then return K else Tk:traceEx("red","Unable to process data, check variable")end else Tk:traceEx("red","No data found!")end end;DataPersistence.set=function(self,R,K)local i=self:load()if i[tostring(R)]then for v,H in pairs(K)do i[tostring(R)][v]=H end else i[tostring(R)]=K end;_f:setGlobal(self.root,json.encode(i))end;DataPersistence.get=function(self,R)local i=self:load()if i and type(i)=="table"then for v,H in pairs(i)do if tostring(v)==tostring(R or"")then return H end end end;return nil end;DataPersistence.reset=function(self)_f:setGlobal(self.root,json.encode({}))end end;processResponse=function(S,T)if S==nil then return nil end;return S(T)end;sendSoapMessage=function(U,w,V,T,callback,W,x)x=x or 0;local y=Tk.Net.HttpRequest(_ip,_port)y:setReadTimeout(2000)local X=[[
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>]]..string.format('<u:%s xmlns:u="%s">%s</u:%s>',V.name,V.service,tostring(T or""),V.name)..[[</s:Body>
</s:Envelope>
]]local a,z,A=y:request("POST",U,{'Content-Type: text/xml; charset="utf-8"','SOAPAction: "'..w..'#'..V.name..'"'},X)y:disconnect()y:dispose()y=nil;if A==0 then if tonumber(z)==200 then if callback~=nil then processResponse(callback,a)end;return true else Tk:trace("status: %s",z)end else Tk:traceEx("red","Communication error code: "..A)if W~=nil and W==true then return true end;if x<10 then Tk:trace("retry #%d action: %s",x,V.name)_f:sleep(1000)return sendSoapMessage(U,w,V,T,callback,W,x+1)else Tk:trace("Error: Code returned %s",tostring(errorcode or"n.c"))end end;return false end;encode=function(Y)if Y~=nil then return Y:gsub("&","&".."amp;"):gsub("<","&".."lt;"):gsub(">","&".."gt;"):gsub('"',"&".."quot;"):gsub("'","&".."apos;")end;return""end;decode=function(Y)if Y~=nil then return Y:gsub("&".."#38;",'&'):gsub("&".."#60;",'<'):gsub("&".."#62;",'>'):gsub("&".."#34;",'"'):gsub("&".."#39;","'"):gsub("&".."lt;","<"):gsub("&".."gt;",">"):gsub("&".."quot;",'"'):gsub("&".."apos;","'"):gsub("&".."amp;","&")end;return""end;exec=function(Z)if Z=="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 Z=="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 Z=="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 Z=="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 Z=="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 Z=="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 Z=="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 Z=="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 Z=="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 Z=="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 Z=="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 Z=="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 _=Z:match("VOL(%d+)")if _~=nil then Tk:traceEx("yellow","Execute command: Set volume")_f:call(_selfId,"setProperty","ui.slVolume.value",tonumber(_))if Sonos:setVolume(tonumber(_))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 _=Z:match("RST(%d+)")if _~=nil then Tk:traceEx("yellow","Execute command: Set radio station")local K=DataPersistence:get(_selfId)if K~=nil and K.radioStations~=nil then local a0=#K.radioStations;if a0==0 or tonumber(_)>a0 then Tk:traceEx("yellow","Cannot execute command: no radio station to play!")os.exit()end;Tk:traceEx("yellow","Number of radio: "..a0)local a1=K.radioStations[tonumber(_)]Tk:traceEx("yellow","Try to play radio: "..a1.title)local a2=encode(a1.res)if Sonos:playRadio(a2,encode(a1.title))then local a3="Set radio station to "..a1.title or"n.c"_f:log(a3)_f:call(_selfId,"setProperty","ui.lblDebug.value",a3)return true end else Tk:traceEx("red","No data found!")end end end;return false end;Tk.isTraceEnabled=false;Sonos:process()
Szene und VD bilden eine Einheit.
Warum willst Du das zusammenkopieren?
Dann kannst Du das VD nur noch mit dem einem einigen Text nutzen.
Szenen kannst Du aber beliebige erstellen, die dann das eine VD nutzen.
Sorry, aber irgendwie stehe ich jetzt total auf dem Schlauch.
Ich habe eine funktionierende Scene die auch mit mir spricht.
Und ich habe ein VD mit Button Process der nichts macht.
Aber wie kann ich denn den Button dazu überreden, die Variable zu aktualisieren und die TTS Scene abzuarbeiten.
Der Button ist nur technisch notwendig. Er bleibt im Hintergrund.
Du bedienst nicht den Butzon, sondern arbeitest NUR mit der Szene.
Das drücken des Buttons macht die Szene automatisch für Dich.
Ahh jetzt hab ich es verstanden. Ich dachte es ist genau andersrum, dass der Button die Szene auslöst.
weil ich ja sonst auch jeden anderen Button im VD drücken kann und dann läuft da das Radio oder ich kann über die Stationstasten den Sender wechseln.
Dann danke ich dir für deine Geduld mit mir und es war wieder mal sehr lehrreich.
gruß secundani
Perfekt!
Moin zusammen,
also, ja meine VD ID ist wirklich die 112…
@ booxm: Danke für den Tipp, machmal sieht man den Wald vor lauter Bäumen nicht.
Ich habe das nun entsprechend geändert, bekomme aber nun dieses Fehler:
Script:
--[[
%% properties
%% globals
--]]
-- 112 is the Virtual Device ID
-- 28 is the Process button ID
local sid, bid = 112, 28
local temp = tostring(fibaro:getValue(144, "value"))
--local temp=tostring(fibaro:getGlobalValue("OWM_Temp"));
--fibaro:debug(temp);
tempcurr=string.gsub(temp, "%.", ",", 1);
fibaro:debug(tempcurr);
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
-- Funktion zur Ermittlung des Datums.
-- Version 1.0
-- Copyright (c) 2015 Nicolas Dörig
-------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------
function germanDate( _type ) -- l=longDate("Montag, 12.September"), s=shortDate("Mo, 12.Sep 2011"), N=numericLong("12.09.2011"), n=numericShort("12.09.11")
local longWochentag = {'Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'}
local shortWochentag = {'So','Mo','Di','Mi','Do','Fr','Sa'}
local longMon = {'Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'}
local shortMon = {'Jan','Feb','Mrz','Apr','Mai','Jun','Jul','Aug','Sep','Okt','Nov','Dez'}
local indexTag = os.date( "%w" ) +1
local indexMon = math.abs( os.date( "%m" ) )
if _type == 's' then
return os.date( shortWochentag[indexTag]..", %d."..shortMon[indexMon].." %Y" )
elseif _type == 'l' then
return os.date( longWochentag[indexTag]..", %d."..longMon[indexMon] )
elseif _type == 'N' then
return os.date( "%d.%m.%Y" )
elseif _type == 'n' then
return os.date( "%d.%m.%y" )
end
end
--print ( 'Kurzdatum mit Wochentag\t' .. germanDate('s') )
--print ( 'Langdatum mit Wochentag\t' .. germanDate('l') )
--print ( 'Datum numerisch lang\t' .. germanDate('N') )
--print ( 'Datum numerisch kurz\t' .. germanDate('n') )
-------------------------------------------------------------------------------------------
--playTTS("de", "Guten Morgen. Es ist ".. germanDate('l') .." und draussen sind es " .. getWeather( 't' ) .." Grad " .. getWeather( 'c' ) ..". Ich starte das Radio fuer dich.", 13000, 15);
--local weather = fibaro:getValue(3, "WeatherConditionConverted")
local weather = fibaro:getGlobal("OW_Weather")
local weather_de
fibaro:debug(weather)
if weather == "overcast clouds" then
weather_de = "und es ist bewoelkt."
elseif weather == "few clouds" then
weather_de = "mit ein paar Wolken."
elseif weather == "scattered clouds" then
weather_de = "bei aufgelockerter Bewölkung."
elseif weather == "broken clouds" then
weather_de = "bei durchbrochener Bewölkung."
elseif weather == "light rain" then
weather_de = "es regnet leicht, vergiss den Schirm nicht!"
elseif weather == "moderate rain" then
weather_de = "es regnet, vergiss den Schirm nicht!"
elseif weather == "heavy intensity rain" then
weather_de = "es regnet stark, vergiss den Schirm nicht!"
elseif weather == "clear sky" then
weather_de = "und der Himmel ist klar."
elseif weather == "snow" then
weather_de = ". Es schenit."
elseif weather == "sleet" then
weather_de = ". Schneeregen."
elseif weather == "drizzle" then
weather_de = ". Nieselregen."
else
--weather_de = "."
weather_de = weather
end
fibaro:debug(weather_de)
---------------------------------------------------------------------------
-- Create TTS params object
--message = "Die aktuelle Temperatur draußen beträgt "..tempcurr.."°Celsius.",
local params = {
--TTS Message
message = "Es ist ".. germanDate('l') .." und draussen sind "..tempcurr.." Grad."..weather_de..".";
--message = "Es ist ".. germanDate('l') .." und draussen sind "..tempcurr.."°Celsius.".. getWeather( 'c' ) ..". Ich starte das Radio fuer dich.";
duration = 'auto'; -- Duration: "auto", xx seconds
language = "de-DE"; -- Language: see http://www.voicerss.org/api/documentation.aspx to get your language code
volume = 18; -- Volume
}
local _f = fibaro
local _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}
-- Make a request to the remote to process params object instantly
_x:set(tostring(sid), { tts = params })
_f:call(sid, "pressButton", bid)
Debug:
[DEBUG] 18:18:24: nil
[DEBUG] 18:18:24: nil
[DEBUG] 18:18:24: nil
[DEBUG] 18:18:24: line 94: attempt to concatenate local 'weather_de' (a nil value)
Hat auch noch einer einen Tipp, wie man die Radiobuttons neu belegen kann?